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 = () => {
|
||||
ProductCategoryAPI.index().then(data => {
|
||||
// Translate ProductCategory.position to array index
|
||||
// Map product categories by position
|
||||
const sortedCategories = data
|
||||
.filter(c => !c.parent_id)
|
||||
.sort((a, b) => a.position - b.position);
|
||||
|
@ -16,7 +16,6 @@ interface ProductsListProps {
|
||||
* This component shows a list of all Products
|
||||
*/
|
||||
export const ProductsList: React.FC<ProductsListProps> = ({ products, onEdit, onDelete }) => {
|
||||
console.log('products: ', products);
|
||||
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>
|
||||
<p>{product.stock.external}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>{FormatLib.price(product.amount)}</p>
|
||||
<span>/ {t('app.admin.store.products_list.unit')}</span>
|
||||
</div>
|
||||
{product.amount &&
|
||||
<div className='price'>
|
||||
<p>{FormatLib.price(product.amount)}</p>
|
||||
<span>/ {t('app.admin.store.products_list.unit')}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className='actions'>
|
||||
<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 { useImmer } from 'use-immer';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { react2angular } from 'react2angular';
|
||||
import { Loader } from '../base/loader';
|
||||
import { IApplication } from '../../models/application';
|
||||
import { Product } from '../../models/product';
|
||||
import { ProductCategory } from '../../models/product-category';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import { ProductsList } from './products-list';
|
||||
import { Product } from '../../models/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;
|
||||
|
||||
@ -23,13 +30,45 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
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(() => {
|
||||
ProductAPI.index().then(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
|
||||
*/
|
||||
@ -58,6 +97,71 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
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 (
|
||||
<div className='products'>
|
||||
<header>
|
||||
@ -69,38 +173,79 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
<div className='layout'>
|
||||
<div className='products-filters span-3'>
|
||||
<header>
|
||||
<h3>Filtrer</h3>
|
||||
<h3>{t('app.admin.store.products.filter')}</h3>
|
||||
<div className='grpBtn'>
|
||||
<FabButton className="is-black">Clear</FabButton>
|
||||
<FabButton onClick={clearAllFilters} className="is-black">{t('app.admin.store.products.filter_clear')}</FabButton>
|
||||
</div>
|
||||
</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 className='products-list span-7'>
|
||||
<div className='status'>
|
||||
<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 className="">
|
||||
<div className="display">
|
||||
<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 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 className='features'>
|
||||
<div className='features-item'>
|
||||
<p>feature name</p>
|
||||
<button><X size={16} /></button>
|
||||
<button><X size={16} weight="light" /></button>
|
||||
</div>
|
||||
<div className='features-item'>
|
||||
<p>long feature name</p>
|
||||
<button><X size={16} /></button>
|
||||
<button><X size={16} weight="light" /></button>
|
||||
</div>
|
||||
</div>
|
||||
<ProductsList
|
||||
products={products}
|
||||
products={filteredProductsList}
|
||||
onEdit={editProduct}
|
||||
onDelete={deleteProduct}
|
||||
/>
|
||||
@ -119,3 +264,44 @@ const ProductsWrapper: React.FC<ProductsProps> = ({ 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/manage-product-category";
|
||||
@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/subscriptions/free-extend-modal";
|
||||
@import "modules/subscriptions/renew-modal";
|
||||
@ -104,7 +107,6 @@
|
||||
@import "modules/user/gender-input";
|
||||
@import "modules/user/user-profile-form";
|
||||
@import "modules/user/user-validation";
|
||||
@import "modules/store/product-form";
|
||||
|
||||
@import "modules/abuses";
|
||||
@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 {
|
||||
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,
|
||||
|
@ -1933,6 +1933,18 @@ en:
|
||||
create_a_product: "Create a product"
|
||||
successfully_deleted: "The product has been successfully deleted"
|
||||
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:
|
||||
visible: "visible"
|
||||
hidden: "hidden"
|
||||
|
Loading…
x
Reference in New Issue
Block a user