Crud create and variant category
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
VITE_BASE_URL = 'https://shop-api.dev.danakcorp.com'
|
||||
# VITE_BASE_URL = 'https://shop-api.dev.danakcorp.com'
|
||||
VITE_BASE_URL = 'http://192.168.1.122:4000'
|
||||
VITE_TOKEN_NAME = 'sh_admin_token'
|
||||
VITE_REFRESH_TOKEN_NAME = 'sh_admin_refresh_token'
|
||||
@@ -26,9 +26,9 @@ const DefaulModal: FC<Props> = (props: Props) => {
|
||||
{
|
||||
props.open && (
|
||||
<Fragment>
|
||||
<div style={{ maxWidth: props.width }} className='xl:justify-center xl:items-center items-end flex overflow-x-hidden overflow-y-auto fixed inset-0 z-[60] h-auto top-0 bottom-0 m-auto outline-none focus:outline-none xl:max-w-xl mx-auto'>
|
||||
<div style={{ maxWidth: props.width }} className='xl:justify-center xl:items-center items-end flex overflow-x-hidden overflow-hidden fixed inset-0 z-[60] h-auto top-0 bottom-0 m-auto outline-none focus:outline-none xl:max-w-xl mx-auto'>
|
||||
<div className='relative xl:h-full h-[80%] bottom-0 left-0 flex xl:items-center sm:h-auto w-full xl:my-6 xl:p-2'>
|
||||
<div className='border-0 h-auto p-5 lg:min-w-full overflow-y-auto rounded-3xl rounded-b-none xl:rounded-b-3xl relative flex flex-col w-full modalGlass2 outline-none focus:outline-none'>
|
||||
<div className='border-0 h-auto p-5 lg:min-w-full overflow-y-auto rounded-3xl rounded-b-none xl:rounded-b-3xl relative flex flex-col w-full bg-white outline-none focus:outline-none'>
|
||||
|
||||
|
||||
{
|
||||
@@ -46,7 +46,7 @@ const DefaulModal: FC<Props> = (props: Props) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div onClick={props.close} className='fixed size-full top-0 bottom-0 right-0 modalGlass inset-0 z-50 '></div>
|
||||
<div onClick={props.close} className='fixed size-full top-0 bottom-0 right-0 bg-black/50 backdrop-blur-md inset-0 z-50 '></div>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ const Select: FC<Props> = (props: Props) => {
|
||||
)}>
|
||||
{
|
||||
props.placeholder &&
|
||||
<option value="" disabled selected>{props.placeholder}</option>
|
||||
<option value="" disabled>{props.placeholder}</option>
|
||||
}
|
||||
{
|
||||
props.items?.map((item) => {
|
||||
|
||||
@@ -121,5 +121,6 @@ export const Pages = {
|
||||
create: "/category/create",
|
||||
update: "/category/update/",
|
||||
detail: "/category/",
|
||||
variant: "/category/variant/",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -18,3 +18,12 @@ export type ErrorType = Error & {
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export interface IGeneralError {
|
||||
status: number;
|
||||
success: boolean;
|
||||
error: {
|
||||
message: string;
|
||||
details: string[];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import type { IGeneralError } from "./types";
|
||||
|
||||
/**
|
||||
* Concatenates truthy classes into a space-separated string.
|
||||
@@ -9,3 +10,15 @@ import { twMerge } from "tailwind-merge";
|
||||
export const clx = (...classes: (string | boolean | undefined)[]): string => {
|
||||
return twMerge(classes.filter(Boolean).join(" "));
|
||||
};
|
||||
|
||||
export const extractErrorMessage = (
|
||||
error: Error | unknown,
|
||||
fallbackMessage: string = "خطایی رخ داده است"
|
||||
): string => {
|
||||
try {
|
||||
const axiosError = error as { response?: { data: IGeneralError } };
|
||||
return axiosError?.response?.data?.error?.details?.[0] || fallbackMessage;
|
||||
} catch {
|
||||
return fallbackMessage;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import Textarea from '../../components/Textarea'
|
||||
import UploadBox from '../../components/UploadBox'
|
||||
import Select from '../../components/Select'
|
||||
import { type CategoryType, type CategoryTreeNode } from './types/Types'
|
||||
import { CategoryThemeEnum } from './enum/Enum'
|
||||
import { useFormik } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import { useCreateCategory, useGetCategories, useSingleUpload } from './hooks/useCategoryData'
|
||||
@@ -149,10 +150,10 @@ const CreateCategory: FC = () => {
|
||||
label='تم دسته'
|
||||
placeholder='انتخاب تم دسته'
|
||||
items={[
|
||||
{ value: 'Color', label: 'رنگی' },
|
||||
{ value: 'Monochrome', label: 'تکرنگ' },
|
||||
{ value: 'Gradient', label: 'گرادیانت' },
|
||||
{ value: 'Minimal', label: 'مینیمال' },
|
||||
{ value: CategoryThemeEnum.Sized, label: 'سایز' },
|
||||
{ value: CategoryThemeEnum.Colored, label: 'رنگ' },
|
||||
{ value: CategoryThemeEnum.Meterage, label: 'متراژ' },
|
||||
{ value: CategoryThemeEnum.No_color_No_sized, label: 'بدون تنوع' },
|
||||
]}
|
||||
value={formik.values.theme}
|
||||
onChange={(e) => formik.setFieldValue('theme', e.target.value)}
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
import { type FC, type JSX, useState } from 'react'
|
||||
import Td from '../../components/Td'
|
||||
import { ArrowDown2, ArrowLeft2, Edit } from 'iconsax-react'
|
||||
import { ArrowDown2, ArrowLeft2, Edit, Lock } from 'iconsax-react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useGetCategories } from './hooks/useCategoryData'
|
||||
import { useGetCategories, useDeleteCategory } from './hooks/useCategoryData'
|
||||
import { type CategoryTreeNode } from './types/Types'
|
||||
import { Pages } from '../../config/Pages'
|
||||
import Button from '../../components/Button'
|
||||
import TrashWithConfrim from '../../components/TrashWithConfrim'
|
||||
|
||||
const CategoryList: FC = () => {
|
||||
|
||||
const { data, isLoading } = useGetCategories();
|
||||
const { data, isLoading, refetch } = useGetCategories();
|
||||
const deleteCategoryMutation = useDeleteCategory();
|
||||
const [expandedCategories, setExpandedCategories] = useState<Set<string>>(new Set());
|
||||
|
||||
const handleDeleteCategory = async (categoryId: string) => {
|
||||
await deleteCategoryMutation.mutateAsync(categoryId);
|
||||
refetch()
|
||||
};
|
||||
|
||||
const toggleExpanded = (categoryId: string) => {
|
||||
setExpandedCategories(prev => {
|
||||
const newSet = new Set(prev);
|
||||
@@ -55,12 +63,20 @@ const CategoryList: FC = () => {
|
||||
</div>
|
||||
</Td>
|
||||
<Td text={category.description} />
|
||||
<Td text={category.parent || 'اصلی'} />
|
||||
<Td text=''>
|
||||
{category.leaf ? <Link to={`${Pages.category.variant}${category._id}`}>
|
||||
<Button className='h-7 text-xs w-fit px-4' label='افزودن تنوع' />
|
||||
</Link> : <Lock size={20} color='#8C90A3' />}
|
||||
</Td>
|
||||
<Td text="">
|
||||
<div className="flex items-center gap-2">
|
||||
<Link to={`${Pages.category.update}${category._id}`} state={{ from: 'list' }}>
|
||||
<Edit size={20} color='#8C90A3' />
|
||||
</Link>
|
||||
<TrashWithConfrim
|
||||
onDelete={() => handleDeleteCategory(category._id)}
|
||||
isLoading={deleteCategoryMutation.isPending}
|
||||
/>
|
||||
{category.children && category.children.length > 0 && (
|
||||
<button
|
||||
onClick={() => toggleExpanded(category._id)}
|
||||
@@ -86,7 +102,7 @@ const CategoryList: FC = () => {
|
||||
// اضافه کردن خود دستهبندی
|
||||
result.push(renderCategoryRow(category, level));
|
||||
|
||||
// اگر دستهبندی expand شده باشد، فرزندانش را اضافه کن
|
||||
// اگر دستهبندی expand شده باشد، فرزندانش را به صورت بازگشتی اضافه کن
|
||||
if (expandedCategories.has(category._id) && category.children && category.children.length > 0) {
|
||||
result.push(...renderCategories(category.children, level + 1));
|
||||
}
|
||||
@@ -110,7 +126,7 @@ const CategoryList: FC = () => {
|
||||
<Td text={'عنوان انگلیسی'} />
|
||||
<Td text={'تصویر'} />
|
||||
<Td text={'توضیحات'} />
|
||||
<Td text={'والد'} />
|
||||
<Td text={'تنوع'} />
|
||||
<Td text={'عملیات'} />
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -7,6 +7,7 @@ import Textarea from '../../components/Textarea'
|
||||
import UploadBox from '../../components/UploadBox'
|
||||
import Select from '../../components/Select'
|
||||
import { type CategoryType, type CategoryTreeNode } from './types/Types'
|
||||
import { CategoryThemeEnum } from './enum/Enum'
|
||||
import { useFormik } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import { useUpdateCategory, useGetCategories, useGetCategoryById, useSingleUpload } from './hooks/useCategoryData'
|
||||
@@ -23,6 +24,7 @@ const UpdateCategory: FC = () => {
|
||||
const [isActive, setIsActive] = useState(true)
|
||||
const [hasIconChanged, setHasIconChanged] = useState(false)
|
||||
const [hasImageChanged, setHasImageChanged] = useState(false)
|
||||
const [isDataLoaded, setIsDataLoaded] = useState(false)
|
||||
|
||||
const singleUpload = useSingleUpload()
|
||||
const updateCategoryMutation = useUpdateCategory()
|
||||
@@ -78,7 +80,7 @@ const UpdateCategory: FC = () => {
|
||||
|
||||
// بارگذاری دادههای دسته برای ویرایش
|
||||
useEffect(() => {
|
||||
if (getCategoryById.data?.results?.data) {
|
||||
if (getCategoryById.data?.results?.data && !isDataLoaded) {
|
||||
const categoryData = getCategoryById.data.results.data
|
||||
formik.setValues({
|
||||
title_fa: categoryData.title_fa || '',
|
||||
@@ -90,8 +92,10 @@ const UpdateCategory: FC = () => {
|
||||
theme: categoryData.theme || '',
|
||||
})
|
||||
setIsActive(!categoryData.deleted)
|
||||
setIsDataLoaded(true)
|
||||
}
|
||||
}, [getCategoryById.data, formik])
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getCategoryById.data, isDataLoaded])
|
||||
|
||||
const categoriesData: CategoryTreeNode[] = getCategories.data?.results?.data || []
|
||||
|
||||
@@ -198,10 +202,10 @@ const UpdateCategory: FC = () => {
|
||||
label='تم دسته'
|
||||
placeholder='انتخاب تم دسته'
|
||||
items={[
|
||||
{ value: 'Color', label: 'رنگی' },
|
||||
{ value: 'Monochrome', label: 'تکرنگ' },
|
||||
{ value: 'Gradient', label: 'گرادیانت' },
|
||||
{ value: 'Minimal', label: 'مینیمال' },
|
||||
{ value: CategoryThemeEnum.Sized, label: 'سایز' },
|
||||
{ value: CategoryThemeEnum.Colored, label: 'رنگ' },
|
||||
{ value: CategoryThemeEnum.Meterage, label: 'متراژ' },
|
||||
{ value: CategoryThemeEnum.No_color_No_sized, label: 'بدون تنوع' },
|
||||
]}
|
||||
value={formik.values.theme}
|
||||
onChange={(e) => formik.setFieldValue('theme', e.target.value)}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
import { type FC } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { CategoryThemeEnum } from './enum/Enum'
|
||||
import TitleLine from '../../components/TitleLine'
|
||||
import { VariantSizesList, VariantColorsList, VariantMeteragesList, VariantModal } from './components'
|
||||
import { useVariantLogic } from './hooks/useVariantLogic'
|
||||
|
||||
const Variant: FC = () => {
|
||||
const { id } = useParams()
|
||||
const {
|
||||
category,
|
||||
theme,
|
||||
categoryVariants,
|
||||
isLoading,
|
||||
isModalOpen,
|
||||
newVariant,
|
||||
openModal,
|
||||
closeModal,
|
||||
setNewVariant,
|
||||
addVariant,
|
||||
deleteVariant,
|
||||
isAddingLoading,
|
||||
isUpdatingLoading
|
||||
} = useVariantLogic({ id })
|
||||
|
||||
if (isLoading) {
|
||||
return <div>در حال بارگذاری...</div>
|
||||
}
|
||||
|
||||
if (!category) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<div className="max-w-6xl mx-auto p-6">
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-6">
|
||||
<TitleLine title="مدیریت Variant دستهبندی" />
|
||||
<div className="mt-4 space-y-2">
|
||||
<h1 className="text-2xl font-bold text-gray-900">
|
||||
دستهبندی یافت نشد
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<div className="flex flex-col items-center justify-center py-12 px-6 border-2 border-dashed border-gray-300 rounded-lg bg-gray-50 hover:bg-gray-100 transition-colors">
|
||||
<div className="text-center space-y-4">
|
||||
<div className="w-16 h-16 mx-auto bg-gray-200 rounded-full flex items-center justify-center">
|
||||
<svg className="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L4.268 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-1">دادهای یافت نشد</h3>
|
||||
<p className="text-gray-500 mb-4">امکان ساخت variant جدید وجود ندارد</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderVariants = () => {
|
||||
switch (theme) {
|
||||
case CategoryThemeEnum.Sized:
|
||||
return (
|
||||
<VariantSizesList
|
||||
sizes={categoryVariants.sizes || []}
|
||||
onAddClick={openModal}
|
||||
onDelete={(index) => deleteVariant('sizes', index)}
|
||||
isLoading={isUpdatingLoading}
|
||||
/>
|
||||
)
|
||||
|
||||
case CategoryThemeEnum.Colored:
|
||||
return (
|
||||
<VariantColorsList
|
||||
colors={categoryVariants.colors || []}
|
||||
onAddClick={openModal}
|
||||
onDelete={(index) => deleteVariant('colors', index)}
|
||||
isLoading={isUpdatingLoading}
|
||||
/>
|
||||
)
|
||||
|
||||
case CategoryThemeEnum.Meterage:
|
||||
return (
|
||||
<VariantMeteragesList
|
||||
meterages={categoryVariants.meterages || []}
|
||||
onAddClick={openModal}
|
||||
onDelete={(index) => deleteVariant('meterages', index)}
|
||||
isLoading={isUpdatingLoading}
|
||||
/>
|
||||
)
|
||||
|
||||
case CategoryThemeEnum.No_color_No_sized:
|
||||
return (
|
||||
<div className="p-4 border border-gray-200 rounded-lg bg-gray-50 text-center">
|
||||
<span className="text-gray-600">این دستهبندی variant ندارد</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
default:
|
||||
return (
|
||||
<div className="p-4 border border-yellow-200 rounded-lg bg-yellow-50 text-center">
|
||||
<span className="text-yellow-800">تم ناشناخته: {theme}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<div className="max-w-6xl mx-auto p-6">
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-6">
|
||||
<TitleLine title="مدیریت Variant دستهبندی" />
|
||||
<div className="mt-4 space-y-2">
|
||||
<h1 className="text-2xl font-bold text-gray-900">
|
||||
{category.title_fa}
|
||||
</h1>
|
||||
<p className="text-gray-600">
|
||||
تم: <span className="font-medium">{category.theme}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
{renderVariants()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{theme && (
|
||||
<VariantModal
|
||||
isOpen={isModalOpen}
|
||||
onClose={closeModal}
|
||||
theme={theme}
|
||||
newVariant={newVariant}
|
||||
onVariantChange={setNewVariant}
|
||||
onAdd={addVariant}
|
||||
isLoading={isAddingLoading || isUpdatingLoading}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Variant
|
||||
@@ -0,0 +1,58 @@
|
||||
import { type FC } from 'react'
|
||||
import { type ColorVariantType } from '../types/Types'
|
||||
import Button from '../../../components/Button'
|
||||
import TrashWithConfrim from '../../../components/TrashWithConfrim'
|
||||
import VariantEmptyState from './VariantEmptyState'
|
||||
|
||||
interface VariantColorsListProps {
|
||||
colors: ColorVariantType[]
|
||||
onAddClick: () => void
|
||||
onDelete: (index: number) => void
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
const VariantColorsList: FC<VariantColorsListProps> = ({
|
||||
colors,
|
||||
onAddClick,
|
||||
onDelete,
|
||||
isLoading
|
||||
}) => {
|
||||
if (!colors || colors.length === 0) {
|
||||
return <VariantEmptyState type="colors" onAddClick={onAddClick} />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-semibold text-gray-800">رنگها:</h3>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="w-auto px-3"
|
||||
onClick={onAddClick}
|
||||
>
|
||||
اضافه کردن رنگ
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
{colors.map((color: ColorVariantType, index: number) => (
|
||||
<div key={color.name} className="p-3 border border-gray-200 rounded-lg bg-white shadow-sm hover:shadow-md transition-shadow flex items-center gap-3 justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className="w-6 h-6 rounded-full border-2 border-gray-300 shadow-sm"
|
||||
style={{ backgroundColor: color.hexColor }}
|
||||
></div>
|
||||
<span className="text-gray-700 font-medium">{color.name}</span>
|
||||
</div>
|
||||
<TrashWithConfrim
|
||||
onDelete={() => onDelete(index)}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default VariantColorsList
|
||||
@@ -0,0 +1,72 @@
|
||||
import { type FC } from 'react'
|
||||
import Button from '../../../components/Button'
|
||||
|
||||
interface VariantEmptyStateProps {
|
||||
type: 'sizes' | 'colors' | 'meterages'
|
||||
onAddClick: () => void
|
||||
}
|
||||
|
||||
const VariantEmptyState: FC<VariantEmptyStateProps> = ({ type, onAddClick }) => {
|
||||
const getContent = () => {
|
||||
switch (type) {
|
||||
case 'sizes':
|
||||
return {
|
||||
icon: (
|
||||
<svg className="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||
</svg>
|
||||
),
|
||||
title: 'هیچ سایزی تعریف نشده',
|
||||
description: 'برای شروع، سایز جدیدی اضافه کنید',
|
||||
buttonText: 'اضافه کردن اولین سایز'
|
||||
}
|
||||
case 'colors':
|
||||
return {
|
||||
icon: (
|
||||
<svg className="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zM21 5a2 2 0 00-2-2h-4a2 2 0 00-2 2v12a4 4 0 004 4h4a2 2 0 002-2V5z" />
|
||||
</svg>
|
||||
),
|
||||
title: 'هیچ رنگی تعریف نشده',
|
||||
description: 'برای شروع، رنگ جدیدی اضافه کنید',
|
||||
buttonText: 'اضافه کردن اولین رنگ'
|
||||
}
|
||||
case 'meterages':
|
||||
return {
|
||||
icon: (
|
||||
<svg className="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
),
|
||||
title: 'هیچ متریاژی تعریف نشده',
|
||||
description: 'برای شروع، متریاژ جدیدی اضافه کنید',
|
||||
buttonText: 'اضافه کردن اولین متریاژ'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { icon, title, description, buttonText } = getContent()
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-12 px-6 border-2 border-dashed border-gray-300 rounded-lg bg-gray-50 hover:bg-gray-100 transition-colors">
|
||||
<div className="text-center space-y-4">
|
||||
<div className="w-16 h-16 mx-auto bg-gray-200 rounded-full flex items-center justify-center">
|
||||
{icon}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-1">{title}</h3>
|
||||
<p className="text-gray-500 mb-4">{description}</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={onAddClick}
|
||||
className="px-6"
|
||||
>
|
||||
{buttonText}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default VariantEmptyState
|
||||
@@ -0,0 +1,52 @@
|
||||
import { type FC } from 'react'
|
||||
import { type VariantType } from '../types/Types'
|
||||
import Button from '../../../components/Button'
|
||||
import TrashWithConfrim from '../../../components/TrashWithConfrim'
|
||||
import VariantEmptyState from './VariantEmptyState'
|
||||
|
||||
interface VariantMeteragesListProps {
|
||||
meterages: VariantType[]
|
||||
onAddClick: () => void
|
||||
onDelete: (index: number) => void
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
const VariantMeteragesList: FC<VariantMeteragesListProps> = ({
|
||||
meterages,
|
||||
onAddClick,
|
||||
onDelete,
|
||||
isLoading
|
||||
}) => {
|
||||
if (!meterages || meterages.length === 0) {
|
||||
return <VariantEmptyState type="meterages" onAddClick={onAddClick} />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-semibold text-gray-800">متریاژ:</h3>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="w-auto px-3"
|
||||
onClick={onAddClick}
|
||||
>
|
||||
اضافه کردن متریاژ
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
{meterages.map((meterage: VariantType, index: number) => (
|
||||
<div key={meterage._id} className="p-3 border border-gray-200 rounded-lg bg-white shadow-sm hover:shadow-md transition-shadow flex items-center justify-between">
|
||||
<span className="text-gray-700">{meterage.value}</span>
|
||||
<TrashWithConfrim
|
||||
onDelete={() => onDelete(index)}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default VariantMeteragesList
|
||||
@@ -0,0 +1,144 @@
|
||||
import { type FC } from 'react'
|
||||
import { CategoryThemeEnum } from '../enum/Enum'
|
||||
import Button from '../../../components/Button'
|
||||
import DefaulModal from '../../../components/DefaulModal'
|
||||
import Input from '../../../components/Input'
|
||||
|
||||
interface VariantModalProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
theme: CategoryThemeEnum
|
||||
newVariant: {
|
||||
value: string
|
||||
name: string
|
||||
hexColor: string
|
||||
}
|
||||
onVariantChange: (variant: { value: string; name: string; hexColor: string }) => void
|
||||
onAdd: () => void
|
||||
isLoading?: boolean
|
||||
}
|
||||
|
||||
const VariantModal: FC<VariantModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
theme,
|
||||
newVariant,
|
||||
onVariantChange,
|
||||
onAdd,
|
||||
isLoading = false
|
||||
}) => {
|
||||
const getModalTitle = () => {
|
||||
switch (theme) {
|
||||
case CategoryThemeEnum.Sized:
|
||||
return 'اضافه کردن سایز جدید'
|
||||
case CategoryThemeEnum.Colored:
|
||||
return 'اضافه کردن رنگ جدید'
|
||||
case CategoryThemeEnum.Meterage:
|
||||
return 'اضافه کردن متریاژ جدید'
|
||||
default:
|
||||
return 'اضافه کردن variant جدید'
|
||||
}
|
||||
}
|
||||
|
||||
const renderFormContent = () => {
|
||||
switch (theme) {
|
||||
case CategoryThemeEnum.Sized:
|
||||
return (
|
||||
<Input
|
||||
label="مقدار سایز"
|
||||
placeholder="مثال: XL, M, 42"
|
||||
value={newVariant.value}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onVariantChange({ ...newVariant, value: e.target.value })}
|
||||
/>
|
||||
)
|
||||
|
||||
case CategoryThemeEnum.Colored:
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Input
|
||||
label="نام رنگ"
|
||||
placeholder="مثال: آبی, قرمز"
|
||||
value={newVariant.name}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onVariantChange({ ...newVariant, name: e.target.value })}
|
||||
/>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm">کد رنگ</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="color"
|
||||
value={newVariant.hexColor}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onVariantChange({ ...newVariant, hexColor: e.target.value })}
|
||||
className="w-12 h-10 border border-gray-300 rounded cursor-pointer"
|
||||
/>
|
||||
<Input
|
||||
value={newVariant.hexColor}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onVariantChange({ ...newVariant, hexColor: e.target.value })}
|
||||
className="flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
case CategoryThemeEnum.Meterage:
|
||||
return (
|
||||
<Input
|
||||
label="مقدار متریاژ"
|
||||
placeholder="مثال: 2 متر, 5 سانتی متر"
|
||||
value={newVariant.value}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onVariantChange({ ...newVariant, value: e.target.value })}
|
||||
/>
|
||||
)
|
||||
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const isFormValid = () => {
|
||||
switch (theme) {
|
||||
case CategoryThemeEnum.Sized:
|
||||
return !!newVariant.value.trim()
|
||||
case CategoryThemeEnum.Colored:
|
||||
return !!newVariant.name.trim()
|
||||
case CategoryThemeEnum.Meterage:
|
||||
return !!newVariant.value.trim()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<DefaulModal
|
||||
open={isOpen}
|
||||
close={onClose}
|
||||
isHeader={true}
|
||||
title_header={getModalTitle()}
|
||||
width={500}
|
||||
>
|
||||
<div className="space-y-6">
|
||||
{renderFormContent()}
|
||||
|
||||
<div className="flex gap-3 pt-4 border-t border-border">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="flex-1"
|
||||
onClick={onClose}
|
||||
>
|
||||
انصراف
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
className="flex-1"
|
||||
onClick={onAdd}
|
||||
disabled={!isFormValid() || isLoading}
|
||||
>
|
||||
{isLoading ? 'در حال اضافه کردن...' : 'اضافه کردن'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DefaulModal>
|
||||
)
|
||||
}
|
||||
|
||||
export default VariantModal
|
||||
@@ -0,0 +1,52 @@
|
||||
import { type FC } from 'react'
|
||||
import { type VariantType } from '../types/Types'
|
||||
import Button from '../../../components/Button'
|
||||
import TrashWithConfrim from '../../../components/TrashWithConfrim'
|
||||
import VariantEmptyState from './VariantEmptyState'
|
||||
|
||||
interface VariantSizesListProps {
|
||||
sizes: VariantType[]
|
||||
onAddClick: () => void
|
||||
onDelete: (index: number) => void
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
const VariantSizesList: FC<VariantSizesListProps> = ({
|
||||
sizes,
|
||||
onAddClick,
|
||||
onDelete,
|
||||
isLoading
|
||||
}) => {
|
||||
if (!sizes || sizes.length === 0) {
|
||||
return <VariantEmptyState type="sizes" onAddClick={onAddClick} />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-semibold text-gray-800">سایزها:</h3>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="w-auto px-3"
|
||||
onClick={onAddClick}
|
||||
>
|
||||
اضافه کردن سایز
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
{sizes.map((size: VariantType, index: number) => (
|
||||
<div key={size._id} className="p-3 border border-gray-200 rounded-lg bg-white shadow-sm hover:shadow-md transition-shadow flex items-center justify-between">
|
||||
<span className="text-gray-700">{size.value}</span>
|
||||
<TrashWithConfrim
|
||||
onDelete={() => onDelete(index)}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default VariantSizesList
|
||||
@@ -3,3 +3,8 @@ export { default as CategoryTree } from "./CategoryTree";
|
||||
export { default as CategoryInfo } from "./CategoryInfo";
|
||||
export { default as CategoryBreadcrumb } from "./CategoryBreadcrumb";
|
||||
export { default as CategorySummary } from "./CategorySummary";
|
||||
export { default as VariantEmptyState } from "./VariantEmptyState";
|
||||
export { default as VariantSizesList } from "./VariantSizesList";
|
||||
export { default as VariantColorsList } from "./VariantColorsList";
|
||||
export { default as VariantMeteragesList } from "./VariantMeteragesList";
|
||||
export { default as VariantModal } from "./VariantModal";
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
export enum CategoryThemeEnum {
|
||||
Sized = "Size",
|
||||
Colored = "Color",
|
||||
Meterage = "Meterage",
|
||||
No_color_No_sized = "noColor_noSize",
|
||||
}
|
||||
|
||||
export const enum categoryAttType {
|
||||
Text = "text",
|
||||
Select = "select",
|
||||
Number = "number",
|
||||
CheckBox = "checkbox",
|
||||
}
|
||||
@@ -41,3 +41,29 @@ export const useSingleUpload = () => {
|
||||
mutationFn: (variables: File) => api.singleUpload(variables),
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetCategoryVariants = (id: string) => {
|
||||
return useQuery({
|
||||
queryKey: ['category', 'variants', id],
|
||||
queryFn: () => api.getCategoryVariants(id),
|
||||
enabled: !!id,
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateCategoryVariant = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.createCategoryVariant,
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateCategoryVariant = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.updateCategoryVariant,
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteCategoryVariant = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.deleteCategoryVariant,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,169 @@
|
||||
import { useState } from 'react'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useGetCategoryVariants, useCreateCategoryVariant, useUpdateCategoryVariant } from './useCategoryData'
|
||||
import { CategoryThemeEnum } from '../enum/Enum'
|
||||
import { type VariantType, type ColorVariantType } from '../types/Types'
|
||||
import { toast } from 'react-toastify'
|
||||
import { extractErrorMessage } from '../../../helpers/utils'
|
||||
|
||||
interface UseVariantLogicProps {
|
||||
id: string | undefined
|
||||
}
|
||||
|
||||
interface CategoryVariantData {
|
||||
category: {
|
||||
categoryVariants: {
|
||||
sizes?: VariantType[]
|
||||
colors?: ColorVariantType[]
|
||||
meterages?: VariantType[]
|
||||
}
|
||||
theme: CategoryThemeEnum
|
||||
title_fa: string
|
||||
}
|
||||
}
|
||||
|
||||
interface ApiResponse {
|
||||
results: {
|
||||
data: CategoryVariantData
|
||||
}
|
||||
}
|
||||
|
||||
export const useVariantLogic = ({ id }: UseVariantLogicProps) => {
|
||||
const queryClient = useQueryClient()
|
||||
const { data, isLoading } = useGetCategoryVariants(id!)
|
||||
const createVariantMutation = useCreateCategoryVariant()
|
||||
const updateVariantMutation = useUpdateCategoryVariant()
|
||||
|
||||
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||
const [newVariant, setNewVariant] = useState({
|
||||
value: '',
|
||||
name: '',
|
||||
hexColor: '#000000'
|
||||
})
|
||||
|
||||
const categoryData = data as ApiResponse | undefined
|
||||
const category = categoryData?.results?.data?.category
|
||||
const theme = category?.theme
|
||||
const categoryVariants = category?.categoryVariants || {
|
||||
sizes: [],
|
||||
colors: [],
|
||||
meterages: []
|
||||
}
|
||||
|
||||
const hasExistingVariants =
|
||||
(categoryVariants.sizes && categoryVariants.sizes.length > 0) ||
|
||||
(categoryVariants.colors && categoryVariants.colors.length > 0) ||
|
||||
(categoryVariants.meterages && categoryVariants.meterages.length > 0)
|
||||
|
||||
const resetModal = () => {
|
||||
setIsModalOpen(false)
|
||||
setNewVariant({ value: '', name: '', hexColor: '#000000' })
|
||||
}
|
||||
|
||||
const openModal = () => setIsModalOpen(true)
|
||||
const closeModal = () => resetModal()
|
||||
|
||||
const addVariant = async () => {
|
||||
if (!id || !categoryData) return
|
||||
|
||||
let variantData
|
||||
|
||||
switch (theme) {
|
||||
case CategoryThemeEnum.Sized:
|
||||
variantData = {
|
||||
id,
|
||||
theme: CategoryThemeEnum.Sized,
|
||||
sizes: hasExistingVariants
|
||||
? [...(categoryVariants.sizes || []), { _id: Date.now(), value: newVariant.value }]
|
||||
: [{ _id: Date.now(), value: newVariant.value }]
|
||||
}
|
||||
break
|
||||
|
||||
case CategoryThemeEnum.Colored:
|
||||
variantData = {
|
||||
id,
|
||||
theme: CategoryThemeEnum.Colored,
|
||||
colors: hasExistingVariants
|
||||
? [...(categoryVariants.colors || []), { name: newVariant.name, hexColor: newVariant.hexColor }]
|
||||
: [{ name: newVariant.name, hexColor: newVariant.hexColor }]
|
||||
}
|
||||
break
|
||||
|
||||
case CategoryThemeEnum.Meterage:
|
||||
variantData = {
|
||||
id,
|
||||
theme: CategoryThemeEnum.Meterage,
|
||||
meterages: hasExistingVariants
|
||||
? [...(categoryVariants.meterages || []), { _id: Date.now(), value: newVariant.value }]
|
||||
: [{ _id: Date.now(), value: newVariant.value }]
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
const mutation = hasExistingVariants ? updateVariantMutation : createVariantMutation
|
||||
|
||||
mutation.mutate(variantData, {
|
||||
onError: (error) => {
|
||||
toast.error(extractErrorMessage(error))
|
||||
}
|
||||
})
|
||||
resetModal()
|
||||
queryClient.invalidateQueries({ queryKey: ['category', 'variants', id] })
|
||||
}
|
||||
|
||||
const deleteVariant = async (variantType: 'sizes' | 'colors' | 'meterages', index: number) => {
|
||||
if (!id || !categoryData) return
|
||||
|
||||
let updatedVariants
|
||||
let variantData
|
||||
|
||||
switch (variantType) {
|
||||
case 'sizes':
|
||||
if (!categoryVariants.sizes || index >= categoryVariants.sizes.length) return
|
||||
updatedVariants = categoryVariants.sizes.filter((_, i) => i !== index)
|
||||
variantData = { id, sizes: updatedVariants }
|
||||
break
|
||||
case 'colors':
|
||||
if (!categoryVariants.colors || index >= categoryVariants.colors.length) return
|
||||
updatedVariants = categoryVariants.colors.filter((_, i) => i !== index)
|
||||
variantData = { id, colors: updatedVariants }
|
||||
break
|
||||
case 'meterages':
|
||||
if (!categoryVariants.meterages || index >= categoryVariants.meterages.length) return
|
||||
updatedVariants = categoryVariants.meterages.filter((_, i) => i !== index)
|
||||
variantData = { id, meterages: updatedVariants }
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
await updateVariantMutation.mutateAsync({ ...variantData, theme: theme! })
|
||||
queryClient.invalidateQueries({ queryKey: ['category', 'variants', id] })
|
||||
}
|
||||
|
||||
return {
|
||||
// Data
|
||||
category,
|
||||
theme,
|
||||
categoryVariants,
|
||||
isLoading,
|
||||
|
||||
// Modal state
|
||||
isModalOpen,
|
||||
newVariant,
|
||||
|
||||
// Actions
|
||||
openModal,
|
||||
closeModal,
|
||||
setNewVariant,
|
||||
addVariant,
|
||||
deleteVariant,
|
||||
|
||||
// Loading states
|
||||
isAddingLoading: createVariantMutation.isPending,
|
||||
isUpdatingLoading: updateVariantMutation.isPending
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from "../../../config/axios";
|
||||
import { type CategoryType, type SingleUploadResponseType, type CategoriesResponseType, type CategoryByIdResponseType } from "../types/Types";
|
||||
import { type CategoryType, type SingleUploadResponseType, type CategoriesResponseType, type CategoryByIdResponseType, type CategoryVariantsResponseType, type CategoryVariantsType } from "../types/Types";
|
||||
|
||||
export const createCategory = async (params: CategoryType) => {
|
||||
const { data } = await axios.post(`/admin/category`, params);
|
||||
@@ -22,7 +22,7 @@ export const updateCategory = async (id: string, params: Partial<CategoryType>)
|
||||
};
|
||||
|
||||
export const deleteCategory = async (id: string) => {
|
||||
const { data } = await axios.delete(`/admin/category/${id}`);
|
||||
const { data } = await axios.delete(`/admin/category/${id}/delete`);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -32,3 +32,28 @@ export const singleUpload = async (params: File): Promise<SingleUploadResponseTy
|
||||
const { data } = await axios.post(`/admin/medias/upload/single`, formData);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getCategoryVariants = async (id: string): Promise<CategoryVariantsResponseType> => {
|
||||
const { data } = await axios.get(`/category/${id}/variants`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const createCategoryVariant = async (params: CategoryVariantsType) => {
|
||||
const { data } = await axios.post(`/admin/category/${params.id}/variants`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const updateCategoryVariant = async (params: CategoryVariantsType) => {
|
||||
const { data } = await axios.patch(`/admin/category/${params.id}/variants`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const deleteCategoryVariant = async (params: { id: string; variantType: 'sizes' | 'colors' | 'meterages'; index: number }) => {
|
||||
const { data } = await axios.delete(`/admin/category/${params.id}/variants`, {
|
||||
data: {
|
||||
variantType: params.variantType,
|
||||
index: params.index
|
||||
}
|
||||
});
|
||||
return data;
|
||||
};
|
||||
@@ -75,3 +75,64 @@ export type CategoryByIdResponseType = {
|
||||
data: CategoryTreeNode;
|
||||
};
|
||||
};
|
||||
|
||||
export type ColorType = {
|
||||
name: string;
|
||||
hexColor: string;
|
||||
};
|
||||
|
||||
export type SizeType = {
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type MeterageType = {
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type ProductVariantType = {
|
||||
theme: string;
|
||||
colors?: ColorType[];
|
||||
sizes?: SizeType[];
|
||||
meterages?: MeterageType[];
|
||||
};
|
||||
|
||||
// تایپهای جدید برای variants دستهبندی
|
||||
export type VariantType = {
|
||||
_id: number;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type ColorVariantType = {
|
||||
name: string;
|
||||
hexColor: string;
|
||||
};
|
||||
|
||||
export type CategoryVariantsType = {
|
||||
id: string;
|
||||
theme: string;
|
||||
sizes?: VariantType[];
|
||||
colors?: ColorVariantType[];
|
||||
meterages?: VariantType[];
|
||||
};
|
||||
|
||||
export type CategoryWithVariantsType = {
|
||||
_id: string;
|
||||
title_fa: string;
|
||||
title_en: string;
|
||||
theme: string;
|
||||
icon: string;
|
||||
imageUrl: string;
|
||||
description: string;
|
||||
leaf: boolean;
|
||||
categoryVariants: CategoryVariantsType;
|
||||
};
|
||||
|
||||
export type CategoryVariantsResponseType = {
|
||||
status: number;
|
||||
success: boolean;
|
||||
results: {
|
||||
data: {
|
||||
category: CategoryWithVariantsType;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ import Create from '../pages/category/Create'
|
||||
import List from '../pages/category/List'
|
||||
import Update from '../pages/category/Update'
|
||||
import { Pages } from '../config/Pages'
|
||||
import Variant from '../pages/category/Variant'
|
||||
|
||||
const MainRouter: FC = () => {
|
||||
|
||||
@@ -30,6 +31,7 @@ const MainRouter: FC = () => {
|
||||
<Route path={Pages.category.create} element={<Create />} />
|
||||
<Route path={Pages.category.list} element={<List />} />
|
||||
<Route path={`${Pages.category.update}:id`} element={<Update />} />
|
||||
<Route path={`${Pages.category.variant}:id`} element={<Variant />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+10
-6
@@ -3,10 +3,13 @@
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"lib": [
|
||||
"ES2022",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
@@ -14,14 +17,15 @@
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"erasableSyntaxOnly": false,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user