This commit is contained in:
dmisamohin 2025-06-06 18:16:12 +03:00
parent 80ef365a6b
commit f642d61d68
3 changed files with 63 additions and 310 deletions

View File

@ -1,7 +1,8 @@
'use client' 'use client'
import { Breadcrumbs, IBreadcrumbProps } from '@/components/Breadcrumbs'; import { Breadcrumbs, IBreadcrumbProps } from '@/components/Breadcrumbs';
import { Input } from '@/components/Input';
import { PageLayout } from '@/components/PageLayout'; import { PageLayout } from '@/components/PageLayout';
import { useState } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form';
const BREADCRUMBS: IBreadcrumbProps[] = [ const BREADCRUMBS: IBreadcrumbProps[] = [
{ {
@ -33,104 +34,55 @@ const BREADCRUMBS: IBreadcrumbProps[] = [
} }
]; ];
type ICreateContractorFormProps = {
name: string;
type: string;
inn: string;
kpp: string;
ogrn: string;
address: string;
phone: string;
email: string;
contactPerson: string;
account: string;
bank: string;
bik: string;
correspondentAccount: string;
};
const CREATE_CONTRACTOR_FORM_INIT: ICreateContractorFormProps = {
name: "",
type: "legal",
inn: "",
kpp: "",
ogrn: "",
address: "",
phone: "",
email: "",
contactPerson: "",
account: "",
bank: "",
bik: "",
correspondentAccount: "",
};
export default function CreateContractor() { export default function CreateContractor() {
const [formData, setFormData] = useState({ const { register, handleSubmit } = useForm<ICreateContractorFormProps>({ defaultValues: CREATE_CONTRACTOR_FORM_INIT })
name: '',
type: 'legal', // или 'individual'
inn: '',
kpp: '',
ogrn: '',
address: '',
phone: '',
email: '',
contactPerson: '',
bankDetails: {
account: '',
bank: '',
bik: '',
correspondentAccount: ''
}
});
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => { const onSubmit: SubmitHandler<ICreateContractorFormProps> = (form) => {
const { name, value } = e.target; console.log("form", form)
if (name.includes('.')) {
const [parent, child] = name.split('.');
// setFormData(prev => ({
// ...prev,
// [parent]: {
// ...prev[parent as keyof typeof formData],
// [child]: value
// }
// }));
} else {
setFormData(prev => ({
...prev,
[name]: value
}));
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
setError('');
try {
// Здесь будет запрос к вашему API
const response = await fetch('/api/contractors', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});
if (!response.ok) {
throw new Error('Ошибка при создании контрагента');
}
const data = await response.json();
// router.push(`/contractors/${data.id}`);
} catch (err) {
setError(err instanceof Error ? err.message : 'Неизвестная ошибка');
} finally {
setIsLoading(false);
}
}; };
return ( return (
<PageLayout pageName='Добавление контрагента'> <PageLayout pageName="Добавление контрагента">
<Breadcrumbs breadcrumbs={BREADCRUMBS} /> <Breadcrumbs breadcrumbs={BREADCRUMBS} />
<div className="bg-white shadow rounded-lg p-6"> <div className="bg-white shadow rounded-lg p-6">
{error && <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">{error}</div>} <form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Основная информация */}
<div className="space-y-4"> <div className="space-y-4">
<h2 className="text-xl font-semibold">Основная информация</h2> <h2 className="text-xl font-semibold">Основная информация</h2>
<div> <Input id="name" type="text" label="Наименование *" {...register("name")} />
<label
htmlFor="name"
className="block text-sm font-medium text-gray-700"
>
Наименование *
</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
required
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 border p-2"
/>
</div>
<div> <div>
<label <label
@ -142,233 +94,46 @@ export default function CreateContractor() {
<select <select
id="type" id="type"
name="type" name="type"
value={formData.type}
onChange={handleChange}
required
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 border p-2" className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 border p-2"
disabled
> >
<option value="legal">Юридическое лицо</option> <option value="legal">Юридическое лицо</option>
<option value="individual">Физическое лицо/ИП</option>
</select> </select>
</div> </div>
<div> <Input id="inn" type="text" label="ИНН *" {...register("inn")} />
<label <Input id="kpp" type="text" label="КПП" {...register("kpp")} />
htmlFor="inn"
className="block text-sm font-medium text-gray-700"
>
ИНН *
</label>
<input
type="text"
id="inn"
name="inn"
value={formData.inn}
onChange={handleChange}
required
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 border p-2"
/>
</div>
{formData.type === "legal" && ( <Input id="ogrn" type="text" label="ОГРН" {...register("ogrn")} />
<div>
<label
htmlFor="kpp"
className="block text-sm font-medium text-gray-700"
>
КПП
</label>
<input
type="text"
id="kpp"
name="kpp"
value={formData.kpp}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 border p-2"
/>
</div>
)}
<div>
<label
htmlFor="ogrn"
className="block text-sm font-medium text-gray-700"
>
{formData.type === "legal" ? "ОГРН" : "ОГРНИП"}
</label>
<input
type="text"
id="ogrn"
name="ogrn"
value={formData.ogrn}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 border p-2"
/>
</div>
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<h2 className="text-xl font-semibold">Контактная информация</h2> <h2 className="text-xl font-semibold">Контактная информация</h2>
<div> <Input id="address" type="text" label="Адрес" {...register("address")} />
<label <Input id="phone" type="tel" label='Телефон' {...register("phone")} />
htmlFor="address" <Input id="email" type="email" label='Эл. почта' {...register("email")} />
className="block text-sm font-medium text-gray-700" <Input id="contactPerson" type="text" label='Контактное лицо' {...register("contactPerson")} />
>
Адрес
</label>
<input
type="text"
id="address"
name="address"
value={formData.address}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 border p-2"
/>
</div>
<div>
<label
htmlFor="phone"
className="block text-sm font-medium text-gray-700"
>
Телефон
</label>
<input
type="tel"
id="phone"
name="phone"
value={formData.phone}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 border p-2"
/>
</div>
<div>
<label
htmlFor="email"
className="block text-sm font-medium text-gray-700"
>
Email
</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 border p-2"
/>
</div>
<div>
<label
htmlFor="contactPerson"
className="block text-sm font-medium text-gray-700"
>
Контактное лицо
</label>
<input
type="text"
id="contactPerson"
name="contactPerson"
value={formData.contactPerson}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 border p-2"
/>
</div>
</div> </div>
{/* Банковские реквизиты */}
<div className="space-y-4 md:col-span-2"> <div className="space-y-4 md:col-span-2">
<h2 className="text-xl font-semibold">Банковские реквизиты</h2> <h2 className="text-xl font-semibold">Банковские реквизиты</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <Input id="account" label='Расчетный счет' type="text" {...register("account")} />
<label <Input id="bank" type="text" label="Банк" {...register("bank")} />
htmlFor="bankDetails.account" <Input id="bik" type="text" label='БИК' {...register("bik")} />
className="block text-sm font-medium text-gray-700" <Input id="correspondentAccount" type="text" label='Корр. счет' {...register("correspondentAccount")} />
>
Расчетный счет
</label>
<input
type="text"
id="bankDetails.account"
name="bankDetails.account"
value={formData.bankDetails.account}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 border p-2"
/>
</div>
<div>
<label
htmlFor="bankDetails.bank"
className="block text-sm font-medium text-gray-700"
>
Банк
</label>
<input
type="text"
id="bankDetails.bank"
name="bankDetails.bank"
value={formData.bankDetails.bank}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 border p-2"
/>
</div>
<div>
<label
htmlFor="bankDetails.bik"
className="block text-sm font-medium text-gray-700"
>
БИК
</label>
<input
type="text"
id="bankDetails.bik"
name="bankDetails.bik"
value={formData.bankDetails.bik}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 border p-2"
/>
</div>
<div>
<label
htmlFor="bankDetails.correspondentAccount"
className="block text-sm font-medium text-gray-700"
>
Корр. счет
</label>
<input
type="text"
id="bankDetails.correspondentAccount"
name="bankDetails.correspondentAccount"
value={formData.bankDetails.correspondentAccount}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 border p-2"
/>
</div>
</div> </div>
</div> </div>
</div> </div>
<div className="flex justify-end space-x-3"> <div className="flex justify-end space-x-3">
<button <button type="button" className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
type="button" Очистить
// onClick={() => router.push('/contractors')}
className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
Отмена
</button> </button>
<button <button type="submit" className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed">
type="submit" Создать контрагента
disabled={isLoading}
className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isLoading ? "Сохранение..." : "Создать контрагента"}
</button> </button>
</div> </div>
</form> </form>

View File

@ -10,7 +10,7 @@ type IInputProps = {
export const Input = ({ label, isError, errorText, className, id, ...props }: IInputProps) => { export const Input = ({ label, isError, errorText, className, id, ...props }: IInputProps) => {
const inputClsx = twMerge( const inputClsx = twMerge(
classNames("w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-1", classNames("mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 border p-2",
{ {
"border-red-300 text-red-900 placeholder-red-300 focus:ring-red-500 focus:border-red-500": isError, "border-red-300 text-red-900 placeholder-red-300 focus:ring-red-500 focus:border-red-500": isError,
"border-gray-300 focus:ring-blue-500 focus:border-blue-500": !isError, "border-gray-300 focus:ring-blue-500 focus:border-blue-500": !isError,

View File

@ -14,6 +14,7 @@ import {
flexRender, flexRender,
RowData, RowData,
} from '@tanstack/react-table' } from '@tanstack/react-table'
import { Input } from '../Input';
type TableProps<T extends RowData> = { type TableProps<T extends RowData> = {
tableData: T[]; tableData: T[];
@ -162,16 +163,8 @@ export function Table<T extends RowData>({ tableData, columns }: TableProps<T>)
) )
} }
function Filter({ function Filter({ column, table }: { column: Column<any, any>, table: TanStackTable<any> }) {
column, const firstValue = table.getPreFilteredRowModel().flatRows[0]?.getValue(column.id);
table,
}: {
column: Column<any, any>;
table: TanStackTable<any>;
}) {
const firstValue = table
.getPreFilteredRowModel()
.flatRows[0]?.getValue(column.id);
const columnFilterValue = column.getFilterValue(); const columnFilterValue = column.getFilterValue();
@ -192,18 +185,13 @@ function Filter({
<input <input
type="number" type="number"
value={(columnFilterValue as [number, number])?.[1] ?? ""} value={(columnFilterValue as [number, number])?.[1] ?? ""}
onChange={(e) => onChange={(e) => column.setFilterValue((old: [number, number]) => [old?.[0], e.target.value])}
column.setFilterValue((old: [number, number]) => [
old?.[0],
e.target.value,
])
}
placeholder={`Max`} placeholder={`Max`}
className="w-24 border shadow rounded" className="w-24 border shadow rounded"
/> />
</div> </div>
) : ( ) : (
<input <Input
type="text" type="text"
value={(columnFilterValue ?? "") as string} value={(columnFilterValue ?? "") as string}
onChange={(e) => column.setFilterValue(e.target.value)} onChange={(e) => column.setFilterValue(e.target.value)}