mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-21 15:54:22 +01:00
(quality) refactored categories sorting + fix ts issues
This commit is contained in:
parent
3f08d831b9
commit
45bac88b26
@ -42,7 +42,7 @@ export const FabModal: React.FC<FabModalProps> = ({ title, isOpen, toggleModal,
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen}
|
<Modal isOpen={isOpen}
|
||||||
className={`fab-modal fab-modal-${width} ${className}`}
|
className={`fab-modal fab-modal-${width} ${className || ''}`}
|
||||||
overlayClassName="fab-modal-overlay"
|
overlayClassName="fab-modal-overlay"
|
||||||
onRequestClose={toggleModal}>
|
onRequestClose={toggleModal}>
|
||||||
{closeButton && <FabButton className="modal-btn--close" onClick={toggleModal}>{t('app.shared.fab_modal.close')}</FabButton>}
|
{closeButton && <FabButton className="modal-btn--close" onClick={toggleModal}>{t('app.shared.fab_modal.close')}</FabButton>}
|
||||||
|
@ -95,7 +95,7 @@ export const StripeCardUpdate: React.FC<StripeCardUpdateProps> = ({ onSubmit, on
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} id="stripe-card" className={`stripe-card-update ${className}`}>
|
<form onSubmit={handleSubmit} id="stripe-card" className={`stripe-card-update ${className || ''}`}>
|
||||||
<CardElement options={cardOptions} />
|
<CardElement options={cardOptions} />
|
||||||
{children}
|
{children}
|
||||||
</form>
|
</form>
|
||||||
|
@ -69,7 +69,6 @@ export const ManageProductCategory: React.FC<ManageProductCategoryProps> = ({ pr
|
|||||||
<div className='manage-product-category'>
|
<div className='manage-product-category'>
|
||||||
{ toggleBtn() }
|
{ toggleBtn() }
|
||||||
<FabModal title={t(`app.admin.store.manage_product_category.${action}`)}
|
<FabModal title={t(`app.admin.store.manage_product_category.${action}`)}
|
||||||
className="fab-modal-lg"
|
|
||||||
width={ModalSize.large}
|
width={ModalSize.large}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
toggleModal={toggleModal}
|
toggleModal={toggleModal}
|
||||||
|
@ -10,6 +10,7 @@ import { HtmlTranslate } from '../../base/html-translate';
|
|||||||
import { IApplication } from '../../../models/application';
|
import { IApplication } from '../../../models/application';
|
||||||
import { Loader } from '../../base/loader';
|
import { Loader } from '../../base/loader';
|
||||||
import { react2angular } from 'react2angular';
|
import { react2angular } from 'react2angular';
|
||||||
|
import ProductLib from '../../../lib/product';
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
@ -54,18 +55,7 @@ const ProductCategories: React.FC<ProductCategoriesProps> = ({ onSuccess, onErro
|
|||||||
*/
|
*/
|
||||||
const refreshCategories = () => {
|
const refreshCategories = () => {
|
||||||
ProductCategoryAPI.index().then(data => {
|
ProductCategoryAPI.index().then(data => {
|
||||||
// Map product categories by position
|
setProductCategories(new ProductLib().sortCategories(data));
|
||||||
const sortedCategories = data
|
|
||||||
.filter(c => !c.parent_id)
|
|
||||||
.sort((a, b) => a.position - b.position);
|
|
||||||
const childrenCategories = data
|
|
||||||
.filter(c => typeof c.parent_id === 'number')
|
|
||||||
.sort((a, b) => b.position - a.position);
|
|
||||||
childrenCategories.forEach(c => {
|
|
||||||
const parentIndex = sortedCategories.findIndex(i => i.id === c.parent_id);
|
|
||||||
sortedCategories.splice(parentIndex + 1, 0, c);
|
|
||||||
});
|
|
||||||
setProductCategories(sortedCategories);
|
|
||||||
}).catch((error) => onError(error));
|
}).catch((error) => onError(error));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useForm, useWatch } from 'react-hook-form';
|
import { SubmitHandler, useForm, useWatch } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import slugify from 'slugify';
|
import slugify from 'slugify';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
@ -19,6 +19,7 @@ import MachineAPI from '../../api/machine';
|
|||||||
import ProductAPI from '../../api/product';
|
import ProductAPI from '../../api/product';
|
||||||
import { Plus } from 'phosphor-react';
|
import { Plus } from 'phosphor-react';
|
||||||
import { ProductStockForm } from './product-stock-form';
|
import { ProductStockForm } from './product-stock-form';
|
||||||
|
import ProductLib from '../../lib/product';
|
||||||
|
|
||||||
interface ProductFormProps {
|
interface ProductFormProps {
|
||||||
product: Product,
|
product: Product,
|
||||||
@ -53,18 +54,7 @@ export const ProductForm: React.FC<ProductFormProps> = ({ product, title, onSucc
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ProductCategoryAPI.index().then(data => {
|
ProductCategoryAPI.index().then(data => {
|
||||||
// Map product categories by position
|
setProductCategories(buildSelectOptions(new ProductLib().sortCategories(data)));
|
||||||
const sortedCategories = data
|
|
||||||
.filter(c => !c.parent_id)
|
|
||||||
.sort((a, b) => a.position - b.position);
|
|
||||||
const childrenCategories = data
|
|
||||||
.filter(c => typeof c.parent_id === 'number')
|
|
||||||
.sort((a, b) => b.position - a.position);
|
|
||||||
childrenCategories.forEach(c => {
|
|
||||||
const parentIndex = sortedCategories.findIndex(i => i.id === c.parent_id);
|
|
||||||
sortedCategories.splice(parentIndex + 1, 0, c);
|
|
||||||
});
|
|
||||||
setProductCategories(buildSelectOptions(sortedCategories));
|
|
||||||
}).catch(onError);
|
}).catch(onError);
|
||||||
MachineAPI.index({ disabled: false }).then(data => {
|
MachineAPI.index({ disabled: false }).then(data => {
|
||||||
setMachines(buildChecklistOptions(data));
|
setMachines(buildChecklistOptions(data));
|
||||||
@ -111,10 +101,8 @@ export const ProductForm: React.FC<ProductFormProps> = ({ product, title, onSucc
|
|||||||
/**
|
/**
|
||||||
* Callback triggered when the form is submitted: process with the product creation or update.
|
* Callback triggered when the form is submitted: process with the product creation or update.
|
||||||
*/
|
*/
|
||||||
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
const onSubmit: SubmitHandler<Product> = (data: Product) => {
|
||||||
return handleSubmit((data: Product) => {
|
saveProduct(data);
|
||||||
saveProduct(data);
|
|
||||||
})(event);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -236,13 +224,13 @@ export const ProductForm: React.FC<ProductFormProps> = ({ product, title, onSucc
|
|||||||
<FabButton className="main-action-btn" onClick={handleSubmit(saveProduct)}>{t('app.admin.store.product_form.save')}</FabButton>
|
<FabButton className="main-action-btn" onClick={handleSubmit(saveProduct)}>{t('app.admin.store.product_form.save')}</FabButton>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<form className="product-form" onSubmit={onSubmit}>
|
<form className="product-form" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className='tabs'>
|
<div className='tabs'>
|
||||||
<p className={!stockTab ? 'is-active' : ''} onClick={() => setStockTab(false)}>{t('app.admin.store.product_form.product_parameters')}</p>
|
<p className={!stockTab ? 'is-active' : ''} onClick={() => setStockTab(false)}>{t('app.admin.store.product_form.product_parameters')}</p>
|
||||||
<p className={stockTab ? 'is-active' : ''} onClick={() => setStockTab(true)}>{t('app.admin.store.product_form.stock_management')}</p>
|
<p className={stockTab ? 'is-active' : ''} onClick={() => setStockTab(true)}>{t('app.admin.store.product_form.stock_management')}</p>
|
||||||
</div>
|
</div>
|
||||||
{stockTab
|
{stockTab
|
||||||
? <ProductStockForm product={product} register={register} control={control} id="stock" onError={onError} onSuccess={onSuccess} />
|
? <ProductStockForm product={product} register={register} control={control} formState={formState} onError={onError} onSuccess={onSuccess} />
|
||||||
: <section>
|
: <section>
|
||||||
<div className="subgrid">
|
<div className="subgrid">
|
||||||
<FormInput id="name"
|
<FormInput id="name"
|
||||||
|
@ -12,6 +12,7 @@ import { FabButton } from '../base/fab-button';
|
|||||||
import { PencilSimple } from 'phosphor-react';
|
import { PencilSimple } from 'phosphor-react';
|
||||||
import { FabModal, ModalSize } from '../base/fab-modal';
|
import { FabModal, ModalSize } from '../base/fab-modal';
|
||||||
import { ProductStockModal } from './product-stock-modal';
|
import { ProductStockModal } from './product-stock-modal';
|
||||||
|
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||||
import { FabStateLabel } from '../base/fab-state-label';
|
import { FabStateLabel } from '../base/fab-state-label';
|
||||||
|
|
||||||
interface ProductStockFormProps<TFieldValues, TContext extends object> {
|
interface ProductStockFormProps<TFieldValues, TContext extends object> {
|
||||||
@ -26,7 +27,7 @@ interface ProductStockFormProps<TFieldValues, TContext extends object> {
|
|||||||
/**
|
/**
|
||||||
* Form tab to manage a product's stock
|
* Form tab to manage a product's stock
|
||||||
*/
|
*/
|
||||||
export const ProductStockForm = <TFieldValues, TContext extends object> ({ product, register, control, formState, onError, onSuccess }: ProductStockFormProps<TFieldValues, TContext>) => {
|
export const ProductStockForm = <TFieldValues extends FieldValues, TContext extends object> ({ product, register, control, formState, onError, onSuccess }: ProductStockFormProps<TFieldValues, TContext>) => {
|
||||||
const { t } = useTranslation('admin');
|
const { t } = useTranslation('admin');
|
||||||
|
|
||||||
const [activeThreshold, setActiveThreshold] = useState<boolean>(false);
|
const [activeThreshold, setActiveThreshold] = useState<boolean>(false);
|
||||||
@ -197,12 +198,11 @@ export const ProductStockForm = <TFieldValues, TContext extends object> ({ produ
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FabModal title={t('app.admin.store.product_stock_form.modal_title')}
|
<FabModal title={t('app.admin.store.product_stock_form.modal_title')}
|
||||||
className="fab-modal-lg"
|
|
||||||
width={ModalSize.large}
|
width={ModalSize.large}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
toggleModal={toggleModal}
|
toggleModal={toggleModal}
|
||||||
closeButton>
|
closeButton>
|
||||||
<ProductStockModal product={product} register={register} control={control} id="stock-modal" onError={onError} onSuccess={onSuccess} />
|
<ProductStockModal product={product} register={register} control={control} formState={formState} onError={onError} onSuccess={onSuccess} />
|
||||||
</FabModal>
|
</FabModal>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
@ -6,6 +6,7 @@ import { Control, FormState } from 'react-hook-form/dist/types/form';
|
|||||||
import { FormSelect } from '../form/form-select';
|
import { FormSelect } from '../form/form-select';
|
||||||
import { FormInput } from '../form/form-input';
|
import { FormInput } from '../form/form-input';
|
||||||
import { FabButton } from '../base/fab-button';
|
import { FabButton } from '../base/fab-button';
|
||||||
|
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||||
|
|
||||||
type selectOption = { value: number, label: string };
|
type selectOption = { value: number, label: string };
|
||||||
|
|
||||||
@ -23,7 +24,7 @@ interface ProductStockModalProps<TFieldValues, TContext extends object> {
|
|||||||
*/
|
*/
|
||||||
// TODO: delete next eslint disable
|
// TODO: delete next eslint disable
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
export const ProductStockModal = <TFieldValues, TContext extends object> ({ product, register, control, formState, onError, onSuccess }: ProductStockModalProps<TFieldValues, TContext>) => {
|
export const ProductStockModal = <TFieldValues extends FieldValues, TContext extends object> ({ product, register, control, formState, onError, onSuccess }: ProductStockModalProps<TFieldValues, TContext>) => {
|
||||||
const { t } = useTranslation('admin');
|
const { t } = useTranslation('admin');
|
||||||
|
|
||||||
const [movement, setMovement] = useState<'in' | 'out'>('in');
|
const [movement, setMovement] = useState<'in' | 'out'>('in');
|
||||||
|
@ -14,6 +14,7 @@ import MachineAPI from '../../api/machine';
|
|||||||
import { AccordionItem } from './accordion-item';
|
import { AccordionItem } from './accordion-item';
|
||||||
import { X } from 'phosphor-react';
|
import { X } from 'phosphor-react';
|
||||||
import { StoreListHeader } from './store-list-header';
|
import { StoreListHeader } from './store-list-header';
|
||||||
|
import ProductLib from '../../lib/product';
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
@ -52,18 +53,7 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ProductCategoryAPI.index().then(data => {
|
ProductCategoryAPI.index().then(data => {
|
||||||
// Map product categories by position
|
setProductCategories(new ProductLib().sortCategories(data));
|
||||||
const sortedCategories = data
|
|
||||||
.filter(c => !c.parent_id)
|
|
||||||
.sort((a, b) => a.position - b.position);
|
|
||||||
const childrenCategories = data
|
|
||||||
.filter(c => typeof c.parent_id === 'number')
|
|
||||||
.sort((a, b) => b.position - a.position);
|
|
||||||
childrenCategories.forEach(c => {
|
|
||||||
const parentIndex = sortedCategories.findIndex(i => i.id === c.parent_id);
|
|
||||||
sortedCategories.splice(parentIndex + 1, 0, c);
|
|
||||||
});
|
|
||||||
setProductCategories(sortedCategories);
|
|
||||||
}).catch(onError);
|
}).catch(onError);
|
||||||
|
|
||||||
MachineAPI.index({ disabled: false }).then(data => {
|
MachineAPI.index({ disabled: false }).then(data => {
|
||||||
|
@ -179,7 +179,7 @@ export const UserProfileForm: React.FC<UserProfileFormProps> = ({ action, size,
|
|||||||
const userNetworks = new UserLib(user).getUserSocialNetworks();
|
const userNetworks = new UserLib(user).getUserSocialNetworks();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className={`user-profile-form user-profile-form--${size} ${className}`} onSubmit={onSubmit}>
|
<form className={`user-profile-form user-profile-form--${size} ${className || ''}`} onSubmit={onSubmit}>
|
||||||
<div className="avatar-group">
|
<div className="avatar-group">
|
||||||
<AvatarInput currentAvatar={output.profile_attributes?.user_avatar_attributes?.attachment_url}
|
<AvatarInput currentAvatar={output.profile_attributes?.user_avatar_attributes?.attachment_url}
|
||||||
userName={`${output.profile_attributes?.first_name} ${output.profile_attributes?.last_name}`}
|
userName={`${output.profile_attributes?.first_name} ${output.profile_attributes?.last_name}`}
|
||||||
|
21
app/frontend/src/javascript/lib/product.ts
Normal file
21
app/frontend/src/javascript/lib/product.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { ProductCategory } from '../models/product-category';
|
||||||
|
|
||||||
|
export default class ProductLib {
|
||||||
|
/**
|
||||||
|
* Map product categories by position
|
||||||
|
* @param categories unsorted categories, as returned by the API
|
||||||
|
*/
|
||||||
|
sortCategories = (categories: Array<ProductCategory>): Array<ProductCategory> => {
|
||||||
|
const sortedCategories = categories
|
||||||
|
.filter(c => !c.parent_id)
|
||||||
|
.sort((a, b) => a.position - b.position);
|
||||||
|
const childrenCategories = categories
|
||||||
|
.filter(c => typeof c.parent_id === 'number')
|
||||||
|
.sort((a, b) => b.position - a.position);
|
||||||
|
childrenCategories.forEach(c => {
|
||||||
|
const parentIndex = sortedCategories.findIndex(i => i.id === c.parent_id);
|
||||||
|
sortedCategories.splice(parentIndex + 1, 0, c);
|
||||||
|
});
|
||||||
|
return sortedCategories;
|
||||||
|
};
|
||||||
|
}
|
@ -16,11 +16,11 @@ export interface Stock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Product {
|
export interface Product {
|
||||||
id: number,
|
id?: number,
|
||||||
name: string,
|
name: string,
|
||||||
slug: string,
|
slug: string,
|
||||||
sku: string,
|
sku?: string,
|
||||||
description: string,
|
description?: string,
|
||||||
is_active: boolean,
|
is_active: boolean,
|
||||||
product_category_id?: number,
|
product_category_id?: number,
|
||||||
amount?: number,
|
amount?: number,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user