From 564895c26f8e1764aea5a7e6325aef9de47e922f Mon Sep 17 00:00:00 2001 From: AlexSserb Date: Tue, 26 Nov 2024 01:37:15 +0400 Subject: [PATCH] feat: additional expenses --- src/client/index.ts | 7 + src/client/models/DeleteExpenseResponse.ts | 9 ++ src/client/models/ExpenseSchemaBase.ts | 14 ++ src/client/models/GetAllExpensesResponse.ts | 11 ++ src/client/models/UpdateExpenseRequest.ts | 9 ++ src/client/models/UpdateExpenseResponse.ts | 9 ++ src/client/models/UpdateExpenseSchema.ts | 12 ++ src/client/services/ExpenseService.ts | 78 ++++++++++ src/modals/modals.ts | 2 + src/pages/AdminPage/AdminPage.tsx | 16 ++- src/pages/AdminPage/hooks/useExpensesList.tsx | 12 ++ .../AdminPage/tabs/Expenses/ExpensesTab.tsx | 134 ++++++++++++++++++ .../hooks/useExpensesTableColumns.tsx | 37 +++++ .../tabs/Expenses/modals/ExpenseFormModal.tsx | 67 +++++++++ .../ProfitChart/hooks/useProfitChart.tsx | 3 +- .../ProfitTable/hooks/useProfitTable.tsx | 3 +- src/pages/StatisticsPage/utils/dates.ts | 8 -- src/types/utils.ts | 11 +- 18 files changed, 430 insertions(+), 12 deletions(-) create mode 100644 src/client/models/DeleteExpenseResponse.ts create mode 100644 src/client/models/ExpenseSchemaBase.ts create mode 100644 src/client/models/GetAllExpensesResponse.ts create mode 100644 src/client/models/UpdateExpenseRequest.ts create mode 100644 src/client/models/UpdateExpenseResponse.ts create mode 100644 src/client/models/UpdateExpenseSchema.ts create mode 100644 src/client/services/ExpenseService.ts create mode 100644 src/pages/AdminPage/hooks/useExpensesList.tsx create mode 100644 src/pages/AdminPage/tabs/Expenses/ExpensesTab.tsx create mode 100644 src/pages/AdminPage/tabs/Expenses/hooks/useExpensesTableColumns.tsx create mode 100644 src/pages/AdminPage/tabs/Expenses/modals/ExpenseFormModal.tsx diff --git a/src/client/index.ts b/src/client/index.ts index ba7a230..551c19e 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -132,6 +132,7 @@ export type { DealUpdateServiceQuantityRequest } from './models/DealUpdateServic export type { DealUpdateServiceQuantityResponse } from './models/DealUpdateServiceQuantityResponse'; export type { DealUpdateServiceRequest } from './models/DealUpdateServiceRequest'; export type { DealUpdateServiceResponse } from './models/DealUpdateServiceResponse'; +export type { DeleteExpenseResponse } from './models/DeleteExpenseResponse'; export type { DeleteMarketplaceRequest } from './models/DeleteMarketplaceRequest'; export type { DeleteMarketplaceResponse } from './models/DeleteMarketplaceResponse'; export type { DeletePaymentRecordRequest } from './models/DeletePaymentRecordRequest'; @@ -145,12 +146,14 @@ export type { DeletePriceCategoryResponse } from './models/DeletePriceCategoryRe export type { DeleteShiftResponse } from './models/DeleteShiftResponse'; export type { DeleteShippingWarehouseRequest } from './models/DeleteShippingWarehouseRequest'; export type { DeleteShippingWarehouseResponse } from './models/DeleteShippingWarehouseResponse'; +export type { ExpenseSchemaBase } from './models/ExpenseSchemaBase'; export type { FinishShiftByIdResponse } from './models/FinishShiftByIdResponse'; export type { FinishShiftResponse } from './models/FinishShiftResponse'; export type { GetAllBarcodeTemplateAttributesResponse } from './models/GetAllBarcodeTemplateAttributesResponse'; export type { GetAllBarcodeTemplateSizesResponse } from './models/GetAllBarcodeTemplateSizesResponse'; export type { GetAllBarcodeTemplatesResponse } from './models/GetAllBarcodeTemplatesResponse'; export type { GetAllBaseMarketplacesResponse } from './models/GetAllBaseMarketplacesResponse'; +export type { GetAllExpensesResponse } from './models/GetAllExpensesResponse'; export type { GetAllPayRatesResponse } from './models/GetAllPayRatesResponse'; export type { GetAllPayrollSchemeResponse } from './models/GetAllPayrollSchemeResponse'; export type { GetAllPositionsResponse } from './models/GetAllPositionsResponse'; @@ -243,6 +246,9 @@ export type { SynchronizeMarketplaceRequest } from './models/SynchronizeMarketpl export type { TaskInfoResponse } from './models/TaskInfoResponse'; export type { TimeTrackingData } from './models/TimeTrackingData'; export type { TimeTrackingRecord } from './models/TimeTrackingRecord'; +export type { UpdateExpenseRequest } from './models/UpdateExpenseRequest'; +export type { UpdateExpenseResponse } from './models/UpdateExpenseResponse'; +export type { UpdateExpenseSchema } from './models/UpdateExpenseSchema'; export type { UpdateMarketplaceRequest } from './models/UpdateMarketplaceRequest'; export type { UpdateMarketplaceResponse } from './models/UpdateMarketplaceResponse'; export type { UpdatePayRateRequest } from './models/UpdatePayRateRequest'; @@ -268,6 +274,7 @@ export { BarcodeService } from './services/BarcodeService'; export { BillingService } from './services/BillingService'; export { ClientService } from './services/ClientService'; export { DealService } from './services/DealService'; +export { ExpenseService } from './services/ExpenseService'; export { MarketplaceService } from './services/MarketplaceService'; export { PayrollService } from './services/PayrollService'; export { PositionService } from './services/PositionService'; diff --git a/src/client/models/DeleteExpenseResponse.ts b/src/client/models/DeleteExpenseResponse.ts new file mode 100644 index 0000000..5dae836 --- /dev/null +++ b/src/client/models/DeleteExpenseResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type DeleteExpenseResponse = { + ok: boolean; + message: string; +}; + diff --git a/src/client/models/ExpenseSchemaBase.ts b/src/client/models/ExpenseSchemaBase.ts new file mode 100644 index 0000000..dce0bb2 --- /dev/null +++ b/src/client/models/ExpenseSchemaBase.ts @@ -0,0 +1,14 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { UserSchema } from './UserSchema'; +export type ExpenseSchemaBase = { + id: number; + name: string; + comment: string; + amount: number; + createdByUser: UserSchema; + spentDate: string; +}; + diff --git a/src/client/models/GetAllExpensesResponse.ts b/src/client/models/GetAllExpensesResponse.ts new file mode 100644 index 0000000..11ef82a --- /dev/null +++ b/src/client/models/GetAllExpensesResponse.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ExpenseSchemaBase } from './ExpenseSchemaBase'; +import type { PaginationInfoSchema } from './PaginationInfoSchema'; +export type GetAllExpensesResponse = { + expenses: Array; + paginationInfo: PaginationInfoSchema; +}; + diff --git a/src/client/models/UpdateExpenseRequest.ts b/src/client/models/UpdateExpenseRequest.ts new file mode 100644 index 0000000..5dd430f --- /dev/null +++ b/src/client/models/UpdateExpenseRequest.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { UpdateExpenseSchema } from './UpdateExpenseSchema'; +export type UpdateExpenseRequest = { + expense: UpdateExpenseSchema; +}; + diff --git a/src/client/models/UpdateExpenseResponse.ts b/src/client/models/UpdateExpenseResponse.ts new file mode 100644 index 0000000..04bb1e5 --- /dev/null +++ b/src/client/models/UpdateExpenseResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type UpdateExpenseResponse = { + ok: boolean; + message: string; +}; + diff --git a/src/client/models/UpdateExpenseSchema.ts b/src/client/models/UpdateExpenseSchema.ts new file mode 100644 index 0000000..fc1eff9 --- /dev/null +++ b/src/client/models/UpdateExpenseSchema.ts @@ -0,0 +1,12 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type UpdateExpenseSchema = { + id?: (number | null); + name: string; + comment?: (string | null); + amount: number; + spentDate: string; +}; + diff --git a/src/client/services/ExpenseService.ts b/src/client/services/ExpenseService.ts new file mode 100644 index 0000000..9aafed3 --- /dev/null +++ b/src/client/services/ExpenseService.ts @@ -0,0 +1,78 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { DeleteExpenseResponse } from '../models/DeleteExpenseResponse'; +import type { GetAllExpensesResponse } from '../models/GetAllExpensesResponse'; +import type { UpdateExpenseRequest } from '../models/UpdateExpenseRequest'; +import type { UpdateExpenseResponse } from '../models/UpdateExpenseResponse'; +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; +export class ExpenseService { + /** + * Get All + * @returns GetAllExpensesResponse Successful Response + * @throws ApiError + */ + public static getAllExpenses({ + page, + itemsPerPage, + }: { + page?: (number | null), + itemsPerPage?: (number | null), + }): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/expense/get-all', + query: { + 'page': page, + 'items_per_page': itemsPerPage, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Update Expense + * @returns UpdateExpenseResponse Successful Response + * @throws ApiError + */ + public static updateExpense({ + requestBody, + }: { + requestBody: UpdateExpenseRequest, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/expense/update', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Delete Expense + * @returns DeleteExpenseResponse Successful Response + * @throws ApiError + */ + public static deleteExpense({ + expenseId, + }: { + expenseId: number, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'DELETE', + url: '/expense/delete/{expense_id}', + path: { + 'expense_id': expenseId, + }, + errors: { + 422: `Validation Error`, + }, + }); + } +} diff --git a/src/modals/modals.ts b/src/modals/modals.ts index 82a945e..d704743 100644 --- a/src/modals/modals.ts +++ b/src/modals/modals.ts @@ -22,6 +22,7 @@ import ShippingWarehouseForm from "../pages/ShippingWarehousesPage/modals/Shippi import MarketplaceFormModal from "../pages/MarketplacesPage/modals/MarketplaceFormModal/MarketplaceFormModal.tsx"; import ServicePriceCategoryForm from "../pages/ServicesPage/modals/ServicePriceCategoryForm.tsx"; import ScanningModal from "./ScanningModal/ScanningModal.tsx"; +import ExpenseFormModal from "../pages/AdminPage/tabs/Expenses/modals/ExpenseFormModal.tsx"; export const modals = { enterDeadline: EnterDeadlineModal, @@ -48,4 +49,5 @@ export const modals = { marketplaceFormModal: MarketplaceFormModal, servicePriceCategoryForm: ServicePriceCategoryForm, scanningModal: ScanningModal, + expenseFormModal: ExpenseFormModal, }; diff --git a/src/pages/AdminPage/AdminPage.tsx b/src/pages/AdminPage/AdminPage.tsx index 9a044fa..0bf78ea 100644 --- a/src/pages/AdminPage/AdminPage.tsx +++ b/src/pages/AdminPage/AdminPage.tsx @@ -3,7 +3,7 @@ import { Tabs } from "@mantine/core"; import PageBlock from "../../components/PageBlock/PageBlock.tsx"; import { IconBriefcase, - IconCalendarUser, + IconCalendarUser, IconCoins, IconCurrencyDollar, IconQrcode, IconUser, } from "@tabler/icons-react"; @@ -13,6 +13,7 @@ import { motion } from "framer-motion"; import FinancesTab from "./tabs/Finances/FinancesTab.tsx"; import WorkTimeTable from "./tabs/WorkTimeTable/ui/WorkTimeTable.tsx"; import { WorkShiftsTab } from "./tabs/WorkShifts/WorkShiftsTab.tsx"; +import { ExpensesTab } from "./tabs/Expenses/ExpensesTab.tsx"; const AdminPage = () => { return ( @@ -48,6 +49,11 @@ const AdminPage = () => { leftSection={}> Смены + }> + Расходы + { + + + + + diff --git a/src/pages/AdminPage/hooks/useExpensesList.tsx b/src/pages/AdminPage/hooks/useExpensesList.tsx new file mode 100644 index 0000000..d29e0a0 --- /dev/null +++ b/src/pages/AdminPage/hooks/useExpensesList.tsx @@ -0,0 +1,12 @@ +import { Pagination } from "../../../types/Pagination.ts"; +import { ObjectListWithPagination } from "../../../hooks/objectList.tsx"; +import { ExpenseService } from "../../../client"; + + +export const useExpensesList = (pagination: Pagination) => + ObjectListWithPagination({ + queryFn: () => ExpenseService.getAllExpenses(pagination), + queryKey: "getExpenses", + getObjectsFn: response => response.expenses, + pagination, + }); diff --git a/src/pages/AdminPage/tabs/Expenses/ExpensesTab.tsx b/src/pages/AdminPage/tabs/Expenses/ExpensesTab.tsx new file mode 100644 index 0000000..043eb3d --- /dev/null +++ b/src/pages/AdminPage/tabs/Expenses/ExpensesTab.tsx @@ -0,0 +1,134 @@ +import { useExpensesList } from "../../hooks/useExpensesList.tsx"; +import { useExpensesTableColumns } from "./hooks/useExpensesTableColumns.tsx"; +import { ExpenseSchemaBase, ExpenseService } from "../../../../client"; +import { dateToString } from "../../../../types/utils.ts"; +import { notifications } from "../../../../shared/lib/notifications.ts"; +import { modals } from "@mantine/modals"; +import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx"; +import { Pagination, Flex, rem, Button, Tooltip, ActionIcon } from "@mantine/core"; +import { useEffect, useState } from "react"; +import { IconEdit, IconTrash } from "@tabler/icons-react"; +import { MRT_TableOptions } from "mantine-react-table"; + + +export const ExpensesTab = () => { + const [totalPages, setTotalPages] = useState(10); + const [page, setPage] = useState(1); + const { + pagination: paginationInfo, + objects: expenses, + refetch, + } = useExpensesList({ page: page, itemsPerPage: 10 }); + const columns = useExpensesTableColumns(); + + useEffect(() => { + if (!paginationInfo) return; + setTotalPages(paginationInfo.totalPages); + }, [paginationInfo]); + + const onUpdate = (expense: ExpenseSchemaBase) => { + ExpenseService.updateExpense({ + requestBody: { + expense: { + ...expense, + spentDate: dateToString(new Date(expense.spentDate)) ?? "", + }, + }, + }) + .then(async ({ ok, message }) => { + notifications.guess(ok, { message }); + await refetch(); + }) + .catch(err => console.log(err)); + }; + + const onCreateClick = () => { + modals.openContextModal({ + modal: "expenseFormModal", + title: "Создание записи о расходах", + withCloseButton: false, + innerProps: { + onCreate: onUpdate, + }, + }); + }; + + const onEditClick = (expense: ExpenseSchemaBase) => { + modals.openContextModal({ + modal: "expenseFormModal", + title: "Редактирование записи о расходах", + withCloseButton: false, + innerProps: { + onChange: event => onUpdate(event), + element: expense, + }, + }); + }; + + const onDeleteClick = (expense: ExpenseSchemaBase) => { + ExpenseService.deleteExpense({ + expenseId: expense.id, + }) + .then(async ({ ok, message }) => { + notifications.guess(ok, { message }); + await refetch(); + }) + .catch(err => console.log(err)); + }; + + return ( + + + + + ), + renderRowActions: ({ row }) => ( + + + onEditClick(row.original)} + variant={"default"}> + + + + + onDeleteClick(row.original)} + variant={"default"}> + + + + + ), + } as MRT_TableOptions + } + /> + {totalPages > 1 && ( + setPage(event)} + value={page} + total={totalPages} + /> + )} + + ); +}; \ No newline at end of file diff --git a/src/pages/AdminPage/tabs/Expenses/hooks/useExpensesTableColumns.tsx b/src/pages/AdminPage/tabs/Expenses/hooks/useExpensesTableColumns.tsx new file mode 100644 index 0000000..f1ddae6 --- /dev/null +++ b/src/pages/AdminPage/tabs/Expenses/hooks/useExpensesTableColumns.tsx @@ -0,0 +1,37 @@ +import { useMemo } from "react"; +import { MRT_ColumnDef } from "mantine-react-table"; +import { ExpenseSchemaBase } from "../../../../../client"; +import { formatDate } from "../../../../../types/utils.ts"; + + +export const useExpensesTableColumns = () => { + return useMemo[]>( + () => [ + { + accessorKey: "spentDate", + header: "Дата", + Cell: ({ row }) => formatDate(row.original.spentDate as string), + }, + { + accessorKey: "name", + header: "Наименование", + }, + { + accessorKey: "comment", + header: "Комментарий", + }, + { + accessorKey: "amount", + header: "Сумма", + }, + { + accessorKey: "createdByUser", + header: "Создал запись", + Cell: ({ row }) => { + return `${row.original.createdByUser.firstName} ${row.original.createdByUser.secondName}`; + }, + }, + ], + [], + ); +}; \ No newline at end of file diff --git a/src/pages/AdminPage/tabs/Expenses/modals/ExpenseFormModal.tsx b/src/pages/AdminPage/tabs/Expenses/modals/ExpenseFormModal.tsx new file mode 100644 index 0000000..338473f --- /dev/null +++ b/src/pages/AdminPage/tabs/Expenses/modals/ExpenseFormModal.tsx @@ -0,0 +1,67 @@ +import { ContextModalProps } from "@mantine/modals"; +import { useForm } from "@mantine/form"; +import { Flex, NumberInput, rem, TextInput } from "@mantine/core"; +import BaseFormModal, { CreateEditFormProps } from "../../../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx"; +import { ExpenseSchemaBase } from "../../../../../client"; +import { DatePickerInput } from "@mantine/dates"; + +type Props = CreateEditFormProps; + +const ExpenseFormModal = ({ + context, + id, + innerProps, + }: ContextModalProps) => { + const isEditing = "element" in innerProps; + const initialValue: Partial = isEditing + ? innerProps.element + : {}; + + const form = useForm>({ + initialValues: initialValue, + validate: { + name: name => !name && "Необходимо указать наименование", + amount: amount => !amount && "Необходимо указать сумму", + spentDate: spentDate => !spentDate && "Необходимо указать дату", + }, + }); + return ( + context.closeContextModal(id)} + {...innerProps}> + + + + + + + + + + ); +}; + +export default ExpenseFormModal; diff --git a/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitChart/hooks/useProfitChart.tsx b/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitChart/hooks/useProfitChart.tsx index aca6dd2..164ff6e 100644 --- a/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitChart/hooks/useProfitChart.tsx +++ b/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitChart/hooks/useProfitChart.tsx @@ -1,9 +1,10 @@ import { ChartFormFilters } from "../../../../../types/ChartFormFilters.ts"; import { useForm } from "@mantine/form"; -import { dateToString, getDefaultDates } from "../../../../../utils/dates.ts"; +import { getDefaultDates } from "../../../../../utils/dates.ts"; import { useEffect, useState } from "react"; import { ProfitChartDataItem, StatisticsService } from "../../../../../../../client"; import { defaultDealStatus } from "../../../../../utils/defaultFilterValues.ts"; +import { dateToString } from "../../../../../../../types/utils.ts"; export const useProfitChart = () => { diff --git a/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTable/hooks/useProfitTable.tsx b/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTable/hooks/useProfitTable.tsx index 8577113..50c1fab 100644 --- a/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTable/hooks/useProfitTable.tsx +++ b/src/pages/StatisticsPage/tabs/ProfitTab/components/ProfitTable/hooks/useProfitTable.tsx @@ -1,11 +1,12 @@ import { useForm } from "@mantine/form"; import { TableFormFilters } from "../../../../../types/TableFormFilters.ts"; -import { dateToString, getDefaultDates } from "../../../../../utils/dates.ts"; +import { getDefaultDates } from "../../../../../utils/dates.ts"; import { GroupStatisticsTable } from "../../ProfitTableSegmentedControl/ProfitTableSegmentedControl.tsx"; import { useEffect, useState } from "react"; import { ProfitTableDataItem, StatisticsService } from "../../../../../../../client"; import { useProfitMantineTable } from "./useProfitMantineTable.tsx"; import { defaultDealStatus } from "../../../../../utils/defaultFilterValues.ts"; +import { dateToString } from "../../../../../../../types/utils.ts"; export const useProfitTable = () => { diff --git a/src/pages/StatisticsPage/utils/dates.ts b/src/pages/StatisticsPage/utils/dates.ts index f25830d..938b9db 100644 --- a/src/pages/StatisticsPage/utils/dates.ts +++ b/src/pages/StatisticsPage/utils/dates.ts @@ -5,11 +5,3 @@ export const getDefaultDates = (): [Date, Date] => { return [dateFrom, dateTo]; }; -export const dateToString = (date: Date | null) => { - if (date === null) return null; - const month = date.getMonth() + 1; - const day = date.getDate(); - const monthStr = month < 10 ? `0${month}` : month; - const dayStr = day < 10 ? `0${day}` : day; - return `${date.getFullYear()}-${monthStr}-${dayStr}`; -}; diff --git a/src/types/utils.ts b/src/types/utils.ts index 4d8c78a..b917ac1 100644 --- a/src/types/utils.ts +++ b/src/types/utils.ts @@ -56,4 +56,13 @@ export const strTimeToFloatHours = (time: string): number => { const minutes = parseInt(values[1]); return hours + minutes / 60; -} \ No newline at end of file +} + +export const dateToString = (date: Date | null) => { + if (date === null) return null; + const month = date.getMonth() + 1; + const day = date.getDate(); + const monthStr = month < 10 ? `0${month}` : month; + const dayStr = day < 10 ? `0${day}` : day; + return `${date.getFullYear()}-${monthStr}-${dayStr}`; +}; \ No newline at end of file