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 { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
@@ -22,59 +22,144 @@ import { Button } from '@/components/ui/button';
import type { CreditProgramBindingModel } from '@/types/types';
import { useAuthStore } from '@/store/workerStore';
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(),
name: z.string().min(5, 'Название должно быть не короче 5 символов'),
name: z.string().min(1, 'Название обязательно'),
cost: z.coerce.number().min(0, 'Стоимость не может быть отрицательной'),
maxCost: z.coerce
.number()
.min(0, 'Максимальная стоимость не может быть отрицательной'),
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;
};
schema: z.ZodType<BaseFormValues | EditFormValues>;
defaultValues?: Partial<BaseFormValues>;
}
export const CreditProgramForm = ({
const BaseCreditProgramForm = ({
onSubmit,
}: CreditProgramFormProps): React.JSX.Element => {
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
id: '',
name: '',
cost: 0,
maxCost: 0,
periodId: '',
currencyCreditPrograms: [],
},
schema,
defaultValues,
}: BaseCreditProgramFormProps): React.JSX.Element => {
const form = useForm<BaseFormValues | EditFormValues>({
resolver: zodResolver(schema),
defaultValues: defaultValues
? {
id: defaultValues.id ?? '',
name: defaultValues.name ?? '',
cost: defaultValues.cost ?? 0,
maxCost: defaultValues.maxCost ?? 0,
periodId: defaultValues.periodId ?? '',
}
: {
id: '',
name: '',
cost: 0,
maxCost: 0,
periodId: '',
},
});
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 handleSubmit = (data: FormValues) => {
const dataWithId = {
...data,
id: crypto.randomUUID(),
};
const payload: CreditProgramBindingModel = {
...dataWithId,
currencyCreditPrograms: data.currencyCreditPrograms.map((currencyId) => ({
currencyId,
})),
storekeeperId: storekeeper?.id,
};
const handleSubmit = (data: BaseFormValues | EditFormValues) => {
if (!storekeeper?.id) {
console.error('Storekeeper ID is not available.');
return;
}
let payload: CreditProgramBindingModel;
if (schema === addSchema) {
const addData = data as BaseFormValues;
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);
};
@@ -139,7 +224,7 @@ export const CreditProgramForm = ({
render={({ field }) => (
<FormItem>
<FormLabel>Период</FormLabel>
<Select onValueChange={field.onChange} value={field.value}>
<Select onValueChange={field.onChange} value={field.value || ''}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Выберите период" />
@@ -147,7 +232,7 @@ export const CreditProgramForm = ({
</FormControl>
<SelectContent>
{periods &&
periods.map((period) => (
periods?.map((period) => (
<SelectItem key={period.id} value={period.id}>
{`${new Date(
period.startTime,
@@ -162,38 +247,7 @@ export const CreditProgramForm = ({
</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>
@@ -201,3 +255,27 @@ export const CreditProgramForm = ({
</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 handleSubmit = (data: BaseFormValues | EditFormValues) => {
// Если это форма редактирования, используем только заполненные поля
const payload: CurrencyBindingModel = {
id: data.id || crypto.randomUUID(),
storekeeperId: storekeeper?.id,

View File

@@ -3,11 +3,22 @@ import { AppSidebar } from '../layout/Sidebar';
import { useCreditPrograms } from '@/hooks/useCreditPrograms';
import { DialogForm } from '../layout/DialogForm';
import { DataTable } from '../layout/DataTable';
import { CreditProgramForm } from '../features/CreditProgramForm';
import {
CreditProgramFormAdd,
CreditProgramFormEdit,
} from '../features/CreditProgramForm';
import type { CreditProgramBindingModel } from '@/types/types';
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',
header: 'ID',
@@ -25,12 +36,12 @@ const columns: ColumnDef<CreditProgramBindingModel>[] = [
header: 'Макс. стоимость',
},
{
accessorKey: 'storekeeperId',
header: 'ID Кладовщика',
accessorKey: 'storekeeperFullName',
header: 'Кладовщик',
},
{
accessorKey: 'periodId',
header: 'ID Периода',
accessorKey: 'formattedPeriod',
header: 'Период',
},
];
@@ -43,12 +54,73 @@ export const CreditPrograms = (): React.JSX.Element => {
createCreditProgram,
updateCreditProgram,
} = 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) => {
console.log(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) {
@@ -67,22 +139,41 @@ export const CreditPrograms = (): React.JSX.Element => {
<main className="flex-1 flex relative">
<AppSidebar
onAddClick={() => {
setIsDialogOpen(true);
setIsAddDialogOpen(true);
}}
onEditClick={() => {
openEditForm();
}}
onEditClick={function (): void {}}
/>
<div className="flex-1 p-4">
<DialogForm<CreditProgramBindingModel>
title="Форма"
description="Описание"
isOpen={isDialogOpen}
onClose={() => setIsDialogOpen(false)}
title="Форма кредитной программы"
description="Добавить новую кредитную программу"
isOpen={isAddDialogOpen}
onClose={() => setIsAddDialogOpen(false)}
onSubmit={handleAdd}
children={<CreditProgramForm />}
/>
>
<CreditProgramFormAdd />
</DialogForm>
{selectedItem && (
<DialogForm<CreditProgramBindingModel>
title="Форма кредитной программы"
description="Изменить кредитную программу"
isOpen={isEditDialogOpen}
onClose={() => setIsEditDialogOpen(false)}
onSubmit={handleEdit}
>
<CreditProgramFormEdit defaultValues={selectedItem} />
</DialogForm>
)}
<div className="">
<DataTable data={creditPrograms || []} columns={columns} />
<DataTable
data={finalData}
columns={columns}
onRowSelected={(id) => handleSelectItem(id)}
selectedRow={selectedItem?.id}
/>
</div>
</div>
</main>