fix: поправил ошибки на формочке кредитных программ

This commit is contained in:
2025-05-20 00:31:38 +04:00
parent 9ed33690cf
commit b1e5b7de93
3 changed files with 255 additions and 87 deletions

View File

@@ -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}
/>
);
};

View File

@@ -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,

View File

@@ -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>