fix: поправил ошибки на формочке кредитных программ
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
@@ -22,59 +22,144 @@ import { Button } from '@/components/ui/button';
|
|||||||
import type { CreditProgramBindingModel } from '@/types/types';
|
import type { CreditProgramBindingModel } from '@/types/types';
|
||||||
import { useAuthStore } from '@/store/workerStore';
|
import { useAuthStore } from '@/store/workerStore';
|
||||||
import { usePeriods } from '@/hooks/usePeriods';
|
import { usePeriods } from '@/hooks/usePeriods';
|
||||||
import { useCurrencies } from '@/hooks/useCurrencies';
|
|
||||||
|
|
||||||
const formSchema = z.object({
|
type BaseFormValues = {
|
||||||
|
id?: string;
|
||||||
|
name: string;
|
||||||
|
cost: number;
|
||||||
|
maxCost: number;
|
||||||
|
periodId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type EditFormValues = Partial<BaseFormValues>;
|
||||||
|
|
||||||
|
const baseSchema = z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
name: z.string().min(5, 'Название должно быть не короче 5 символов'),
|
name: z.string().min(1, 'Название обязательно'),
|
||||||
cost: z.coerce.number().min(0, 'Стоимость не может быть отрицательной'),
|
cost: z.coerce.number().min(0, 'Стоимость не может быть отрицательной'),
|
||||||
maxCost: z.coerce
|
maxCost: z.coerce
|
||||||
.number()
|
.number()
|
||||||
.min(0, 'Максимальная стоимость не может быть отрицательной'),
|
.min(0, 'Максимальная стоимость не может быть отрицательной'),
|
||||||
periodId: z.string().min(1, 'Выберите период'),
|
periodId: z.string().min(1, 'Выберите период'),
|
||||||
currencyCreditPrograms: z
|
|
||||||
.array(z.string())
|
|
||||||
.min(1, 'Выберите хотя бы одну валюту'),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type FormValues = z.infer<typeof formSchema>;
|
const addSchema = baseSchema;
|
||||||
|
|
||||||
type CreditProgramFormProps = {
|
const editSchema = z.object({
|
||||||
|
id: z.string().optional(),
|
||||||
|
name: z.string().min(1, 'Название обязательно').optional(),
|
||||||
|
cost: z.coerce
|
||||||
|
.number()
|
||||||
|
.min(0, 'Стоимость не может быть отрицательной')
|
||||||
|
.optional(),
|
||||||
|
maxCost: z.coerce
|
||||||
|
.number()
|
||||||
|
.min(0, 'Максимальная стоимость не может быть отрицательной')
|
||||||
|
.optional(),
|
||||||
|
periodId: z.string().min(1, 'Выберите период').optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
interface BaseCreditProgramFormProps {
|
||||||
onSubmit: (data: CreditProgramBindingModel) => void;
|
onSubmit: (data: CreditProgramBindingModel) => void;
|
||||||
};
|
schema: z.ZodType<BaseFormValues | EditFormValues>;
|
||||||
|
defaultValues?: Partial<BaseFormValues>;
|
||||||
|
}
|
||||||
|
|
||||||
export const CreditProgramForm = ({
|
const BaseCreditProgramForm = ({
|
||||||
onSubmit,
|
onSubmit,
|
||||||
}: CreditProgramFormProps): React.JSX.Element => {
|
schema,
|
||||||
const form = useForm<FormValues>({
|
defaultValues,
|
||||||
resolver: zodResolver(formSchema),
|
}: BaseCreditProgramFormProps): React.JSX.Element => {
|
||||||
defaultValues: {
|
const form = useForm<BaseFormValues | EditFormValues>({
|
||||||
id: '',
|
resolver: zodResolver(schema),
|
||||||
name: '',
|
defaultValues: defaultValues
|
||||||
cost: 0,
|
? {
|
||||||
maxCost: 0,
|
id: defaultValues.id ?? '',
|
||||||
periodId: '',
|
name: defaultValues.name ?? '',
|
||||||
currencyCreditPrograms: [],
|
cost: defaultValues.cost ?? 0,
|
||||||
},
|
maxCost: defaultValues.maxCost ?? 0,
|
||||||
|
periodId: defaultValues.periodId ?? '',
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
cost: 0,
|
||||||
|
maxCost: 0,
|
||||||
|
periodId: '',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { periods } = usePeriods();
|
const { periods } = usePeriods();
|
||||||
const { currencies } = useCurrencies();
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (defaultValues) {
|
||||||
|
form.reset({
|
||||||
|
id: defaultValues.id ?? '',
|
||||||
|
name: defaultValues.name ?? '',
|
||||||
|
cost: defaultValues.cost ?? 0,
|
||||||
|
maxCost: defaultValues.maxCost ?? 0,
|
||||||
|
periodId: defaultValues.periodId ?? '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [defaultValues, form]);
|
||||||
|
|
||||||
const storekeeper = useAuthStore((store) => store.user);
|
const storekeeper = useAuthStore((store) => store.user);
|
||||||
|
|
||||||
const handleSubmit = (data: FormValues) => {
|
const handleSubmit = (data: BaseFormValues | EditFormValues) => {
|
||||||
const dataWithId = {
|
if (!storekeeper?.id) {
|
||||||
...data,
|
console.error('Storekeeper ID is not available.');
|
||||||
id: crypto.randomUUID(),
|
return;
|
||||||
};
|
}
|
||||||
const payload: CreditProgramBindingModel = {
|
|
||||||
...dataWithId,
|
let payload: CreditProgramBindingModel;
|
||||||
currencyCreditPrograms: data.currencyCreditPrograms.map((currencyId) => ({
|
|
||||||
currencyId,
|
if (schema === addSchema) {
|
||||||
})),
|
const addData = data as BaseFormValues;
|
||||||
storekeeperId: storekeeper?.id,
|
payload = {
|
||||||
};
|
id: addData.id || crypto.randomUUID(),
|
||||||
|
storekeeperId: storekeeper.id,
|
||||||
|
name: addData.name,
|
||||||
|
cost: addData.cost,
|
||||||
|
maxCost: addData.maxCost,
|
||||||
|
periodId: addData.periodId,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const editData = data as EditFormValues;
|
||||||
|
const currentDefaultValues = defaultValues as Partial<BaseFormValues>;
|
||||||
|
|
||||||
|
const changedData: Partial<CreditProgramBindingModel> = {};
|
||||||
|
|
||||||
|
if (editData.id !== undefined && editData.id !== currentDefaultValues?.id)
|
||||||
|
changedData.id = editData.id;
|
||||||
|
if (
|
||||||
|
editData.name !== undefined &&
|
||||||
|
editData.name !== currentDefaultValues?.name
|
||||||
|
)
|
||||||
|
changedData.name = editData.name;
|
||||||
|
if (
|
||||||
|
editData.cost !== undefined &&
|
||||||
|
editData.cost !== currentDefaultValues?.cost
|
||||||
|
)
|
||||||
|
changedData.cost = editData.cost;
|
||||||
|
if (
|
||||||
|
editData.maxCost !== undefined &&
|
||||||
|
editData.maxCost !== currentDefaultValues?.maxCost
|
||||||
|
)
|
||||||
|
changedData.maxCost = editData.maxCost;
|
||||||
|
if (
|
||||||
|
editData.periodId !== undefined &&
|
||||||
|
editData.periodId !== currentDefaultValues?.periodId
|
||||||
|
)
|
||||||
|
changedData.periodId = editData.periodId;
|
||||||
|
|
||||||
|
if (currentDefaultValues?.id) changedData.id = currentDefaultValues.id;
|
||||||
|
changedData.storekeeperId = storekeeper.id;
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
...(defaultValues as CreditProgramBindingModel),
|
||||||
|
...changedData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
onSubmit(payload);
|
onSubmit(payload);
|
||||||
};
|
};
|
||||||
@@ -139,7 +224,7 @@ export const CreditProgramForm = ({
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Период</FormLabel>
|
<FormLabel>Период</FormLabel>
|
||||||
<Select onValueChange={field.onChange} value={field.value}>
|
<Select onValueChange={field.onChange} value={field.value || ''}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Выберите период" />
|
<SelectValue placeholder="Выберите период" />
|
||||||
@@ -147,7 +232,7 @@ export const CreditProgramForm = ({
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{periods &&
|
{periods &&
|
||||||
periods.map((period) => (
|
periods?.map((period) => (
|
||||||
<SelectItem key={period.id} value={period.id}>
|
<SelectItem key={period.id} value={period.id}>
|
||||||
{`${new Date(
|
{`${new Date(
|
||||||
period.startTime,
|
period.startTime,
|
||||||
@@ -162,38 +247,7 @@ export const CreditProgramForm = ({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="currencyCreditPrograms"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Валюты</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<div className="relative">
|
|
||||||
<select
|
|
||||||
multiple
|
|
||||||
value={field.value}
|
|
||||||
onChange={(e) => {
|
|
||||||
const selected = Array.from(e.target.selectedOptions).map(
|
|
||||||
(option) => option.value,
|
|
||||||
);
|
|
||||||
field.onChange(selected);
|
|
||||||
}}
|
|
||||||
className="w-full border rounded-md p-2 h-24"
|
|
||||||
>
|
|
||||||
{currencies &&
|
|
||||||
currencies.map((currency) => (
|
|
||||||
<option key={currency.id} value={currency.id}>
|
|
||||||
{currency.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Button type="submit" className="w-full">
|
<Button type="submit" className="w-full">
|
||||||
Сохранить
|
Сохранить
|
||||||
</Button>
|
</Button>
|
||||||
@@ -201,3 +255,27 @@ export const CreditProgramForm = ({
|
|||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CreditProgramFormAdd = ({
|
||||||
|
onSubmit,
|
||||||
|
}: {
|
||||||
|
onSubmit: (data: CreditProgramBindingModel) => void;
|
||||||
|
}): React.JSX.Element => {
|
||||||
|
return <BaseCreditProgramForm onSubmit={onSubmit} schema={addSchema} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CreditProgramFormEdit = ({
|
||||||
|
onSubmit,
|
||||||
|
defaultValues,
|
||||||
|
}: {
|
||||||
|
onSubmit: (data: CreditProgramBindingModel) => void;
|
||||||
|
defaultValues: Partial<BaseFormValues>;
|
||||||
|
}): React.JSX.Element => {
|
||||||
|
return (
|
||||||
|
<BaseCreditProgramForm
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
schema={editSchema}
|
||||||
|
defaultValues={defaultValues}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ const BaseCurrencyForm = ({
|
|||||||
const storekeeper = useAuthStore((store) => store.user);
|
const storekeeper = useAuthStore((store) => store.user);
|
||||||
|
|
||||||
const handleSubmit = (data: BaseFormValues | EditFormValues) => {
|
const handleSubmit = (data: BaseFormValues | EditFormValues) => {
|
||||||
// Если это форма редактирования, используем только заполненные поля
|
|
||||||
const payload: CurrencyBindingModel = {
|
const payload: CurrencyBindingModel = {
|
||||||
id: data.id || crypto.randomUUID(),
|
id: data.id || crypto.randomUUID(),
|
||||||
storekeeperId: storekeeper?.id,
|
storekeeperId: storekeeper?.id,
|
||||||
|
|||||||
@@ -3,11 +3,22 @@ import { AppSidebar } from '../layout/Sidebar';
|
|||||||
import { useCreditPrograms } from '@/hooks/useCreditPrograms';
|
import { useCreditPrograms } from '@/hooks/useCreditPrograms';
|
||||||
import { DialogForm } from '../layout/DialogForm';
|
import { DialogForm } from '../layout/DialogForm';
|
||||||
import { DataTable } from '../layout/DataTable';
|
import { DataTable } from '../layout/DataTable';
|
||||||
import { CreditProgramForm } from '../features/CreditProgramForm';
|
import {
|
||||||
|
CreditProgramFormAdd,
|
||||||
|
CreditProgramFormEdit,
|
||||||
|
} from '../features/CreditProgramForm';
|
||||||
import type { CreditProgramBindingModel } from '@/types/types';
|
import type { CreditProgramBindingModel } from '@/types/types';
|
||||||
import type { ColumnDef } from '../layout/DataTable';
|
import type { ColumnDef } from '../layout/DataTable';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { usePeriods } from '@/hooks/usePeriods';
|
||||||
|
import { useStorekeepers } from '@/hooks/useStorekeepers';
|
||||||
|
|
||||||
const columns: ColumnDef<CreditProgramBindingModel>[] = [
|
interface CreditProgramTableData extends CreditProgramBindingModel {
|
||||||
|
formattedPeriod: string;
|
||||||
|
storekeeperFullName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns: ColumnDef<CreditProgramTableData>[] = [
|
||||||
{
|
{
|
||||||
accessorKey: 'id',
|
accessorKey: 'id',
|
||||||
header: 'ID',
|
header: 'ID',
|
||||||
@@ -25,12 +36,12 @@ const columns: ColumnDef<CreditProgramBindingModel>[] = [
|
|||||||
header: 'Макс. стоимость',
|
header: 'Макс. стоимость',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'storekeeperId',
|
accessorKey: 'storekeeperFullName',
|
||||||
header: 'ID Кладовщика',
|
header: 'Кладовщик',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'periodId',
|
accessorKey: 'formattedPeriod',
|
||||||
header: 'ID Периода',
|
header: 'Период',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -43,12 +54,73 @@ export const CreditPrograms = (): React.JSX.Element => {
|
|||||||
createCreditProgram,
|
createCreditProgram,
|
||||||
updateCreditProgram,
|
updateCreditProgram,
|
||||||
} = useCreditPrograms();
|
} = useCreditPrograms();
|
||||||
|
const { periods } = usePeriods();
|
||||||
|
const { storekeepers } = useStorekeepers();
|
||||||
|
|
||||||
const [isDialogOpen, setIsDialogOpen] = React.useState<boolean>(false);
|
const finalData = React.useMemo(() => {
|
||||||
|
if (!creditPrograms || !periods || !storekeepers) return [];
|
||||||
|
|
||||||
|
return creditPrograms.map((program) => {
|
||||||
|
const period = periods?.find((p) => p.id === program.periodId);
|
||||||
|
const storekeeper = storekeepers?.find(
|
||||||
|
(s) => s.id === program.storekeeperId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const formattedPeriod = period
|
||||||
|
? `${new Date(period.startTime).toLocaleDateString()} - ${new Date(
|
||||||
|
period.endTime,
|
||||||
|
).toLocaleDateString()}`
|
||||||
|
: 'Неизвестный период';
|
||||||
|
|
||||||
|
const storekeeperFullName = storekeeper
|
||||||
|
? [storekeeper.surname, storekeeper.name, storekeeper.middleName]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ') || 'Неизвестный кладовщик'
|
||||||
|
: 'Неизвестный кладовщик';
|
||||||
|
|
||||||
|
return {
|
||||||
|
...program,
|
||||||
|
formattedPeriod,
|
||||||
|
storekeeperFullName,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [creditPrograms, periods, storekeepers]);
|
||||||
|
|
||||||
|
const [isAddDialogOpen, setIsAddDialogOpen] = React.useState<boolean>(false);
|
||||||
|
const [isEditDialogOpen, setIsEditDialogOpen] =
|
||||||
|
React.useState<boolean>(false);
|
||||||
|
const [selectedItem, setSelectedItem] = React.useState<
|
||||||
|
CreditProgramBindingModel | undefined
|
||||||
|
>();
|
||||||
|
|
||||||
const handleAdd = (data: CreditProgramBindingModel) => {
|
const handleAdd = (data: CreditProgramBindingModel) => {
|
||||||
console.log(data);
|
|
||||||
createCreditProgram(data);
|
createCreditProgram(data);
|
||||||
|
setIsAddDialogOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = (data: CreditProgramBindingModel) => {
|
||||||
|
if (selectedItem) {
|
||||||
|
updateCreditProgram({
|
||||||
|
...selectedItem,
|
||||||
|
...data,
|
||||||
|
});
|
||||||
|
setIsEditDialogOpen(false);
|
||||||
|
setSelectedItem(undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectItem = (id: string | undefined) => {
|
||||||
|
const item = creditPrograms?.find((cp) => cp.id === id);
|
||||||
|
setSelectedItem(item);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openEditForm = () => {
|
||||||
|
if (!selectedItem) {
|
||||||
|
toast('Выберите элемент для редактирования');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsEditDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
@@ -67,22 +139,41 @@ export const CreditPrograms = (): React.JSX.Element => {
|
|||||||
<main className="flex-1 flex relative">
|
<main className="flex-1 flex relative">
|
||||||
<AppSidebar
|
<AppSidebar
|
||||||
onAddClick={() => {
|
onAddClick={() => {
|
||||||
setIsDialogOpen(true);
|
setIsAddDialogOpen(true);
|
||||||
|
}}
|
||||||
|
onEditClick={() => {
|
||||||
|
openEditForm();
|
||||||
}}
|
}}
|
||||||
onEditClick={function (): void {}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex-1 p-4">
|
<div className="flex-1 p-4">
|
||||||
<DialogForm<CreditProgramBindingModel>
|
<DialogForm<CreditProgramBindingModel>
|
||||||
title="Форма"
|
title="Форма кредитной программы"
|
||||||
description="Описание"
|
description="Добавить новую кредитную программу"
|
||||||
isOpen={isDialogOpen}
|
isOpen={isAddDialogOpen}
|
||||||
onClose={() => setIsDialogOpen(false)}
|
onClose={() => setIsAddDialogOpen(false)}
|
||||||
onSubmit={handleAdd}
|
onSubmit={handleAdd}
|
||||||
children={<CreditProgramForm />}
|
>
|
||||||
/>
|
<CreditProgramFormAdd />
|
||||||
|
</DialogForm>
|
||||||
|
{selectedItem && (
|
||||||
|
<DialogForm<CreditProgramBindingModel>
|
||||||
|
title="Форма кредитной программы"
|
||||||
|
description="Изменить кредитную программу"
|
||||||
|
isOpen={isEditDialogOpen}
|
||||||
|
onClose={() => setIsEditDialogOpen(false)}
|
||||||
|
onSubmit={handleEdit}
|
||||||
|
>
|
||||||
|
<CreditProgramFormEdit defaultValues={selectedItem} />
|
||||||
|
</DialogForm>
|
||||||
|
)}
|
||||||
<div className="">
|
<div className="">
|
||||||
<DataTable data={creditPrograms || []} columns={columns} />
|
<DataTable
|
||||||
|
data={finalData}
|
||||||
|
columns={columns}
|
||||||
|
onRowSelected={(id) => handleSelectItem(id)}
|
||||||
|
selectedRow={selectedItem?.id}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
Reference in New Issue
Block a user