feat: crappy reordering

This commit is contained in:
2024-10-07 23:42:01 +03:00
parent ae0d4ff6f0
commit 223ed0577a
13 changed files with 290 additions and 66 deletions

View File

@@ -4,7 +4,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"dev": "vite --force",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
@@ -15,7 +15,7 @@
"@fortawesome/free-regular-svg-icons": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
"@hello-pangea/dnd": "^16.6.0",
"@hello-pangea/dnd": "^17.0.0",
"@mantine/core": "^7.11.2",
"@mantine/dates": "^7.11.2",
"@mantine/dropzone": "^7.11.2",

View File

@@ -182,6 +182,8 @@ export type { ProductUpdateResponse } from './models/ProductUpdateResponse';
export type { ProductUploadImageResponse } from './models/ProductUploadImageResponse';
export type { RoleSchema } from './models/RoleSchema';
export type { ServiceCategoryPriceSchema } from './models/ServiceCategoryPriceSchema';
export type { ServiceCategoryReorderRequest } from './models/ServiceCategoryReorderRequest';
export type { ServiceCategoryReorderResponse } from './models/ServiceCategoryReorderResponse';
export type { ServiceCategorySchema } from './models/ServiceCategorySchema';
export type { ServiceCreateCategoryRequest } from './models/ServiceCreateCategoryRequest';
export type { ServiceCreateCategoryResponse } from './models/ServiceCreateCategoryResponse';
@@ -193,6 +195,8 @@ export type { ServiceGetAllCategoriesResponse } from './models/ServiceGetAllCate
export type { ServiceGetAllResponse } from './models/ServiceGetAllResponse';
export type { ServicePriceCategorySchema } from './models/ServicePriceCategorySchema';
export type { ServicePriceRangeSchema } from './models/ServicePriceRangeSchema';
export type { ServiceReorderRequest } from './models/ServiceReorderRequest';
export type { ServiceReorderResponse } from './models/ServiceReorderResponse';
export type { ServiceSchema } from './models/ServiceSchema';
export type { ServiceUpdateRequest } from './models/ServiceUpdateRequest';
export type { ServiceUpdateResponse } from './models/ServiceUpdateResponse';

View File

@@ -0,0 +1,11 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ServiceCategoryReorderRequest = {
moveDown: boolean;
moveUp: boolean;
categoryId: number;
serviceType: number;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ServiceCategoryReorderResponse = {
ok: boolean;
message: string;
};

View File

@@ -5,5 +5,7 @@
export type ServiceCategorySchema = {
id: number;
name: string;
dealServiceRank: string;
productServiceRank: string;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ServiceReorderRequest = {
drainingServiceId: number;
hoveredServiceId: number;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type ServiceReorderResponse = {
ok: boolean;
message: string;
};

View File

@@ -14,5 +14,6 @@ export type ServiceSchema = {
priceRanges: Array<ServicePriceRangeSchema>;
categoryPrices: Array<ServiceCategoryPriceSchema>;
cost: (number | null);
rank: string;
};

View File

@@ -11,6 +11,8 @@ import type { DeletePriceCategoryRequest } from '../models/DeletePriceCategoryRe
import type { DeletePriceCategoryResponse } from '../models/DeletePriceCategoryResponse';
import type { GetAllPriceCategoriesResponse } from '../models/GetAllPriceCategoriesResponse';
import type { GetAllServicesKitsResponse } from '../models/GetAllServicesKitsResponse';
import type { ServiceCategoryReorderRequest } from '../models/ServiceCategoryReorderRequest';
import type { ServiceCategoryReorderResponse } from '../models/ServiceCategoryReorderResponse';
import type { ServiceCreateCategoryRequest } from '../models/ServiceCreateCategoryRequest';
import type { ServiceCreateCategoryResponse } from '../models/ServiceCreateCategoryResponse';
import type { ServiceCreateRequest } from '../models/ServiceCreateRequest';
@@ -19,6 +21,8 @@ import type { ServiceDeleteRequest } from '../models/ServiceDeleteRequest';
import type { ServiceDeleteResponse } from '../models/ServiceDeleteResponse';
import type { ServiceGetAllCategoriesResponse } from '../models/ServiceGetAllCategoriesResponse';
import type { ServiceGetAllResponse } from '../models/ServiceGetAllResponse';
import type { ServiceReorderRequest } from '../models/ServiceReorderRequest';
import type { ServiceReorderResponse } from '../models/ServiceReorderResponse';
import type { ServiceUpdateRequest } from '../models/ServiceUpdateRequest';
import type { ServiceUpdateResponse } from '../models/ServiceUpdateResponse';
import type { UpdatePriceCategoryRequest } from '../models/UpdatePriceCategoryRequest';
@@ -100,6 +104,26 @@ export class ServiceService {
},
});
}
/**
* Reorder
* @returns ServiceReorderResponse Successful Response
* @throws ApiError
*/
public static reorderService({
requestBody,
}: {
requestBody: ServiceReorderRequest,
}): CancelablePromise<ServiceReorderResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/service/reorder',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* Get All Categories
* @returns ServiceGetAllCategoriesResponse Successful Response
@@ -131,6 +155,26 @@ export class ServiceService {
},
});
}
/**
* Reorder Category
* @returns ServiceCategoryReorderResponse Successful Response
* @throws ApiError
*/
public static reorderServiceCategory({
requestBody,
}: {
requestBody: ServiceCategoryReorderRequest,
}): CancelablePromise<ServiceCategoryReorderResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/service/categories/reorder',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* Get All Service Types
* @returns BaseEnumListSchema Successful Response

View File

@@ -1,19 +1,42 @@
import { ServiceSchema } from "../../../../client";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import { ServiceSchema, ServiceService } from "../../../../client";
import { FC } from "react";
import { useServicesTableColumns } from "./columns.tsx";
import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
import { MRT_TableOptions } from "mantine-react-table";
import { CRUDTableProps } from "../../../../types/CRUDTable.tsx";
import { ActionIcon, Flex, Tooltip } from "@mantine/core";
import { IconEdit, IconTrash } from "@tabler/icons-react";
import { IconArrowDown, IconArrowUp, IconEdit, IconTrash } from "@tabler/icons-react";
import { modals } from "@mantine/modals";
import { notifications } from "../../../../shared/lib/notifications.ts";
import { useQueryClient } from "@tanstack/react-query";
import { ServicesTab } from "../ServiceTypeSegmentedControl/ServiceTypeSegmentedControl.tsx";
const ServicesTable: FC<CRUDTableProps<ServiceSchema>> = ({
items,
onDelete,
onChange,
}) => {
type RestProps = {
serviceType: ServicesTab;
editMode: boolean;
}
type Props = CRUDTableProps<ServiceSchema> & RestProps;
const ServicesTable: FC<Props> = ({
items,
onDelete,
onChange,
serviceType,
editMode,
}) => {
const queryClient = useQueryClient();
const columns = useServicesTableColumns();
const categoryRanks = items.map(item => item.category).map(category => {
if (serviceType === ServicesTab.DEAL_SERVICE) {
return category.dealServiceRank;
}
return category.productServiceRank;
});
const minRank = categoryRanks.sort()[0];
const maxRank = categoryRanks.sort()[categoryRanks.length - 1];
console.log(minRank, maxRank);
const onEditClick = (service: ServiceSchema) => {
if (!onChange) return;
@@ -31,33 +54,125 @@ const ServicesTable: FC<CRUDTableProps<ServiceSchema>> = ({
<BaseTable
data={items}
columns={columns}
restProps={
{
enableGrouping: true,
initialState: { grouping: ["category"] },
enableColumnActions: false,
enableRowActions: true,
renderRowActions: ({ row }) => (
<Flex gap="md">
<Tooltip label="Редактировать">
<ActionIcon
onClick={() => onEditClick(row.original)}
variant={"default"}>
<IconEdit />
</ActionIcon>
</Tooltip>
<Tooltip label="Удалить">
<ActionIcon
onClick={() => {
if (onDelete) onDelete(row.original);
}}
variant={"default"}>
<IconTrash />
</ActionIcon>
</Tooltip>
</Flex>
),
} as MRT_TableOptions<ServiceSchema>
restProps={{
enableGrouping: true,
initialState: {
grouping: ["category"],
},
state: {
columnVisibility: {
"mrt-row-drag": editMode,
},
},
enableColumnActions: false,
enableRowOrdering: true,
mantineRowDragHandleProps: ({ table }) => ({
onDragEnd: () => {
const { draggingRow, hoveredRow } = table.getState();
if (!hoveredRow?.original || !draggingRow?.original) return;
ServiceService.reorderService({
requestBody: {
drainingServiceId: draggingRow.original.id,
hoveredServiceId: hoveredRow.original.id,
},
}).then(({ ok, message }) => {
if (!ok) {
notifications.guess(ok, { message });
return;
}
queryClient.invalidateQueries({
queryKey: ["getAllServices"],
}).then(() => {
});
});
},
}),
displayColumnDefOptions: {
"mrt-row-drag": {
AggregatedCell: ({ row }) => {
const rank = serviceType === ServicesTab.DEAL_SERVICE ? row.original.category.dealServiceRank : row.original.category.productServiceRank;
return (
<Flex gap={"xs"}>
<Tooltip label="Поднять вверх">
<ActionIcon
disabled={rank === minRank}
onClick={() => {
ServiceService.reorderServiceCategory({
requestBody: {
serviceType,
categoryId: row.original.category.id,
moveDown: false,
moveUp: true,
},
}).then(({ ok, message }) => {
if (!ok) {
notifications.guess(ok, { message });
return;
}
queryClient.invalidateQueries({
queryKey: ["getAllServices"],
}).then(() => {
});
});
}}
variant={"default"}>
<IconArrowUp />
</ActionIcon>
</Tooltip>
<Tooltip label="Опустить вниз">
<ActionIcon
disabled={rank === maxRank}
onClick={() => {
ServiceService.reorderServiceCategory({
requestBody: {
serviceType,
categoryId: row.original.category.id,
moveDown: true,
moveUp: false,
},
}).then(({ ok, message }) => {
if (!ok) {
notifications.guess(ok, { message });
return;
}
queryClient.invalidateQueries({
queryKey: ["getAllServices"],
}).then(() => {
});
});
}}
variant={"default"}>
<IconArrowDown />
</ActionIcon>
</Tooltip>
</Flex>
);
},
},
},
enableRowActions: true,
renderRowActions: ({ row }) => (
<Flex gap="xs">
<Tooltip label="Редактировать">
<ActionIcon
onClick={() => onEditClick(row.original)}
variant={"default"}>
<IconEdit />
</ActionIcon>
</Tooltip>
<Tooltip label="Удалить">
<ActionIcon
onClick={() => {
if (onDelete) onDelete(row.original);
}}
variant={"default"}>
<IconTrash />
</ActionIcon>
</Tooltip>
</Flex>
),
} as MRT_TableOptions<ServiceSchema>
}
/>
);

View File

@@ -10,7 +10,7 @@ export const useServicesTableColumns = () => {
<>
<List>
{service.priceRanges.map(range => (
<List.Item>
<List.Item key={range.id}>
{`${range.fromQuantity} - ${range.toQuantity}: ${range.price}`}
</List.Item>
))}
@@ -24,9 +24,9 @@ export const useServicesTableColumns = () => {
{
accessorKey: "category",
header: "Категория",
enableGrouping: false,
accessorFn: row => `${row.category.name}`,
enableColumnOrdering: true,
enableSorting: false,
accessorFn: row => row.category.name,
},
{
accessorKey: "name",
@@ -47,7 +47,8 @@ export const useServicesTableColumns = () => {
enableGrouping: false,
enableSorting: false,
},
],
[]
[],
);
};

View File

@@ -21,29 +21,32 @@ import PriceCategoryInput, {
type Props = CreateEditFormProps<ServiceSchema>;
const CreateServiceModal = ({
context,
id,
innerProps,
}: ContextModalProps<Props>) => {
context,
id,
innerProps,
}: ContextModalProps<Props>) => {
const [priceType, setPriceType] = useState<ServicePriceType>(
ServicePriceType.DEFAULT
ServicePriceType.DEFAULT,
);
const isEditing = "onChange" in innerProps;
const initialValues: ServiceSchema = isEditing
? innerProps.element
: {
id: -1,
name: "",
price: 0,
category: {
id: -1,
name: "",
},
serviceType: -1,
priceRanges: [] as ServicePriceRangeSchema[],
cost: null,
categoryPrices: [],
};
id: -1,
name: "",
price: 0,
category: {
id: -1,
name: "",
dealServiceRank: "",
productServiceRank: "",
},
serviceType: -1,
priceRanges: [] as ServicePriceRangeSchema[],
cost: null,
categoryPrices: [],
rank: "",
};
const form = useForm<ServiceSchema>({
initialValues: initialValues,
@@ -82,7 +85,7 @@ const CreateServiceModal = ({
return (
<RangePriceInput
{...(form.getInputProps(
"priceRanges"
"priceRanges",
) as PriceRangeInputType)}
/>
);
@@ -90,7 +93,7 @@ const CreateServiceModal = ({
return (
<PriceCategoryInput
{...(form.getInputProps(
"categoryPrices"
"categoryPrices",
) as PriceCategoryInputProps)}
/>
);

View File

@@ -2,7 +2,7 @@ import { FC, useState } from "react";
import ServicesTable from "../components/ServicesTable/ServicesTable.tsx";
import PageBlock from "../../../components/PageBlock/PageBlock.tsx";
import styles from "./ServicesPage.module.css";
import { Button } from "@mantine/core";
import { Button, Flex, rem, Switch } from "@mantine/core";
import ServiceTypeSegmentedControl, {
ServicesTab,
} from "../components/ServiceTypeSegmentedControl/ServiceTypeSegmentedControl.tsx";
@@ -15,6 +15,7 @@ import { ObjectStateToTableProps } from "../../../types/utils.ts";
export const ServicesPage: FC = () => {
const [serviceType, setServiceType] = useState(ServicesTab.DEAL_SERVICE);
const [isEditMode, setIsEditMode] = useState(false);
const {
services,
onServiceDelete,
@@ -38,16 +39,24 @@ export const ServicesPage: FC = () => {
/>
);
case ServicesTab.DEAL_SERVICE:
case ServicesTab.PRODUCT_SERVICE:
case ServicesTab.PRODUCT_SERVICE: {
const servicesOrdered = services.filter(service => service.serviceType === serviceType).sort((a, b) => {
if (serviceType === ServicesTab.DEAL_SERVICE) {
return a.category.dealServiceRank.localeCompare(b.category.dealServiceRank);
}
return a.category.productServiceRank.localeCompare(b.category.productServiceRank);
},
);
return (
<ServicesTable
onDelete={onServiceDelete}
onChange={onServiceUpdate}
items={services.filter(
service => service.serviceType == serviceType
)}
items={servicesOrdered}
serviceType={serviceType}
editMode={isEditMode}
/>
);
}
case ServicesTab.SERVICES_PRICE_CATEGORIES:
return (
<ServicePriceCategoryTable
@@ -70,7 +79,7 @@ export const ServicesPage: FC = () => {
case ServicesTab.DEAL_SERVICE:
case ServicesTab.PRODUCT_SERVICE:
return (
<>
<Flex align={"center"} gap={rem(10)}>
<Button
onClick={onCreateClick}
variant={"default"}>
@@ -81,7 +90,14 @@ export const ServicesPage: FC = () => {
variant={"default"}>
Создать категорию
</Button>
</>
<Switch
variant={"default"}
label={"Режим редактирования"}
checked={isEditMode}
onChange={() => setIsEditMode(!isEditMode)}
/>
</Flex>
);
case ServicesTab.SERVICES_PRICE_CATEGORIES:
return (