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 { useEffect, useRef, useState } from "react"
|
||||||
import {
|
import { InputBase, ActionIcon, Popover } from "@mantine/core"
|
||||||
InputBase,
|
import { IconCalendar } from "@tabler/icons-react"
|
||||||
ActionIcon,
|
import { DatePicker } from "@mantine/dates"
|
||||||
Popover,
|
import dayjs from "dayjs"
|
||||||
} from "@mantine/core";
|
|
||||||
import { IconCalendar } from "@tabler/icons-react";
|
|
||||||
import { DatePicker } from "@mantine/dates";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
|
|
||||||
interface Props {
|
interface MaskedDateInputProps {
|
||||||
value?: Date | null;
|
value?: Date | null
|
||||||
onChange: (value: Date | null) => void;
|
onChange: (value: Date | null) => void
|
||||||
label?: string;
|
label?: string
|
||||||
required?: boolean;
|
required?: boolean
|
||||||
placeholder?: string;
|
placeholder?: string
|
||||||
error?: string;
|
error?: string
|
||||||
|
maxDate?: Date | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentYear = new Date().getFullYear();
|
const currentYear = new Date().getFullYear()
|
||||||
|
|
||||||
function parseDDMMYYYY(input: string): Date | null {
|
function parseDDMMYYYY(input: string): Date | null {
|
||||||
const [dd, mm, yyyy] = input.split(".");
|
const [dd, mm, yyyy] = input.split(".")
|
||||||
const day = parseInt(dd, 10);
|
const day = parseInt(dd, 10)
|
||||||
const month = parseInt(mm, 10);
|
const month = parseInt(mm, 10)
|
||||||
const year = parseInt(yyyy, 10);
|
const year = parseInt(yyyy, 10)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isNaN(day) || isNaN(month) || isNaN(year) ||
|
isNaN(day) || isNaN(month) || isNaN(year) ||
|
||||||
day < 1 || day > 31 ||
|
day < 1 || day > 31 ||
|
||||||
month < 1 || month > 12 ||
|
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);
|
const date = dayjs(`${year}-${month}-${day}`, "YYYY-M-D", true)
|
||||||
return date.isValid() ? date.toDate() : null;
|
return date.isValid() ? date.toDate() : null
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDDMMYYYY(date: Date): string {
|
function formatDDMMYYYY(date: Date): string {
|
||||||
return dayjs(date).format("DD.MM.YYYY");
|
return dayjs(date).format("DD.MM.YYYY")
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MaskedDateInput({
|
export default function MaskedDateInput({
|
||||||
@ -49,74 +46,46 @@ export default function MaskedDateInput({
|
|||||||
required,
|
required,
|
||||||
placeholder = "ДД.MM.ГГГГ",
|
placeholder = "ДД.MM.ГГГГ",
|
||||||
error,
|
error,
|
||||||
}: Props) {
|
maxDate
|
||||||
const [inputValue, setInputValue] = useState(value ? formatDDMMYYYY(value) : "");
|
}: MaskedDateInputProps) {
|
||||||
const [opened, setOpened] = useState(false);
|
const [inputValue, setInputValue] = useState(value ? formatDDMMYYYY(value) : "")
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const [opened, setOpened] = useState(false)
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (value) {
|
if (value) {
|
||||||
const formatted = formatDDMMYYYY(value);
|
const formatted = formatDDMMYYYY(value);
|
||||||
if (formatted !== inputValue) {
|
if (formatted !== inputValue) {
|
||||||
setInputValue(formatted);
|
setInputValue(formatted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [value]);
|
}, [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(".");
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
let raw = e.target.value.replace(/[^\d]/g, '').slice(0, 8);
|
let raw = e.target.value.replace(/[^\d]/g, '').slice(0, 8)
|
||||||
let day = raw.slice(0, 2);
|
let day = raw.slice(0, 2)
|
||||||
let month = raw.slice(2, 4);
|
let month = raw.slice(2, 4)
|
||||||
let year = raw.slice(4, 8);
|
let year = raw.slice(4, 8)
|
||||||
|
|
||||||
if (day.length === 1 && parseInt(day) > 3) day = '0' + day;
|
if (day.length === 1 && parseInt(day) > 3) day = '0' + day
|
||||||
if (month.length === 1 && parseInt(month) > 1) month = '0' + month;
|
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);
|
const parsed = parseDDMMYYYY(newValue)
|
||||||
if (parsed) onChange?.(parsed);
|
if (parsed) onChange?.(parsed)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleDatePick = (date: Date | null) => {
|
const handleDatePick = (date: Date | null) => {
|
||||||
if (!date) return;
|
if (!date) return
|
||||||
const formatted = formatDDMMYYYY(date);
|
const formatted = formatDDMMYYYY(date)
|
||||||
setInputValue(formatted);
|
setInputValue(formatted)
|
||||||
onChange(date);
|
onChange(date)
|
||||||
setOpened(false);
|
setOpened(false)
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InputBase
|
<InputBase
|
||||||
@ -152,11 +121,11 @@ export default function MaskedDateInput({
|
|||||||
value={value}
|
value={value}
|
||||||
onChange={handleDatePick}
|
onChange={handleDatePick}
|
||||||
defaultLevel="month"
|
defaultLevel="month"
|
||||||
maxDate={new Date()}
|
maxDate={maxDate}
|
||||||
/>
|
/>
|
||||||
</Popover.Dropdown>
|
</Popover.Dropdown>
|
||||||
</Popover>
|
</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 'dayjs/locale/ru'
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react"
|
||||||
import {
|
import { Button, TextInput, Table, ActionIcon, Modal, Checkbox } from "@mantine/core"
|
||||||
Button,
|
import { DateValue } from "@mantine/dates"
|
||||||
TextInput,
|
import { useForm } from "@mantine/form"
|
||||||
Table,
|
import { IconEdit, IconTrash } from "@tabler/icons-react"
|
||||||
ActionIcon,
|
import { fetchDictionary, createDictionaryItem, updateDictionaryItem, deleteDictionaryItem } from "../api/api"
|
||||||
Modal,
|
import MaskedDateInput from '../components/MaskedDateInput'
|
||||||
Checkbox,
|
import { IDriver, IDriverLicense } from '../interfaces/Driver'
|
||||||
} 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';
|
|
||||||
|
|
||||||
const DriverForm = () => {
|
const DriverForm = () => {
|
||||||
const [drivers, setDrivers] = useState<any[]>([]);
|
const [drivers, setDrivers] = useState<any[]>([])
|
||||||
const [licenseCategories, setLicenseCategories] = useState<{ value: string; label: string }[]>([]);
|
const [licenseCategories, setLicenseCategories] = useState<{ value: string; label: string }[]>([])
|
||||||
const [selectedDriver, setSelectedDriver] = useState<any | null>(null);
|
const [selectedDriver, setSelectedDriver] = useState<any | null>(null)
|
||||||
const [selectedLicense, setSelectedLicense] = useState<any | null>(null);
|
const [selectedLicense, setSelectedLicense] = useState<any | null>(null)
|
||||||
const [modalOpened, setModalOpened] = useState(false);
|
const [modalOpened, setModalOpened] = useState(false)
|
||||||
const [licenseModalOpened, setLicenseModalOpened] = useState(false);
|
const [licenseModalOpened, setLicenseModalOpened] = useState(false)
|
||||||
const [dateValue, setDateValue] = useState('');
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchDictionary("driver_license_category").then((data) => {
|
fetchDictionary("driver_license_category").then((data) => {
|
||||||
@ -37,14 +25,14 @@ const DriverForm = () => {
|
|||||||
setLicenseCategories(options);
|
setLicenseCategories(options);
|
||||||
});
|
});
|
||||||
|
|
||||||
fetchDictionary("driver").then(setDrivers);
|
fetchDictionary("driver").then(setDrivers)
|
||||||
}, []);
|
}, [])
|
||||||
|
|
||||||
const driverForm = useForm({
|
const driverForm = useForm<Partial<IDriver>>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
fullname: "",
|
fullname: "",
|
||||||
snils: "",
|
snils: "",
|
||||||
birthday: null,
|
birthday: "",
|
||||||
iin: "",
|
iin: "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -61,9 +49,9 @@ const DriverForm = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function dateToYYYYMMDD(date) {
|
function dateToYYYYMMDD(date: DateValue) {
|
||||||
if (!(date instanceof Date) || isNaN(date)) {
|
if (!date) {
|
||||||
return ""; // Handle invalid Date objects
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const year = date.getFullYear();
|
const year = date.getFullYear();
|
||||||
@ -114,17 +102,17 @@ const DriverForm = () => {
|
|||||||
driver.id === values.driverId
|
driver.id === values.driverId
|
||||||
? {
|
? {
|
||||||
...driver,
|
...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
|
: driver
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const newLicense = await createDictionaryItem("driver_license", values);
|
const newLicense = await createDictionaryItem("driver_license", values)
|
||||||
const updatedDriver = await updateDictionaryItem("driver_connection", {
|
// const updatedDriver = await updateDictionaryItem("driver_connection", {
|
||||||
driver_id: values.driverId,
|
// driver_id: values.driverId,
|
||||||
driver_license_id: newLicense.id,
|
// driver_license_id: newLicense.id,
|
||||||
});
|
// })
|
||||||
|
|
||||||
setDrivers((prev) =>
|
setDrivers((prev) =>
|
||||||
prev.map((driver) =>
|
prev.map((driver) =>
|
||||||
@ -132,7 +120,7 @@ const DriverForm = () => {
|
|||||||
? { ...driver, license: [...(driver.license || []), newLicense] }
|
? { ...driver, license: [...(driver.license || []), newLicense] }
|
||||||
: driver
|
: driver
|
||||||
)
|
)
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
setLicenseModalOpened(false);
|
setLicenseModalOpened(false);
|
||||||
@ -144,7 +132,7 @@ const DriverForm = () => {
|
|||||||
await deleteDictionaryItem("driver_license", licenseId);
|
await deleteDictionaryItem("driver_license", licenseId);
|
||||||
setDrivers((prev) =>
|
setDrivers((prev) =>
|
||||||
prev.map((driver) =>
|
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.Td>
|
||||||
</Table.Tr>
|
</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.Tr>
|
||||||
<Table.Td>{dr}</Table.Td>
|
<Table.Td>{JSON.stringify(dr)}</Table.Td>
|
||||||
</Table.Tr>
|
</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 (
|
return (
|
||||||
@ -249,7 +220,7 @@ const DriverForm = () => {
|
|||||||
</Table.Thead>
|
</Table.Thead>
|
||||||
<Table.Tbody>
|
<Table.Tbody>
|
||||||
{drivers.flatMap((driver) =>
|
{drivers.flatMap((driver) =>
|
||||||
driver.license?.map((license) => (
|
driver.license?.map((license: IDriverLicense) => (
|
||||||
<Table.Tr key={license.id}>
|
<Table.Tr key={license.id}>
|
||||||
<Table.Td>{driver.fullname}</Table.Td>
|
<Table.Td>{driver.fullname}</Table.Td>
|
||||||
<Table.Td>{license.series_number}</Table.Td>
|
<Table.Td>{license.series_number}</Table.Td>
|
||||||
@ -275,6 +246,7 @@ const DriverForm = () => {
|
|||||||
<MaskedDateInput
|
<MaskedDateInput
|
||||||
label="Дата рождения"
|
label="Дата рождения"
|
||||||
required
|
required
|
||||||
|
maxDate={new Date()}
|
||||||
{...driverForm.getInputProps("birthday")}
|
{...driverForm.getInputProps("birthday")}
|
||||||
/>
|
/>
|
||||||
<Button fullWidth mt="md" type="submit">
|
<Button fullWidth mt="md" type="submit">
|
||||||
|
|||||||
Reference in New Issue
Block a user