feat: income
This commit is contained in:
@@ -15,7 +15,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";
|
||||
import { TransactionsTab } from "./tabs/Transactions/TransactionsTab.tsx";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../../redux/store.ts";
|
||||
|
||||
@@ -62,9 +62,9 @@ const AdminPage = () => {
|
||||
</Tabs.Tab>
|
||||
{isAdmin && (
|
||||
<Tabs.Tab
|
||||
value={"expenses"}
|
||||
value={"transactions"}
|
||||
leftSection={<IconCoins />}>
|
||||
Расходы
|
||||
Доходы и расходы
|
||||
</Tabs.Tab>
|
||||
)}
|
||||
</Tabs.List>
|
||||
@@ -108,12 +108,12 @@ const AdminPage = () => {
|
||||
<WorkShiftsTab />
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value={"expenses"}>
|
||||
<Tabs.Panel value={"transactions"}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.2 }}>
|
||||
<ExpensesTab />
|
||||
<TransactionsTab />
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
|
||||
10
src/pages/AdminPage/hooks/useAllTransactionTagsList.tsx
Normal file
10
src/pages/AdminPage/hooks/useAllTransactionTagsList.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import ObjectList from "../../../hooks/objectList.tsx";
|
||||
import { TransactionService } from "../../../client";
|
||||
|
||||
const useAllTransactionTagsList = () =>
|
||||
ObjectList({
|
||||
queryFn: () => TransactionService.getAllTransactionTags(),
|
||||
getObjectsFn: response => response.tags,
|
||||
queryKey: "getAllTransactionTags",
|
||||
});
|
||||
export default useAllTransactionTagsList;
|
||||
@@ -1,10 +0,0 @@
|
||||
import ObjectList from "../../../hooks/objectList.tsx";
|
||||
import { ExpenseService } from "../../../client";
|
||||
|
||||
const useExpenseTagsList = () =>
|
||||
ObjectList({
|
||||
queryFn: ExpenseService.getAllExpenseTags,
|
||||
getObjectsFn: response => response.tags,
|
||||
queryKey: "getAllExpenseTags",
|
||||
});
|
||||
export default useExpenseTagsList;
|
||||
@@ -1,12 +0,0 @@
|
||||
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,
|
||||
});
|
||||
16
src/pages/AdminPage/hooks/useTransactionTagsList.tsx
Normal file
16
src/pages/AdminPage/hooks/useTransactionTagsList.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import ObjectList from "../../../hooks/objectList.tsx";
|
||||
import { TransactionService } from "../../../client";
|
||||
|
||||
type Props = {
|
||||
isIncome: boolean;
|
||||
}
|
||||
|
||||
const useTransactionTagsList = ({ isIncome }: Props) =>
|
||||
ObjectList({
|
||||
queryFn: () => TransactionService.getTransactionTags(
|
||||
{ isIncome },
|
||||
),
|
||||
getObjectsFn: response => response.tags,
|
||||
queryKey: "getTransactionTags",
|
||||
});
|
||||
export default useTransactionTagsList;
|
||||
21
src/pages/AdminPage/hooks/useTransactionsList.tsx
Normal file
21
src/pages/AdminPage/hooks/useTransactionsList.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Pagination } from "../../../types/Pagination.ts";
|
||||
import { ObjectListWithPagination } from "../../../hooks/objectList.tsx";
|
||||
import { TransactionService } from "../../../client";
|
||||
|
||||
type TransitionType = {
|
||||
isIncome: boolean
|
||||
};
|
||||
type Props = Pagination & TransitionType;
|
||||
|
||||
export const useTransactionsList = (props: Props) =>
|
||||
ObjectListWithPagination({
|
||||
queryFn: () => TransactionService.getAllTransactions({
|
||||
requestBody: {
|
||||
isIncome: props.isIncome,
|
||||
},
|
||||
...props,
|
||||
}),
|
||||
queryKey: "getTransactions",
|
||||
getObjectsFn: response => response.transactions,
|
||||
pagination: props,
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
import { TagsInput, TagsInputProps } from "@mantine/core";
|
||||
import useExpenseTagsList from "../../../hooks/useExpenseTagsList.tsx";
|
||||
|
||||
|
||||
type Props = Omit<TagsInputProps, "data">;
|
||||
|
||||
const ExpenseTagsInput = (props: Props) => {
|
||||
const { objects } = useExpenseTagsList();
|
||||
return (
|
||||
<TagsInput
|
||||
data={objects.map(object => object.name)}
|
||||
{...props}
|
||||
label={"Теги"}
|
||||
placeholder={"Выберите теги"}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExpenseTagsInput;
|
||||
@@ -1,39 +1,44 @@
|
||||
import { useExpensesList } from "../../hooks/useExpensesList.tsx";
|
||||
import { useExpensesTableColumns } from "./hooks/useExpensesTableColumns.tsx";
|
||||
import { ExpenseSchemaBase, ExpenseService } from "../../../../client";
|
||||
import { useTransactionsList } from "../../hooks/useTransactionsList.tsx";
|
||||
import { useTransactionsTableColumns } from "./hooks/useTransactionsTableColumns.tsx";
|
||||
import { TransactionSchemaBase, TransactionService } 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 { ActionIcon, Button, Flex, Pagination, rem, Tooltip } from "@mantine/core";
|
||||
import { useEffect, useState } from "react";
|
||||
import { IconEdit, IconTrash } from "@tabler/icons-react";
|
||||
import { MRT_TableOptions } from "mantine-react-table";
|
||||
import { Expense } from "./types/Expense.tsx";
|
||||
import { Transaction } from "./types/Transaction.tsx";
|
||||
import { TransactionsSegmentedControl, TransactionType } from "./components/TransactionsSegmentedControl.tsx";
|
||||
|
||||
|
||||
export const ExpensesTab = () => {
|
||||
export const TransactionsTab = () => {
|
||||
const [transactionType, setTransactionType] = useState<TransactionType>(TransactionType.EXPENSE);
|
||||
const isIncome = transactionType === TransactionType.INCOME;
|
||||
|
||||
const [totalPages, setTotalPages] = useState(10);
|
||||
const [page, setPage] = useState(1);
|
||||
const {
|
||||
pagination: paginationInfo,
|
||||
objects: expenses,
|
||||
objects: transactions,
|
||||
refetch,
|
||||
} = useExpensesList({ page: page, itemsPerPage: 10 });
|
||||
const columns = useExpensesTableColumns();
|
||||
} = useTransactionsList({ page: page, itemsPerPage: 10, isIncome });
|
||||
const columns = useTransactionsTableColumns();
|
||||
|
||||
useEffect(() => {
|
||||
if (!paginationInfo ) return;
|
||||
if (!paginationInfo) return;
|
||||
if (!paginationInfo.totalPages) setTotalPages(0);
|
||||
else setTotalPages(paginationInfo.totalPages);
|
||||
}, [paginationInfo]);
|
||||
|
||||
const onUpdate = (expense: Expense) => {
|
||||
ExpenseService.updateExpense({
|
||||
const onUpdate = (transaction: Transaction) => {
|
||||
TransactionService.updateTransaction({
|
||||
requestBody: {
|
||||
expense: {
|
||||
...expense,
|
||||
spentDate: dateToString(new Date(expense.spentDate)) ?? "",
|
||||
transaction: {
|
||||
...transaction,
|
||||
spentDate: dateToString(new Date(transaction.spentDate)) ?? "",
|
||||
isIncome,
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -44,33 +49,37 @@ export const ExpensesTab = () => {
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const modalTitleSuffix = isIncome ? "доходах" : "расходах";
|
||||
|
||||
const onCreateClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: "expenseFormModal",
|
||||
title: "Создание записи о расходах",
|
||||
modal: "transactionFormModal",
|
||||
title: `Создание записи о ${modalTitleSuffix}`,
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
onCreate: onUpdate,
|
||||
isIncome,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onEditClick = (expense: ExpenseSchemaBase) => {
|
||||
const expenseToEdit = { ...expense, tags: expense.tags.map(tag => tag.name) };
|
||||
const onEditClick = (transaction: TransactionSchemaBase) => {
|
||||
const expenseToEdit = { ...transaction, tags: transaction.tags.map(tag => tag.name) };
|
||||
modals.openContextModal({
|
||||
modal: "expenseFormModal",
|
||||
title: "Редактирование записи о расходах",
|
||||
modal: "transactionFormModal",
|
||||
title: `Редактирование записи о ${modalTitleSuffix}`,
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
onChange: event => onUpdate(event),
|
||||
onChange: onUpdate,
|
||||
element: expenseToEdit,
|
||||
isIncome,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onDeleteClick = (expense: ExpenseSchemaBase) => {
|
||||
ExpenseService.deleteExpense({
|
||||
expenseId: expense.id,
|
||||
const onDeleteClick = (transaction: TransactionSchemaBase) => {
|
||||
TransactionService.deleteTransaction({
|
||||
transactionId: transaction.id,
|
||||
})
|
||||
.then(async ({ ok, message }) => {
|
||||
notifications.guess(ok, { message });
|
||||
@@ -81,24 +90,48 @@ export const ExpensesTab = () => {
|
||||
|
||||
const onTagChange = async () => {
|
||||
await refetch();
|
||||
}
|
||||
};
|
||||
|
||||
const onTagsEditClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: "expenseTagsModal",
|
||||
modal: "transactionTagsModal",
|
||||
title: "Редактирование тегов",
|
||||
withCloseButton: false,
|
||||
innerProps: { onChange: onTagChange },
|
||||
innerProps: {
|
||||
onChange: onTagChange,
|
||||
isIncome,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getCreateTransactionLabel = (): string => {
|
||||
if (transactionType === TransactionType.EXPENSE) {
|
||||
return "Создать запись о расходах";
|
||||
}
|
||||
return "Создать запись о доходах";
|
||||
};
|
||||
|
||||
const getEditTagLabel = (): string => {
|
||||
if (transactionType === TransactionType.EXPENSE) {
|
||||
return "Редактировать теги для расходов";
|
||||
}
|
||||
return "Редактировать теги для доходов";
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
direction={"column"}
|
||||
h={"100%"}
|
||||
gap={rem(10)}>
|
||||
gap={0}>
|
||||
<TransactionsSegmentedControl
|
||||
value={transactionType.toString()}
|
||||
onChange={event => {
|
||||
setPage(1);
|
||||
setTransactionType(parseInt(event));
|
||||
}}
|
||||
/>
|
||||
<BaseTable
|
||||
data={expenses}
|
||||
data={transactions}
|
||||
columns={columns}
|
||||
restProps={
|
||||
{
|
||||
@@ -111,12 +144,12 @@ export const ExpensesTab = () => {
|
||||
<Button
|
||||
variant={"default"}
|
||||
onClick={() => onCreateClick()}>
|
||||
Создать запись о расходах
|
||||
{getCreateTransactionLabel()}
|
||||
</Button>
|
||||
<Button
|
||||
variant={"default"}
|
||||
onClick={() => onTagsEditClick()}>
|
||||
Редактировать теги
|
||||
{getEditTagLabel()}
|
||||
</Button>
|
||||
</Flex>
|
||||
),
|
||||
@@ -138,7 +171,7 @@ export const ExpensesTab = () => {
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
),
|
||||
} as MRT_TableOptions<ExpenseSchemaBase>
|
||||
} as MRT_TableOptions<TransactionSchemaBase>
|
||||
}
|
||||
/>
|
||||
{totalPages > 1 && (
|
||||
@@ -1,31 +1,35 @@
|
||||
import { BaseExpenseTagSchema } from "../../../../../client";
|
||||
import { BaseTransactionTagSchema } from "../../../../../client";
|
||||
import { Button, Stack, TextInput } from "@mantine/core";
|
||||
import { useState } from "react";
|
||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||
|
||||
|
||||
type Props = {
|
||||
onCreate: (tag: BaseExpenseTagSchema) => void;
|
||||
onCreate: (tag: BaseTransactionTagSchema) => void;
|
||||
isIncome: boolean;
|
||||
}
|
||||
|
||||
const CreateExpenseTagForm = ({ onCreate }: Props) => {
|
||||
const [expenseTag, setExpenseTag] = useState<string>("");
|
||||
const CreateTransactionTagForm = ({ onCreate, isIncome }: Props) => {
|
||||
const [transactionTag, setTransactionTag] = useState<string>("");
|
||||
|
||||
const onCreateClick = () => {
|
||||
if (expenseTag.length === 0) {
|
||||
if (transactionTag.length === 0) {
|
||||
notifications.error({ message: "Нельзя добавить тег без названия" });
|
||||
return;
|
||||
}
|
||||
onCreate({ name: expenseTag });
|
||||
setExpenseTag("");
|
||||
onCreate({
|
||||
name: transactionTag,
|
||||
isIncome: isIncome,
|
||||
});
|
||||
setTransactionTag("");
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<TextInput
|
||||
label={"Добавить тег"}
|
||||
value={expenseTag}
|
||||
onChange={event => setExpenseTag(event.target.value)}
|
||||
value={transactionTag}
|
||||
onChange={event => setTransactionTag(event.target.value)}
|
||||
/>
|
||||
<Button
|
||||
variant={"default"}
|
||||
@@ -36,4 +40,4 @@ const CreateExpenseTagForm = ({ onCreate }: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateExpenseTagForm;
|
||||
export default CreateTransactionTagForm;
|
||||
@@ -0,0 +1,22 @@
|
||||
import { TagsInput, TagsInputProps } from "@mantine/core";
|
||||
import useTransactionTagsList from "../../../hooks/useTransactionTagsList.tsx";
|
||||
|
||||
|
||||
type IsIncome = {
|
||||
isIncome: boolean;
|
||||
}
|
||||
type Props = Omit<TagsInputProps, "data"> & IsIncome;
|
||||
|
||||
const TransactionTagsInput = (props: Props) => {
|
||||
const { objects } = useTransactionTagsList({ isIncome: props.isIncome });
|
||||
return (
|
||||
<TagsInput
|
||||
data={objects.map(object => object.name)}
|
||||
{...props}
|
||||
label={"Теги"}
|
||||
placeholder={"Выберите теги"}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default TransactionTagsInput;
|
||||
@@ -1,33 +1,36 @@
|
||||
import { CRUDTableProps } from "../../../../../types/CRUDTable.tsx";
|
||||
import { BaseExpenseTagSchema, ExpenseTagSchema } from "../../../../../client";
|
||||
import { BaseTransactionTagSchema, TransactionTagSchema } from "../../../../../client";
|
||||
import { FC, useState } from "react";
|
||||
import { ActionIcon, Flex, Stack, Tooltip } from "@mantine/core";
|
||||
import { IconCheck, IconEdit, IconTrash } from "@tabler/icons-react";
|
||||
import { MRT_TableOptions } from "mantine-react-table";
|
||||
import { BaseTable } from "../../../../../components/BaseTable/BaseTable.tsx";
|
||||
import { useExpenseTagsTableColumns } from "../hooks/useExpenseTagsTableColumns.tsx";
|
||||
import CreateExpenseTagForm from "./CreateExpenseTagForm.tsx";
|
||||
import { useTransactionTagsTableColumns } from "../hooks/useTransactionTagsTableColumns.tsx";
|
||||
import CreateTransactionTagForm from "./CreateTransactionTagForm.tsx";
|
||||
|
||||
type isIncome = {
|
||||
isIncome: boolean;
|
||||
}
|
||||
type Props = CRUDTableProps<TransactionTagSchema, BaseTransactionTagSchema> & isIncome;
|
||||
|
||||
type Props = CRUDTableProps<ExpenseTagSchema, BaseExpenseTagSchema>;
|
||||
|
||||
const ExpenseTagsTable: FC<Props> = ({ items, onCreate, onDelete, onChange }) => {
|
||||
const TransactionTagsTable: FC<Props> = ({ items, onCreate, onDelete, onChange, isIncome }) => {
|
||||
const [editingTagId, setEditingTagId] = useState<number>(-1);
|
||||
const [tagName, setTagName] = useState<string>("");
|
||||
|
||||
const columns = useExpenseTagsTableColumns({ editingTagId, setTagName });
|
||||
const columns = useTransactionTagsTableColumns({ editingTagId, setTagName });
|
||||
|
||||
const onStartEditing = (tag: ExpenseTagSchema) => {
|
||||
const onStartEditing = (tag: TransactionTagSchema) => {
|
||||
setEditingTagId(tag.id);
|
||||
setTagName(tag.name);
|
||||
};
|
||||
|
||||
const onFinishEditing = (tag: ExpenseTagSchema) => {
|
||||
const onFinishEditing = (tag: TransactionTagSchema) => {
|
||||
if (!onChange) return;
|
||||
if (tag.name !== tagName) {
|
||||
onChange({
|
||||
id: editingTagId,
|
||||
name: tagName,
|
||||
isIncome: isIncome,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,7 +40,10 @@ const ExpenseTagsTable: FC<Props> = ({ items, onCreate, onDelete, onChange }) =>
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<CreateExpenseTagForm onCreate={item => onCreate && onCreate(item)} />
|
||||
<CreateTransactionTagForm
|
||||
onCreate={item => onCreate && onCreate(item)}
|
||||
isIncome={isIncome}
|
||||
/>
|
||||
<BaseTable
|
||||
data={items}
|
||||
columns={columns}
|
||||
@@ -74,11 +80,11 @@ const ExpenseTagsTable: FC<Props> = ({ items, onCreate, onDelete, onChange }) =>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
),
|
||||
} as MRT_TableOptions<ExpenseTagSchema>
|
||||
} as MRT_TableOptions<TransactionTagSchema>
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExpenseTagsTable;
|
||||
export default TransactionTagsTable;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
|
||||
import { FC } from "react";
|
||||
|
||||
export enum TransactionType {
|
||||
EXPENSE,
|
||||
INCOME,
|
||||
}
|
||||
|
||||
type Props = Omit<SegmentedControlProps, "data">;
|
||||
const data = [
|
||||
{
|
||||
label: "Расходы",
|
||||
value: TransactionType.EXPENSE.toString(),
|
||||
},
|
||||
{
|
||||
label: "Доходы",
|
||||
value: TransactionType.INCOME.toString(),
|
||||
},
|
||||
];
|
||||
|
||||
export const TransactionsSegmentedControl: FC<Props> = props => {
|
||||
return (
|
||||
<SegmentedControl
|
||||
data={data}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMemo } from "react";
|
||||
import { MRT_ColumnDef } from "mantine-react-table";
|
||||
import { ExpenseTagSchema } from "../../../../../client";
|
||||
import { TransactionTagSchema } from "../../../../../client";
|
||||
import { TextInput } from "@mantine/core";
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ type Props = {
|
||||
setTagName: (tagName: string) => void;
|
||||
}
|
||||
|
||||
export const useExpenseTagsTableColumns = ({
|
||||
export const useTransactionTagsTableColumns = ({
|
||||
editingTagId, setTagName,
|
||||
}: Props) => {
|
||||
return useMemo<MRT_ColumnDef<ExpenseTagSchema>[]>(
|
||||
return useMemo<MRT_ColumnDef<TransactionTagSchema>[]>(
|
||||
() => [
|
||||
{
|
||||
accessorKey: "name",
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useMemo } from "react";
|
||||
import { MRT_ColumnDef } from "mantine-react-table";
|
||||
import { ExpenseSchemaBase } from "../../../../../client";
|
||||
import { TransactionSchemaBase } from "../../../../../client";
|
||||
import { formatDate } from "../../../../../types/utils.ts";
|
||||
|
||||
|
||||
export const useExpensesTableColumns = () => {
|
||||
return useMemo<MRT_ColumnDef<ExpenseSchemaBase>[]>(
|
||||
export const useTransactionsTableColumns = () => {
|
||||
return useMemo<MRT_ColumnDef<TransactionSchemaBase>[]>(
|
||||
() => [
|
||||
{
|
||||
accessorKey: "spentDate",
|
||||
@@ -3,22 +3,25 @@ import { useForm } from "@mantine/form";
|
||||
import { Flex, NumberInput, rem, TextInput } from "@mantine/core";
|
||||
import BaseFormModal, { CreateEditFormProps } from "../../../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
|
||||
import { DatePickerInput } from "@mantine/dates";
|
||||
import ExpenseTagsInput from "../components/ExpenseTagsInput.tsx";
|
||||
import { Expense } from "../types/Expense.tsx";
|
||||
import TransactionTagsInput from "../components/TransactionTagsInput.tsx";
|
||||
import { Transaction } from "../types/Transaction.tsx";
|
||||
|
||||
type Props = CreateEditFormProps<Expense>;
|
||||
type IsIncome = {
|
||||
isIncome: boolean;
|
||||
}
|
||||
type Props = CreateEditFormProps<Transaction> & IsIncome;
|
||||
|
||||
const ExpenseFormModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const TransactionFormModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const isEditing = "element" in innerProps;
|
||||
const initialValue: Partial<Expense> = isEditing
|
||||
const initialValue: Partial<Transaction> = isEditing
|
||||
? innerProps.element
|
||||
: {};
|
||||
|
||||
const form = useForm<Partial<Expense>>({
|
||||
const form = useForm<Partial<Transaction>>({
|
||||
initialValues: initialValue,
|
||||
validate: {
|
||||
name: name => !name && "Необходимо указать наименование",
|
||||
@@ -59,8 +62,9 @@ const ExpenseFormModal = ({
|
||||
placeholder={"Укажите дату расхода"}
|
||||
valueFormat={"DD.MM.YYYY"}
|
||||
/>
|
||||
<ExpenseTagsInput
|
||||
<TransactionTagsInput
|
||||
{...form.getInputProps("tags")}
|
||||
isIncome={innerProps.isIncome}
|
||||
/>
|
||||
</Flex>
|
||||
</BaseFormModal.Body>
|
||||
@@ -68,4 +72,4 @@ const ExpenseFormModal = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default ExpenseFormModal;
|
||||
export default TransactionFormModal;
|
||||
@@ -1,23 +1,24 @@
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import { BaseExpenseTagSchema, ExpenseService, ExpenseTagSchema } from "../../../../../client";
|
||||
import useExpenseTagsList from "../../../hooks/useExpenseTagsList.tsx";
|
||||
import ExpenseTagsTable from "../components/ExpenseTagsTable.tsx";
|
||||
import { BaseTransactionTagSchema, TransactionService, TransactionTagSchema } from "../../../../../client";
|
||||
import useTransactionTagsList from "../../../hooks/useTransactionTagsList.tsx";
|
||||
import TransactionTagsTable from "../components/TransactionTagsTable.tsx";
|
||||
import { useCRUD } from "../../../../../hooks/useCRUD.tsx";
|
||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||
|
||||
type Props = {
|
||||
onChange: () => Promise<void>;
|
||||
isIncome: boolean;
|
||||
};
|
||||
|
||||
const ExpenseTagsModal = ({
|
||||
const TransactionTagsModal = ({
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const { onChange: onTagChange } = innerProps;
|
||||
const { objects: expenseTags, refetch } = useExpenseTagsList();
|
||||
const { objects: expenseTags, refetch } = useTransactionTagsList({ isIncome: innerProps.isIncome });
|
||||
|
||||
const crud = useCRUD<ExpenseTagSchema, BaseExpenseTagSchema>({
|
||||
const crud = useCRUD<TransactionTagSchema, BaseTransactionTagSchema>({
|
||||
onChange: tag => {
|
||||
ExpenseService.updateExpenseTag({
|
||||
TransactionService.updateTransactionTag({
|
||||
requestBody: {
|
||||
tag,
|
||||
},
|
||||
@@ -31,9 +32,12 @@ const ExpenseTagsModal = ({
|
||||
.catch(err => console.log(err));
|
||||
},
|
||||
onCreate: tag => {
|
||||
ExpenseService.createExpenseTag({
|
||||
TransactionService.createTransactionTag({
|
||||
requestBody: {
|
||||
tag,
|
||||
tag: {
|
||||
...tag,
|
||||
isIncome: innerProps.isIncome,
|
||||
}
|
||||
},
|
||||
})
|
||||
.then(async ({ ok, message }) => {
|
||||
@@ -45,7 +49,7 @@ const ExpenseTagsModal = ({
|
||||
.catch(err => console.log(err));
|
||||
},
|
||||
onDelete: (tag) => {
|
||||
ExpenseService.deleteExpenseTag({
|
||||
TransactionService.deleteTransactionTag({
|
||||
tagId: tag.id,
|
||||
})
|
||||
.then(async ({ ok, message }) => {
|
||||
@@ -58,8 +62,12 @@ const ExpenseTagsModal = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<ExpenseTagsTable {...crud} items={expenseTags} />
|
||||
<TransactionTagsTable
|
||||
{...crud}
|
||||
items={expenseTags}
|
||||
isIncome={innerProps.isIncome}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExpenseTagsModal;
|
||||
export default TransactionTagsModal;
|
||||
@@ -1,11 +1,12 @@
|
||||
import type { UserSchema } from "../../../../../client";
|
||||
|
||||
export type Expense = {
|
||||
export type Transaction = {
|
||||
id: number;
|
||||
name: string;
|
||||
comment: string;
|
||||
amount: number;
|
||||
createdByUser: UserSchema;
|
||||
spentDate: string;
|
||||
isIncome: boolean;
|
||||
tags: Array<string>;
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
import { FC } from "react";
|
||||
import useExpenseTagsList from "../../../AdminPage/hooks/useExpenseTagsList.tsx";
|
||||
import { ExpenseTagSchema } from "../../../../client";
|
||||
import ObjectSelect, { ObjectSelectProps } from "../../../../components/ObjectSelect/ObjectSelect.tsx";
|
||||
|
||||
type Props = Omit<
|
||||
ObjectSelectProps<ExpenseTagSchema | null>,
|
||||
"data" | "getValueFn" | "getLabelFn"
|
||||
>;
|
||||
const ExpenseTagSelect: FC<Props> = props => {
|
||||
const { objects: tags } = useExpenseTagsList();
|
||||
return (
|
||||
<ObjectSelect
|
||||
data={tags}
|
||||
getLabelFn={(tag: ExpenseTagSchema) => tag.name}
|
||||
getValueFn={(tag: ExpenseTagSchema) => tag.id.toString()}
|
||||
clearable
|
||||
{...props}
|
||||
onClear={() => props.onChange(null)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default ExpenseTagSelect;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { FC } from "react";
|
||||
import { TransactionTagSchema } from "../../../../client";
|
||||
import ObjectSelect, { ObjectSelectProps } from "../../../../components/ObjectSelect/ObjectSelect.tsx";
|
||||
import useAllTransactionTagsList from "../../../AdminPage/hooks/useAllTransactionTagsList.tsx";
|
||||
|
||||
type IsIncome = {
|
||||
isIncome: boolean;
|
||||
}
|
||||
type Props = Omit<
|
||||
ObjectSelectProps<TransactionTagSchema | null>,
|
||||
"data" | "getValueFn" | "getLabelFn"
|
||||
> & IsIncome;
|
||||
const TransactionTagSelect: FC<Props> = props => {
|
||||
let { objects: tags } = useAllTransactionTagsList();
|
||||
tags = tags.filter(tag => tag.isIncome === props.isIncome);
|
||||
|
||||
return (
|
||||
<ObjectSelect
|
||||
data={tags}
|
||||
getLabelFn={(tag: TransactionTagSchema) => tag.name}
|
||||
getValueFn={(tag: TransactionTagSchema) => tag.id.toString()}
|
||||
clearable
|
||||
{...props}
|
||||
onClear={() => props.onChange(null)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default TransactionTagSelect;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DatePickerInput, DatePickerInputProps } from "@mantine/dates";
|
||||
import { Divider, Stack, Text } from "@mantine/core";
|
||||
import ClientSelectNew from "../../../../../../components/Selects/ClientSelectNew/ClientSelectNew.tsx";
|
||||
import { BaseMarketplaceSchema, ClientSchema, ExpenseTagSchema, UserSchema } from "../../../../../../client";
|
||||
import { BaseMarketplaceSchema, ClientSchema, TransactionTagSchema, UserSchema } from "../../../../../../client";
|
||||
import { ObjectSelectProps } from "../../../../../../components/ObjectSelect/ObjectSelect.tsx";
|
||||
import BaseMarketplaceSelect
|
||||
from "../../../../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
|
||||
@@ -9,35 +9,34 @@ import DealStatusSelect from "../../../../../DealsPage/components/DealStatusSele
|
||||
import { DealStatusType } from "../../../../../../shared/enums/DealStatus.ts";
|
||||
import { ProfitTableSegmentedControl } from "../ProfitTableSegmentedControl/ProfitTableSegmentedControl.tsx";
|
||||
import ManagerSelect from "../../../../../../components/ManagerSelect/ManagerSelect.tsx";
|
||||
import ExpenseTagSelect from "../../../../components/ExpenseTagSelect/ExpenseTagSelect.tsx";
|
||||
import TransactionTagSelect from "../../../../components/ExpenseTagSelect/TransactionTagSelect.tsx";
|
||||
|
||||
|
||||
type SelectProps<T> = Omit<
|
||||
ObjectSelectProps<T>,
|
||||
"data" | "getValueFn" | "getLabelFn"
|
||||
>;
|
||||
|
||||
type FiltersProps = {
|
||||
datePickerProps?: DatePickerInputProps<"range">;
|
||||
|
||||
clientSelectProps?: Omit<ObjectSelectProps<ClientSchema>, "data">;
|
||||
onClientClear?: () => void;
|
||||
|
||||
baseMarketplaceSelectProps?: Omit<
|
||||
ObjectSelectProps<BaseMarketplaceSchema>,
|
||||
"data" | "getValueFn" | "getLabelFn"
|
||||
>;
|
||||
baseMarketplaceSelectProps?: SelectProps<BaseMarketplaceSchema>;
|
||||
onBaseMarketplaceClear?: () => void;
|
||||
|
||||
dealStatusSelectProps?: Omit<ObjectSelectProps<DealStatusType>, "data">;
|
||||
onDealStatusClear?: () => void;
|
||||
|
||||
managerSelectProps?: Omit<
|
||||
ObjectSelectProps<UserSchema | null>,
|
||||
"data" | "getValueFn" | "getLabelFn"
|
||||
>;
|
||||
managerSelectProps?: SelectProps<UserSchema | null | undefined>;
|
||||
onManagerClear?: () => void;
|
||||
|
||||
tagSelectProps?: Omit<
|
||||
ObjectSelectProps<ExpenseTagSchema | null>,
|
||||
"data" | "getValueFn" | "getLabelFn"
|
||||
>;
|
||||
onTagClear?: () => void;
|
||||
expenseTagSelectProps?: SelectProps<TransactionTagSchema | null>;
|
||||
onExpenseTagClear?: () => void;
|
||||
|
||||
incomeTagSelectProps?: SelectProps<TransactionTagSchema | null>;
|
||||
onIncomeTagClear?: () => void;
|
||||
|
||||
groupTableByProps?: {
|
||||
value: string,
|
||||
@@ -46,20 +45,24 @@ type FiltersProps = {
|
||||
}
|
||||
|
||||
export const Filters = (props: FiltersProps) => {
|
||||
const {
|
||||
datePickerProps,
|
||||
clientSelectProps,
|
||||
onClientClear,
|
||||
baseMarketplaceSelectProps,
|
||||
onBaseMarketplaceClear,
|
||||
dealStatusSelectProps,
|
||||
onDealStatusClear,
|
||||
managerSelectProps,
|
||||
onManagerClear,
|
||||
tagSelectProps,
|
||||
onTagClear,
|
||||
groupTableByProps,
|
||||
} = props;
|
||||
const getTransactionTagsSelect = (isIncome: boolean) => {
|
||||
const selectProps = isIncome ? props.incomeTagSelectProps : props.expenseTagSelectProps;
|
||||
if (!selectProps) return;
|
||||
const onClear = isIncome ? props.onIncomeTagClear : props.onExpenseTagClear;
|
||||
const label = "Фильтры для " + (isIncome ? "доходов:" : "расходов:");
|
||||
return (
|
||||
<>
|
||||
<Divider />
|
||||
<Text>{label}</Text>
|
||||
<TransactionTagSelect
|
||||
{...selectProps}
|
||||
onClear={onClear}
|
||||
placeholder={"Выберите тег"}
|
||||
isIncome={isIncome}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack mb={"lg"}>
|
||||
@@ -67,64 +70,55 @@ export const Filters = (props: FiltersProps) => {
|
||||
<Text>
|
||||
Фильтры для выручки и прибыли:
|
||||
</Text>
|
||||
{datePickerProps &&
|
||||
{props.datePickerProps &&
|
||||
<DatePickerInput
|
||||
{...datePickerProps}
|
||||
{...props.datePickerProps}
|
||||
type="range"
|
||||
placeholder="Выберите даты"
|
||||
maxDate={new Date()}
|
||||
valueFormat={"DD.MM.YYYY"}
|
||||
/>
|
||||
}
|
||||
{dealStatusSelectProps &&
|
||||
{props.dealStatusSelectProps &&
|
||||
<DealStatusSelect
|
||||
{...dealStatusSelectProps}
|
||||
onClear={onDealStatusClear}
|
||||
{...props.dealStatusSelectProps}
|
||||
onClear={props.onDealStatusClear}
|
||||
clearable
|
||||
placeholder={"Выберите статус"}
|
||||
/>
|
||||
}
|
||||
{clientSelectProps &&
|
||||
{props.clientSelectProps &&
|
||||
<ClientSelectNew
|
||||
{...clientSelectProps}
|
||||
onClear={onClientClear}
|
||||
{...props.clientSelectProps}
|
||||
onClear={props.onClientClear}
|
||||
clearable
|
||||
searchable
|
||||
placeholder={"Выберите клиента"}
|
||||
/>
|
||||
}
|
||||
{baseMarketplaceSelectProps &&
|
||||
{props.baseMarketplaceSelectProps &&
|
||||
<BaseMarketplaceSelect
|
||||
{...baseMarketplaceSelectProps}
|
||||
onClear={onBaseMarketplaceClear}
|
||||
{...props.baseMarketplaceSelectProps}
|
||||
onClear={props.onBaseMarketplaceClear}
|
||||
clearable
|
||||
placeholder={"Выберите маркетплейс"}
|
||||
/>
|
||||
}
|
||||
{managerSelectProps &&
|
||||
{props.managerSelectProps &&
|
||||
<ManagerSelect
|
||||
{...managerSelectProps}
|
||||
onClear={onManagerClear}
|
||||
{...props.managerSelectProps}
|
||||
onClear={props.onManagerClear}
|
||||
placeholder={"Выберите менеджера"}
|
||||
/>
|
||||
}
|
||||
{tagSelectProps &&
|
||||
<>
|
||||
<Divider />
|
||||
<Text>Фильтры для расходов:</Text>
|
||||
<ExpenseTagSelect
|
||||
{...tagSelectProps}
|
||||
onClear={onTagClear}
|
||||
placeholder={"Выберите тег"}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
{groupTableByProps &&
|
||||
{getTransactionTagsSelect(false)}
|
||||
{getTransactionTagsSelect(true)}
|
||||
{props.groupTableByProps &&
|
||||
<>
|
||||
<Divider />
|
||||
<Text>Группировка таблицы:</Text>
|
||||
<ProfitTableSegmentedControl
|
||||
{...groupTableByProps}
|
||||
{...props.groupTableByProps}
|
||||
orientation={"vertical"}
|
||||
size={"md"}
|
||||
w={"100%"}
|
||||
|
||||
@@ -29,7 +29,8 @@ const useProfitTabContextState = () => {
|
||||
marketplace: null,
|
||||
dealStatus: defaultDealStatus,
|
||||
manager: null,
|
||||
tag: null,
|
||||
expenseTag: null,
|
||||
incomeTag: null,
|
||||
},
|
||||
});
|
||||
const [isChartLoading, setIsChartLoading] = useState(false);
|
||||
@@ -49,7 +50,8 @@ const useProfitTabContextState = () => {
|
||||
baseMarketplaceKey: form.values.marketplace?.key ?? "all",
|
||||
dealStatusId: form.values.dealStatus?.id ?? -1,
|
||||
managerId: form.values.manager?.id ?? -1,
|
||||
tagId: form.values.tag?.id ?? -1,
|
||||
expenseTagId: form.values.expenseTag?.id ?? -1,
|
||||
incomeTagId: form.values.incomeTag?.id ?? -1,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -44,8 +44,10 @@ export const ProfitFiltersModal = ({ form }: Props) => {
|
||||
onDealStatusClear={() => form.setFieldValue("dealStatus", null)}
|
||||
managerSelectProps={form.getInputProps("manager")}
|
||||
onManagerClear={() => form.setFieldValue("manager", null)}
|
||||
tagSelectProps={form.getInputProps("tag")}
|
||||
onTagClear={() => form.setFieldValue("tag", null)}
|
||||
expenseTagSelectProps={form.getInputProps("expenseTag")}
|
||||
onExpenseTagClear={() => form.setFieldValue("expenseTag", null)}
|
||||
incomeTagSelectProps={form.getInputProps("incomeTag")}
|
||||
onIncomeTagClear={() => form.setFieldValue("incomeTag", null)}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
GroupStatisticsTable,
|
||||
} from "../tabs/ProfitTab/components/ProfitTableSegmentedControl/ProfitTableSegmentedControl.tsx";
|
||||
import { BaseMarketplaceSchema, ClientSchema, ExpenseTagSchema, UserSchema } from "../../../client";
|
||||
import { BaseMarketplaceSchema, ClientSchema, TransactionTagSchema, UserSchema } from "../../../client";
|
||||
import { DealStatusType } from "../../../shared/enums/DealStatus.ts";
|
||||
|
||||
export interface FormFilters {
|
||||
@@ -11,5 +11,6 @@ export interface FormFilters {
|
||||
marketplace: BaseMarketplaceSchema | null;
|
||||
dealStatus: DealStatusType | null;
|
||||
manager: UserSchema | null;
|
||||
tag: ExpenseTagSchema | null;
|
||||
expenseTag: TransactionTagSchema | null;
|
||||
incomeTag: TransactionTagSchema | null;
|
||||
}
|
||||
Reference in New Issue
Block a user