nestjs rewrite
This commit is contained in:
207
client/src/pages/Fuel.tsx
Normal file
207
client/src/pages/Fuel.tsx
Normal file
@ -0,0 +1,207 @@
|
||||
import { ActionIcon, Button, Flex, Input, Loader, LoadingOverlay, Modal, Overlay, Table, Tabs, TextInput, useMantineColorScheme } from "@mantine/core";
|
||||
import { IconMathMax, IconPlus, IconTableMinus, IconTablePlus } from "@tabler/icons-react";
|
||||
import { FuelExpenseDto, FuelExpenseDtoHeaders, FuelLimitDto, FuelLimitDtoHeaders } from "../dto/fuel/fuel.dto";
|
||||
import useSWR from "swr";
|
||||
import { fetcher } from "../http/axiosInstanceNest";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import { DateInput, DatePicker } from '@mantine/dates'
|
||||
import { SubmitHandler, useForm } from "react-hook-form";
|
||||
|
||||
import { AgGridReact } from "ag-grid-react";
|
||||
import { AllCommunityModule, ColDef, ModuleRegistry } from 'ag-grid-community'
|
||||
|
||||
ModuleRegistry.registerModules([AllCommunityModule])
|
||||
|
||||
type FieldType = 'text' | 'number' | 'select' | 'date'
|
||||
|
||||
interface IPostInclude {
|
||||
field: string
|
||||
field_type: FieldType
|
||||
}
|
||||
|
||||
interface ITableSchema {
|
||||
label: string
|
||||
value: string
|
||||
get: string
|
||||
post: string
|
||||
headers: { [key: string]: string }
|
||||
post_include: IPostInclude[]
|
||||
icon: JSX.Element
|
||||
}
|
||||
|
||||
export default function FuelPage() {
|
||||
const tables: ITableSchema[] = [
|
||||
{
|
||||
label: 'Расходы',
|
||||
value: 'expenses',
|
||||
get: '/fuel/expenses',
|
||||
post: '/fuel/expenses',
|
||||
headers: FuelExpenseDtoHeaders,
|
||||
post_include: [
|
||||
{
|
||||
field: 'id_boiler',
|
||||
field_type: 'text'
|
||||
},
|
||||
{
|
||||
field: 'id_fuel',
|
||||
field_type: 'text'
|
||||
},
|
||||
{
|
||||
field: 'date',
|
||||
field_type: 'date'
|
||||
},
|
||||
{
|
||||
field: 'value',
|
||||
field_type: 'text'
|
||||
}
|
||||
],
|
||||
icon: <IconTableMinus size={12} />
|
||||
},
|
||||
{
|
||||
label: 'Лимиты',
|
||||
value: 'limits',
|
||||
get: '/fuel/limits',
|
||||
post: '/fuel/limits',
|
||||
headers: FuelLimitDtoHeaders,
|
||||
post_include: [
|
||||
{
|
||||
field: 'id_boiler',
|
||||
field_type: 'text'
|
||||
},
|
||||
{
|
||||
field: 'id_fuel',
|
||||
field_type: 'text'
|
||||
},
|
||||
{
|
||||
field: 'month',
|
||||
field_type: 'number'
|
||||
},
|
||||
{
|
||||
field: 'year',
|
||||
field_type: 'number'
|
||||
},
|
||||
{
|
||||
field: 'value',
|
||||
field_type: 'text'
|
||||
}
|
||||
],
|
||||
icon: <IconMathMax size={12} />,
|
||||
}
|
||||
]
|
||||
|
||||
const [currentTab, setCurrentTab] = useState(tables[0])
|
||||
|
||||
const { data, isLoading } = useSWR(currentTab.get, () => fetcher(currentTab.get), { revalidateOnFocus: false })
|
||||
|
||||
const [openedCreateModal, { open: openCreateModal, close: closeCreateModal }] = useDisclosure(false)
|
||||
|
||||
const { colorScheme } = useMantineColorScheme()
|
||||
|
||||
useEffect(() => {
|
||||
if (colorScheme === 'dark') {
|
||||
document.body.dataset.agThemeMode = 'dark'
|
||||
} else {
|
||||
document.body.dataset.agThemeMode = 'light'
|
||||
}
|
||||
}, [colorScheme])
|
||||
|
||||
return (
|
||||
<>
|
||||
<ModalCreate openedCreateModal={openedCreateModal} closeCreateModal={closeCreateModal} currentTab={currentTab} />
|
||||
|
||||
<Tabs defaultValue={tables[0].value} w='100%' onChange={(tab) => setCurrentTab(tables.find(table => table.value === tab) || tables[0])}>
|
||||
<Tabs.List>
|
||||
{tables.map((table, index) => (
|
||||
<Tabs.Tab key={index} value={table.value} leftSection={table.icon}>
|
||||
{table.label}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs.List>
|
||||
|
||||
<Flex p='sm'>
|
||||
<Button leftSection={<IconPlus />} onClick={openCreateModal}>
|
||||
Добавить
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
{tables.map((table, index) => (
|
||||
<Tabs.Panel key={index} value={table.value} w='100%' h='100%'>
|
||||
{isLoading ?
|
||||
<Flex w='100%' justify={'center'} p='md'>
|
||||
<Loader />
|
||||
</Flex>
|
||||
:
|
||||
<>
|
||||
<AgGridReact
|
||||
//rowData={data}
|
||||
rowData={[
|
||||
Object.keys(table.headers).reduce((obj, key) => ({ ...obj, [key]: 'test' }), {}),
|
||||
Object.keys(table.headers).reduce((obj, key) => ({ ...obj, [key]: 'test' }), {})
|
||||
]}
|
||||
columnDefs={Object.keys(table.headers).map((header) => ({
|
||||
field: header
|
||||
})) as ColDef[]}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
</Tabs.Panel>
|
||||
))}
|
||||
</Tabs>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const ModalCreate = ({
|
||||
openedCreateModal,
|
||||
closeCreateModal,
|
||||
currentTab
|
||||
}: {
|
||||
openedCreateModal: boolean
|
||||
closeCreateModal: () => void
|
||||
currentTab: ITableSchema
|
||||
}) => {
|
||||
const { register, handleSubmit, reset, watch, formState: { errors, isSubmitting, dirtyFields, isValid } } = useForm({
|
||||
mode: 'onChange',
|
||||
})
|
||||
|
||||
const onSubmit: SubmitHandler<any> = async (values: any) => {
|
||||
console.log('Values to submit:', values)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal withinPortal opened={openedCreateModal} onClose={closeCreateModal}>
|
||||
<LoadingOverlay visible={isSubmitting} />
|
||||
|
||||
<Flex direction='column' gap='sm' component='form' onSubmit={handleSubmit(onSubmit)}>
|
||||
{currentTab.post_include.map((header, index) => {
|
||||
switch (header.field_type) {
|
||||
case 'date':
|
||||
return (
|
||||
<DateInput label={header.field} />
|
||||
)
|
||||
case 'text':
|
||||
return (
|
||||
<TextInput key={index} label={header.field} {...register(header.field, {
|
||||
required: true
|
||||
})} />
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<TextInput key={index} label={header.field} {...register(header.field, {
|
||||
required: true
|
||||
})} />
|
||||
)
|
||||
}
|
||||
})}
|
||||
|
||||
<Button mt='xl' type='submit'>
|
||||
Добавить
|
||||
</Button>
|
||||
</Flex>
|
||||
</Modal>
|
||||
)
|
||||
}
|
@ -13,9 +13,9 @@ function MapTest() {
|
||||
const tabs = [
|
||||
{
|
||||
id: uuidv4(),
|
||||
year: 2023,
|
||||
year: 2018,
|
||||
region: 11,
|
||||
district: 145,
|
||||
district: 146,
|
||||
},
|
||||
// {
|
||||
// id: uuidv4(),
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { ActionIcon, Button, Flex, Group, Input, Stack, Text, TextInput } from "@mantine/core"
|
||||
import { ActionIcon, Button, Flex, Group, Stack, Text, TextInput } from "@mantine/core"
|
||||
import { useEffect, useState } from "react"
|
||||
import createReport, { listCommands } from 'docx-templates'
|
||||
import { Dropzone, DropzoneProps, IMAGE_MIME_TYPE, MS_WORD_MIME_TYPE } from '@mantine/dropzone'
|
||||
import { IconFile, IconFileTypeDocx, IconPhoto, IconPlus, IconUpload, IconX } from "@tabler/icons-react"
|
||||
import { Dropzone, IMAGE_MIME_TYPE } from '@mantine/dropzone'
|
||||
import { IconFileTypeDocx, IconPlus, IconUpload, IconX } from "@tabler/icons-react"
|
||||
import { CommandSummary } from "docx-templates/lib/types"
|
||||
import { Control, Controller, FieldValues, SubmitHandler, useFieldArray, useForm, UseFormRegister } from "react-hook-form"
|
||||
import { TemplateHandler } from 'easy-template-x'
|
||||
|
||||
const xslTemplate = `<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsl:stylesheet version="1.0" xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
@ -962,54 +961,6 @@ const handleGenerateExcel = () => {
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
const TemplateFormET = ({
|
||||
templateUrl
|
||||
}: {
|
||||
templateUrl: string
|
||||
}) => {
|
||||
const [templateUint8Array, setTemplateUint8Array] = useState<Uint8Array | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const loadTemplate = async (templateUrl: string) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const response = await fetch(templateUrl)
|
||||
const templateArrayBuffer = await response.arrayBuffer()
|
||||
|
||||
setTemplateUint8Array(new Uint8Array(templateArrayBuffer))
|
||||
} catch (error) {
|
||||
console.error("Error generating DOCX:", error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const loadTags = async (templateUint8Array: Uint8Array) => {
|
||||
const handler = new TemplateHandler()
|
||||
const tags = await handler.parseTags(templateUint8Array)
|
||||
|
||||
console.log(tags)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (templateUint8Array) {
|
||||
loadTags(templateUint8Array)
|
||||
}
|
||||
}, [templateUint8Array])
|
||||
|
||||
useEffect(() => {
|
||||
if (templateUrl) {
|
||||
loadTemplate(templateUrl)
|
||||
}
|
||||
}, [templateUrl])
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface TemplateCommand extends CommandSummary {
|
||||
children?: CommandSummary[]
|
||||
}
|
||||
@ -1098,6 +1049,7 @@ const FormLoop = ({
|
||||
))
|
||||
}
|
||||
</Stack>
|
||||
|
||||
<ActionIcon onClick={() => {
|
||||
if (command.children) {
|
||||
append(command.children.map(c => c.code).reduce((acc, key) => {
|
||||
@ -1132,51 +1084,51 @@ const renderCommand = (
|
||||
|
||||
if (command.type === 'IMAGE') {
|
||||
return (
|
||||
<Controller
|
||||
key={key}
|
||||
name={name}
|
||||
control={control}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Dropzone
|
||||
accept={IMAGE_MIME_TYPE}
|
||||
maxSize={5 * 1024 ** 2}
|
||||
onReject={(files) => console.log('rejected files', files)}
|
||||
onDrop={(files) => {
|
||||
console.log(files[0])
|
||||
files[0].arrayBuffer().then(res => {
|
||||
onChange({
|
||||
width: 6,
|
||||
height: 6,
|
||||
data: new Uint8Array(res),
|
||||
extension: files[0]?.path?.match(/\.[^.]+$/)?.[0] || ""
|
||||
<Stack gap={0}>
|
||||
<Text size='sm' fw={500}>{command.code}</Text>
|
||||
<Controller
|
||||
key={key}
|
||||
name={name}
|
||||
control={control}
|
||||
render={({ field: { onChange } }) => (
|
||||
<Dropzone
|
||||
accept={IMAGE_MIME_TYPE}
|
||||
maxSize={5 * 1024 ** 2}
|
||||
onReject={(files) => console.log('rejected files', files)}
|
||||
onDrop={(files) => {
|
||||
console.log(files[0])
|
||||
files[0].arrayBuffer().then(res => {
|
||||
onChange({
|
||||
width: 6,
|
||||
height: 6,
|
||||
data: new Uint8Array(res),
|
||||
extension: files[0]?.path?.match(/\.[^.]+$/)?.[0] || ""
|
||||
})
|
||||
})
|
||||
})
|
||||
}}
|
||||
maxFiles={1}
|
||||
>
|
||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||
<Dropzone.Accept>
|
||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||
</Dropzone.Accept>
|
||||
<Dropzone.Reject>
|
||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||
</Dropzone.Reject>
|
||||
<Dropzone.Idle>
|
||||
<IconFileTypeDocx size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||
</Dropzone.Idle>
|
||||
}}
|
||||
maxFiles={1}
|
||||
>
|
||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||
<Dropzone.Accept>
|
||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||
</Dropzone.Accept>
|
||||
<Dropzone.Reject>
|
||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||
</Dropzone.Reject>
|
||||
<Dropzone.Idle>
|
||||
<IconFileTypeDocx size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||
</Dropzone.Idle>
|
||||
|
||||
<div>
|
||||
<Text size="xl" inline>
|
||||
Drag files here or click to select files
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" inline mt={7}>
|
||||
Attach as many files as you like, each file should not exceed 5mb
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</Dropzone>
|
||||
)}
|
||||
/>
|
||||
<div>
|
||||
<Text size="xl" inline>
|
||||
Перетащите файлы сюда или нажмите, чтобы выбрать их
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</Dropzone>
|
||||
)}
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1186,7 +1138,7 @@ const TemplateForm = ({
|
||||
}: {
|
||||
templateUrl: string,
|
||||
}) => {
|
||||
const { register, control, handleSubmit, reset, watch, formState } = useForm({
|
||||
const { register, control, handleSubmit } = useForm({
|
||||
mode: 'onChange',
|
||||
})
|
||||
|
||||
@ -1276,6 +1228,15 @@ const TemplateForm = ({
|
||||
}
|
||||
}, [templateUrl])
|
||||
|
||||
useEffect(() => {
|
||||
console.log(loading)
|
||||
}, [loading])
|
||||
|
||||
useEffect(() => {
|
||||
console.log(saving)
|
||||
}, [saving])
|
||||
|
||||
|
||||
if (commandList) {
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
@ -1283,13 +1244,16 @@ const TemplateForm = ({
|
||||
{commandList.map(command => {
|
||||
if (command.type === 'FOR') {
|
||||
return (
|
||||
<FormLoop key={command.code} control={control} register={register} command={command} />
|
||||
<Stack gap={0} key={command.code}>
|
||||
<Text size='sm' fw={500}>{command.code}</Text>
|
||||
<FormLoop control={control} register={register} command={command} />
|
||||
</Stack>
|
||||
)
|
||||
} else {
|
||||
return renderCommand(control, register, command, command.code, command.code, command.code)
|
||||
}
|
||||
})}
|
||||
<Button type='submit'>Submit</Button>
|
||||
<Button ml='auto' w='fit-content' type='submit'>Сохранить</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
)
|
||||
@ -1297,8 +1261,6 @@ const TemplateForm = ({
|
||||
}
|
||||
|
||||
const PrintReport = () => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
return (
|
||||
<Stack p='sm' gap='sm' w='100%'>
|
||||
<TemplateForm templateUrl="/template_table.docx" />
|
||||
|
Reference in New Issue
Block a user