mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-26 20:54:21 +01:00
(wip) Products list filters
This commit is contained in:
parent
d118d045c6
commit
86a40bc096
@ -54,7 +54,7 @@ const ProductCategories: React.FC<ProductCategoriesProps> = ({ onSuccess, onErro
|
|||||||
*/
|
*/
|
||||||
const refreshCategories = () => {
|
const refreshCategories = () => {
|
||||||
ProductCategoryAPI.index().then(data => {
|
ProductCategoryAPI.index().then(data => {
|
||||||
// Translate ProductCategory.position to array index
|
// Map product categories by position
|
||||||
const sortedCategories = data
|
const sortedCategories = data
|
||||||
.filter(c => !c.parent_id)
|
.filter(c => !c.parent_id)
|
||||||
.sort((a, b) => a.position - b.position);
|
.sort((a, b) => a.position - b.position);
|
||||||
|
@ -16,7 +16,6 @@ interface ProductsListProps {
|
|||||||
* This component shows a list of all Products
|
* This component shows a list of all Products
|
||||||
*/
|
*/
|
||||||
export const ProductsList: React.FC<ProductsListProps> = ({ products, onEdit, onDelete }) => {
|
export const ProductsList: React.FC<ProductsListProps> = ({ products, onEdit, onDelete }) => {
|
||||||
console.log('products: ', products);
|
|
||||||
const { t } = useTranslation('admin');
|
const { t } = useTranslation('admin');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -83,10 +82,12 @@ export const ProductsList: React.FC<ProductsListProps> = ({ products, onEdit, on
|
|||||||
<span>{t('app.admin.store.products_list.stock.external')}</span>
|
<span>{t('app.admin.store.products_list.stock.external')}</span>
|
||||||
<p>{product.stock.external}</p>
|
<p>{product.stock.external}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
{product.amount &&
|
||||||
|
<div className='price'>
|
||||||
<p>{FormatLib.price(product.amount)}</p>
|
<p>{FormatLib.price(product.amount)}</p>
|
||||||
<span>/ {t('app.admin.store.products_list.unit')}</span>
|
<span>/ {t('app.admin.store.products_list.unit')}</span>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className='actions'>
|
<div className='actions'>
|
||||||
<div className='manage'>
|
<div className='manage'>
|
||||||
|
@ -1,13 +1,20 @@
|
|||||||
|
// TODO: Remove next eslint-disable
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useImmer } from 'use-immer';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { react2angular } from 'react2angular';
|
import { react2angular } from 'react2angular';
|
||||||
import { Loader } from '../base/loader';
|
import { Loader } from '../base/loader';
|
||||||
import { IApplication } from '../../models/application';
|
import { IApplication } from '../../models/application';
|
||||||
|
import { Product } from '../../models/product';
|
||||||
|
import { ProductCategory } from '../../models/product-category';
|
||||||
import { FabButton } from '../base/fab-button';
|
import { FabButton } from '../base/fab-button';
|
||||||
import { ProductsList } from './products-list';
|
import { ProductsList } from './products-list';
|
||||||
import { Product } from '../../models/product';
|
|
||||||
import ProductAPI from '../../api/product';
|
import ProductAPI from '../../api/product';
|
||||||
import { X } from 'phosphor-react';
|
import ProductCategoryAPI from '../../api/product-category';
|
||||||
|
import MachineAPI from '../../api/machine';
|
||||||
|
import { CaretDown, X } from 'phosphor-react';
|
||||||
|
import Switch from 'react-switch';
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
@ -23,13 +30,45 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
|||||||
const { t } = useTranslation('admin');
|
const { t } = useTranslation('admin');
|
||||||
|
|
||||||
const [products, setProducts] = useState<Array<Product>>([]);
|
const [products, setProducts] = useState<Array<Product>>([]);
|
||||||
|
const [filteredProductsList, setFilteredProductList] = useImmer<Array<Product>>([]);
|
||||||
|
const [filterVisible, setFilterVisible] = useState<boolean>(false);
|
||||||
|
const [clearFilters, setClearFilters] = useState<boolean>(false);
|
||||||
|
const [filters, setFilters] = useImmer<Filters>(initFilters);
|
||||||
|
const [productCategories, setProductCategories] = useState<ProductCategory[]>([]);
|
||||||
|
const [machines, setMachines] = useState<checklistOption[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ProductAPI.index().then(data => {
|
ProductAPI.index().then(data => {
|
||||||
setProducts(data);
|
setProducts(data);
|
||||||
|
setFilteredProductList(data);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ProductCategoryAPI.index().then(data => {
|
||||||
|
// Map product categories by position
|
||||||
|
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);
|
||||||
|
MachineAPI.index({ disabled: false }).then(data => {
|
||||||
|
setMachines(buildChecklistOptions(data));
|
||||||
|
}).catch(onError);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
applyFilters();
|
||||||
|
setClearFilters(false);
|
||||||
|
}, [filterVisible, clearFilters]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Goto edit product page
|
* Goto edit product page
|
||||||
*/
|
*/
|
||||||
@ -58,6 +97,71 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
|||||||
window.location.href = '/#!/admin/store/products/new';
|
window.location.href = '/#!/admin/store/products/new';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter: toggle hidden products visibility
|
||||||
|
*/
|
||||||
|
const toggleVisible = (checked: boolean) => {
|
||||||
|
setFilterVisible(checked);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter: by categories
|
||||||
|
*/
|
||||||
|
const handleSelectCategory = (c: ProductCategory, checked) => {
|
||||||
|
let list = [...filters.categories];
|
||||||
|
const children = productCategories
|
||||||
|
.filter(el => el.parent_id === c.id)
|
||||||
|
.map(el => el.id);
|
||||||
|
const siblings = productCategories
|
||||||
|
.filter(el => el.parent_id === c.parent_id && el.parent_id !== null);
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
list.push(c.id);
|
||||||
|
if (children.length) {
|
||||||
|
const unic = Array.from(new Set([...list, ...children]));
|
||||||
|
list = [...unic];
|
||||||
|
}
|
||||||
|
if (siblings.length && siblings.every(el => list.includes(el.id))) {
|
||||||
|
list.push(siblings[0].parent_id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
list.splice(list.indexOf(c.id), 1);
|
||||||
|
if (c.parent_id && list.includes(c.parent_id)) {
|
||||||
|
list.splice(list.indexOf(c.parent_id), 1);
|
||||||
|
}
|
||||||
|
if (children.length) {
|
||||||
|
children.forEach(child => {
|
||||||
|
list.splice(list.indexOf(child), 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setFilters(draft => {
|
||||||
|
return { ...draft, categories: list };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply filters
|
||||||
|
*/
|
||||||
|
const applyFilters = () => {
|
||||||
|
let updatedList = [...products];
|
||||||
|
if (filterVisible) {
|
||||||
|
updatedList = updatedList.filter(p => p.is_active);
|
||||||
|
}
|
||||||
|
if (filters.categories.length) {
|
||||||
|
updatedList = updatedList.filter(p => filters.categories.includes(p.product_category_id));
|
||||||
|
}
|
||||||
|
setFilteredProductList(updatedList);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear filters
|
||||||
|
*/
|
||||||
|
const clearAllFilters = () => {
|
||||||
|
setFilters(initFilters);
|
||||||
|
setClearFilters(true);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='products'>
|
<div className='products'>
|
||||||
<header>
|
<header>
|
||||||
@ -69,38 +173,79 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
|||||||
<div className='layout'>
|
<div className='layout'>
|
||||||
<div className='products-filters span-3'>
|
<div className='products-filters span-3'>
|
||||||
<header>
|
<header>
|
||||||
<h3>Filtrer</h3>
|
<h3>{t('app.admin.store.products.filter')}</h3>
|
||||||
<div className='grpBtn'>
|
<div className='grpBtn'>
|
||||||
<FabButton className="is-black">Clear</FabButton>
|
<FabButton onClick={clearAllFilters} className="is-black">{t('app.admin.store.products.filter_clear')}</FabButton>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
<div className='accordion'>
|
||||||
|
<div className='accordion-item'>
|
||||||
|
<input type="checkbox" defaultChecked />
|
||||||
|
<header>{t('app.admin.store.products.filter_categories')}
|
||||||
|
<CaretDown size={16} weight="bold" /></header>
|
||||||
|
<div className='content'>
|
||||||
|
<div className="list scrollbar">
|
||||||
|
{productCategories.map(pc => (
|
||||||
|
<label key={pc.id} className={pc.parent_id ? 'offset' : ''}>
|
||||||
|
<input type="checkbox" checked={filters.categories.includes(pc.id)} onChange={(event) => handleSelectCategory(pc, event.target.checked)} />
|
||||||
|
<p key={pc.id}>{pc.name}</p>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<FabButton onClick={applyFilters} className="is-info">{t('app.admin.store.products.filter_apply')}</FabButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='accordion-item'>
|
||||||
|
<input type="checkbox" defaultChecked />
|
||||||
|
<header>{t('app.admin.store.products.filter_machines')}
|
||||||
|
<CaretDown size={16} weight="bold" /></header>
|
||||||
|
<div className='content'>
|
||||||
|
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Minus saepe aperiam autem eum magni nihil odio totam enim similique error! Est veritatis illum adipisci aspernatur sit nulla voluptate. Exercitationem, totam!
|
||||||
|
<FabButton className="is-info">{t('app.admin.store.products.filter_apply')}</FabButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='products-list span-7'>
|
<div className='products-list span-7'>
|
||||||
<div className='status'>
|
<div className='status'>
|
||||||
<div className='count'>
|
<div className='count'>
|
||||||
<p>Result count: <span>{products.length}</span></p>
|
<p>{t('app.admin.store.products.result_count')}<span>{filteredProductsList.length}</span></p>
|
||||||
</div>
|
</div>
|
||||||
<div className="">
|
<div className="display">
|
||||||
<div className='sort'>
|
<div className='sort'>
|
||||||
<p>Display options:</p>
|
<p>{t('app.admin.store.products.display_options')}</p>
|
||||||
|
<select>
|
||||||
|
<option value="A">A</option>
|
||||||
|
<option value="B">B</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className='visibility'>
|
<div className='visibility'>
|
||||||
|
<label>
|
||||||
|
<span>{t('app.admin.store.products.visible_only')}</span>
|
||||||
|
<Switch
|
||||||
|
checked={filterVisible}
|
||||||
|
onChange={(checked) => toggleVisible(checked)}
|
||||||
|
width={40}
|
||||||
|
height={19}
|
||||||
|
uncheckedIcon={false}
|
||||||
|
checkedIcon={false}
|
||||||
|
handleDiameter={15} />
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='features'>
|
<div className='features'>
|
||||||
<div className='features-item'>
|
<div className='features-item'>
|
||||||
<p>feature name</p>
|
<p>feature name</p>
|
||||||
<button><X size={16} /></button>
|
<button><X size={16} weight="light" /></button>
|
||||||
</div>
|
</div>
|
||||||
<div className='features-item'>
|
<div className='features-item'>
|
||||||
<p>long feature name</p>
|
<p>long feature name</p>
|
||||||
<button><X size={16} /></button>
|
<button><X size={16} weight="light" /></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ProductsList
|
<ProductsList
|
||||||
products={products}
|
products={filteredProductsList}
|
||||||
onEdit={editProduct}
|
onEdit={editProduct}
|
||||||
onDelete={deleteProduct}
|
onDelete={deleteProduct}
|
||||||
/>
|
/>
|
||||||
@ -119,3 +264,44 @@ const ProductsWrapper: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Application.Components.component('products', react2angular(ProductsWrapper, ['onSuccess', 'onError']));
|
Application.Components.component('products', react2angular(ProductsWrapper, ['onSuccess', 'onError']));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option format, expected by checklist
|
||||||
|
*/
|
||||||
|
type checklistOption = { value: number, label: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const initFilters: Filters = {
|
||||||
|
categories: [],
|
||||||
|
machines: [],
|
||||||
|
keywords: [],
|
||||||
|
internalStock: {
|
||||||
|
from: 0,
|
||||||
|
to: null
|
||||||
|
},
|
||||||
|
externalStock: {
|
||||||
|
from: 0,
|
||||||
|
to: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Stock {
|
||||||
|
from: number,
|
||||||
|
to: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Filters {
|
||||||
|
categories: number[],
|
||||||
|
machines: number[],
|
||||||
|
keywords: string[],
|
||||||
|
internalStock: Stock,
|
||||||
|
externalStock: Stock
|
||||||
|
}
|
||||||
|
@ -91,6 +91,9 @@
|
|||||||
@import "modules/store/_utilities";
|
@import "modules/store/_utilities";
|
||||||
@import "modules/store/manage-product-category";
|
@import "modules/store/manage-product-category";
|
||||||
@import "modules/store/product-categories";
|
@import "modules/store/product-categories";
|
||||||
|
@import "modules/store/product-form";
|
||||||
|
@import "modules/store/products-filters";
|
||||||
|
@import "modules/store/products-list";
|
||||||
@import "modules/store/products";
|
@import "modules/store/products";
|
||||||
@import "modules/subscriptions/free-extend-modal";
|
@import "modules/subscriptions/free-extend-modal";
|
||||||
@import "modules/subscriptions/renew-modal";
|
@import "modules/subscriptions/renew-modal";
|
||||||
@ -104,7 +107,6 @@
|
|||||||
@import "modules/user/gender-input";
|
@import "modules/user/gender-input";
|
||||||
@import "modules/user/user-profile-form";
|
@import "modules/user/user-profile-form";
|
||||||
@import "modules/user/user-validation";
|
@import "modules/user/user-validation";
|
||||||
@import "modules/store/product-form";
|
|
||||||
|
|
||||||
@import "modules/abuses";
|
@import "modules/abuses";
|
||||||
@import "modules/cookies";
|
@import "modules/cookies";
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
.products-filters {
|
||||||
|
padding-top: 1.6rem;
|
||||||
|
border-top: 1px solid var(--gray-soft-dark);
|
||||||
|
|
||||||
|
.accordion {
|
||||||
|
&-item:not(:last-of-type) {
|
||||||
|
margin-bottom: 1.6rem;
|
||||||
|
border-bottom: 1px solid var(--gray-soft-darkest);
|
||||||
|
}
|
||||||
|
&-item {
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 1.6rem;
|
||||||
|
|
||||||
|
& > input[type=checkbox] {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
& > input[type=checkbox]:checked ~ .content {
|
||||||
|
max-height: 0;
|
||||||
|
}
|
||||||
|
& > input[type=checkbox]:checked ~ header svg {
|
||||||
|
transform: rotateZ(180deg);
|
||||||
|
}
|
||||||
|
header {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
@include text-base(600);
|
||||||
|
svg { transition: transform 250ms ease-in-out; }
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
max-height: 24rem;
|
||||||
|
padding-top: 1.6rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
transition: max-height 500ms ease-in-out;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.list {
|
||||||
|
overflow: hidden auto;
|
||||||
|
label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
input[type=checkbox] { margin: 0 0.8rem 0 0; }
|
||||||
|
p { margin: 0; }
|
||||||
|
&.offset { margin-left: 1.6rem; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
margin-top: 0.8rem;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom scrollbar
|
||||||
|
.scrollbar {
|
||||||
|
&::-webkit-scrollbar-track
|
||||||
|
{
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #d9d9d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar
|
||||||
|
{
|
||||||
|
width: 12px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb
|
||||||
|
{
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border: 2px solid #d9d9d9
|
||||||
|
}
|
||||||
|
}
|
181
app/frontend/src/stylesheets/modules/store/products-list.scss
Normal file
181
app/frontend/src/stylesheets/modules/store/products-list.scss
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
.products-list {
|
||||||
|
.status {
|
||||||
|
padding: 1.6rem 2.4rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
background-color: var(--gray-soft);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
p { margin: 0; }
|
||||||
|
|
||||||
|
.count {
|
||||||
|
p {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
@include text-sm;
|
||||||
|
span {
|
||||||
|
margin-left: 1.6rem;
|
||||||
|
@include text-lg(600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.display {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
& > *:not(:first-child) {
|
||||||
|
margin-left: 2rem;
|
||||||
|
padding-left: 2rem;
|
||||||
|
border-left: 1px solid var(--gray-hard-darkest);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visibility {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
label {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: 400;
|
||||||
|
cursor: pointer;
|
||||||
|
span {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.features {
|
||||||
|
margin: 2.4rem 0 1.6rem;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.6rem 2.4rem;
|
||||||
|
&-item {
|
||||||
|
padding-left: 1.6rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--information-light);
|
||||||
|
border-radius: 100px;
|
||||||
|
color: var(--information-dark);
|
||||||
|
overflow: hidden;
|
||||||
|
p { margin: 0; }
|
||||||
|
button {
|
||||||
|
width: 3.2rem;
|
||||||
|
height: 3.2rem;
|
||||||
|
margin-left: 0.8rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
--status-color: var(--gray-hard-darkest);
|
||||||
|
&.low { --status-color: var(--alert-light); }
|
||||||
|
&.out-of-stock { --status-color: var(--alert); }
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1.6rem 0.8rem;
|
||||||
|
border: 1px solid var(--gray-soft-dark);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background-color: var(--gray-soft-lightest);
|
||||||
|
&.out-of-stock { border-color: var(--status-color); }
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-top: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemInfo {
|
||||||
|
min-width: 20ch;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&-thumbnail {
|
||||||
|
width: 4.8rem;
|
||||||
|
height: 4.8rem;
|
||||||
|
margin-right: 1.6rem;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background-color: var(--gray-soft);
|
||||||
|
}
|
||||||
|
&-name {
|
||||||
|
margin: 0;
|
||||||
|
@include text-base;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--gray-hard-darkest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.details {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 120px repeat(2, minmax(min-content, 120px)) 120px;
|
||||||
|
justify-items: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.6rem;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 4rem;
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
@include text-base(600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.visibility {
|
||||||
|
justify-self: center;
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--gray-soft-light);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
&::before {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 1rem;
|
||||||
|
content: "";
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
background-color: var(--gray-hard);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
&.is-active::before {
|
||||||
|
background-color: var(--success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.stock {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
color: var(--status-color);
|
||||||
|
span { @include text-xs; }
|
||||||
|
}
|
||||||
|
.price {
|
||||||
|
justify-self: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
.manage {
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
button {
|
||||||
|
@include btn;
|
||||||
|
border-radius: 0;
|
||||||
|
color: var(--gray-soft-lightest);
|
||||||
|
&:hover { opacity: 0.75; }
|
||||||
|
}
|
||||||
|
.edit-btn {background: var(--gray-hard-darkest) }
|
||||||
|
.delete-btn {background: var(--main) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -72,155 +72,6 @@
|
|||||||
.layout {
|
.layout {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-filters {
|
|
||||||
padding-top: 1.6rem;
|
|
||||||
border-top: 1px solid var(--gray-soft-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
&-list {
|
|
||||||
.status {
|
|
||||||
padding: 1.6rem 2.4rem;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
background-color: var(--gray-soft);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
p { margin: 0; }
|
|
||||||
.count {
|
|
||||||
p {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
@include text-sm;
|
|
||||||
span {
|
|
||||||
margin-left: 1.6rem;
|
|
||||||
@include text-lg(600);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.features {
|
|
||||||
margin: 2.4rem 0 1.6rem;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1.6rem 2.4rem;
|
|
||||||
&-item {
|
|
||||||
padding-left: 1.6rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
background-color: var(--information-light);
|
|
||||||
border-radius: 100px;
|
|
||||||
color: var(--information-dark);
|
|
||||||
p { margin: 0; }
|
|
||||||
button {
|
|
||||||
width: 3.2rem;
|
|
||||||
height: 3.2rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-item {
|
|
||||||
--status-color: var(--gray-hard-darkest);
|
|
||||||
&.low { --status-color: var(--alert-light); }
|
|
||||||
&.out-of-stock { --status-color: var(--alert); }
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 1.6rem 0.8rem;
|
|
||||||
border: 1px solid var(--gray-soft-dark);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
background-color: var(--gray-soft-lightest);
|
|
||||||
&.out-of-stock { border-color: var(--status-color); }
|
|
||||||
&:not(:first-child) {
|
|
||||||
margin-top: 1.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.itemInfo {
|
|
||||||
min-width: 20ch;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
&-thumbnail {
|
|
||||||
width: 4.8rem;
|
|
||||||
height: 4.8rem;
|
|
||||||
margin-right: 1.6rem;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
background-color: var(--gray-soft);
|
|
||||||
}
|
|
||||||
&-name {
|
|
||||||
margin: 0;
|
|
||||||
@include text-base;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--gray-hard-darkest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.details {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 140px repeat(3, minmax(120px, 1fr));
|
|
||||||
justify-items: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: 4rem;
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
@include text-base(600);
|
|
||||||
}
|
|
||||||
|
|
||||||
.visibility {
|
|
||||||
justify-self: center;
|
|
||||||
padding: 0.4rem 0.8rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
background-color: var(--gray-soft-light);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
&::before {
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-right: 1rem;
|
|
||||||
content: "";
|
|
||||||
width: 1rem;
|
|
||||||
height: 1rem;
|
|
||||||
background-color: var(--gray-hard);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
&.is-active::before {
|
|
||||||
background-color: var(--success);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.stock {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
color: var(--status-color);
|
|
||||||
span { @include text-xs; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
|
||||||
.manage {
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
button {
|
|
||||||
@include btn;
|
|
||||||
border-radius: 0;
|
|
||||||
color: var(--gray-soft-lightest);
|
|
||||||
&:hover { opacity: 0.75; }
|
|
||||||
}
|
|
||||||
.edit-btn {background: var(--gray-hard-darkest) }
|
|
||||||
.delete-btn {background: var(--main) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.new-product,
|
.new-product,
|
||||||
|
@ -1933,6 +1933,18 @@ en:
|
|||||||
create_a_product: "Create a product"
|
create_a_product: "Create a product"
|
||||||
successfully_deleted: "The product has been successfully deleted"
|
successfully_deleted: "The product has been successfully deleted"
|
||||||
unable_to_delete: "Unable to delete the product: "
|
unable_to_delete: "Unable to delete the product: "
|
||||||
|
filter: "Filter"
|
||||||
|
filter_clear: "Clear all"
|
||||||
|
filter_apply: "Apply"
|
||||||
|
filter_categories: "By categories"
|
||||||
|
filter_machines: "By machines"
|
||||||
|
filter_keywords_reference: "By keywords or reference"
|
||||||
|
filter_stock: "By stock status"
|
||||||
|
filter_stock_from: "From"
|
||||||
|
filter_stock_to: "to"
|
||||||
|
result_count: "Result count:"
|
||||||
|
display_options: "Display options:"
|
||||||
|
visible_only: "Visible products only"
|
||||||
products_list:
|
products_list:
|
||||||
visible: "visible"
|
visible: "visible"
|
||||||
hidden: "hidden"
|
hidden: "hidden"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user