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 { 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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user