list of requests
This commit is contained in:
@@ -0,0 +1,25 @@
|
|||||||
|
import Td from "@/components/Td";
|
||||||
|
import { type FC, Fragment } from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
tdCount: number;
|
||||||
|
trCount?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DefaultTableSkeleton: FC<Props> = ({ tdCount, trCount = 5 }) => {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
{Array.from({ length: trCount }).map((_, rowIndex) => (
|
||||||
|
<tr className="w-full h-[64px] md:h-[74px] bg-white border-t border-[#EAEDF5]" key={rowIndex}>
|
||||||
|
{Array.from({ length: tdCount }).map((_, colIndex) => (
|
||||||
|
<Td key={colIndex}>
|
||||||
|
<div className="h-4 w-20 max-w-full rounded bg-gray-200 animate-pulse" />
|
||||||
|
</Td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DefaultTableSkeleton;
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Group, Rect, Text } from "react-konva";
|
||||||
|
import Konva from "konva";
|
||||||
|
import type { EditorObject } from "@/pages/editor/store/editorStore";
|
||||||
|
|
||||||
|
type EditorTableProps = {
|
||||||
|
obj: EditorObject;
|
||||||
|
selectedCellId: string | null;
|
||||||
|
onCellClick: (tableId: string, cellId: string, node: Konva.Node, e?: Konva.KonvaEventObject<MouseEvent>) => void;
|
||||||
|
onCellDblClick: (tableId: string, cellId: string, x: number, y: number, width: number, height: number, text: string) => void;
|
||||||
|
onDragEnd: (tableId: string, x: number, y: number) => void;
|
||||||
|
draggable: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const EditorTable = ({ obj, selectedCellId, onCellClick, onCellDblClick, onDragEnd, draggable }: EditorTableProps) => {
|
||||||
|
const groupRef = React.useRef<Konva.Group>(null);
|
||||||
|
const tableData = obj.tableData;
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (groupRef.current) {
|
||||||
|
groupRef.current.setAttr("id", obj.id);
|
||||||
|
}
|
||||||
|
}, [obj.id]);
|
||||||
|
|
||||||
|
if (!tableData) return null;
|
||||||
|
|
||||||
|
const { rows, cols, cells, cellWidth, cellHeight, stroke, strokeWidth } = tableData;
|
||||||
|
|
||||||
|
const handleCellClick = (cellId: string, e: Konva.KonvaEventObject<MouseEvent>) => {
|
||||||
|
e.cancelBubble = true;
|
||||||
|
if (groupRef.current) {
|
||||||
|
onCellClick(obj.id, cellId, groupRef.current, e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCellDblClick = (cellId: string, e: Konva.KonvaEventObject<MouseEvent>) => {
|
||||||
|
e.cancelBubble = true;
|
||||||
|
const stage = e.target.getStage();
|
||||||
|
if (!stage || !groupRef.current) return;
|
||||||
|
|
||||||
|
const cell = cells[cellId];
|
||||||
|
if (!cell) return;
|
||||||
|
|
||||||
|
const targetNode = e.target;
|
||||||
|
const box = targetNode.getClientRect();
|
||||||
|
const stageBox = stage.container().getBoundingClientRect();
|
||||||
|
const x = stageBox.left + box.x;
|
||||||
|
const y = stageBox.top + box.y;
|
||||||
|
const width = box.width;
|
||||||
|
const height = box.height;
|
||||||
|
|
||||||
|
onCellDblClick(obj.id, cellId, x, y, width, height, cell.text);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragEnd = () => {
|
||||||
|
if (groupRef.current) {
|
||||||
|
const pos = groupRef.current.position();
|
||||||
|
onDragEnd(obj.id, pos.x, pos.y);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group ref={groupRef} x={obj.x} y={obj.y} draggable={draggable} onDragEnd={handleDragEnd}>
|
||||||
|
{Array.from({ length: rows }).map((_, row) =>
|
||||||
|
Array.from({ length: cols }).map((_, col) => {
|
||||||
|
const cellId = `cell-${row}-${col}`;
|
||||||
|
const cell = cells[cellId];
|
||||||
|
if (!cell) return null;
|
||||||
|
|
||||||
|
const x = col * cellWidth;
|
||||||
|
const y = row * cellHeight;
|
||||||
|
const isCellSelected = selectedCellId === cellId;
|
||||||
|
const isTopLeft = row === 0 && col === 0;
|
||||||
|
const isTopRight = row === 0 && col === cols - 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group key={cellId}>
|
||||||
|
<Rect
|
||||||
|
x={x}
|
||||||
|
y={y}
|
||||||
|
width={cellWidth}
|
||||||
|
height={cellHeight}
|
||||||
|
fill={cell.background}
|
||||||
|
stroke={isCellSelected ? "#3b82f6" : stroke || "#808080"}
|
||||||
|
strokeWidth={isCellSelected ? 3 : strokeWidth || 1}
|
||||||
|
cornerRadius={isTopLeft ? [8, 0, 0, 0] : isTopRight ? [0, 8, 0, 0] : 0}
|
||||||
|
onClick={(e) => handleCellClick(cellId, e)}
|
||||||
|
onDblClick={(e) => handleCellDblClick(cellId, e)}
|
||||||
|
/>
|
||||||
|
{cell.text && (
|
||||||
|
<Text
|
||||||
|
x={x}
|
||||||
|
y={y}
|
||||||
|
width={cellWidth}
|
||||||
|
height={cellHeight}
|
||||||
|
text={cell.text}
|
||||||
|
fontSize={14}
|
||||||
|
fontFamily="Arial"
|
||||||
|
fill="#000000"
|
||||||
|
align="center"
|
||||||
|
verticalAlign="middle"
|
||||||
|
onClick={(e) => handleCellClick(cellId, e)}
|
||||||
|
onDblClick={(e) => handleCellDblClick(cellId, e)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditorTable;
|
||||||
+95
-121
@@ -1,134 +1,108 @@
|
|||||||
import React from "react";
|
import DefaultTableSkeleton from "@/components/DefaultTableSkeleton";
|
||||||
import { Group, Rect, Text } from "react-konva";
|
import Td from "@/components/Td";
|
||||||
import Konva from "konva";
|
import type { ColumnType, RowDataType, TableProps } from "@/components/types/TableTypes";
|
||||||
import type { EditorObject } from "@/pages/editor/store/editorStore";
|
import { type ReactElement } from "react";
|
||||||
|
|
||||||
type TableProps = {
|
const Table = <T extends RowDataType>({
|
||||||
obj: EditorObject;
|
columns,
|
||||||
selectedCellId: string | null;
|
data,
|
||||||
onCellClick: (tableId: string, cellId: string, node: Konva.Node, e?: Konva.KonvaEventObject<MouseEvent>) => void;
|
isLoading = false,
|
||||||
onCellDblClick: (tableId: string, cellId: string, x: number, y: number, width: number, height: number, text: string) => void;
|
onRowClick,
|
||||||
onDragEnd: (tableId: string, x: number, y: number) => void;
|
className = "",
|
||||||
draggable: boolean;
|
rowClassName = "",
|
||||||
};
|
noDataMessage = "هیچ دادهای یافت نشد",
|
||||||
|
headerClassName = "bg-gray-50",
|
||||||
|
emptyRowsCount = 5,
|
||||||
|
showHeader = true,
|
||||||
|
actions = null,
|
||||||
|
actionsPosition = "top",
|
||||||
|
}: TableProps<T>): ReactElement => {
|
||||||
|
const getRowClassName = (item: T, index: number): string => {
|
||||||
|
if (typeof rowClassName === "function") {
|
||||||
|
return rowClassName(item, index);
|
||||||
|
}
|
||||||
|
return rowClassName;
|
||||||
|
};
|
||||||
|
|
||||||
const Table = ({
|
const getCellClassName = (column: ColumnType<T>): string => {
|
||||||
obj,
|
const alignClass = column.align ? `text-${column.align}` : "";
|
||||||
selectedCellId,
|
return `px-3 md:px-6 py-3 md:py-4 whitespace-nowrap text-xs ${alignClass} ${column.className || ""}`.trim();
|
||||||
onCellClick,
|
};
|
||||||
onCellDblClick,
|
|
||||||
onDragEnd,
|
|
||||||
draggable,
|
|
||||||
}: TableProps) => {
|
|
||||||
const groupRef = React.useRef<Konva.Group>(null);
|
|
||||||
const tableData = obj.tableData;
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
const handleRowClick = (item: T) => {
|
||||||
if (groupRef.current) {
|
if (onRowClick) {
|
||||||
groupRef.current.setAttr("id", obj.id);
|
onRowClick(item.id, item);
|
||||||
}
|
}
|
||||||
}, [obj.id]);
|
};
|
||||||
|
|
||||||
if (!tableData) return null;
|
const renderTableBody = () => {
|
||||||
|
if (isLoading) {
|
||||||
|
return <DefaultTableSkeleton tdCount={columns.length} trCount={emptyRowsCount} />;
|
||||||
|
}
|
||||||
|
|
||||||
const { rows, cols, cells, cellWidth, cellHeight, stroke, strokeWidth } = tableData;
|
if (!data || data.length === 0) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={columns.length} className="px-3 md:px-6 py-6 md:py-8 text-center text-gray-500">
|
||||||
|
{noDataMessage}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const handleCellClick = (cellId: string, e: Konva.KonvaEventObject<MouseEvent>) => {
|
return data.map((item, rowIndex) => (
|
||||||
e.cancelBubble = true;
|
<tr
|
||||||
if (groupRef.current) {
|
key={item.id}
|
||||||
onCellClick(obj.id, cellId, groupRef.current, e);
|
className={`w-full h-[64px] md:h-[74px] bg-white border-t border-[#EAEDF5] hover:bg-[#F4F5F9] ${onRowClick ? "cursor-pointer" : ""} ${getRowClassName(item, rowIndex)}`}
|
||||||
}
|
onClick={() => handleRowClick(item)}
|
||||||
};
|
>
|
||||||
|
{columns.map((col) => (
|
||||||
|
<td key={`${item.id}-${col.key}`} className={getCellClassName(col)} style={{ width: col.width }}>
|
||||||
|
{col.render ? col.render(item) : (item[col.key] as React.ReactNode)}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
const handleCellDblClick = (cellId: string, e: Konva.KonvaEventObject<MouseEvent>) => {
|
const renderHeader = () => {
|
||||||
e.cancelBubble = true;
|
if (!showHeader || actionsPosition === "header-replace") return null;
|
||||||
const stage = e.target.getStage();
|
|
||||||
if (!stage || !groupRef.current) return;
|
|
||||||
|
|
||||||
const cell = cells[cellId];
|
|
||||||
if (!cell) return;
|
|
||||||
|
|
||||||
// Get the target node (Rect or Text)
|
|
||||||
const targetNode = e.target;
|
|
||||||
|
|
||||||
// Get the client rect which includes all transformations (scale, position, etc.)
|
|
||||||
const box = targetNode.getClientRect();
|
|
||||||
const stageBox = stage.container().getBoundingClientRect();
|
|
||||||
|
|
||||||
// Calculate position relative to viewport
|
|
||||||
const x = stageBox.left + box.x;
|
|
||||||
const y = stageBox.top + box.y;
|
|
||||||
const width = box.width;
|
|
||||||
const height = box.height;
|
|
||||||
|
|
||||||
onCellDblClick(obj.id, cellId, x, y, width, height, cell.text);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDragEnd = () => {
|
|
||||||
if (groupRef.current) {
|
|
||||||
const pos = groupRef.current.position();
|
|
||||||
onDragEnd(obj.id, pos.x, pos.y);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group
|
<thead className={`h-[60px] md:h-[69px] bg-white text-sm text-[#8C90A3] ${headerClassName}`}>
|
||||||
ref={groupRef}
|
<tr>
|
||||||
x={obj.x}
|
{columns.map((col) => (
|
||||||
y={obj.y}
|
<Td key={col.key} text={col.title} />
|
||||||
draggable={draggable}
|
))}
|
||||||
onDragEnd={handleDragEnd}
|
</tr>
|
||||||
>
|
</thead>
|
||||||
{Array.from({ length: rows }).map((_, row) =>
|
|
||||||
Array.from({ length: cols }).map((_, col) => {
|
|
||||||
const cellId = `cell-${row}-${col}`;
|
|
||||||
const cell = cells[cellId];
|
|
||||||
if (!cell) return null;
|
|
||||||
|
|
||||||
const x = col * cellWidth;
|
|
||||||
const y = row * cellHeight;
|
|
||||||
const isCellSelected = selectedCellId === cellId;
|
|
||||||
|
|
||||||
const isTopLeft = row === 0 && col === 0;
|
|
||||||
const isTopRight = row === 0 && col === cols - 1;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Group key={cellId}>
|
|
||||||
<Rect
|
|
||||||
x={x}
|
|
||||||
y={y}
|
|
||||||
width={cellWidth}
|
|
||||||
height={cellHeight}
|
|
||||||
fill={cell.background}
|
|
||||||
stroke={isCellSelected ? "#3b82f6" : stroke || "#808080"}
|
|
||||||
strokeWidth={isCellSelected ? 3 : strokeWidth || 1}
|
|
||||||
cornerRadius={isTopLeft ? [8, 0, 0, 0] : isTopRight ? [0, 8, 0, 0] : 0}
|
|
||||||
onClick={(e) => handleCellClick(cellId, e)}
|
|
||||||
onDblClick={(e) => handleCellDblClick(cellId, e)}
|
|
||||||
/>
|
|
||||||
{cell.text && (
|
|
||||||
<Text
|
|
||||||
x={x}
|
|
||||||
y={y}
|
|
||||||
width={cellWidth}
|
|
||||||
height={cellHeight}
|
|
||||||
text={cell.text}
|
|
||||||
fontSize={14}
|
|
||||||
fontFamily="Arial"
|
|
||||||
fill="#000000"
|
|
||||||
align="center"
|
|
||||||
verticalAlign="middle"
|
|
||||||
onClick={(e) => handleCellClick(cellId, e)}
|
|
||||||
onDblClick={(e) => handleCellDblClick(cellId, e)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Group>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
</Group>
|
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderActions = () => {
|
||||||
|
if (!actions) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-[64px] rounded-t-2xl md:rounded-t-3xl md:h-[74px] bg-white flex items-center px-3 md:px-6">
|
||||||
|
<div className="flex items-center gap-2 md:gap-4">{actions}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`relative w-full ${className}`}>
|
||||||
|
{(actionsPosition === "top" || actionsPosition === "above-header") && renderActions()}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`overflow-x-auto overflow-hidden ${showHeader && actionsPosition !== "header-replace" ? "rounded-2xl md:rounded-3xl" : "rounded-b-2xl md:rounded-b-3xl"} bg-white`}
|
||||||
|
>
|
||||||
|
<table className="w-full text-sm border-collapse min-w-[600px]">
|
||||||
|
{renderHeader()}
|
||||||
|
<tbody>{renderTableBody()}</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Table;
|
export default Table;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { type FC, type ReactNode } from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
text?: string | number | ReactNode;
|
||||||
|
children?: ReactNode;
|
||||||
|
dir?: string;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Td: FC<Props> = ({ text, children, dir, className }) => {
|
||||||
|
return (
|
||||||
|
<td
|
||||||
|
className={`px-3 md:px-6 py-3 md:py-4 whitespace-nowrap text-xs ${className || ""}`}
|
||||||
|
style={{ direction: dir === "ltr" ? "ltr" : "rtl" }}
|
||||||
|
>
|
||||||
|
{text ?? children}
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Td;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
|
export type RowDataType = Record<string, unknown> & { id: string | number };
|
||||||
|
|
||||||
|
export type ColumnType<T extends RowDataType = RowDataType> = {
|
||||||
|
title: string;
|
||||||
|
key: string;
|
||||||
|
render?: (item: T) => ReactNode;
|
||||||
|
width?: string | number;
|
||||||
|
align?: "left" | "center" | "right";
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TableProps<T extends RowDataType = RowDataType> = {
|
||||||
|
columns: ColumnType<T>[];
|
||||||
|
data?: T[];
|
||||||
|
isLoading?: boolean;
|
||||||
|
onRowClick?: (id: T["id"], item: T) => void;
|
||||||
|
className?: string;
|
||||||
|
rowClassName?: string | ((item: T, index: number) => string);
|
||||||
|
noDataMessage?: ReactNode;
|
||||||
|
headerClassName?: string;
|
||||||
|
emptyRowsCount?: number;
|
||||||
|
showHeader?: boolean;
|
||||||
|
actions?: ReactNode;
|
||||||
|
actionsPosition?: "top" | "header-replace" | "above-header";
|
||||||
|
};
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import Button from "@/components/Button";
|
||||||
|
import Table from "@/components/Table";
|
||||||
|
import type { ColumnType } from "@/components/types/TableTypes";
|
||||||
|
import { Paths } from "@/config/Paths";
|
||||||
|
import { formatDateShort, formatPrice } from "@/helpers/func";
|
||||||
|
import { usePageTitle } from "@/hooks/usePageTitle";
|
||||||
|
import { getTotalPrice } from "@/pages/designer/designerRequestForm";
|
||||||
|
import { useGetDesignRequests } from "@/pages/designer/hooks/useDesignerData";
|
||||||
|
import type { RequestDesignItemType } from "@/pages/designer/types/Types";
|
||||||
|
import { type FC, useMemo } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
const getInvoiceUrl = (invoiceId: string) => `${import.meta.env.VITE_INVOICE_URL}${invoiceId}`;
|
||||||
|
|
||||||
|
const RequestDesignList: FC = () => {
|
||||||
|
usePageTitle("لیست درخواستهای طراحی");
|
||||||
|
const { data, isPending } = useGetDesignRequests();
|
||||||
|
const requests = data?.data ?? [];
|
||||||
|
|
||||||
|
const columns = useMemo<ColumnType<RequestDesignItemType>[]>(
|
||||||
|
() => [
|
||||||
|
{ title: "عنوان", key: "title", className: "font-medium text-black" },
|
||||||
|
{ title: "تاریخ ثبت", key: "createdAt", render: (item) => formatDateShort(item.createdAt) },
|
||||||
|
{ title: "زمان تحویل", key: "expectedDate", render: (item) => formatDateShort(item.expectedDate) },
|
||||||
|
{ title: "تعداد صفحات", key: "count", render: (item) => item.count.toLocaleString("fa-IR") },
|
||||||
|
{
|
||||||
|
title: "مبلغ",
|
||||||
|
key: "totalPrice",
|
||||||
|
render: (item) => `${formatPrice(getTotalPrice(item.count))} تومان`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "وضعیت",
|
||||||
|
key: "paidAt",
|
||||||
|
render: (item) => {
|
||||||
|
const isPaid = Boolean(item.paidAt);
|
||||||
|
return (
|
||||||
|
<span className={`inline-flex rounded-full px-3 py-1 text-xs ${isPaid ? "bg-[#E8F5E9] text-[#2E7D32]" : "bg-[#FFF3E0] text-[#E65100]"}`}>{isPaid ? "پرداخت شده" : "در انتظار پرداخت"}</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "عملیات",
|
||||||
|
key: "actions",
|
||||||
|
render: (item) => {
|
||||||
|
if (item.paidAt) {
|
||||||
|
return <span className="text-[#999999]">—</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
className="w-fit px-4 py-1.5 text-xs"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
window.open(getInvoiceUrl(item.invoiceId), "_blank");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
پرداخت
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-4 w-full">
|
||||||
|
<div className="flex flex-wrap items-center justify-between gap-4">
|
||||||
|
<h1> درخواستهای طراحی</h1>
|
||||||
|
<Link to={Paths.designer.request}>
|
||||||
|
<Button className="w-fit px-6">درخواست جدید</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
data={requests}
|
||||||
|
isLoading={isPending}
|
||||||
|
className="mt-6"
|
||||||
|
noDataMessage={
|
||||||
|
<div className="flex flex-col items-center gap-5 py-2">
|
||||||
|
<span>هنوز درخواست طراحی ثبت نشده است.</span>
|
||||||
|
<Link to={Paths.designer.request}>
|
||||||
|
<Button className="w-fit px-6">ثبت درخواست طراحی</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RequestDesignList;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import * as api from "../service/DesignerService";
|
import * as api from "../service/DesignerService";
|
||||||
|
|
||||||
export const useInitiateDesignRequest = () => {
|
export const useInitiateDesignRequest = () => {
|
||||||
@@ -6,3 +6,10 @@ export const useInitiateDesignRequest = () => {
|
|||||||
mutationFn: api.initiateDesignRequest,
|
mutationFn: api.initiateDesignRequest,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetDesignRequests = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["design-requests"],
|
||||||
|
queryFn: api.getDesignRequests,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import axios from "@/config/axios";
|
import axios from "@/config/axios";
|
||||||
import type {
|
import type { DesignRequestInitiateParams, DesignRequestsResponse, RequestDesignResponse } from "../types/Types";
|
||||||
DesignRequestInitiateParams,
|
|
||||||
RequestDesignResponse,
|
|
||||||
} from "../types/Types";
|
|
||||||
|
|
||||||
export const initiateDesignRequest = async (
|
export const initiateDesignRequest = async (params: DesignRequestInitiateParams): Promise<RequestDesignResponse> => {
|
||||||
params: DesignRequestInitiateParams,
|
const { data } = await axios.post("/admin/design-request/initiate", params);
|
||||||
): Promise<RequestDesignResponse> => {
|
return data;
|
||||||
const { data } = await axios.post(
|
};
|
||||||
"/admin/design-request/initiate",
|
|
||||||
params,
|
export const getDesignRequests = async (): Promise<DesignRequestsResponse> => {
|
||||||
);
|
const { data } = await axios.get("/admin/design-request");
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -47,3 +47,20 @@ export type DesignRequestType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type RequestDesignResponse = BaseResponse<DesignRequestType>;
|
export type RequestDesignResponse = BaseResponse<DesignRequestType>;
|
||||||
|
|
||||||
|
export type RequestDesignItemType = {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
deletedAt: string | null;
|
||||||
|
title: string;
|
||||||
|
count: number;
|
||||||
|
desc: string;
|
||||||
|
expectedDate: string;
|
||||||
|
attachments: string[];
|
||||||
|
invoiceId: string;
|
||||||
|
paidAt: string | null;
|
||||||
|
business: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DesignRequestsResponse = BaseResponse<RequestDesignItemType[]>;
|
||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
VideoShape,
|
VideoShape,
|
||||||
AudioShape,
|
AudioShape,
|
||||||
} from "../tools";
|
} from "../tools";
|
||||||
import Table from "@/components/Table";
|
import EditorTable from "@/components/EditorTable";
|
||||||
|
|
||||||
type ObjectRendererProps = {
|
type ObjectRendererProps = {
|
||||||
obj: EditorObject;
|
obj: EditorObject;
|
||||||
@@ -234,7 +234,7 @@ const ObjectRenderer = ({
|
|||||||
break;
|
break;
|
||||||
case "grid":
|
case "grid":
|
||||||
shapeElement = (
|
shapeElement = (
|
||||||
<Table
|
<EditorTable
|
||||||
key={obj.id}
|
key={obj.id}
|
||||||
obj={obj}
|
obj={obj}
|
||||||
selectedCellId={selectedCellId || null}
|
selectedCellId={selectedCellId || null}
|
||||||
|
|||||||
+15
-30
@@ -2,6 +2,7 @@ import { isBusinessCatalogPath, isViewerPath, Paths } from "@/config/Paths";
|
|||||||
import { clx } from "@/helpers/utils";
|
import { clx } from "@/helpers/utils";
|
||||||
import Business from "@/pages/business/Business";
|
import Business from "@/pages/business/Business";
|
||||||
import CatalogueList from "@/pages/catalogue/List";
|
import CatalogueList from "@/pages/catalogue/List";
|
||||||
|
import RequestDesignList from "@/pages/designer/List";
|
||||||
import DesignerRequest from "@/pages/designer/Request";
|
import DesignerRequest from "@/pages/designer/Request";
|
||||||
import Editor from "@/pages/editor/Editor";
|
import Editor from "@/pages/editor/Editor";
|
||||||
import Home from "@/pages/home/Home";
|
import Home from "@/pages/home/Home";
|
||||||
@@ -19,46 +20,25 @@ const MainRouter = () => {
|
|||||||
const isBusinessCatalogRoute = isBusinessCatalogPath(location.pathname);
|
const isBusinessCatalogRoute = isBusinessCatalogPath(location.pathname);
|
||||||
const hideAppChrome = isViewerRoute || isBusinessCatalogRoute;
|
const hideAppChrome = isViewerRoute || isBusinessCatalogRoute;
|
||||||
const hiddenSideBarRoutes = [Paths.editor];
|
const hiddenSideBarRoutes = [Paths.editor];
|
||||||
const shouldRenderSideBar =
|
const shouldRenderSideBar = !hiddenSideBarRoutes.includes(location.pathname) && !hideAppChrome;
|
||||||
!hiddenSideBarRoutes.includes(location.pathname) && !hideAppChrome;
|
|
||||||
const routeHasLocalSidebar = location.pathname.includes("editor");
|
const routeHasLocalSidebar = location.pathname.includes("editor");
|
||||||
const hasSidebarSpace = shouldRenderSideBar || routeHasLocalSidebar;
|
const hasSidebarSpace = shouldRenderSideBar || routeHasLocalSidebar;
|
||||||
const headerSidebarSize = routeHasLocalSidebar ? "wide" : "default";
|
const headerSidebarSize = routeHasLocalSidebar ? "wide" : "default";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={clx("flex min-h-full flex-col overflow-hidden", isViewerRoute ? "fixed inset-0 z-50 bg-neutral-100" : "p-4")}>
|
||||||
className={clx(
|
|
||||||
"flex min-h-full flex-col overflow-hidden",
|
|
||||||
isViewerRoute ? "fixed inset-0 z-50 bg-neutral-100" : "p-4",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{!hideAppChrome && shouldRenderSideBar ? <SideBar /> : null}
|
{!hideAppChrome && shouldRenderSideBar ? <SideBar /> : null}
|
||||||
{!hideAppChrome ? (
|
{!hideAppChrome ? <Header hasMainSidebar={hasSidebarSpace} sidebarSize={headerSidebarSize} /> : null}
|
||||||
<Header
|
|
||||||
hasMainSidebar={hasSidebarSpace}
|
|
||||||
sidebarSize={headerSidebarSize}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<div
|
<div
|
||||||
className={clx(
|
className={clx(
|
||||||
"flex flex-1 flex-col min-h-0",
|
"flex flex-1 flex-col min-h-0",
|
||||||
!hideAppChrome && "mt-[68px] xl:mt-[81px]",
|
!hideAppChrome && "mt-[68px] xl:mt-[81px]",
|
||||||
!hideAppChrome && shouldRenderSideBar && "xl:ms-[269px]",
|
!hideAppChrome && shouldRenderSideBar && "xl:ms-[269px]",
|
||||||
!hideAppChrome &&
|
!hideAppChrome && shouldRenderSideBar && hasSubMenu && "xl:ms-[305px]",
|
||||||
shouldRenderSideBar &&
|
|
||||||
hasSubMenu &&
|
|
||||||
"xl:ms-[305px]",
|
|
||||||
!hideAppChrome && routeHasLocalSidebar && "xl:mr-[374px]",
|
!hideAppChrome && routeHasLocalSidebar && "xl:mr-[374px]",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div className={clx("flex-1 flex flex-col w-full min-h-0", isViewerRoute ? "h-full overflow-hidden" : "overflow-auto max-h-[calc(100vh-113px)]")}>
|
||||||
className={clx(
|
|
||||||
"flex-1 flex flex-col w-full min-h-0",
|
|
||||||
isViewerRoute
|
|
||||||
? "h-full overflow-hidden"
|
|
||||||
: "overflow-auto max-h-[calc(100vh-113px)]",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="flex-1 h-full flex min-h-0">
|
<div className="flex-1 h-full flex min-h-0">
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
@@ -94,10 +74,7 @@ const MainRouter = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route path={Paths.catalog.business + ":slug"} element={<Business />} />
|
||||||
path={Paths.catalog.business + ":slug"}
|
|
||||||
element={<Business />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={Paths.catalog.list}
|
path={Paths.catalog.list}
|
||||||
@@ -115,6 +92,14 @@ const MainRouter = () => {
|
|||||||
</PrivateRoute>
|
</PrivateRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path={Paths.designer.list}
|
||||||
|
element={
|
||||||
|
<PrivateRoute>
|
||||||
|
<RequestDesignList />
|
||||||
|
</PrivateRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -68,11 +68,11 @@ const SideBar: FC = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<SideBarItem
|
<SideBarItem
|
||||||
icon={<Brush2 variant={isActive(Paths.designer.request) ? "Bold" : "Outline"} color={isActive(Paths.designer.request) ? "black" : "#8C90A3"} size={iconSizeSideBar} />}
|
icon={<Brush2 variant={isActive(Paths.designer.list) ? "Bold" : "Outline"} color={isActive(Paths.designer.list) ? "black" : "#8C90A3"} size={iconSizeSideBar} />}
|
||||||
title={t("sidebar.request_design")}
|
title={t("sidebar.request_design")}
|
||||||
isActive={isActive(Paths.designer.request)}
|
isActive={isActive(Paths.designer.list)}
|
||||||
link={Paths.designer.request}
|
link={Paths.designer.list}
|
||||||
activeName={Paths.designer.request}
|
activeName={Paths.designer.list}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user