Created
March 26, 2021 09:49
-
-
Save mluxe/7dec97dfbedbeb33cc5a3cb752a83ba0 to your computer and use it in GitHub Desktop.
React scroll to load pagination
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React from "react"; | |
| import './product-list.scss' | |
| export class ProductCard extends React.Component { | |
| constructor(props){ | |
| super(props); | |
| this.state = {}; | |
| } | |
| render(){ | |
| const {product} = this.props; | |
| return <div className="product-card"> | |
| <div>{product.description}</div> | |
| </div>; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React from "react"; | |
| import debounce from 'lodash.debounce'; | |
| import './product-list.scss' | |
| import { ProductService } from "../../shared/services/product.service"; | |
| import { formatNumber } from "../../shared/func/utils-func"; | |
| export class ProductItem extends React.Component { | |
| product_service; | |
| constructor(props){ | |
| super(props); | |
| this.state = { | |
| category_id: props.product.category_id || 0, | |
| notes: props.product.notes || '', | |
| saving: false | |
| }; | |
| this.product_service = new ProductService(); | |
| this.handleCategoryChange = this.handleCategoryChange.bind(this); | |
| this.handleNoteChange = this.handleNoteChange.bind(this); | |
| this.handleExcludeProduct = this.handleExcludeProduct.bind(this); | |
| this.handleAutoApplyMerchant = this.handleAutoApplyMerchant.bind(this); | |
| this.onNoteChangeDebounced = debounce(this.onNoteChangeDebounced, 1000); | |
| } | |
| handleCategoryChange(event){ | |
| const new_category_id = parseInt(event.target.value); | |
| this.setState({ category_id: new_category_id }); | |
| this.setState({saving: true}); | |
| this.product_service.setCategory(this.props.product.id, new_category_id).then((data) => { | |
| if(data.success){ | |
| const category = this.props.categories.find(i => i.id === new_category_id); | |
| this.props.product.category_id = category.id; | |
| this.props.product.category = category; | |
| this.props.product.is_excluded = category.auto_exclude; | |
| } | |
| this.setState({saving: false}); | |
| }); | |
| } | |
| handleExcludeProduct(event){ | |
| const id = event.target.getAttribute('data-id'); | |
| const excluded = event.target.checked; | |
| this.props.product.is_excluded = excluded; | |
| this.setState({saving: true}); | |
| this.product_service.exclude(id, excluded).then((success) => { | |
| this.setState({saving: false}); | |
| }); | |
| } | |
| handleNoteChange(event){ | |
| this.setState({ notes: event.target.value }); | |
| this.onNoteChangeDebounced(event.target.value) | |
| } | |
| onNoteChangeDebounced = (notes) => { | |
| this.setState({saving: true}); | |
| this.product_service.set(this.props.product.id, 'notes', notes) | |
| .then((success) => { | |
| if(success){ | |
| this.props.product.notes = notes; | |
| } | |
| this.setState({saving: false}); | |
| }); | |
| } | |
| renderCategoryGroupBadge(){ | |
| const { categories } = this.props; | |
| const category = categories.find(i => i.id === this.state.category_id); | |
| if(category === undefined){ | |
| return; | |
| } | |
| return <span className="badge border border-secondary text-secondary"> | |
| {category.category_group} | |
| </span> | |
| } | |
| handleAutoApplyMerchant(){ | |
| this.setState({saving: true}); | |
| this.product_service.autoApplyMerchant(this.props.product.id) | |
| .then((data)=>{ | |
| if(data.merchant != null){ | |
| const trans = this.props.product; | |
| trans.category_id = data.merchant.category_id; | |
| trans.merchant_id = data.merchant.id; | |
| trans.category = data.merchant.category; | |
| trans.merchant = data.merchant; | |
| trans.merchant_name = data.merchant.name; | |
| trans.is_excluded = data.merchant.category.auto_exclude; | |
| this.setState({saving: false, category_id: trans.category_id}); | |
| }else{ | |
| this.setState({saving: false}); | |
| } | |
| }); | |
| } | |
| renderCategoryBadge(){ | |
| const { categories, product } = this.props; | |
| const category_type = (product.credit > 0? "income": "expense") | |
| const category_options = categories.filter(i => i.type === category_type); | |
| let category_group; | |
| /* | |
| let groups = [...new Set(category_options.map(item => item.category_group))]; | |
| const selection = <select name="category" className="hidden-selection" value={this.state.category_id} onChange={this.handleCategoryChange}> | |
| <option value="">No Category</option> | |
| {groups.map((group, i) => { | |
| const options = category_options.filter(item => item.category_group === group); | |
| return <optgroup label={group} key={`opt-group-${group}`}> | |
| {options.map((category, i) => ( | |
| <option value={category.id} key={i}>{category.name}</option> | |
| ))} | |
| </optgroup> | |
| })} | |
| </select> | |
| */ | |
| const selection = <select name="category" className="hidden-selection" value={this.state.category_id || ''} onChange={this.handleCategoryChange}> | |
| <option value="">No Category</option> | |
| {category_options.map((category, i) => { | |
| const results = []; | |
| if(category_group !== category.category_group){ | |
| results.push(<optgroup label={category.category_group} key={`opt-group-${category.category_group}`}>{category.category_group}</optgroup>); | |
| category_group = category.category_group; | |
| } | |
| results.push(<option value={category.id} key={i}>{category.name}</option>); | |
| return results; | |
| })} | |
| </select> | |
| let category_name = "No Category"; | |
| const category = categories.find(i => i.id === this.state.category_id); | |
| if(category !== undefined){ | |
| category_name = `${category.name}`; | |
| } | |
| return <span className="badge border border-secondary text-secondary"> | |
| {category_name} | |
| {selection} | |
| </span> | |
| } | |
| render(){ | |
| const { product, providers } = this.props; | |
| const provider = providers.find(i => i.id === product.provider_id); | |
| const amount = product.product_amount; | |
| let notes; | |
| notes = <span className="product-notes"> | |
| <input type="text" className="notes-input hover-visible" value={this.state.notes || ''} placeholder="Enter notes" onChange={this.handleNoteChange}/> | |
| <span className="notes-text hover-hidden">{this.state.notes}</span> | |
| </span> | |
| return <tr className="product"> | |
| <td className="text-vertical">{product.product_date.substr(0, 10)}</td> | |
| <td className=""> | |
| <div className=""> | |
| <div className="product-details"> | |
| <div className="product-description"> | |
| <div style={{'display': 'inline-block', 'paddingRight': '.5rem'}}> | |
| {product.description} | |
| {product.local_currency !== product.product_currency? <span className="local_amount">{product.local_currency}: {product.local_amount}, rate: {product.exchange_rate}</span>:''} | |
| </div> | |
| {notes} | |
| </div> | |
| {/* <div className="product-notes"> | |
| <input type="text" className="form-control" value={this.state.notes || ''} placeholder="Notes" onChange={this.handleNoteChange}/> | |
| </div> */} | |
| </div> | |
| <div className="product-tags"> | |
| <span className="badge border border-secondary text-secondary">{product.product_currency } {product.product_currency !== product.local_currency? `/ ${product.local_currency}`:''}</span> | |
| <span className="badge border border-secondary text-secondary">{provider?.catalog || '000000' }</span> | |
| {/* <span className="badge border border-secondary text-secondary">{provider?.catalog || ''}</span> */} | |
| <span className="badge border border-secondary text-secondary">{provider?.account_number}</span> | |
| <span className="badge border border-secondary text-secondary">{provider?.name}</span> | |
| <span className="badge border border-secondary text-secondary">{product.product_code}</span> | |
| {this.renderCategoryGroupBadge()} | |
| {this.renderCategoryBadge()} | |
| <span className="badge border border-success text-success">{product.merchant_name}</span> | |
| {/* <span className="badge border border-info text-info"> | |
| <input type="text" className="notes-control text-info" value={this.state.notes || ''} placeholder="Enter notes" onChange={this.handleNoteChange}/> | |
| </span> */} | |
| {this.state.saving? <span className="badge"><i className="indicator fa fa-spin fa-spinner"></i></span>:''} | |
| <div className="pull-right"> | |
| <span className="badge text-info hover-visible"> | |
| <div className="custom-control custom-switch custom-control-sm"> | |
| <input type="checkbox" id={`tran-exclude-${product.id}`} className="custom-control-input" checked={product.is_excluded || false} data-id={product.id} onChange={this.handleExcludeProduct} /> | |
| <label className="custom-control-label" htmlFor={`tran-exclude-${product.id}`}>Exclude</label> | |
| </div> | |
| </span> | |
| </div> | |
| </div> | |
| </div> | |
| </td> | |
| <td className="text-right text-vertical"> | |
| <div className={`amount ${amount < 0? 'text-danger' : 'text-success'} ${product.is_excluded? 'exclude-amount':''}`}> | |
| { formatNumber(amount) } | |
| </div> | |
| <div className="balance"> | |
| {product.balance? formatNumber(product.balance) : '-' } | |
| </div> | |
| </td> | |
| <td className="text-center text-vertical"> | |
| { this.state.saving && <i className="fa fa-sync fa-spin"></i>} | |
| { !this.state.saving && <button className="btn btn-link fal fa-sync btn-sync-merchant" onClick={this.handleAutoApplyMerchant}></button> } | |
| </td> | |
| </tr>; | |
| } | |
| } | |
| // export interface ProductListProps{ | |
| // products: Product[]; | |
| // categories: Category[]; | |
| // providers: BankAccount[]; | |
| // } | |
| export class ProductList extends React.Component{ | |
| constructor(props){ | |
| super(props); | |
| this.state = { | |
| sort: 'product_date', | |
| dir: 'desc' | |
| }; | |
| } | |
| sort(column){ | |
| if(this.props.onSort){ | |
| let dir = this.state.dir; | |
| dir= (dir === 'desc'? 'asc': 'desc'); | |
| this.props.onSort(column, dir); | |
| this.setState({ sort: column, dir: dir }); | |
| } | |
| } | |
| render(){ | |
| const products = this.props.products; | |
| let empty_products; | |
| if(products.length === 0){ | |
| empty_products = <tr><td colSpan={4}>No products</td></tr>; | |
| } | |
| let current_month = '';//products[0].product_date?.substr(0, 7); | |
| return <table className="table table-bordered table-sm table-hover-border product-list"> | |
| <thead> | |
| <tr> | |
| <th style={{'width': '6rem'}} className={`sortable ${this.state.sort==='product_date'? this.state.dir:''}`} onClick={() => this.sort('product_date')}>Date</th> | |
| <th className={`sortable ${this.state.sort==='description'? this.state.dir:''}`} onClick={() => this.sort('description')}>Details</th> | |
| <th style={{'width': '5rem'}} className={`text-right sortable ${this.state.sort==='product_amount'? this.state.dir:''}`} onClick={() => this.sort('product_amount')}>Amount</th> | |
| <th className="text-right" style={{'width': '2rem'}} ></th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| { products.map((trans, i) => { | |
| let results = []; | |
| const product = (<ProductItem product={trans} categories={this.props.categories} providers={this.props.providers} key={trans.id}></ProductItem>) | |
| if(current_month !== trans.product_date.substr(0, 7)){ | |
| current_month = trans.product_date.substr(0, 7); | |
| results.push( | |
| <tr className="section-header" key={`month-category-${current_month}`}> | |
| <td colSpan={6}>{current_month}</td> | |
| </tr> | |
| ); | |
| } | |
| results.push(product); | |
| return results; | |
| })} | |
| {empty_products} | |
| </tbody> | |
| </table>; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| .product-list-no-cross-line{ | |
| .product{ | |
| .text-success{ | |
| &.exclude-amount { | |
| text-decoration: none !important; | |
| color: #62c462 !important; | |
| } | |
| } | |
| .text-danger{ | |
| &.exclude-amount { | |
| text-decoration: none !important; | |
| color: #ee5f5b !important; | |
| } | |
| } | |
| } | |
| } | |
| table.product-list{ | |
| > thead { | |
| > tr > th.sortable{ | |
| cursor: pointer; | |
| &.asc:after{ | |
| font-family: "Font Awesome 5 Pro"; | |
| content: "\f161"; | |
| padding-left: 2px; | |
| } | |
| &.desc:after{ | |
| font-family: "Font Awesome 5 Pro"; | |
| content: "\f160"; | |
| padding-left: 2px; | |
| } | |
| } | |
| } | |
| } | |
| .product { | |
| .product-details{ | |
| .product-description{ | |
| padding: 0 .5rem .3rem .5rem; | |
| .product-notes{ | |
| color: #999; | |
| // &:before{ | |
| // content: "( "; | |
| // } | |
| // &:after{ | |
| // content: " )"; | |
| // } | |
| } | |
| .local_amount{ | |
| font-weight: bold; | |
| padding: 0 .5rem; | |
| &:before{ | |
| content: "[ "; | |
| } | |
| &:after{ | |
| content: " ]"; | |
| } | |
| } | |
| } | |
| .product-notes{ | |
| margin-bottom: .3rem; | |
| position: relative; | |
| > input{ | |
| border: none; | |
| padding: .2rem .5rem; | |
| line-height: 1rem; | |
| height: calc(1.1em + 4px); | |
| border-radius: 0; | |
| color: #fff; | |
| font-size: .8rem; | |
| transition: background-color 20.5 ease-out; | |
| min-width: 15rem; | |
| &::placeholder{ | |
| color: #fff; | |
| } | |
| // &:hover { | |
| // background-color: #4b7ccd; | |
| // color: #fff; | |
| // border: solid 1px darken(#4b7ccd, 15%); | |
| // } | |
| } | |
| .notes-input{ | |
| display: none; | |
| } | |
| .notes-text{ | |
| display: inline; | |
| padding: .2rem .5rem; | |
| } | |
| } | |
| } | |
| &:hover{ | |
| .notes-input{ | |
| display: inline !important; | |
| background-color: #848b90; | |
| color: #7a8288; | |
| border: solid 1px darken(#4b7ccd, 15%); | |
| } | |
| .notes-text{ | |
| display: none; | |
| } | |
| } | |
| .product-tags{ | |
| margin-bottom: .5rem; | |
| .badge{ | |
| &:first{ | |
| margin-left: 0; | |
| } | |
| position: relative; | |
| margin: 0 5px; | |
| } | |
| input.notes-control{ | |
| border: none; | |
| font-size: .7rem; | |
| padding: 0; | |
| } | |
| } | |
| .exclude-amount{ | |
| text-decoration: line-through; | |
| color: #999 !important; | |
| } | |
| .text-vertical{ | |
| vertical-align: middle; | |
| } | |
| .amount{ | |
| font-size: 1.5rem; | |
| } | |
| .balance{ | |
| color: #666; | |
| font-size: 0.7rem; | |
| } | |
| .hover-visible{ | |
| display: none; | |
| } | |
| .hover-hidden{ | |
| display: block; | |
| } | |
| &:hover{ | |
| .hover-visible{ | |
| display: block; | |
| } | |
| .hover-hidden{ | |
| display: none !important; | |
| } | |
| } | |
| .btn-sync-merchant{ | |
| &:hover { | |
| text-decoration: none; | |
| } | |
| } | |
| } | |
| select.hidden-selection{ | |
| position: absolute; | |
| top: 0px; | |
| left: 0px; | |
| cursor: pointer; | |
| opacity: 0; | |
| } | |
| .custom-control-sm{ | |
| min-height: 0.8rem; | |
| font-size: 0.8rem; | |
| .custom-control-input{ | |
| height: 0.8rem; | |
| } | |
| .custom-control-label{ | |
| padding-top: 2px; | |
| cursor: pointer; | |
| &:before{ | |
| left: -2.16875rem; | |
| top: 0rem; | |
| height: 0.9rem; | |
| } | |
| &:after{ | |
| left:calc(-2.16875rem + 4px); | |
| height: calc(1rem - 8px); | |
| top: 3px; | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React from "react"; | |
| import debounce from 'lodash.debounce'; | |
| import './product-search-bar.scss'; | |
| import Moment from 'moment'; | |
| import DatePicker from "react-datepicker"; | |
| import "react-datepicker/dist/react-datepicker.css"; | |
| export class ProductSearchBar extends React.Component { | |
| constructor(props){ | |
| super(props); | |
| this.state = { | |
| // filter: props.filter | |
| keyword: (props.filter.keyword || ''), | |
| amount: (props.filter.amount || '') | |
| }; | |
| this.handleFilterChange = this.handleFilterChange.bind(this); | |
| this.handleTextInputChange = this.handleTextInputChange.bind(this); | |
| this.handleIsCategorisedChange = this.handleIsCategorisedChange.bind(this); | |
| this.handleDateChange = this.handleDateChange.bind(this); | |
| this.onTextInputChangeDebounce = debounce(this.onTextInputChangeDebounce, 700); | |
| } | |
| componentDidMount(){ | |
| } | |
| static getDerivedStateFromProps(props, state){ | |
| state.keyword = props.filter.keyword; | |
| state.amount = props.filter.amount; | |
| return state; | |
| } | |
| handleFilterChange(e){ | |
| const target = e.target; | |
| const name = target.name; | |
| // @ts-ignore | |
| this.props.filter[name] = target.value; | |
| this.props.onFilterChange(); | |
| } | |
| handleDateChange = (date) => { | |
| this.props.filter.month = (date == null? '':Moment(date).format("yyyy-MM-DD")); | |
| this.props.onFilterChange(); | |
| } | |
| handleTextInputChange(e){ | |
| // this.setState({}) | |
| const state = this.state; | |
| const name = e.target.name; | |
| const val = e.target.value; | |
| state[name] = val; | |
| this.props.filter[name] = val; | |
| this.setState(state); | |
| this.onTextInputChangeDebounce(val); | |
| } | |
| handleIsCategorisedChange(e){ | |
| const val = e.target.checked; | |
| this.props.filter.uncategorised_only = val?1:0; | |
| this.props.onFilterChange(); | |
| } | |
| onTextInputChangeDebounce(val){ | |
| this.props.onFilterChange();//this.state.filter); | |
| } | |
| renderInput( props, openCalendar, closeCalendar ){ | |
| function clear(){ | |
| props.onChange({target: {value: ''}}); | |
| } | |
| return ( | |
| <div> | |
| <input {...props} /> | |
| <button onClick={openCalendar}>open calendar</button> | |
| <button onClick={closeCalendar}>close calendar</button> | |
| <button onClick={clear}>clear</button> | |
| </div> | |
| ); | |
| } | |
| render(){ | |
| const { providers, categories } = this.props; | |
| let date, category_group; | |
| if(this.props.filter.month){ | |
| date = Moment(this.props.filter.month, "yyyy-MM-dd").toDate(); | |
| } | |
| return <div className="product-filter"> | |
| <div className="form-inline"> | |
| <div className="input-group mb-2 mr-sm-2"> | |
| <DatePicker className="form-control" showMonthYearPicker dateFormat="MMM yyyy" placeholderText="All" name="product_date" selected={date} onChange={this.handleDateChange}></DatePicker> | |
| <div className="input-group-append"> | |
| <span className="input-group-text fs-xl"> | |
| <i className="fa fa-calendar"></i> | |
| </span> | |
| </div> | |
| </div> | |
| <div className="form-group mb-2 mr-sm-2"> | |
| <select className="form-control" name="provider_id" value={this.props.filter.provider_id} onChange={this.handleFilterChange}> | |
| <option value="">All Bank providers</option> | |
| { providers.map((provider, i) => ( | |
| <option value={provider.id} key={provider.id}>{provider.provider_number?.substr(-4)} - {provider.provider_type} - {provider.card_holder_name}</option> | |
| ))} | |
| </select> | |
| </div> | |
| <div className="form-group mb-2 mr-sm-2"> | |
| <select className="form-control" name="category_id" value={this.props.filter.category_id} onChange={this.handleFilterChange}> | |
| <option value="">All Categories</option> | |
| {categories.map((category, i) => { | |
| const results = []; | |
| if(category_group !== category.category_group){ | |
| results.push(<optgroup label={category.category_group} key={`opt-group-${category.category_group}`}>{category.category_group}</optgroup>); | |
| category_group = category.category_group; | |
| } | |
| results.push(<option value={category.id} key={i}>{category.name}</option>); | |
| return results; | |
| })} | |
| </select> | |
| </div> | |
| <div className="input-group mb-2 mr-sm-2"> | |
| <input type="text" className="form-control" placeholder="Keyword" name="keyword" style={{width: "25rem"}} value={this.state.keyword} onChange={this.handleTextInputChange}/> | |
| <div className="input-group-append"> | |
| <button className="btn btn-primary fa fa-search"></button> | |
| </div> | |
| </div> | |
| <div className="input-group mb-2 mr-sm-2"> | |
| <div className="input-group-prepend"> | |
| <button className="btn btn-primary fa fa-pound-sign"></button> | |
| </div> | |
| <input type="text" className="form-control" placeholder="Amount" name="amount" value={this.state.amount} onChange={this.handleTextInputChange}/> | |
| </div> | |
| <div className="form-group mb-2 mr-sm-2"> | |
| <div className="custom-control custom-switch"> | |
| <input type="checkbox" id="uncategorised_only" className="custom-control-input" name="uncategorised_only" checked={this.props.filter.uncategorised_only || false} onChange={this.handleIsCategorisedChange} /> | |
| <label className="custom-control-label" htmlFor="uncategorised_only"> | |
| Uncategoried Only | |
| </label> | |
| </div> | |
| </div> | |
| </div> | |
| </div>; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| .product-filter{ | |
| margin-bottom: 1rem; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React from "react"; | |
| import { ProductSearchBar } from "./product-search-bar"; | |
| import { ProductList } from "./product-list"; | |
| import { ProductService } from "../../shared/services/product.service"; | |
| import { CategoryService } from "../../shared/services/category.service"; | |
| import { ProviderService } from "../../shared/services/bank-account.service"; | |
| import InfiniteScroll from 'react-infinite-scroller'; | |
| import { objectToQueryParams } from "../../shared/func/utils-func"; | |
| import { parse } from 'query-string'; | |
| export class ProductsModule extends React.Component{ | |
| product_service; | |
| category_service; | |
| provider_service; | |
| constructor(props){ | |
| super(props); | |
| this.state = { | |
| products: [], | |
| categories: [], | |
| providers: [], | |
| has_more: false, | |
| page: 1, | |
| page_size: 20, | |
| loading: false, | |
| sort: 'product_date', | |
| dir: 'desc', | |
| filter: { | |
| uncategorised_only: false | |
| } | |
| }; | |
| this.product_service = new ProductService(); | |
| this.category_service = new CategoryService(); | |
| this.provider_service = new ProviderService(); | |
| this.handleFilterChange = this.handleFilterChange.bind(this); | |
| } | |
| componentDidMount(){ | |
| // this.refreshProducts(); | |
| let params = parse(this.props.location.search) | |
| this.setState({ filter: params, has_more: true }); | |
| this.category_service.getAll() | |
| .then((data) => this.setState({ categories: data })); | |
| this.provider_service.getAll() | |
| .then((data2) => this.setState({ providers: data2 })); | |
| } | |
| handleLoadProduct(){ | |
| if(this.state.loading){ | |
| return; | |
| } | |
| this.setState({loading: true}); | |
| const page = this.state.page; | |
| const page_size = this.state.page_size; | |
| const filter = this.state.filter; | |
| filter.sort = this.state.sort; | |
| filter.dir = this.state.dir; | |
| this.product_service.search(filter, page, page_size) | |
| .then((data) => { | |
| const trans = this.state.products; | |
| trans.push(...data); | |
| this.setState({ products: trans, has_more: data.length >= page_size, page: page + 1, loading: false }); | |
| }); | |
| } | |
| handleFilterChange(){// filter: ProductFilter){ | |
| this.props.history.push({ pathname: this.props.location.pathname, search: '?'+objectToQueryParams(this.state.filter) }); | |
| this.setState({ products: [], page: 1, has_more: true }); | |
| } | |
| handleSort(column, dir){ | |
| this.setState({ products: [], page: 1, has_more: true, dir: dir, sort: column }); | |
| } | |
| render(){ | |
| return ( | |
| <div> | |
| <h2>Products</h2> | |
| <div> | |
| <ProductSearchBar providers={this.state.providers} categories={this.state.categories} filter={this.state.filter} onFilterChange={this.handleFilterChange}></ProductSearchBar> | |
| </div> | |
| <div> | |
| <InfiniteScroll | |
| pageStart={0} | |
| loadMore={(page) => this.handleLoadProduct()} | |
| hasMore={this.state.has_more} | |
| loader={<div className="load-indicator text-center" key={'load-indicator'}><i className="fa fa-spin fa-spinner"></i> Loading ...</div>}> | |
| <ProductList products={this.state.products} categories={this.state.categories} providers={this.state.providers} onSort={this.handleSort.bind(this)}></ProductList> | |
| </InfiniteScroll> | |
| </div> | |
| </div> | |
| ) | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { productsModule } from "../products.module"; | |
| const base_url = '/products'; | |
| const routes = [ | |
| { | |
| path: `${base_url}`, exact: true, component: ProductsModule, | |
| // routes: [ | |
| // { path: `${base_url}/bank-accounts/add`, exact: true, component: null }, | |
| // { path: `${base_url}/bank-accounts/:id/edit`, exact: true, component: null }, | |
| // ] | |
| }, | |
| ]; | |
| export default routes; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment