MaskedDateInput: Pass maxDate into DatePicker; Add interfaces for Driver, DriverLicense, DriverCategory
This commit is contained in:
@ -1,45 +1,42 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
InputBase,
|
||||
ActionIcon,
|
||||
Popover,
|
||||
} from "@mantine/core";
|
||||
import { IconCalendar } from "@tabler/icons-react";
|
||||
import { DatePicker } from "@mantine/dates";
|
||||
import dayjs from "dayjs";
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { InputBase, ActionIcon, Popover } from "@mantine/core"
|
||||
import { IconCalendar } from "@tabler/icons-react"
|
||||
import { DatePicker } from "@mantine/dates"
|
||||
import dayjs from "dayjs"
|
||||
|
||||
interface Props {
|
||||
value?: Date | null;
|
||||
onChange: (value: Date | null) => void;
|
||||
label?: string;
|
||||
required?: boolean;
|
||||
placeholder?: string;
|
||||
error?: string;
|
||||
interface MaskedDateInputProps {
|
||||
value?: Date | null
|
||||
onChange: (value: Date | null) => void
|
||||
label?: string
|
||||
required?: boolean
|
||||
placeholder?: string
|
||||
error?: string
|
||||
maxDate?: Date | undefined
|
||||
}
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
const currentYear = new Date().getFullYear()
|
||||
|
||||
function parseDDMMYYYY(input: string): Date | null {
|
||||
const [dd, mm, yyyy] = input.split(".");
|
||||
const day = parseInt(dd, 10);
|
||||
const month = parseInt(mm, 10);
|
||||
const year = parseInt(yyyy, 10);
|
||||
const [dd, mm, yyyy] = input.split(".")
|
||||
const day = parseInt(dd, 10)
|
||||
const month = parseInt(mm, 10)
|
||||
const year = parseInt(yyyy, 10)
|
||||
|
||||
if (
|
||||
isNaN(day) || isNaN(month) || isNaN(year) ||
|
||||
day < 1 || day > 31 ||
|
||||
month < 1 || month > 12 ||
|
||||
year < 1900 || year > currentYear
|
||||
year < 1900 //|| year > currentYear
|
||||
) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
const date = dayjs(`${year}-${month}-${day}`, "YYYY-M-D", true);
|
||||
return date.isValid() ? date.toDate() : null;
|
||||
const date = dayjs(`${year}-${month}-${day}`, "YYYY-M-D", true)
|
||||
return date.isValid() ? date.toDate() : null
|
||||
}
|
||||
|
||||
function formatDDMMYYYY(date: Date): string {
|
||||
return dayjs(date).format("DD.MM.YYYY");
|
||||
return dayjs(date).format("DD.MM.YYYY")
|
||||
}
|
||||
|
||||
export default function MaskedDateInput({
|
||||
@ -49,74 +46,46 @@ export default function MaskedDateInput({
|
||||
required,
|
||||
placeholder = "ДД.MM.ГГГГ",
|
||||
error,
|
||||
}: Props) {
|
||||
const [inputValue, setInputValue] = useState(value ? formatDDMMYYYY(value) : "");
|
||||
const [opened, setOpened] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
maxDate
|
||||
}: MaskedDateInputProps) {
|
||||
const [inputValue, setInputValue] = useState(value ? formatDDMMYYYY(value) : "")
|
||||
const [opened, setOpened] = useState(false)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (value) {
|
||||
const formatted = formatDDMMYYYY(value);
|
||||
if (formatted !== inputValue) {
|
||||
setInputValue(formatted);
|
||||
setInputValue(formatted)
|
||||
}
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
const correctDateString = (raw: string): string => {
|
||||
const nums = raw.replace(/\D/g, "").slice(0, 8);
|
||||
const parts: string[] = [];
|
||||
|
||||
if (nums.length >= 2) {
|
||||
const day = Math.min(parseInt(nums.slice(0, 2)), 31);
|
||||
parts.push(day.toString().padStart(2, "0"));
|
||||
} else if (nums.length >= 1) {
|
||||
parts.push(nums.slice(0, 1));
|
||||
}
|
||||
|
||||
if (nums.length >= 4) {
|
||||
const month = Math.min(parseInt(nums.slice(2, 4)), 12);
|
||||
parts.push(month.toString().padStart(2, "0"));
|
||||
} else if (nums.length >= 3) {
|
||||
parts.push(nums.slice(2, 3));
|
||||
}
|
||||
|
||||
if (nums.length >= 8) {
|
||||
const yearRaw = parseInt(nums.slice(4, 8));
|
||||
const year = Math.min(Math.max(1900, yearRaw), currentYear);
|
||||
parts.push(year.toString());
|
||||
} else if (nums.length >= 5) {
|
||||
parts.push(nums.slice(4));
|
||||
}
|
||||
|
||||
return parts.join(".");
|
||||
};
|
||||
}, [value])
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
let raw = e.target.value.replace(/[^\d]/g, '').slice(0, 8);
|
||||
let day = raw.slice(0, 2);
|
||||
let month = raw.slice(2, 4);
|
||||
let year = raw.slice(4, 8);
|
||||
let raw = e.target.value.replace(/[^\d]/g, '').slice(0, 8)
|
||||
let day = raw.slice(0, 2)
|
||||
let month = raw.slice(2, 4)
|
||||
let year = raw.slice(4, 8)
|
||||
|
||||
if (day.length === 1 && parseInt(day) > 3) day = '0' + day;
|
||||
if (month.length === 1 && parseInt(month) > 1) month = '0' + month;
|
||||
if (day.length === 1 && parseInt(day) > 3) day = '0' + day
|
||||
if (month.length === 1 && parseInt(month) > 1) month = '0' + month
|
||||
|
||||
const newValue = [day, month, year].filter(Boolean).join('.');
|
||||
const newValue = [day, month, year].filter(Boolean).join('.')
|
||||
|
||||
setInputValue(newValue);
|
||||
setInputValue(newValue)
|
||||
|
||||
const parsed = parseDDMMYYYY(newValue);
|
||||
if (parsed) onChange?.(parsed);
|
||||
const parsed = parseDDMMYYYY(newValue)
|
||||
if (parsed) onChange?.(parsed)
|
||||
};
|
||||
|
||||
|
||||
const handleDatePick = (date: Date | null) => {
|
||||
if (!date) return;
|
||||
const formatted = formatDDMMYYYY(date);
|
||||
setInputValue(formatted);
|
||||
onChange(date);
|
||||
setOpened(false);
|
||||
};
|
||||
if (!date) return
|
||||
const formatted = formatDDMMYYYY(date)
|
||||
setInputValue(formatted)
|
||||
onChange(date)
|
||||
setOpened(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<InputBase
|
||||
@ -152,11 +121,11 @@ export default function MaskedDateInput({
|
||||
value={value}
|
||||
onChange={handleDatePick}
|
||||
defaultLevel="month"
|
||||
maxDate={new Date()}
|
||||
maxDate={maxDate}
|
||||
/>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
22
src/interfaces/Driver.ts
Normal file
22
src/interfaces/Driver.ts
Normal file
@ -0,0 +1,22 @@
|
||||
export interface IDriver {
|
||||
id: number
|
||||
fullname: string
|
||||
snils: string
|
||||
birthday: string
|
||||
iin: string
|
||||
}
|
||||
|
||||
export interface IDriverLicense {
|
||||
id: number
|
||||
series_number: string
|
||||
form_date: string
|
||||
to_date: string
|
||||
is_actual: boolean
|
||||
categories: IDriverLicenseCategory[]
|
||||
}
|
||||
|
||||
export interface IDriverLicenseCategory {
|
||||
id: number
|
||||
name: string
|
||||
name_short: string
|
||||
}
|
||||
@ -1,32 +1,20 @@
|
||||
import 'dayjs/locale/ru';
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
Button,
|
||||
TextInput,
|
||||
Table,
|
||||
ActionIcon,
|
||||
Modal,
|
||||
Checkbox,
|
||||
} from "@mantine/core";
|
||||
import { DateInput } from "@mantine/dates";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { IconEdit, IconTrash } from "@tabler/icons-react";
|
||||
import {
|
||||
fetchDictionary,
|
||||
createDictionaryItem,
|
||||
updateDictionaryItem,
|
||||
deleteDictionaryItem,
|
||||
} from "../api/api";
|
||||
import MaskedDateInput from '../components/MaskedDateInput';
|
||||
import 'dayjs/locale/ru'
|
||||
import { useState, useEffect } from "react"
|
||||
import { Button, TextInput, Table, ActionIcon, Modal, Checkbox } from "@mantine/core"
|
||||
import { DateValue } from "@mantine/dates"
|
||||
import { useForm } from "@mantine/form"
|
||||
import { IconEdit, IconTrash } from "@tabler/icons-react"
|
||||
import { fetchDictionary, createDictionaryItem, updateDictionaryItem, deleteDictionaryItem } from "../api/api"
|
||||
import MaskedDateInput from '../components/MaskedDateInput'
|
||||
import { IDriver, IDriverLicense } from '../interfaces/Driver'
|
||||
|
||||
const DriverForm = () => {
|
||||
const [drivers, setDrivers] = useState<any[]>([]);
|
||||
const [licenseCategories, setLicenseCategories] = useState<{ value: string; label: string }[]>([]);
|
||||
const [selectedDriver, setSelectedDriver] = useState<any | null>(null);
|
||||
const [selectedLicense, setSelectedLicense] = useState<any | null>(null);
|
||||
const [modalOpened, setModalOpened] = useState(false);
|
||||
const [licenseModalOpened, setLicenseModalOpened] = useState(false);
|
||||
const [dateValue, setDateValue] = useState('');
|
||||
const [drivers, setDrivers] = useState<any[]>([])
|
||||
const [licenseCategories, setLicenseCategories] = useState<{ value: string; label: string }[]>([])
|
||||
const [selectedDriver, setSelectedDriver] = useState<any | null>(null)
|
||||
const [selectedLicense, setSelectedLicense] = useState<any | null>(null)
|
||||
const [modalOpened, setModalOpened] = useState(false)
|
||||
const [licenseModalOpened, setLicenseModalOpened] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
fetchDictionary("driver_license_category").then((data) => {
|
||||
@ -37,14 +25,14 @@ const DriverForm = () => {
|
||||
setLicenseCategories(options);
|
||||
});
|
||||
|
||||
fetchDictionary("driver").then(setDrivers);
|
||||
}, []);
|
||||
fetchDictionary("driver").then(setDrivers)
|
||||
}, [])
|
||||
|
||||
const driverForm = useForm({
|
||||
const driverForm = useForm<Partial<IDriver>>({
|
||||
initialValues: {
|
||||
fullname: "",
|
||||
snils: "",
|
||||
birthday: null,
|
||||
birthday: "",
|
||||
iin: "",
|
||||
},
|
||||
});
|
||||
@ -61,9 +49,9 @@ const DriverForm = () => {
|
||||
},
|
||||
});
|
||||
|
||||
function dateToYYYYMMDD(date) {
|
||||
if (!(date instanceof Date) || isNaN(date)) {
|
||||
return ""; // Handle invalid Date objects
|
||||
function dateToYYYYMMDD(date: DateValue) {
|
||||
if (!date) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const year = date.getFullYear();
|
||||
@ -114,17 +102,17 @@ const DriverForm = () => {
|
||||
driver.id === values.driverId
|
||||
? {
|
||||
...driver,
|
||||
license: driver.license.map((lic) => (lic.id === selectedLicense.id ? updatedLicense : lic)),
|
||||
license: driver.license.map((lic: IDriverLicense) => (lic.id === selectedLicense.id ? updatedLicense : lic)),
|
||||
}
|
||||
: driver
|
||||
)
|
||||
);
|
||||
} else {
|
||||
const newLicense = await createDictionaryItem("driver_license", values);
|
||||
const updatedDriver = await updateDictionaryItem("driver_connection", {
|
||||
driver_id: values.driverId,
|
||||
driver_license_id: newLicense.id,
|
||||
});
|
||||
const newLicense = await createDictionaryItem("driver_license", values)
|
||||
// const updatedDriver = await updateDictionaryItem("driver_connection", {
|
||||
// driver_id: values.driverId,
|
||||
// driver_license_id: newLicense.id,
|
||||
// })
|
||||
|
||||
setDrivers((prev) =>
|
||||
prev.map((driver) =>
|
||||
@ -132,7 +120,7 @@ const DriverForm = () => {
|
||||
? { ...driver, license: [...(driver.license || []), newLicense] }
|
||||
: driver
|
||||
)
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
setLicenseModalOpened(false);
|
||||
@ -144,7 +132,7 @@ const DriverForm = () => {
|
||||
await deleteDictionaryItem("driver_license", licenseId);
|
||||
setDrivers((prev) =>
|
||||
prev.map((driver) =>
|
||||
driver.id === driverId ? { ...driver, license: driver.license.filter((lic) => lic.id !== licenseId) } : driver
|
||||
driver.id === driverId ? { ...driver, license: driver.license.filter((lic: IDriverLicense) => lic.id !== licenseId) } : driver
|
||||
)
|
||||
);
|
||||
};
|
||||
@ -187,30 +175,13 @@ const DriverForm = () => {
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
|
||||
{driver.license && Array.isArray(driver.license) && driver.license.map(dr => (
|
||||
{driver.license && Array.isArray(driver.license) && driver.license.map((dr: IDriver) => (
|
||||
<Table.Tr>
|
||||
<Table.Td>{dr}</Table.Td>
|
||||
<Table.Td>{JSON.stringify(dr)}</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<Table.Tr onClick={() => setOpened(!opened)}>
|
||||
<Table.Td>{driver.fullname}</Table.Td>
|
||||
<Table.Td>{driver.snils}</Table.Td>
|
||||
<Table.Td>{driver.birthday}</Table.Td>
|
||||
<Table.Td>{driver.iin}</Table.Td>
|
||||
<Table.Td>
|
||||
<ActionIcon color="blue" onClick={() => handleEditDriver(driver)} style={{ marginRight: "10px" }}>
|
||||
<IconEdit />
|
||||
</ActionIcon>
|
||||
<ActionIcon color="red" onClick={() => handleDeleteDriver(driver.id)}>
|
||||
<IconTrash />
|
||||
</ActionIcon>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
@ -249,7 +220,7 @@ const DriverForm = () => {
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{drivers.flatMap((driver) =>
|
||||
driver.license?.map((license) => (
|
||||
driver.license?.map((license: IDriverLicense) => (
|
||||
<Table.Tr key={license.id}>
|
||||
<Table.Td>{driver.fullname}</Table.Td>
|
||||
<Table.Td>{license.series_number}</Table.Td>
|
||||
@ -275,6 +246,7 @@ const DriverForm = () => {
|
||||
<MaskedDateInput
|
||||
label="Дата рождения"
|
||||
required
|
||||
maxDate={new Date()}
|
||||
{...driverForm.getInputProps("birthday")}
|
||||
/>
|
||||
<Button fullWidth mt="md" type="submit">
|
||||
@ -296,7 +268,7 @@ const DriverForm = () => {
|
||||
required
|
||||
{...licenseForm.getInputProps("to_date")}
|
||||
/>
|
||||
<Checkbox defaultChecked label="Действующее" {...licenseForm.getInputProps("is_actual")} style={{ marginTop: "10px" }}/>
|
||||
<Checkbox defaultChecked label="Действующее" {...licenseForm.getInputProps("is_actual")} style={{ marginTop: "10px" }} />
|
||||
<Button fullWidth mt="md" type="submit">
|
||||
Сохранить
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user