mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-30 19:52:20 +01:00
(feat) Add filters
This commit is contained in:
parent
dff0cb26be
commit
9a10c992c2
@ -204,13 +204,13 @@ const Orders: React.FC<OrdersProps> = ({ currentUser, onSuccess, onError }) => {
|
||||
>
|
||||
<div className='content'>
|
||||
<div className="group">
|
||||
<div className="period">
|
||||
from
|
||||
<div className="range">
|
||||
<FormInput id="period_from"
|
||||
label={t('app.admin.store.orders.filter_period_from')}
|
||||
register={register}
|
||||
type="date" />
|
||||
to
|
||||
<FormInput id="period_to"
|
||||
label={t('app.admin.store.orders.filter_period_to')}
|
||||
register={register}
|
||||
type="date" />
|
||||
</div>
|
||||
|
@ -6,6 +6,7 @@ import { Loader } from '../base/loader';
|
||||
import { IApplication } from '../../models/application';
|
||||
import { Product } from '../../models/product';
|
||||
import { ProductCategory } from '../../models/product-category';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import { ProductItem } from './product-item';
|
||||
import ProductAPI from '../../api/product';
|
||||
@ -16,6 +17,8 @@ import { X } from 'phosphor-react';
|
||||
import { StoreListHeader } from './store-list-header';
|
||||
import { FabPagination } from '../base/fab-pagination';
|
||||
import ProductLib from '../../lib/product';
|
||||
import { FormInput } from '../form/form-input';
|
||||
import { FormSelect } from '../form/form-select';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
@ -29,12 +32,12 @@ interface ProductsProps {
|
||||
*/
|
||||
type selectOption = { value: number, label: string };
|
||||
|
||||
/**
|
||||
* This component shows the admin view of the store
|
||||
*/
|
||||
/** This component shows the admin view of the store */
|
||||
const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
const { register, control, getValues } = useForm();
|
||||
|
||||
const [filteredProductsList, setFilteredProductList] = useImmer<Array<Product>>([]);
|
||||
const [features, setFeatures] = useImmer<Filters>(initFilters);
|
||||
const [filterVisible, setFilterVisible] = useState<boolean>(false);
|
||||
@ -48,7 +51,7 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
const [currentPage, setCurrentPage] = useState<number>(1);
|
||||
|
||||
useEffect(() => {
|
||||
ProductAPI.index({ page: 1 }).then(data => {
|
||||
ProductAPI.index({ page: 1, is_active: filterVisible }).then(data => {
|
||||
setPageCount(data.total_pages);
|
||||
setFilteredProductList(data.products);
|
||||
});
|
||||
@ -62,30 +65,32 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
}).catch(onError);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
ProductAPI.index({ page: currentPage }).then(data => {
|
||||
setFilteredProductList(data.products);
|
||||
setPageCount(data.total_pages);
|
||||
window.document.getElementById('content-main').scrollTo({ top: 100, behavior: 'smooth' });
|
||||
});
|
||||
}, [currentPage]);
|
||||
|
||||
useEffect(() => {
|
||||
applyFilters();
|
||||
setClearFilters(false);
|
||||
setUpdate(false);
|
||||
}, [filterVisible, clearFilters, update === true]);
|
||||
|
||||
/**
|
||||
* Goto edit product page
|
||||
*/
|
||||
/** Handle products pagination */
|
||||
const handlePagination = (page: number) => {
|
||||
if (page !== currentPage) {
|
||||
ProductAPI.index({ page, is_active: filterVisible }).then(data => {
|
||||
setCurrentPage(page);
|
||||
setFilteredProductList(data.products);
|
||||
setPageCount(data.total_pages);
|
||||
window.document.getElementById('content-main').scrollTo({ top: 100, behavior: 'smooth' });
|
||||
}).catch(() => {
|
||||
onError(t('app.admin.store.products.unexpected_error_occurred'));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/** Goto edit product page */
|
||||
const editProduct = (product: Product) => {
|
||||
window.location.href = `/#!/admin/store/products/${product.id}/edit`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a product
|
||||
*/
|
||||
/** Delete a product */
|
||||
const deleteProduct = async (productId: number): Promise<void> => {
|
||||
try {
|
||||
await ProductAPI.destroy(productId);
|
||||
@ -97,24 +102,18 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Goto new product page
|
||||
*/
|
||||
/** Goto new product page */
|
||||
const newProduct = (): void => {
|
||||
window.location.href = '/#!/admin/store/products/new';
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter: toggle non-available products visibility
|
||||
*/
|
||||
/** Filter: toggle non-available products visibility */
|
||||
const toggleVisible = (checked: boolean) => {
|
||||
setFilterVisible(!filterVisible);
|
||||
console.log('Display on the shelf product only:', checked);
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter: by categories
|
||||
*/
|
||||
/** Filter: by categories */
|
||||
const handleSelectCategory = (c: ProductCategory, checked: boolean, instantUpdate?: boolean) => {
|
||||
let list = [...filters.categories];
|
||||
const children = productCategories
|
||||
@ -151,9 +150,7 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter: by machines
|
||||
*/
|
||||
/** Filter: by machines */
|
||||
const handleSelectMachine = (m: checklistOption, checked, instantUpdate?) => {
|
||||
const list = [...filters.machines];
|
||||
checked
|
||||
@ -167,16 +164,39 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Display option: sorting
|
||||
*/
|
||||
/** Filter: by keyword or ref */
|
||||
const handleKeyword = (evt: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFilters(draft => {
|
||||
return { ...draft, keywords: evt.target.value };
|
||||
});
|
||||
};
|
||||
|
||||
/** Filter: by stock range */
|
||||
const handleStockRange = () => {
|
||||
setFilters(draft => {
|
||||
return {
|
||||
...draft,
|
||||
stock_type: buildStockOptions()[getValues('stock_type')].label,
|
||||
stock_from: getValues('stock_from'),
|
||||
stock_to: getValues('stock_to')
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/** Creates sorting options to the react-select format */
|
||||
const buildStockOptions = (): Array<selectOption> => {
|
||||
return [
|
||||
{ value: 0, label: t('app.admin.store.products.stock_internal') },
|
||||
{ value: 1, label: t('app.admin.store.products.stock_external') }
|
||||
];
|
||||
};
|
||||
|
||||
/** Display option: sorting */
|
||||
const handleSorting = (option: selectOption) => {
|
||||
console.log('Sort option:', option);
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply filters
|
||||
*/
|
||||
/** Apply filters */
|
||||
const applyFilters = () => {
|
||||
let tags = initFilters;
|
||||
|
||||
@ -192,19 +212,15 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
console.log('Apply filters:', filters);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear filters
|
||||
*/
|
||||
/** Clear filters */
|
||||
const clearAllFilters = () => {
|
||||
setFilters(initFilters);
|
||||
setClearFilters(true);
|
||||
console.log('Clear all filters');
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates sorting options to the react-select format
|
||||
*/
|
||||
const buildOptions = (): Array<selectOption> => {
|
||||
/** Creates sorting options to the react-select format */
|
||||
const buildSortOptions = (): Array<selectOption> => {
|
||||
return [
|
||||
{ value: 0, label: t('app.admin.store.products.sort.name_az') },
|
||||
{ value: 1, label: t('app.admin.store.products.sort.name_za') },
|
||||
@ -213,9 +229,7 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Open/close accordion items
|
||||
*/
|
||||
/** Open/close accordion items */
|
||||
const handleAccordion = (id, state) => {
|
||||
setAccordion({ ...accordion, [id]: state });
|
||||
};
|
||||
@ -271,12 +285,54 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
<FabButton onClick={() => setUpdate(true)} className="is-info">{t('app.admin.store.products.filter_apply')}</FabButton>
|
||||
</div>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem id={2}
|
||||
isOpen={accordion[2]}
|
||||
onChange={handleAccordion}
|
||||
label={t('app.admin.store.products.filter_keywords_reference')}
|
||||
>
|
||||
<div className="content">
|
||||
<div className="group">
|
||||
<input type="text" onChange={event => handleKeyword(event)} />
|
||||
<FabButton onClick={() => setUpdate(true)} className="is-info">{t('app.admin.store.products.filter_apply')}</FabButton>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem id={3}
|
||||
isOpen={accordion[3]}
|
||||
onChange={handleAccordion}
|
||||
label={t('app.admin.store.products.filter_stock')}
|
||||
>
|
||||
<div className="content">
|
||||
<div className="group">
|
||||
<FormSelect id="stock_type"
|
||||
options={buildStockOptions()}
|
||||
valueDefault={0}
|
||||
control={control}
|
||||
/>
|
||||
<div className='range'>
|
||||
<FormInput id="stock_from"
|
||||
label={t('app.admin.store.products.filter_stock_from')}
|
||||
register={register}
|
||||
defaultValue={filters.stock_from}
|
||||
type="number" />
|
||||
<FormInput id="stock_to"
|
||||
label={t('app.admin.store.products.filter_stock_to')}
|
||||
register={register}
|
||||
defaultValue={filters.stock_to}
|
||||
type="number" />
|
||||
</div>
|
||||
<FabButton onClick={handleStockRange} className="is-info">{t('app.admin.store.products.filter_apply')}</FabButton>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
</div>
|
||||
<div className='store-list'>
|
||||
<StoreListHeader
|
||||
productsCount={filteredProductsList.length}
|
||||
selectOptions={buildOptions()}
|
||||
selectOptions={buildSortOptions()}
|
||||
onSelectOptionsChange={handleSorting}
|
||||
switchChecked={filterVisible}
|
||||
onSwitch={toggleVisible}
|
||||
@ -307,7 +363,7 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
))}
|
||||
</div>
|
||||
{pageCount > 1 &&
|
||||
<FabPagination pageCount={pageCount} currentPage={currentPage} selectPage={setCurrentPage} />
|
||||
<FabPagination pageCount={pageCount} currentPage={currentPage} selectPage={handlePagination} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@ -324,45 +380,30 @@ const ProductsWrapper: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
|
||||
Application.Components.component('products', react2angular(ProductsWrapper, ['onSuccess', 'onError']));
|
||||
|
||||
/**
|
||||
* Option format, expected by checklist
|
||||
*/
|
||||
/** Option format, expected by checklist */
|
||||
type checklistOption = { value: number, label: string };
|
||||
|
||||
/**
|
||||
* Convert the provided array of items to the checklist format
|
||||
*/
|
||||
/** Convert the provided array of items to the checklist format */
|
||||
const buildChecklistOptions = (items: Array<{ id?: number, name: string }>): Array<checklistOption> => {
|
||||
return items.map(t => {
|
||||
return { value: t.id, label: t.name };
|
||||
});
|
||||
};
|
||||
|
||||
interface Stock {
|
||||
from: number,
|
||||
to: number
|
||||
}
|
||||
|
||||
interface Filters {
|
||||
instant: boolean,
|
||||
categories: ProductCategory[],
|
||||
machines: checklistOption[],
|
||||
keywords: string[],
|
||||
internalStock: Stock,
|
||||
externalStock: Stock
|
||||
stock_type: 'internal' | 'external',
|
||||
stock_from: number,
|
||||
stock_to: number
|
||||
}
|
||||
|
||||
const initFilters: Filters = {
|
||||
instant: false,
|
||||
categories: [],
|
||||
machines: [],
|
||||
keywords: [],
|
||||
internalStock: {
|
||||
from: 0,
|
||||
to: null
|
||||
},
|
||||
externalStock: {
|
||||
from: 0,
|
||||
to: null
|
||||
}
|
||||
stock_type: 'internal',
|
||||
stock_from: 0,
|
||||
stock_to: 0
|
||||
};
|
||||
|
@ -103,17 +103,19 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
opacity: 1;
|
||||
&.u-scrollbar { overflow: hidden auto; }
|
||||
|
||||
&.u-scrollbar {
|
||||
overflow: hidden auto;
|
||||
label {
|
||||
margin: 0 0.8rem 0 0;
|
||||
padding: 0.6rem;
|
||||
&:hover { background-color: var(--gray-soft-light); }
|
||||
}
|
||||
}
|
||||
|
||||
& > label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&:hover {
|
||||
background-color: var(--gray-soft-light);
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type=checkbox] { margin: 0 0.8rem 0 0; }
|
||||
p {
|
||||
margin: 0;
|
||||
@ -121,9 +123,11 @@
|
||||
}
|
||||
&.offset { margin-left: 1.6rem; }
|
||||
}
|
||||
.form-item-field { width: 100%; }
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
min-height: 4rem;
|
||||
padding: 0 0.8rem;
|
||||
@ -144,9 +148,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.period {
|
||||
.range {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1.6rem;
|
||||
label {
|
||||
margin: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
@ -1934,6 +1934,7 @@ en:
|
||||
required: "This field is required"
|
||||
slug_pattern: "Only lowercase alphanumeric groups of characters separated by an hyphen"
|
||||
products:
|
||||
unexpected_error_occurred: "An unexpected error occurred. Please try again later."
|
||||
all_products: "All products"
|
||||
create_a_product: "Create a product"
|
||||
successfully_deleted: "The product has been successfully deleted"
|
||||
@ -1945,6 +1946,8 @@ en:
|
||||
filter_machines: "By machines"
|
||||
filter_keywords_reference: "By keywords or reference"
|
||||
filter_stock: "By stock status"
|
||||
stock_internal: "Private stock"
|
||||
stock_external: "Public stock"
|
||||
filter_stock_from: "From"
|
||||
filter_stock_to: "to"
|
||||
sort:
|
||||
@ -2043,6 +2046,8 @@ en:
|
||||
filter_status: "By status"
|
||||
filter_client: "By client"
|
||||
filter_period: "By period"
|
||||
filter_period_from: "From"
|
||||
filter_period_to: "to"
|
||||
status:
|
||||
error: "Payment error"
|
||||
canceled: "Canceled"
|
||||
|
Loading…
x
Reference in New Issue
Block a user