mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-26 20:54:21 +01:00
(wip) Convert product-category to RFH
This commit is contained in:
parent
5826f462d6
commit
39c8ec3c3e
@ -0,0 +1,84 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ProductCategory } from '../../models/product-category';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import { FabModal, ModalSize } from '../base/fab-modal';
|
||||
import { ProductCategoryForm } from './product-category-form';
|
||||
|
||||
interface ManageProductCategoryProps {
|
||||
action: 'create' | 'update' | 'delete',
|
||||
productCategories: Array<ProductCategory>,
|
||||
productCategory?: ProductCategory,
|
||||
onSuccess: (message: string) => void,
|
||||
onError: (message: string) => void,
|
||||
}
|
||||
|
||||
/**
|
||||
* This component shows a button.
|
||||
* When clicked, we show a modal dialog allowing to fill the parameters of a product category (create new or update existing).
|
||||
*/
|
||||
export const ManageProductCategory: React.FC<ManageProductCategoryProps> = ({ productCategories, productCategory, action, onSuccess, onError }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
// is the modal open?
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
|
||||
/**
|
||||
* Opens/closes the product category modal
|
||||
*/
|
||||
const toggleModal = (): void => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
/**
|
||||
* Close the modal if the form submission was successful
|
||||
*/
|
||||
const handleSuccess = (message) => {
|
||||
setIsOpen(false);
|
||||
onSuccess(message);
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the appropriate button depending on the action type
|
||||
*/
|
||||
const toggleBtn = () => {
|
||||
switch (action) {
|
||||
case 'create':
|
||||
return (
|
||||
<FabButton type='button'
|
||||
className="create-button"
|
||||
onClick={toggleModal}>
|
||||
{t('app.admin.store.manage_product_category.create')}
|
||||
</FabButton>
|
||||
);
|
||||
case 'update':
|
||||
return (<FabButton type='button'
|
||||
icon={<i className="fas fa-pen" />}
|
||||
className="edit-button"
|
||||
onClick={toggleModal} />);
|
||||
case 'delete':
|
||||
return (<FabButton type='button'
|
||||
icon={<i className="fa fa-trash" />}
|
||||
className="delete-button"
|
||||
onClick={toggleModal} />);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='manage-product-category'>
|
||||
{ toggleBtn() }
|
||||
<FabModal title={t(`app.admin.store.manage_product_category.${action}`)}
|
||||
className="fab-modal-lg"
|
||||
width={ModalSize.large}
|
||||
isOpen={isOpen}
|
||||
toggleModal={toggleModal}
|
||||
closeButton>
|
||||
{ (action === 'update' || action === 'delete') && <p className='subtitle'>{productCategory.name}</p>}
|
||||
<ProductCategoryForm action={action}
|
||||
productCategories={productCategories}
|
||||
productCategory={productCategory}
|
||||
onSuccess={handleSuccess} onError={onError} />
|
||||
</FabModal>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,47 +1,39 @@
|
||||
import React from 'react';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import { ProductCategory } from '../../models/product-category';
|
||||
import { DotsSixVertical } from 'phosphor-react';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import { ManageProductCategory } from './manage-product-category';
|
||||
|
||||
interface ProductCategoriesListProps {
|
||||
productCategories: Array<ProductCategory>,
|
||||
onEdit: (category: ProductCategory) => void,
|
||||
onDelete: (categoryId: number) => void,
|
||||
onSuccess: (message: string) => void,
|
||||
onError: (message: string) => void,
|
||||
}
|
||||
|
||||
/**
|
||||
* This component shows a Tree list of all Product's Categories
|
||||
*/
|
||||
export const ProductCategoriesList: React.FC<ProductCategoriesListProps> = ({ productCategories, onEdit, onDelete }) => {
|
||||
/**
|
||||
* Init the process of editing the given product category
|
||||
*/
|
||||
const editProductCategory = (category: ProductCategory): () => void => {
|
||||
return (): void => {
|
||||
onEdit(category);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Init the process of delete the given product category
|
||||
*/
|
||||
const deleteProductCategory = (categoryId: number): () => void => {
|
||||
return (): void => {
|
||||
onDelete(categoryId);
|
||||
};
|
||||
};
|
||||
|
||||
export const ProductCategoriesList: React.FC<ProductCategoriesListProps> = ({ productCategories, onSuccess, onError }) => {
|
||||
return (
|
||||
<div>
|
||||
<div className='product-categories-list'>
|
||||
{productCategories.map((category) => (
|
||||
<div key={category.id}>
|
||||
{category.name}
|
||||
<div className="buttons">
|
||||
<FabButton className="edit-btn" onClick={editProductCategory(category)}>
|
||||
<i className="fa fa-edit" />
|
||||
</FabButton>
|
||||
<FabButton className="delete-btn" onClick={deleteProductCategory(category.id)}>
|
||||
<i className="fa fa-trash" />
|
||||
</FabButton>
|
||||
<div key={category.id} className='product-categories-item'>
|
||||
<div className='itemInfo'>
|
||||
<p className='itemInfo-name'>{category.name}</p>
|
||||
<span className='itemInfo-count'>[count]</span>
|
||||
</div>
|
||||
<div className='action'>
|
||||
<div className='manage'>
|
||||
<ManageProductCategory action='update'
|
||||
productCategories={productCategories}
|
||||
productCategory={category}
|
||||
onSuccess={onSuccess} onError={onError} />
|
||||
<ManageProductCategory action='delete'
|
||||
productCategories={productCategories}
|
||||
productCategory={category}
|
||||
onSuccess={onSuccess} onError={onError} />
|
||||
</div>
|
||||
<FabButton icon={<DotsSixVertical size={16} />} className='draghandle' />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
@ -1,15 +1,14 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { react2angular } from 'react2angular';
|
||||
import { HtmlTranslate } from '../base/html-translate';
|
||||
import { Loader } from '../base/loader';
|
||||
import { IApplication } from '../../models/application';
|
||||
import { FabAlert } from '../base/fab-alert';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import { ProductCategoriesList } from './product-categories-list';
|
||||
import { ProductCategoryModal } from './product-category-modal';
|
||||
import { ProductCategory } from '../../models/product-category';
|
||||
import ProductCategoryAPI from '../../api/product-category';
|
||||
import { ManageProductCategory } from './manage-product-category';
|
||||
import { ProductCategoriesList } from './product-categories-list';
|
||||
import { FabAlert } from '../base/fab-alert';
|
||||
import { HtmlTranslate } from '../base/html-translate';
|
||||
import { IApplication } from '../../models/application';
|
||||
import { Loader } from '../base/loader';
|
||||
import { react2angular } from 'react2angular';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
@ -24,81 +23,46 @@ interface ProductCategoriesProps {
|
||||
const ProductCategories: React.FC<ProductCategoriesProps> = ({ onSuccess, onError }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
const [isOpenProductCategoryModal, setIsOpenProductCategoryModal] = useState<boolean>(false);
|
||||
// List of all products' categories
|
||||
const [productCategories, setProductCategories] = useState<Array<ProductCategory>>([]);
|
||||
const [productCategory, setProductCategory] = useState<ProductCategory>(null);
|
||||
|
||||
// load the categories list on component mount
|
||||
useEffect(() => {
|
||||
ProductCategoryAPI.index().then(data => {
|
||||
setProductCategories(data);
|
||||
});
|
||||
refreshCategories();
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Open create new product category modal
|
||||
* The creation/edition/deletion was successful.
|
||||
* Show the provided message and refresh the list
|
||||
*/
|
||||
const openProductCategoryModal = () => {
|
||||
setIsOpenProductCategoryModal(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* toggle create/edit product category modal
|
||||
*/
|
||||
const toggleCreateAndEditProductCategoryModal = () => {
|
||||
setIsOpenProductCategoryModal(!isOpenProductCategoryModal);
|
||||
};
|
||||
|
||||
/**
|
||||
* callback handle save product category success
|
||||
*/
|
||||
const onSaveProductCategorySuccess = (message: string) => {
|
||||
setIsOpenProductCategoryModal(false);
|
||||
const handleSuccess = (message: string): void => {
|
||||
onSuccess(message);
|
||||
refreshCategories();
|
||||
};
|
||||
|
||||
/**
|
||||
* Refresh the list of categories
|
||||
*/
|
||||
const refreshCategories = () => {
|
||||
ProductCategoryAPI.index().then(data => {
|
||||
setProductCategories(data);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Open edit the product category modal
|
||||
*/
|
||||
const editProductCategory = (category: ProductCategory) => {
|
||||
setProductCategory(category);
|
||||
setIsOpenProductCategoryModal(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a product category
|
||||
*/
|
||||
const deleteProductCategory = async (categoryId: number): Promise<void> => {
|
||||
try {
|
||||
await ProductCategoryAPI.destroy(categoryId);
|
||||
const data = await ProductCategoryAPI.index();
|
||||
setProductCategories(data);
|
||||
onSuccess(t('app.admin.store.product_categories.successfully_deleted'));
|
||||
} catch (e) {
|
||||
onError(t('app.admin.store.product_categories.unable_to_delete') + e);
|
||||
}
|
||||
}).catch((error) => onError(error));
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>{t('app.admin.store.product_categories.the_categories')}</h2>
|
||||
<FabButton className="save" onClick={openProductCategoryModal}>{t('app.admin.store.product_categories.create_a_product_category')}</FabButton>
|
||||
<ProductCategoryModal isOpen={isOpenProductCategoryModal}
|
||||
productCategories={productCategories}
|
||||
productCategory={productCategory}
|
||||
toggleModal={toggleCreateAndEditProductCategoryModal}
|
||||
onSuccess={onSaveProductCategorySuccess}
|
||||
onError={onError} />
|
||||
<div className='product-categories'>
|
||||
<header>
|
||||
<h2>{t('app.admin.store.product_categories.title')}</h2>
|
||||
<ManageProductCategory action='create'
|
||||
productCategories={productCategories}
|
||||
onSuccess={handleSuccess} onError={onError} />
|
||||
</header>
|
||||
<FabAlert level="warning">
|
||||
<HtmlTranslate trKey="app.admin.store.product_categories.info" />
|
||||
</FabAlert>
|
||||
<ProductCategoriesList
|
||||
productCategories={productCategories}
|
||||
onEdit={editProductCategory}
|
||||
onDelete={deleteProductCategory}
|
||||
/>
|
||||
onSuccess={handleSuccess} onError={onError} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,38 +1,39 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Select from 'react-select';
|
||||
import { useForm, SubmitHandler } from 'react-hook-form';
|
||||
import slugify from 'slugify';
|
||||
import { FabInput } from '../base/fab-input';
|
||||
import { FormInput } from '../form/form-input';
|
||||
import { FormSelect } from '../form/form-select';
|
||||
import { ProductCategory } from '../../models/product-category';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import { FabAlert } from '../base/fab-alert';
|
||||
import ProductCategoryAPI from '../../api/product-category';
|
||||
|
||||
interface ProductCategoryFormProps {
|
||||
action: 'create' | 'update' | 'delete',
|
||||
productCategories: Array<ProductCategory>,
|
||||
productCategory?: ProductCategory,
|
||||
onChange: (field: string, value: string | number) => void,
|
||||
onSuccess: (message: string) => void,
|
||||
onError: (message: string) => void,
|
||||
}
|
||||
|
||||
/**
|
||||
* Option format, expected by react-select
|
||||
* @see https://github.com/JedWatson/react-select
|
||||
*/
|
||||
type selectOption = { value: number, label: string };
|
||||
type selectOption = { value: number, label: string };
|
||||
|
||||
/**
|
||||
* Form to set create/edit supporting documents type
|
||||
* Form to create/edit/delete a product category
|
||||
*/
|
||||
export const ProductCategoryForm: React.FC<ProductCategoryFormProps> = ({ productCategories, productCategory, onChange }) => {
|
||||
export const ProductCategoryForm: React.FC<ProductCategoryFormProps> = ({ action, productCategories, productCategory, onSuccess, onError }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
const { register, watch, setValue, control, handleSubmit } = useForm<ProductCategory>({ defaultValues: { ...productCategory } });
|
||||
|
||||
// filter all first level product categorie
|
||||
const parents = productCategories.filter(c => !c.parent_id);
|
||||
|
||||
const [slug, setSlug] = useState<string>(productCategory?.slug || '');
|
||||
|
||||
/**
|
||||
* Return the default first level product category, formatted to match the react-select format
|
||||
*/
|
||||
const defaultValue = { value: productCategory?.parent_id, label: productCategory?.name };
|
||||
|
||||
/**
|
||||
* Convert all parents to the react-select format
|
||||
*/
|
||||
@ -42,58 +43,63 @@ export const ProductCategoryForm: React.FC<ProductCategoryFormProps> = ({ produc
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered when the selection of parent product category has changed.
|
||||
*/
|
||||
const handleCategoryParentChange = (option: selectOption): void => {
|
||||
onChange('parent_id', option.value);
|
||||
};
|
||||
// Create slug from category's name
|
||||
useEffect(() => {
|
||||
const subscription = watch((value, { name }) => {
|
||||
if (name === 'name') {
|
||||
const _slug = slugify(value.name, { lower: true });
|
||||
setValue('slug', _slug);
|
||||
}
|
||||
});
|
||||
return () => subscription.unsubscribe();
|
||||
}, [watch]);
|
||||
|
||||
/**
|
||||
* Callback triggered when the name has changed.
|
||||
*/
|
||||
const handleNameChange = (value: string): void => {
|
||||
onChange('name', value);
|
||||
const _slug = slugify(value, { lower: true });
|
||||
setSlug(_slug);
|
||||
onChange('slug', _slug);
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered when the slug has changed.
|
||||
*/
|
||||
const handleSlugChange = (value: string): void => {
|
||||
onChange('slug', value);
|
||||
// Form submit
|
||||
const onSubmit: SubmitHandler<ProductCategory> = (category: ProductCategory) => {
|
||||
switch (action) {
|
||||
case 'create':
|
||||
console.log('create:', category);
|
||||
break;
|
||||
case 'update':
|
||||
console.log('update:', category);
|
||||
break;
|
||||
case 'delete':
|
||||
ProductCategoryAPI.destroy(category.id).then(() => {
|
||||
onSuccess(t('app.admin.store.product_category_form.delete.success'));
|
||||
}).catch((error) => {
|
||||
onError(t('app.admin.store.product_category_form.delete.error') + error);
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="product-category-form">
|
||||
<form name="productCategoryForm">
|
||||
<div className="field">
|
||||
<FabInput id="product_category_name"
|
||||
icon={<i className="fa fa-edit" />}
|
||||
defaultValue={productCategory?.name || ''}
|
||||
placeholder={t('app.admin.store.product_category_form.name')}
|
||||
onChange={handleNameChange}
|
||||
debounce={200}
|
||||
required/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<FabInput id="product_category_slug"
|
||||
icon={<i className="fa fa-edit" />}
|
||||
defaultValue={slug}
|
||||
placeholder={t('app.admin.store.product_category_form.slug')}
|
||||
onChange={handleSlugChange}
|
||||
debounce={200}
|
||||
required/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<Select defaultValue={defaultValue}
|
||||
placeholder={t('app.admin.store.product_category_form.select_parent_product_category')}
|
||||
onChange={handleCategoryParentChange}
|
||||
options={buildOptions()} />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<form onSubmit={handleSubmit(onSubmit)} name="productCategoryForm" className="product-category-form">
|
||||
{ action === 'delete'
|
||||
? <>
|
||||
<FabAlert level='danger'>
|
||||
{t('app.admin.store.product_category_form.delete.confirm')}
|
||||
</FabAlert>
|
||||
<FabButton type='submit'>{t('app.admin.store.product_category_form.save')}</FabButton>
|
||||
</>
|
||||
: <>
|
||||
<FormInput id='name'
|
||||
register={register}
|
||||
rules={{ required: 'true' }}
|
||||
label={t('app.admin.store.product_category_form.name')}
|
||||
defaultValue={productCategory?.name || ''} />
|
||||
<FormInput id='slug'
|
||||
register={register}
|
||||
rules={{ required: 'true' }}
|
||||
label={t('app.admin.store.product_category_form.slug')}
|
||||
defaultValue={productCategory?.slug} />
|
||||
<FormSelect id='parent_id'
|
||||
control={control}
|
||||
options={buildOptions()}
|
||||
label={t('app.admin.store.product_category_form.select_parent_product_category')} />
|
||||
<FabButton type='submit'>{t('app.admin.store.product_category_form.save')}</FabButton>
|
||||
</>
|
||||
}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
@ -1,100 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FabModal } from '../base/fab-modal';
|
||||
import { ProductCategoryForm } from './product-category-form';
|
||||
import { ProductCategory } from '../../models/product-category';
|
||||
import ProductCategoryAPI from '../../api/product-category';
|
||||
|
||||
interface ProductCategoryModalProps {
|
||||
isOpen: boolean,
|
||||
toggleModal: () => void,
|
||||
onSuccess: (message: string) => void,
|
||||
onError: (message: string) => void,
|
||||
productCategories: Array<ProductCategory>,
|
||||
productCategory?: ProductCategory,
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if string is a valid url slug
|
||||
*/
|
||||
function checkIfValidURLSlug (str: string): boolean {
|
||||
// Regular expression to check if string is a valid url slug
|
||||
const regexExp = /^[a-z0-9]+(?:-[a-z0-9]+)*$/g;
|
||||
|
||||
return regexExp.test(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modal dialog to create/edit a category of product
|
||||
*/
|
||||
export const ProductCategoryModal: React.FC<ProductCategoryModalProps> = ({ isOpen, toggleModal, onSuccess, onError, productCategories, productCategory }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
const [data, setData] = useState<ProductCategory>({
|
||||
id: productCategory?.id,
|
||||
name: productCategory?.name || '',
|
||||
slug: productCategory?.slug || '',
|
||||
parent_id: productCategory?.parent_id,
|
||||
position: productCategory?.position
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setData({
|
||||
id: productCategory?.id,
|
||||
name: productCategory?.name || '',
|
||||
slug: productCategory?.slug || '',
|
||||
parent_id: productCategory?.parent_id,
|
||||
position: productCategory?.position
|
||||
});
|
||||
}, [productCategory]);
|
||||
|
||||
/**
|
||||
* Callback triggered when an inner form field has changed: updates the internal state accordingly
|
||||
*/
|
||||
const handleChanged = (field: string, value: string | number) => {
|
||||
setData({
|
||||
...data,
|
||||
[field]: value
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Save the current product category to the API
|
||||
*/
|
||||
const handleSave = async (): Promise<void> => {
|
||||
try {
|
||||
if (productCategory?.id) {
|
||||
await ProductCategoryAPI.update(data);
|
||||
onSuccess(t('app.admin.store.product_category_modal.successfully_updated'));
|
||||
} else {
|
||||
await ProductCategoryAPI.create(data);
|
||||
onSuccess(t('app.admin.store.product_category_modal.successfully_created'));
|
||||
}
|
||||
} catch (e) {
|
||||
if (productCategory?.id) {
|
||||
onError(t('app.admin.store.product_category_modal.unable_to_update') + e);
|
||||
} else {
|
||||
onError(t('app.admin.store.product_category_modal.unable_to_create') + e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the form is valid (not empty, url valid slug)
|
||||
*/
|
||||
const isPreventedSaveProductCategory = (): boolean => {
|
||||
return !data.name || !data.slug || !checkIfValidURLSlug(data.slug);
|
||||
};
|
||||
|
||||
return (
|
||||
<FabModal title={t(`app.admin.store.product_category_modal.${productCategory ? 'edit' : 'new'}_product_category`)}
|
||||
isOpen={isOpen}
|
||||
toggleModal={toggleModal}
|
||||
closeButton={true}
|
||||
confirmButton={t('app.admin.store.product_category_modal.save')}
|
||||
onConfirm={handleSave}
|
||||
preventConfirm={isPreventedSaveProductCategory()}>
|
||||
<ProductCategoryForm productCategory={productCategory} productCategories={productCategories} onChange={handleChanged}/>
|
||||
</FabModal>
|
||||
);
|
||||
};
|
@ -86,7 +86,7 @@ Application.Controllers.controller('MainNavController', ['$scope', 'settingsProm
|
||||
{
|
||||
state: 'app.admin.store',
|
||||
linkText: 'app.public.common.manage_the_store',
|
||||
linkIcon: 'cogs',
|
||||
linkIcon: 'cart-plus',
|
||||
authorizedRoles: ['admin', 'manager']
|
||||
},
|
||||
$scope.$root.modules.trainings && {
|
||||
|
@ -85,6 +85,8 @@
|
||||
@import "modules/settings/check-list-setting";
|
||||
@import "modules/settings/user-validation-setting";
|
||||
@import "modules/socials/fab-socials";
|
||||
@import "modules/store/manage-product-category";
|
||||
@import "modules/store/product-categories";
|
||||
@import "modules/subscriptions/free-extend-modal";
|
||||
@import "modules/subscriptions/renew-modal";
|
||||
@import "modules/supporting-documents/supporting-documents-files";
|
||||
|
@ -46,4 +46,7 @@
|
||||
&--icon {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
&--icon-only {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +81,12 @@
|
||||
position: relative;
|
||||
padding: 15px;
|
||||
|
||||
.subtitle {
|
||||
margin-bottom: 3.2rem;
|
||||
@include title-base;
|
||||
color: var(--gray-hard-darkest);
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -64,6 +64,7 @@
|
||||
border: 1px solid var(--gray-soft-dark);
|
||||
border-radius: var(--border-radius);
|
||||
transition: border-color ease-in-out 0.15s;
|
||||
font-weight: 400;
|
||||
|
||||
.icon,
|
||||
.addon {
|
||||
|
@ -0,0 +1,3 @@
|
||||
.manage-product-category {
|
||||
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
@mixin btn {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
&:active {
|
||||
color: currentColor;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.product-categories {
|
||||
max-width: 1300px;
|
||||
margin: 0 auto;
|
||||
|
||||
header {
|
||||
padding: 2.4rem 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
h2 {
|
||||
margin: 0;
|
||||
@include title-lg;
|
||||
color: var(--gray-hard-darkest);
|
||||
}
|
||||
}
|
||||
|
||||
.create-button {
|
||||
background-color: var(--gray-hard-darkest);
|
||||
border-color: var(--gray-hard-darkest);
|
||||
color: var(--gray-soft-lightest);
|
||||
&:hover {
|
||||
background-color: var(--gray-hard-light);
|
||||
border-color: var(--gray-hard-light);
|
||||
}
|
||||
}
|
||||
|
||||
&-list {
|
||||
& > *:not(:last-of-type) {
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
}
|
||||
&-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.6rem 1.6rem;
|
||||
border: 1px solid var(--gray-soft-dark);
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
.itemInfo {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
&-name {
|
||||
margin: 0;
|
||||
@include text-base;
|
||||
font-weight: 600;
|
||||
color: var(--gray-hard-darkest);
|
||||
}
|
||||
&-count {
|
||||
margin-left: 2.4rem;
|
||||
@include text-sm;
|
||||
font-weight: 500;
|
||||
color: var(--information);
|
||||
}
|
||||
}
|
||||
|
||||
.action {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
.manage {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
border-radius: var(--border-radius);
|
||||
button {
|
||||
@include btn;
|
||||
border-radius: 0;
|
||||
color: var(--gray-soft-lightest);
|
||||
&:hover { opacity: 0.75; }
|
||||
}
|
||||
.edit-button {background: var(--gray-hard-darkest) }
|
||||
.delete-button {background: var(--error) }
|
||||
}
|
||||
}
|
||||
|
||||
.draghandle {
|
||||
@include btn;
|
||||
cursor: grab;
|
||||
}
|
||||
}
|
||||
}
|
@ -1894,17 +1894,18 @@ en:
|
||||
title: "Documentation"
|
||||
content: "Click here to access the API online documentation."
|
||||
store:
|
||||
manage_the_store: "Manage the Store Fablab"
|
||||
manage_the_store: "Manage the Store"
|
||||
settings: "Settings"
|
||||
all_products: "All products"
|
||||
categories_of_store: "Categories of store"
|
||||
categories_of_store: "Store's categories"
|
||||
the_orders: "Orders"
|
||||
product_categories:
|
||||
create_a_product_category: "Create a category"
|
||||
the_categories: "Categories"
|
||||
title: "Categories"
|
||||
info: "<strong>Information:</strong></br>Find below all the categories created. The categories are arranged on two levels maximum, you can arrange them with a drag and drop. The order of the categories will be identical on the public view and the list below. Please note that you can delete a category or a sub-category even if they are associated with products. The latter will be left without categories. If you delete a category that contains sub-categories, the latter will also be deleted. <strong>Make sure that your categories are well arranged and save your choice.</strong>"
|
||||
successfully_deleted: "The category has been successfully deleted"
|
||||
unable_to_delete: "Unable to delete the category: "
|
||||
manage_product_category:
|
||||
create: "Create a product category"
|
||||
update: "Modify the product category"
|
||||
delete: "Delete the product category"
|
||||
product_category_modal:
|
||||
successfully_created: "The new category has been created."
|
||||
unable_to_create: "Unable to create the category: "
|
||||
@ -1912,8 +1913,12 @@ en:
|
||||
unable_to_update: "Unable to modify the category: "
|
||||
new_product_category: "Create a category"
|
||||
edit_product_category: "Modify a category"
|
||||
save: "Save"
|
||||
product_category_form:
|
||||
name: "Name of category"
|
||||
slug: "Name of URL"
|
||||
select_parent_product_category: "Choose a parent category (N1)"
|
||||
delete:
|
||||
confirm: "Do you really want to delete this product category?"
|
||||
error: "Unable to delete the category: "
|
||||
success: "The category has been successfully deleted"
|
||||
save: "Save"
|
||||
|
@ -1900,20 +1900,25 @@ fr:
|
||||
categories_of_store: "Les catégories de la boutique"
|
||||
the_orders: "Les commandes"
|
||||
product_categories:
|
||||
create_a_product_category: "Créer une catégorie"
|
||||
the_categories: "Les catégories"
|
||||
info: "<strong>Information:</strong></br>Retrouvez ci-dessous toutes les catégories créés. Les catégories se rangent sur deux niveux maximum, vous pouvez les agancer avec un glisser-déposer. L'ordre d'affichage des catégories sera identique sur la vue publique et la liste ci-dessous. Attention, Vous pouvez supprimer une catégorie ou une sous-catégorie même si elles sont associées à des produits. Ces derniers se retrouveront sans catégories. Si vous supprimez une catégorie contenant des sous-catégories, ces dernières seront elles aussi supprimées. <strong>Veillez au bon agencement de vos catégories et sauvegarder votre choix.</strong>"
|
||||
successfully_deleted: "La catégorie a bien été supprimé"
|
||||
unable_to_delete: "Impossible de supprimer the category: "
|
||||
title: "Les catégories"
|
||||
info: "<strong>Information:</strong></br>Retrouvez ci-dessous toutes les catégories créés. Les catégories se rangent sur deux niveaux maximum, vous pouvez les agencer avec un glisser-déposer. L'ordre d'affichage des catégories sera identique sur la vue publique et la liste ci-dessous. Attention, Vous pouvez supprimer une catégorie ou une sous-catégorie même si elles sont associées à des produits. Ces derniers se retrouveront sans catégories. Si vous supprimez une catégorie contenant des sous-catégories, ces dernières seront elles aussi supprimées. <strong>Veillez au bon agencement de vos catégories et sauvegarder votre choix.</strong>"
|
||||
manage_product_category:
|
||||
create: "Create a product category"
|
||||
update: "Update the product category"
|
||||
delete: "Delete the product category"
|
||||
product_category_modal:
|
||||
successfully_created: "La catégorie a bien été créée."
|
||||
unable_to_create: "Impossible de créer la catégorie : "
|
||||
successfully_updated: "La nouvelle catégorie a bien été mise à jour."
|
||||
unable_to_update: "Impossible de modifier la catégorie : "
|
||||
new_product_category: "Créer une catégorie"
|
||||
edit_product_category: "Modifier la catéogirie"
|
||||
save: "Sauvgarder"
|
||||
edit_product_category: "Modifier la catégorie"
|
||||
product_category_form:
|
||||
name: "Nom de la catégorie"
|
||||
slug: "Nom de l'URL"
|
||||
select_parent_product_category: "Choisir une catégorie parent (N1)"
|
||||
delete:
|
||||
confirm: "Do you really want to delete this product category?"
|
||||
error: "Impossible de supprimer the catégorie : "
|
||||
success: "La catégorie a bien été supprimée"
|
||||
save: "Enregistrer"
|
||||
|
Loading…
x
Reference in New Issue
Block a user