Compare commits
11 Commits
7775333603
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa91bdee65 | ||
|
|
12bda4ab8f | ||
|
|
ec73e51a2d | ||
|
|
705af6bfa8 | ||
|
|
0e71c0e8b2 | ||
|
|
c229f2dbc4 | ||
|
|
f1fe46b11a | ||
|
|
5f0ec367e0 | ||
|
|
2237aecdf2 | ||
|
|
8dccf8e554 | ||
|
|
027a8e5493 |
@@ -16,26 +16,32 @@
|
||||
"@dnd-kit/modifiers": "^9.0.0",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"@mantine/code-highlight": "^7.15.2",
|
||||
"@mantine/core": "^7.15.2",
|
||||
"@mantine/hooks": "^7.15.2",
|
||||
"@material-tailwind/react": "^2.1.10",
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"@supabase/supabase-js": "^2.38.4",
|
||||
"ai": "^4.0.22",
|
||||
"antd": "^5.11.0",
|
||||
"apexcharts": "^4.3.0",
|
||||
"code-highlight": "^1.0.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"dnd-kit": "^0.0.2",
|
||||
"heroicons": "^2.2.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jspdf": "^2.5.2",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^18.2.0",
|
||||
"react-apexcharts": "^1.7.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^5.4.0",
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"react-router-dom": "^6.18.0",
|
||||
"react-use": "^17.6.0",
|
||||
"recharts": "^2.9.0",
|
||||
"recharts": "^2.15.0",
|
||||
"remixicon": "^4.6.0",
|
||||
"styled-components": "^6.1.0",
|
||||
"uuid": "^11.0.3",
|
||||
"zod": "^3.24.1"
|
||||
|
||||
111
src/components/Barchart/index.jsx
Normal file
111
src/components/Barchart/index.jsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
CardHeader,
|
||||
Typography,
|
||||
} from "@material-tailwind/react";
|
||||
import Chart from "react-apexcharts";
|
||||
const chartConfig = {
|
||||
type: "bar",
|
||||
height: 240,
|
||||
series: [
|
||||
{
|
||||
name: "Sales",
|
||||
data: [50, 40, 300, 320, 500, 350, 200, 230, 500],
|
||||
},
|
||||
],
|
||||
options: {
|
||||
chart: {
|
||||
toolbar: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
title: {
|
||||
show: "",
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
},
|
||||
colors: ["#020617"],
|
||||
plotOptions: {
|
||||
bar: {
|
||||
columnWidth: "40%",
|
||||
borderRadius: 2,
|
||||
},
|
||||
},
|
||||
xaxis: {
|
||||
axisTicks: {
|
||||
show: false,
|
||||
},
|
||||
axisBorder: {
|
||||
show: false,
|
||||
},
|
||||
labels: {
|
||||
style: {
|
||||
colors: "#616161",
|
||||
fontSize: "12px",
|
||||
fontFamily: "inherit",
|
||||
fontWeight: 400,
|
||||
},
|
||||
},
|
||||
categories: [
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec",
|
||||
],
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
style: {
|
||||
colors: "#616161",
|
||||
fontSize: "12px",
|
||||
fontFamily: "inherit",
|
||||
fontWeight: 400,
|
||||
},
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: "#dddddd",
|
||||
strokeDashArray: 5,
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
top: 5,
|
||||
right: 20,
|
||||
},
|
||||
},
|
||||
fill: {
|
||||
opacity: 0.8,
|
||||
},
|
||||
tooltip: {
|
||||
theme: "dark",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default function Example(props) {
|
||||
return (
|
||||
<Card {...props}>
|
||||
<CardHeader
|
||||
floated={false}
|
||||
shadow={false}
|
||||
color="transparent"
|
||||
className="flex flex-col gap-4 rounded-none md:flex-row md:items-center"
|
||||
>
|
||||
</CardHeader>
|
||||
<CardBody className="px-2 pb-0">
|
||||
<Chart {...chartConfig} />
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
import { Button, Drawer, Input, Space, message } from 'antd';
|
||||
import { useChat } from "ai/react";
|
||||
import { CodeHighlight } from "@mantine/code-highlight";
|
||||
import { DownloadOutlined, EditOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons';
|
||||
import { useRef, useEffect, useState } from 'react';
|
||||
import { useSessionStorage } from 'react-use';
|
||||
import Editor from "@monaco-editor/react";
|
||||
|
||||
export default function ChatAIDrawer({ open, onClose, onExport }) {
|
||||
const STORAGE_KEY = 'chat_history';
|
||||
const [storedMessages, setStoredMessages] = useSessionStorage(STORAGE_KEY, []);
|
||||
|
||||
const { messages, input, handleSubmit, handleInputChange, isLoading, setMessages } = useChat({
|
||||
api: "https://test-ai-quirkyai.vercel.app/api/chat",
|
||||
initialMessages: storedMessages.length>0? JSON.parse(storedMessages):[],
|
||||
});
|
||||
|
||||
const messagesEndRef = useRef(null);
|
||||
useEffect(() => {
|
||||
setStoredMessages(JSON.stringify(messages));
|
||||
}, [messages, setStoredMessages]);
|
||||
|
||||
// 新消息时自动滚动到底部
|
||||
useEffect(() => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
}, [messages]);
|
||||
|
||||
// 修改导出函数,接收单条消息内容
|
||||
const handleExport = (content) => {
|
||||
try {
|
||||
const jsonContent = JSON.parse(content);
|
||||
onExport?.(jsonContent);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
message.error('导出失败,请重试');
|
||||
}
|
||||
};
|
||||
|
||||
const [editingMessageId, setEditingMessageId] = useState(null);
|
||||
const [editingContent, setEditingContent] = useState('');
|
||||
|
||||
// 优化编辑处理函数
|
||||
const handleEdit = (message) => {
|
||||
if(isLoading) return;
|
||||
setEditingContent(message.content);
|
||||
setEditingMessageId(message.id);
|
||||
};
|
||||
|
||||
const handleSaveEdit = () => {
|
||||
try {
|
||||
JSON.parse(editingContent);
|
||||
setMessages(messages.map(msg =>
|
||||
msg.id === editingMessageId
|
||||
? { ...msg, content: editingContent }
|
||||
: msg
|
||||
));
|
||||
setEditingMessageId(null);
|
||||
message.success('编辑成功');
|
||||
} catch (error) {
|
||||
message.error('请输入有效的 JSON 格式');
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancelEdit = () => {
|
||||
setEditingMessageId(null);
|
||||
setEditingContent('');
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
title={
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-lg font-medium text-gray-800">AI 助手</span>
|
||||
<Button
|
||||
size="small"
|
||||
className="hover:bg-gray-100"
|
||||
onClick={() => {
|
||||
setMessages([]);
|
||||
setStoredMessages('[]');
|
||||
message.success('历史记录已清空');
|
||||
}}
|
||||
>
|
||||
清空历史
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
placement="right"
|
||||
width={800}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className="flex flex-col h-[calc(100vh-108px)]">
|
||||
<div className="flex-1 overflow-y-auto px-4 space-y-6">
|
||||
{messages.map((message) => (
|
||||
<div
|
||||
key={message.id}
|
||||
className={`rounded-lg p-4 transition-all ${
|
||||
message.role === 'assistant'
|
||||
? 'bg-blue-50 hover:bg-blue-100'
|
||||
: 'bg-gray-50 hover:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<span className={`font-medium ${
|
||||
message.role === 'assistant' ? 'text-blue-600' : 'text-gray-600'
|
||||
}`}>
|
||||
{message.role === 'assistant' ? 'AI 助手' : '用户'}
|
||||
</span>
|
||||
{message.role === 'assistant' && (
|
||||
<Space>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<EditOutlined />}
|
||||
disabled={isLoading}
|
||||
onClick={() => handleEdit(message)}
|
||||
className="text-gray-500 hover:text-blue-600"
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<DownloadOutlined />}
|
||||
onClick={() => handleExport(message.content)}
|
||||
className="text-gray-500 hover:text-blue-600"
|
||||
>
|
||||
导出
|
||||
</Button>
|
||||
</Space>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{message.role === "assistant" ? (
|
||||
<div className="relative">
|
||||
{editingMessageId === message.id ? (
|
||||
<div className="rounded-lg border border-blue-200">
|
||||
<Editor
|
||||
height="300px"
|
||||
defaultLanguage="json"
|
||||
value={editingContent}
|
||||
theme="vs-light"
|
||||
options={{
|
||||
minimap: { enabled: false },
|
||||
scrollBeyondLastLine: false,
|
||||
fontSize: 14,
|
||||
lineNumbers: 'on',
|
||||
renderLineHighlight: 'none',
|
||||
roundedSelection: true,
|
||||
}}
|
||||
onChange={setEditingContent}
|
||||
onMount={(editor) => {
|
||||
editor.getModel()?.updateOptions({ tabSize: 2 });
|
||||
editor.focus();
|
||||
}}
|
||||
/>
|
||||
<div className="flex justify-end gap-2 p-2 bg-gray-50 border-t">
|
||||
<Button
|
||||
size="small"
|
||||
icon={<CloseOutlined />}
|
||||
onClick={handleCancelEdit}
|
||||
className="hover:bg-gray-200"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
icon={<CheckOutlined />}
|
||||
onClick={handleSaveEdit}
|
||||
className="bg-blue-600 hover:bg-blue-700"
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<CodeHighlight
|
||||
code={message.content}
|
||||
language="json"
|
||||
copyLabel="复制代码"
|
||||
copiedLabel="已复制!"
|
||||
withLineNumbers
|
||||
className="rounded-lg"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-gray-700 whitespace-pre-wrap break-words">
|
||||
{message.content}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
<div className="border-t bg-white p-4">
|
||||
<form onSubmit={handleSubmit} className="flex gap-2">
|
||||
<Input
|
||||
value={input}
|
||||
placeholder="请输入您的问题..."
|
||||
onChange={handleInputChange}
|
||||
disabled={isLoading}
|
||||
className="flex-1 rounded-lg border-gray-300 hover:border-blue-400 focus:border-blue-600 focus:shadow-blue-100"
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
loading={isLoading}
|
||||
className="rounded-lg bg-blue-600 hover:bg-blue-700"
|
||||
>
|
||||
发送
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
@@ -14,8 +14,24 @@ const Sidebar = ({ collapsed }) => {
|
||||
const { isDarkMode } = useTheme();
|
||||
const { user } = useAuth();
|
||||
|
||||
const getSelectedKeys = useMemo(() => {
|
||||
const pathname = location.pathname.substring(1);
|
||||
|
||||
const pathParts = pathname.split('/');
|
||||
let key = '';
|
||||
|
||||
if (pathParts.length >= 2) {
|
||||
const baseKey = `${pathParts[0]}/${pathParts[1]}`;
|
||||
key = pathname.includes('/') ? baseKey : pathname;
|
||||
} else {
|
||||
key = pathname;
|
||||
}
|
||||
|
||||
return [key];
|
||||
}, [location.pathname]);
|
||||
|
||||
const menuClient = useMemo(() => {
|
||||
if (!user?.id||user.menukeys?.length===0) return [];
|
||||
if (!user?.id || user.menukeys?.length === 0) return [];
|
||||
return getMenuItems(user?.menukeys || []);
|
||||
}, [user]);
|
||||
|
||||
@@ -44,7 +60,7 @@ const Sidebar = ({ collapsed }) => {
|
||||
theme={isDarkMode ? "dark" : "light"}
|
||||
width={256}
|
||||
collapsedWidth={80}
|
||||
className={`app-sidebar ${collapsed ? "collapsed" : ""}`}
|
||||
className={`app-sidebar ${collapsed ? "collapsed" : ""} overflow-auto`}
|
||||
>
|
||||
<Logo collapsed={collapsed} isDarkMode={isDarkMode} />
|
||||
<Menu
|
||||
@@ -54,7 +70,6 @@ const Sidebar = ({ collapsed }) => {
|
||||
defaultOpenKeys={defaultOpenKeys}
|
||||
items={menuClient}
|
||||
onClick={handleMenuClick}
|
||||
style={{ overflow: 'auto' }}
|
||||
/>
|
||||
</Sider>
|
||||
);
|
||||
|
||||
49
src/components/Piechart/index.jsx
Normal file
49
src/components/Piechart/index.jsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
CardHeader,
|
||||
Typography,
|
||||
} from "@material-tailwind/react";
|
||||
import Chart from "react-apexcharts";
|
||||
|
||||
export default function index(props) {
|
||||
const chartConfig = {
|
||||
type: "pie",
|
||||
width: 280,
|
||||
height: 280,
|
||||
series: [44, 55, 13, 43, 22],
|
||||
options: {
|
||||
chart: {
|
||||
toolbar: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
title: {
|
||||
show: "",
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
},
|
||||
colors: ["#020617", "#ff8f00", "#00897b", "#1e88e5", "#d81b60"],
|
||||
legend: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
return (
|
||||
|
||||
<Card {...props}>
|
||||
<CardHeader
|
||||
floated={false}
|
||||
shadow={false}
|
||||
color="transparent"
|
||||
className="flex flex-col gap-4 rounded-none md:flex-row md:items-center"
|
||||
>
|
||||
</CardHeader>
|
||||
<CardBody className="px-2 pb-0">
|
||||
<Chart {...chartConfig} />
|
||||
</CardBody>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -187,50 +187,44 @@ const SectionList = ({
|
||||
{availableSections.map((section) => (
|
||||
<div
|
||||
key={section.id}
|
||||
className="group relative bg-white rounded-lg shadow-sm border border-gray-200 hover:shadow-lg transition-all duration-300 cursor-pointer"
|
||||
className="group relative bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 hover:shadow-lg transition-all duration-300 cursor-pointer"
|
||||
onClick={() => handleUseTemplate(section, add)}
|
||||
>
|
||||
<div className="p-6">
|
||||
<div className="text-center mb-4">
|
||||
<h3 className="text-lg font-medium group-hover:text-blue-500 transition-colors">
|
||||
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 group-hover:text-blue-500 transition-colors">
|
||||
{section.attributes.name}
|
||||
</h3>
|
||||
<div className="text-sm text-gray-500 mt-1">
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
||||
{section.attributes.items?.length || 0} 个项目
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 mt-4 border-t pt-4">
|
||||
{(section.attributes.items || [])
|
||||
.slice(0, 3)
|
||||
.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex justify-between items-center"
|
||||
>
|
||||
<span className="text-sm text-gray-600 truncate flex-1">
|
||||
<div className="space-y-2 mt-4 border-t dark:border-gray-700 pt-4">
|
||||
{(section.attributes.items || []).slice(0, 3).map((item, index) => (
|
||||
<div key={index} className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600 dark:text-gray-300 truncate flex-1">
|
||||
{item.name}
|
||||
</span>
|
||||
<span className="text-sm text-gray-500 ml-2">
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400 ml-2">
|
||||
{formatExchangeRate(currentCurrency, item.price)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{(section.attributes.items || []).length > 3 && (
|
||||
<div className="text-sm text-gray-500 text-center">
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 text-center">
|
||||
还有 {section.attributes.items.length - 3} 个项目...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-4 pt-4 border-t flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">总金额</span>
|
||||
<span className="text-base font-medium text-blue-500">
|
||||
<div className="mt-4 pt-4 border-t dark:border-gray-700 flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600 dark:text-gray-300">总金额</span>
|
||||
<span className="text-base font-medium text-blue-500 dark:text-blue-400">
|
||||
{formatExchangeRate(
|
||||
currentCurrency,
|
||||
(section.attributes.items || []).reduce(
|
||||
(sum, item) =>
|
||||
sum + (item.price * (item.quantity || 1) || 0),
|
||||
(sum, item) => sum + (item.price * (item.quantity || 1) || 0),
|
||||
0
|
||||
)
|
||||
)}
|
||||
@@ -241,12 +235,12 @@ const SectionList = ({
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center">
|
||||
<div className="flex justify-center mt-6">
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => handleCreateCustom(add, fieldsLength)}
|
||||
className="bg-blue-600 hover:bg-blue-700 border-0 shadow-md hover:shadow-lg transition-all duration-200 h-10 px-6 rounded-lg flex items-center gap-2"
|
||||
className="bg-blue-600 dark:bg-blue-500 hover:bg-blue-700 dark:hover:bg-blue-600 border-0 shadow-md hover:shadow-lg transition-all duration-200 h-10 px-6 rounded-lg flex items-center gap-2"
|
||||
>
|
||||
<span className="font-medium">自定义模块</span>
|
||||
</Button>
|
||||
@@ -256,7 +250,7 @@ const SectionList = ({
|
||||
<div className="flex flex-col items-center justify-center py-16 px-4">
|
||||
<div className="w-48 h-48 mb-8">
|
||||
<svg
|
||||
className="w-full h-full text-gray-200"
|
||||
className="w-full h-full text-gray-200 dark:text-gray-700"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -270,10 +264,10 @@ const SectionList = ({
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-medium text-gray-900 mb-2">
|
||||
<h3 className="text-xl font-medium text-gray-900 dark:text-gray-100 mb-2">
|
||||
暂无可用模板
|
||||
</h3>
|
||||
<p className="text-gray-500 text-center max-w-sm mb-8">
|
||||
<p className="text-gray-500 dark:text-gray-400 text-center max-w-sm mb-8">
|
||||
您可以选择创建一个自定义模块开始使用
|
||||
</p>
|
||||
<Button
|
||||
@@ -281,7 +275,7 @@ const SectionList = ({
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => handleCreateCustom(add, fieldsLength)}
|
||||
size="large"
|
||||
className="shadow-md hover:shadow-lg transition-shadow"
|
||||
className="shadow-md hover:shadow-lg transition-shadow dark:bg-blue-500 dark:hover:bg-blue-600"
|
||||
>
|
||||
创建自定义模块
|
||||
</Button>
|
||||
|
||||
@@ -199,7 +199,7 @@ const SectionList = ({ form, isView, formValues, type }) => {
|
||||
{availableSections.map((section) => (
|
||||
<div
|
||||
key={section.id}
|
||||
className="group relative bg-white rounded-lg shadow-sm border border-gray-200 hover:shadow-lg transition-all duration-300 cursor-pointer"
|
||||
className="group relative bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 hover:shadow-lg transition-all duration-300 cursor-pointer"
|
||||
onClick={() => handleUseTemplate(section, add)}
|
||||
>
|
||||
<div className="p-6">
|
||||
@@ -241,7 +241,7 @@ const SectionList = ({ form, isView, formValues, type }) => {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center">
|
||||
<div className="flex justify-center mt-6">
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
|
||||
@@ -3,20 +3,33 @@ import { Navigate, useLocation } from "react-router-dom";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
|
||||
const PUBLIC_PATHS = ['login', '404','home'];
|
||||
|
||||
export const ProtectedRoute = ({ children }) => {
|
||||
const { user } = useAuth();
|
||||
const location = useLocation();
|
||||
const currentPath = location.pathname.replace(/^\//, '');
|
||||
|
||||
// 如果是公共路径,直接显示
|
||||
if (PUBLIC_PATHS.includes(currentPath) || currentPath === '*') {
|
||||
return children;
|
||||
}
|
||||
|
||||
// 如果用户未登录,重定向到登录页面,并携带当前路径
|
||||
if (!user?.id) {
|
||||
return <Navigate
|
||||
to={`/login?redirectTo=${encodeURIComponent(location.pathname + location.search)}`}
|
||||
replace
|
||||
/>;
|
||||
}
|
||||
|
||||
// 如果用户已登录,检查权限
|
||||
if (user?.id) {
|
||||
const hasPermission = user.menukeys?.some(key => {
|
||||
return currentPath === key || currentPath.startsWith(`${key}/`);
|
||||
});
|
||||
|
||||
if (!hasPermission) {
|
||||
return <Navigate to="/home" replace />;
|
||||
return <Navigate to="/dashboard" replace />;
|
||||
}
|
||||
|
||||
return children;
|
||||
|
||||
@@ -8,16 +8,17 @@ const StatCard = ({ icon, title, count, amount, color = '#1677ff' }) => {
|
||||
<Card
|
||||
styles={{
|
||||
body: {
|
||||
padding: '20px',
|
||||
padding: '0',
|
||||
height: '100%',
|
||||
background: 'var(--color-bg-container)',
|
||||
}
|
||||
}}
|
||||
className="h-full hover:shadow-md transition-shadow duration-300"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="p-3 sm:p-5">
|
||||
<div className="flex items-start gap-2 sm:gap-4">
|
||||
<div
|
||||
className="w-12 h-12 rounded-full flex items-center justify-center text-lg"
|
||||
className="w-10 h-10 sm:w-12 sm:h-12 rounded-full flex items-center justify-center text-base sm:text-lg"
|
||||
style={{
|
||||
backgroundColor: `${color}15`,
|
||||
}}
|
||||
@@ -25,17 +26,18 @@ const StatCard = ({ icon, title, count, amount, color = '#1677ff' }) => {
|
||||
<span style={{ color }}>{icon}</span>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-base text-gray-500 dark:text-gray-400 m-0">
|
||||
<h3 className="text-sm sm:text-base text-gray-500 dark:text-gray-400 m-0">
|
||||
{title}
|
||||
</h3>
|
||||
<div className="text-sm text-gray-400 dark:text-gray-500 mt-1">
|
||||
<div className="text-xs sm:text-sm text-gray-400 dark:text-gray-500 mt-0.5 sm:mt-1">
|
||||
{count} invoices
|
||||
</div>
|
||||
<div className="text-xl font-semibold mt-2 dark:text-white">
|
||||
<div className="text-lg sm:text-xl font-semibold mt-1 sm:mt-2 dark:text-white">
|
||||
{formatCurrency(amount)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -42,7 +42,7 @@ const stats = [
|
||||
|
||||
const StatisticsOverview = () => {
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-5 gap-4">
|
||||
{stats.map((stat, index) => (
|
||||
<StatCard key={index} {...stat} />
|
||||
))}
|
||||
|
||||
@@ -29,8 +29,6 @@ export default function DifyChatDrawer({ open, onClose, onExport }) {
|
||||
|
||||
useEffect(() => {
|
||||
if (storedMessages && storedMessages.length > 0) {
|
||||
console.log(storedMessages,'storedMessages');
|
||||
|
||||
setMessages(storedMessages);
|
||||
}
|
||||
}, []);
|
||||
@@ -106,10 +104,11 @@ export default function DifyChatDrawer({ open, onClose, onExport }) {
|
||||
width={800}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
className="rounded-l-xl"
|
||||
>
|
||||
<div className="flex flex-col h-[calc(100vh-108px)]">
|
||||
<div className="flex-1 overflow-y-auto px-4 space-y-6">
|
||||
{messages.map((message) => (
|
||||
{messages.length > 0 && messages.map((message) => (
|
||||
<div
|
||||
key={message.id}
|
||||
className={`rounded-lg p-4 transition-all ${
|
||||
|
||||
@@ -8,14 +8,13 @@ import { v4 as uuidv4 } from "uuid";
|
||||
const AuthContext = createContext({});
|
||||
|
||||
export const AuthProvider = ({ children }) => {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [user, setUser] = useState({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
//处理google登录
|
||||
const hash = window.location.hash.substring(1);
|
||||
const hashParams = new URLSearchParams(hash);
|
||||
const accessToken = hashParams.get("access_token");
|
||||
@@ -62,12 +61,7 @@ export const AuthProvider = ({ children }) => {
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
// useEffect(() => {
|
||||
// const redirectTo = searchParams.get("redirectTo");
|
||||
// if (redirectTo) {
|
||||
// navigate(redirectTo);
|
||||
// }
|
||||
// }, [location.pathname]);
|
||||
|
||||
|
||||
//检查时候在管理模块团队中,没有就自动加入
|
||||
const checkInTeam = async (user) => {
|
||||
@@ -121,7 +115,20 @@ export const AuthProvider = ({ children }) => {
|
||||
message.error(error.message || "登录失败,请稍后重试");
|
||||
return;
|
||||
}
|
||||
setUser(data.user);
|
||||
|
||||
const role = await checkInTeam(data.user);
|
||||
const menukey = await fetchMenuList(role);
|
||||
setUser({ ...data.user, adminRole: role, menukeys: menukey });
|
||||
|
||||
// 获取重定向路径
|
||||
const redirectTo = searchParams.get("redirectTo");
|
||||
if (redirectTo) {
|
||||
// 如果有重定向路径,则导航到该路径
|
||||
navigate(decodeURIComponent(redirectTo), { replace: true });
|
||||
} else {
|
||||
navigate("/dashboard", { replace: true });
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
message.error(error.message || "登录失败,请稍后重试");
|
||||
@@ -134,13 +141,12 @@ export const AuthProvider = ({ children }) => {
|
||||
const signInWithGoogle = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const redirectTo = searchParams.get("redirectTo");
|
||||
const redirectTo = searchParams.get("redirectTo") || "/dashboard";
|
||||
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: "google",
|
||||
options: {
|
||||
redirectTo: `${window.location.origin}/login?redirectTo=${
|
||||
redirectTo ?? ""
|
||||
}`,
|
||||
redirectTo: `${window.location.origin}/login?redirectTo=${encodeURIComponent(redirectTo)}`,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -182,13 +188,13 @@ export const AuthProvider = ({ children }) => {
|
||||
}
|
||||
};
|
||||
const fetchMenuList = async (role) => {
|
||||
console.log(role,'role');
|
||||
if(!role) return;
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('resources')
|
||||
.select('*')
|
||||
.eq('type', 'menuKey')
|
||||
.eq('attributes->>roleName', role) // 添加这行来筛选 OWNER 角色
|
||||
.eq('attributes->>roleName', role)
|
||||
.single();
|
||||
if (error) throw error;
|
||||
if(data?.attributes?.menuKeys){
|
||||
@@ -214,7 +220,8 @@ export const AuthProvider = ({ children }) => {
|
||||
}
|
||||
setUser({});
|
||||
message.success("已成功登出");
|
||||
navigate(`/login?redirectTo=${location.pathname}`, { replace: true });
|
||||
// 保存当前完整路径作为重定向 URL
|
||||
navigate(`/login?redirectTo=${encodeURIComponent(location.pathname + location.search)}`, { replace: true });
|
||||
} catch (error) {
|
||||
message.error(error.message || "登出失败,请稍后重试");
|
||||
} finally {
|
||||
|
||||
@@ -6,8 +6,6 @@ class SupabaseService {
|
||||
let query = supabase
|
||||
.from(table)
|
||||
.select(options.select || "*", { count: "exact" });
|
||||
|
||||
// 处理精确匹配条件
|
||||
if (options.match) {
|
||||
query = query.match(options.match);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Card } from 'antd';
|
||||
import StatisticsOverview from '@/components/dashboard/StatisticsOverview';
|
||||
|
||||
import Bar from '@/components/Barchart'
|
||||
import Pie from '@/components/Piechart'
|
||||
const Dashboard = () => {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Card
|
||||
@@ -14,8 +16,13 @@ const Dashboard = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div>测试:member可见,最低权限</div>
|
||||
{/* <StatisticsOverview /> */}
|
||||
<StatisticsOverview />
|
||||
<div className='flex w-full mt-20 gap-5'>
|
||||
|
||||
<Bar className="flex-1 flex justify-center items-center"/>
|
||||
<Pie className="flex-1 flex justify-center items-center"/>
|
||||
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -158,7 +158,7 @@ const CustomerPage = () => {
|
||||
columns={columns}
|
||||
dataSource={customers}
|
||||
rowKey="id"
|
||||
scroll={{ x: true }}
|
||||
scroll={{ x: true}}
|
||||
loading={loading}
|
||||
onChange={handleTableChange}
|
||||
pagination={{
|
||||
|
||||
@@ -256,7 +256,7 @@ export default function ProjectDetail() {
|
||||
}, [id, templateId]);
|
||||
|
||||
return (
|
||||
<div className="bg-gradient-to-b from-gray-50 to-white dark:from-gray-800 dark:to-gray-900/90 min-h-screen p-2">
|
||||
<div className="bg-gradient-to-b min-h-screen p-2">
|
||||
<Card
|
||||
className="shadow-lg rounded-lg border-0"
|
||||
title={
|
||||
|
||||
@@ -338,7 +338,7 @@ const ProjectPage = () => {
|
||||
columns={columns}
|
||||
dataSource={projects}
|
||||
rowKey="id"
|
||||
scroll={{ x: true }}
|
||||
scroll={{ x: true}}
|
||||
loading={loadingProjects}
|
||||
onChange={handleTableChange}
|
||||
pagination={{
|
||||
|
||||
@@ -22,7 +22,6 @@ import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import SectionList from '@/components/SectionList'
|
||||
import DifyChatDrawer from '@/components/difyChatAi';
|
||||
import ChatAIDrawer from '@/components/ChatAi';
|
||||
const { Title } = Typography;
|
||||
|
||||
// 添加货币符号映射
|
||||
@@ -59,8 +58,7 @@ const QuotationForm = () => {
|
||||
|
||||
// 计算小节总额
|
||||
const calculateSectionTotal = useMemo(
|
||||
() =>
|
||||
(items = []) => {
|
||||
() => (items = []) => {
|
||||
if (!Array.isArray(items)) return 0;
|
||||
return items.reduce((sum, item) => {
|
||||
if (!item) return sum;
|
||||
@@ -72,11 +70,10 @@ const QuotationForm = () => {
|
||||
|
||||
// 计算总金额
|
||||
const calculateTotalAmount = useMemo(
|
||||
() =>
|
||||
(sections = []) => {
|
||||
if (!Array.isArray(sections)) return 0;
|
||||
() => (sections = []) => {
|
||||
if (!sections || !Array.isArray(sections)) return 0;
|
||||
return sections.reduce((sum, section) => {
|
||||
if (!section) return sum;
|
||||
if (!section || !section.items) return sum;
|
||||
return sum + calculateSectionTotal(section.items);
|
||||
}, 0);
|
||||
},
|
||||
@@ -318,36 +315,40 @@ const QuotationForm = () => {
|
||||
const [vercelOpen, setVercelOpen] = useState(false);
|
||||
|
||||
const handleExport = (data) => {
|
||||
if(data?.activityName&&data?.currency){
|
||||
try {
|
||||
const jsonData = typeof data === 'string' ? JSON.parse(data) : data;
|
||||
|
||||
if (jsonData?.quataName && jsonData?.currency) {
|
||||
const quotationData = {
|
||||
quataName: data.activityName,
|
||||
description: data.description,
|
||||
currency: data.currency || "TWD",
|
||||
sections: data.sections.map((section) => ({
|
||||
quataName: jsonData.quataName,
|
||||
description: jsonData.description || '',
|
||||
currency: jsonData.currency || "TWD",
|
||||
sections: jsonData.sections.map((section) => ({
|
||||
key: uuidv4(),
|
||||
sectionName: section.sectionName,
|
||||
items: section.items.map((item) => ({
|
||||
key: uuidv4(),
|
||||
name: item.name,
|
||||
quantity: item.quantity,
|
||||
price: item.price,
|
||||
description: item.description,
|
||||
unit: item.unit,
|
||||
quantity: Number(item.quantity) || 0,
|
||||
price: Number(item.price) || 0,
|
||||
description: item.description || "",
|
||||
unit: item.unit || "",
|
||||
})),
|
||||
})),
|
||||
};
|
||||
setCurrentCurrency(data.currency || "TWD");
|
||||
setCurrentCurrency(jsonData.currency || "TWD");
|
||||
form.setFieldsValue(quotationData);
|
||||
setFormValues(quotationData);
|
||||
setTaxRate(data.taxRate || 0);
|
||||
setTaxRate(Number(jsonData.taxRate) || 0);
|
||||
message.success('已添加报价单');
|
||||
|
||||
}else{
|
||||
const _data={
|
||||
...data,
|
||||
} else {
|
||||
const newSection = {
|
||||
...jsonData,
|
||||
key: uuidv4(),
|
||||
}
|
||||
const newSections = [...formValues.sections, _data];
|
||||
};
|
||||
const currentSections = form.getFieldValue('sections') || [];
|
||||
const newSections = [...currentSections, newSection];
|
||||
|
||||
form.setFieldValue('sections', newSections);
|
||||
const currentFormValues = form.getFieldsValue();
|
||||
setFormValues({
|
||||
@@ -359,10 +360,14 @@ const QuotationForm = () => {
|
||||
|
||||
setDifyOpen(false);
|
||||
setVercelOpen(false);
|
||||
} catch (error) {
|
||||
console.error('Export error:', error);
|
||||
message.error('导出失败:数据格式错误');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-gradient-to-b from-gray-50 to-white dark:from-gray-800 dark:to-gray-900/90 min-h-screen p-2">
|
||||
<div className="bg-gradient-to-b min-h-screen p-2">
|
||||
<Card
|
||||
className="shadow-lg rounded-lg border-0"
|
||||
title={
|
||||
@@ -401,9 +406,9 @@ const QuotationForm = () => {
|
||||
<Button onClick={() => setDifyOpen(true)}>
|
||||
AI for Dify
|
||||
</Button>
|
||||
<Button onClick={() => setVercelOpen(true)}>
|
||||
{/* <Button onClick={() => setVercelOpen(true)}>
|
||||
AI for Vercel
|
||||
</Button>
|
||||
</Button> */}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -511,11 +516,7 @@ const QuotationForm = () => {
|
||||
onClose={() => setDifyOpen(false)}
|
||||
onExport={handleExport}
|
||||
/>
|
||||
<ChatAIDrawer
|
||||
open={vercelOpen}
|
||||
onClose={() => setVercelOpen(false)}
|
||||
onExport={handleExport}
|
||||
/>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -381,7 +381,7 @@ useEffect(()=>{
|
||||
columns={columns}
|
||||
dataSource={quotations}
|
||||
rowKey="id"
|
||||
scroll={{ x: true }}
|
||||
scroll={{ x: true}}
|
||||
loading={loadingQuotations}
|
||||
onChange={handleTableChange}
|
||||
pagination={{
|
||||
@@ -405,7 +405,7 @@ useEffect(()=>{
|
||||
<Button
|
||||
key="custom"
|
||||
onClick={() => handleConfirm()}
|
||||
className=" gap-2 px-6 rounded-full hover:bg-gray-100"
|
||||
className="gap-2 px-6 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 dark:text-gray-200"
|
||||
>
|
||||
<FileAddOutlined /> 自定义创建
|
||||
</Button>,
|
||||
@@ -414,28 +414,28 @@ useEffect(()=>{
|
||||
type="primary"
|
||||
disabled={!selectedTemplateId}
|
||||
onClick={handleConfirm}
|
||||
className="gap-2 px-6 rounded-full bg-blue-600 hover:bg-blue-700"
|
||||
className="gap-2 px-6 rounded-full "
|
||||
>
|
||||
<AppstoreOutlined /> 使用选中模板
|
||||
</Button>,
|
||||
]}
|
||||
width={900}
|
||||
className="template-modal"
|
||||
className="template-modal "
|
||||
>
|
||||
{loading ? (
|
||||
<div className="flex justify-center items-center h-[400px]">
|
||||
<div className="flex justify-center items-center h-[400px] ">
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
) : templates.length === 0 ? (
|
||||
<Empty description="暂无可用模板" />
|
||||
<Empty description={<span className="dark:text-gray-400">暂无可用模板</span>} />
|
||||
) : (
|
||||
<div className="max-h-[600px] overflow-y-auto px-4">
|
||||
<div className="max-h-[600px] overflow-y-auto px-4 ">
|
||||
{getTemplatesByCategory().map((group, groupIndex) => (
|
||||
<div key={groupIndex} className="mb-10 last:mb-2">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<h3 className="text-xl font-medium text-gray-900">
|
||||
<h3 className="text-xl font-medium text-gray-900 dark:text-gray-100">
|
||||
{group.name}
|
||||
<span className="ml-2 text-sm text-gray-500">
|
||||
<span className="ml-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
({group.templates.length})
|
||||
</span>
|
||||
</h3>
|
||||
@@ -450,18 +450,18 @@ useEffect(()=>{
|
||||
relative p-6 rounded-xl cursor-pointer transition-all duration-200
|
||||
${
|
||||
selectedTemplateId === template.id
|
||||
? "ring-2 ring-blue-500 bg-blue-50/40"
|
||||
: "hover:shadow-lg border border-gray-200 hover:border-blue-200"
|
||||
? "ring-2 ring-blue-500 dark:ring-blue-400 bg-blue-50/40 dark:bg-blue-900/30"
|
||||
: "hover:shadow-lg border border-gray-200 dark:border-gray-700 hover:border-blue-200 dark:hover:border-blue-700"
|
||||
}
|
||||
transform hover:-translate-y-1
|
||||
transform hover:-translate-y-1 dark:bg-gray-800
|
||||
`}
|
||||
>
|
||||
<div className="flex justify-between items-start gap-3 mb-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
<h4 className="text-lg font-medium text-gray-900 truncate">
|
||||
<h4 className="text-lg font-medium text-gray-900 dark:text-gray-100 truncate">
|
||||
{template.attributes.templateName}
|
||||
</h4>
|
||||
<p className="text-sm text-gray-600 mt-2 line-clamp-2">
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mt-2 line-clamp-2">
|
||||
{template.attributes.description || "暂无描述"}
|
||||
</p>
|
||||
</div>
|
||||
@@ -475,16 +475,16 @@ useEffect(()=>{
|
||||
rounded-lg p-3 text-sm border
|
||||
${
|
||||
selectedTemplateId === template.id
|
||||
? "bg-white border-blue-200"
|
||||
: "bg-gray-50 border-gray-100"
|
||||
? "bg-white dark:bg-gray-700 border-blue-200 dark:border-blue-600"
|
||||
: "bg-gray-50 dark:bg-gray-800 border-gray-100 dark:border-gray-700"
|
||||
}
|
||||
`}
|
||||
>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="font-medium text-gray-700 truncate flex-1">
|
||||
<span className="font-medium text-gray-700 dark:text-gray-200 truncate flex-1">
|
||||
{section.sectionName}
|
||||
</span>
|
||||
<span className="text-gray-500 ml-2 text-xs px-2 py-0.5 bg-gray-100 rounded-full">
|
||||
<span className="text-gray-500 dark:text-gray-400 ml-2 text-xs px-2 py-0.5 bg-gray-100 dark:bg-gray-600 rounded-full">
|
||||
{section.items.length}项
|
||||
</span>
|
||||
</div>
|
||||
@@ -494,7 +494,7 @@ useEffect(()=>{
|
||||
|
||||
{selectedTemplateId === template.id && (
|
||||
<div className="absolute top-4 right-4">
|
||||
<div className="w-6 h-6 bg-blue-600 rounded-full flex items-center justify-center shadow-md">
|
||||
<div className="w-6 h-6 bg-blue-600 dark:bg-blue-500 rounded-full flex items-center justify-center shadow-md">
|
||||
<svg
|
||||
className="w-4 h-4 text-white"
|
||||
fill="currentColor"
|
||||
|
||||
@@ -7,7 +7,7 @@ import html2canvas from 'html2canvas';
|
||||
import jsPDF from 'jspdf';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
import { EXCHANGE_RATE,defaultSymbol } from '@/utils/exchange_rate';
|
||||
import { EXCHANGE_RATE, defaultSymbol } from '@/utils/exchange_rate';
|
||||
|
||||
const QuotationPreview = () => {
|
||||
const { id } = useParams();
|
||||
@@ -114,17 +114,6 @@ const QuotationPreview = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 导出按钮组件
|
||||
const ExportPDFButton = () => (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<DownloadOutlined />}
|
||||
onClick={exportPDF}
|
||||
>
|
||||
导出PDF
|
||||
</Button>
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center h-full">
|
||||
@@ -139,52 +128,69 @@ const QuotationPreview = () => {
|
||||
const currencySymbol = EXCHANGE_RATE[attributes.currency]?.symbol || defaultSymbol;
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6">
|
||||
<div className="min-h-screen bg-gradient-to-b from-gray-50 to-gray-100 dark:from-gray-800 dark:to-gray-900 p-6 transition-colors duration-200">
|
||||
<Card
|
||||
title={
|
||||
className="max-w-4xl mx-auto shadow-lg rounded-2xl bg-white dark:bg-gray-800 transition-colors duration-200"
|
||||
bodyStyle={{ padding: 0 }}
|
||||
>
|
||||
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
|
||||
<div className="flex justify-between items-center">
|
||||
<Space>
|
||||
<FileTextOutlined className="text-blue-500" />
|
||||
<span>报价单预览</span>
|
||||
<FileTextOutlined className="text-blue-500 dark:text-blue-400" />
|
||||
<span className="text-gray-800 dark:text-gray-200 font-medium">报价单预览</span>
|
||||
</Space>
|
||||
<ExportPDFButton />
|
||||
</div>
|
||||
}
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<DownloadOutlined />}
|
||||
onClick={exportPDF}
|
||||
className="bg-blue-500 hover:bg-blue-600 border-none rounded-full shadow-md hover:shadow-lg transition-all duration-200"
|
||||
>
|
||||
<div id="quotation-content" className="p-6">
|
||||
<div className="text-center mb-8">
|
||||
<Title level={2}>{attributes.quataName}</Title>
|
||||
<Text type="secondary">创建日期:{new Date(quotation.created_at).toLocaleDateString()}</Text>
|
||||
导出PDF
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 p-4 rounded-lg mb-6">
|
||||
<Title level={4}>基本信息</Title>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div id="quotation-content" className="p-8 bg-white" style={{ width: '210mm', margin: '0 auto' }}>
|
||||
<div className="text-center mb-8">
|
||||
<Title level={2} className="dark:text-gray-200">{attributes.quataName}</Title>
|
||||
<Text type="secondary" className="dark:text-gray-400">
|
||||
创建日期:{new Date(quotation.created_at).toLocaleDateString()}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 dark:bg-gray-700 p-6 rounded-2xl mb-6">
|
||||
<Title level={4} className="dark:text-gray-200 mb-4">基本信息</Title>
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div>
|
||||
<Text type="secondary">客户:</Text>
|
||||
<Space>
|
||||
<Text type="secondary" className="dark:text-gray-400">客户:</Text>
|
||||
<Space className="mt-2">
|
||||
{attributes.customers?.map(customer => (
|
||||
<Tag key={customer.id} color="blue">{customer.name}</Tag>
|
||||
<Tag
|
||||
key={customer.id}
|
||||
className="px-3 py-1 rounded-full bg-blue-100 text-blue-600 dark:bg-blue-900 dark:text-blue-200 border-none"
|
||||
>
|
||||
{customer.name}
|
||||
</Tag>
|
||||
))}
|
||||
</Space>
|
||||
</div>
|
||||
<div>
|
||||
<Text type="secondary">货币类型:</Text>
|
||||
<Text>{attributes.currency}</Text>
|
||||
<Text type="secondary" className="dark:text-gray-400">货币类型:</Text>
|
||||
<Text className="dark:text-gray-200 ml-2">{attributes.currency}</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{attributes.sections?.map((section, sIndex) => (
|
||||
<div key={sIndex} className="mb-6">
|
||||
<div className="flex items-center gap-2 h-full">
|
||||
<div key={sIndex} className="mb-8">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="h-4 w-1 bg-blue-500 rounded-full" />
|
||||
<h2>{section.sectionName}</h2>
|
||||
<h2 className="text-lg font-medium dark:text-gray-200">{section.sectionName}</h2>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<div className="overflow-x-auto rounded-2xl border border-gray-200 dark:border-gray-700">
|
||||
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead className="bg-gray-50 dark:bg-gray-800">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">项目明细</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">描述/备注</th>
|
||||
@@ -194,9 +200,9 @@ const QuotationPreview = () => {
|
||||
<th className="px-4 py-3 text-right text-sm font-medium text-gray-500">小计</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
{section.items.map((item, iIndex) => (
|
||||
<tr key={iIndex}>
|
||||
<tr key={iIndex} className="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-150">
|
||||
<td className="px-4 py-3 text-sm text-gray-900">{item.name}</td>
|
||||
<td className="px-4 py-3 text-sm text-gray-500">{item.description}</td>
|
||||
<td className="px-4 py-3 text-sm text-gray-500">{item.unit}</td>
|
||||
@@ -216,9 +222,9 @@ const QuotationPreview = () => {
|
||||
))}
|
||||
|
||||
{/* 金额汇总 */}
|
||||
<div className="mt-8 border-t pt-4">
|
||||
<div className="flex justify-end space-y-2">
|
||||
<div className="w-64 space-y-2">
|
||||
<div className="mt-8 border-t border-gray-200 dark:border-gray-700 pt-6">
|
||||
<div className="flex justify-end">
|
||||
<div className="w-64 space-y-3">
|
||||
<div className="flex justify-between">
|
||||
<Text>税前总计:</Text>
|
||||
<Text>{currencySymbol}{attributes.beforeTaxAmount?.toLocaleString()}</Text>
|
||||
@@ -238,9 +244,9 @@ const QuotationPreview = () => {
|
||||
</div>
|
||||
)}
|
||||
<Divider className="my-2" />
|
||||
<div className="flex justify-between">
|
||||
<Text strong>最终金额:</Text>
|
||||
<Text strong className="text-blue-500 text-xl">
|
||||
<div className="flex justify-between items-center">
|
||||
<Text strong className="dark:text-gray-200">最终金额:</Text>
|
||||
<Text strong className="text-xl text-blue-500 dark:text-blue-400">
|
||||
{currencySymbol}{(attributes.discount || attributes.afterTaxAmount)?.toLocaleString()}
|
||||
</Text>
|
||||
</div>
|
||||
@@ -251,9 +257,9 @@ const QuotationPreview = () => {
|
||||
{/* 补充说明 */}
|
||||
{attributes.description && (
|
||||
<div className="mt-8">
|
||||
<Title level={4}>补充说明</Title>
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<Text>{attributes.description}</Text>
|
||||
<Title level={4} className="dark:text-gray-200">补充说明</Title>
|
||||
<div className="bg-gray-50 dark:bg-gray-700 p-6 rounded-2xl">
|
||||
<Text className="dark:text-gray-300">{attributes.description}</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -36,7 +36,7 @@ const ServiceForm = () => {
|
||||
return <div>无效的模板类型</div>;
|
||||
}
|
||||
return (
|
||||
<div className="bg-gradient-to-b from-gray-50 to-white dark:from-gray-800 dark:to-gray-900/90 min-h-screen p-2" >
|
||||
<div className="bg-gradient-to-b min-h-screen p-2" >
|
||||
<Card
|
||||
className="shadow-lg rounded-lg border-0"
|
||||
title={
|
||||
|
||||
@@ -68,11 +68,18 @@ const ServicePage = () => {
|
||||
},
|
||||
};
|
||||
|
||||
// 添加分页相关状态
|
||||
const [pagination, setPagination] = useState({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
// 获取服务模板列表
|
||||
const fetchServices = async () => {
|
||||
const fetchServices = async (page = pagination.current, pageSize = pagination.pageSize) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { data: services } = await supabaseService.select("resources", {
|
||||
const { data: services, total } = await supabaseService.select("resources", {
|
||||
filter: {
|
||||
type: { eq: "serviceTemplate" },
|
||||
...(selectedType
|
||||
@@ -83,9 +90,17 @@ const ServicePage = () => {
|
||||
column: "created_at",
|
||||
ascending: false,
|
||||
},
|
||||
page,
|
||||
pageSize,
|
||||
});
|
||||
|
||||
setData(services || []);
|
||||
setPagination((prev) => ({
|
||||
...prev,
|
||||
current: page,
|
||||
pageSize,
|
||||
total,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("获取服务模板失败:", error);
|
||||
message.error("获取服务模板失败");
|
||||
@@ -93,12 +108,15 @@ const ServicePage = () => {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setPagination((prev) => ({ ...prev, current: 1 }));
|
||||
fetchServices(1, pagination.pageSize);
|
||||
}, [selectedType]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchUnits();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
fetchServices();
|
||||
}, [selectedType]);
|
||||
|
||||
const fetchUnits = async () => {
|
||||
setloadingUnits(true);
|
||||
@@ -263,7 +281,6 @@ const ServicePage = () => {
|
||||
title: "名称",
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
width: "15%",
|
||||
render: (text, item, index) => {
|
||||
const isEditing =
|
||||
editingKey === `${record.id}-${section.key}-${index}`;
|
||||
@@ -286,7 +303,6 @@ const ServicePage = () => {
|
||||
title: "单位",
|
||||
dataIndex: "unit",
|
||||
key: "unit",
|
||||
width: "10%",
|
||||
render: (text, item, index) => {
|
||||
const isEditing =
|
||||
editingKey === `${record.id}-${section.key}-${index}`;
|
||||
@@ -320,7 +336,6 @@ const ServicePage = () => {
|
||||
title: "单价",
|
||||
dataIndex: "price",
|
||||
key: "price",
|
||||
width: "10%",
|
||||
render: (text, item, index) => {
|
||||
const isEditing =
|
||||
editingKey === `${record.id}-${section.key}-${index}`;
|
||||
@@ -343,7 +358,6 @@ const ServicePage = () => {
|
||||
title: "数量",
|
||||
dataIndex: "quantity",
|
||||
key: "quantity",
|
||||
width: "10%",
|
||||
render: (text, item, index) => {
|
||||
const isEditing =
|
||||
editingKey === `${record.id}-${section.key}-${index}`;
|
||||
@@ -677,7 +691,7 @@ const ServicePage = () => {
|
||||
</Popconfirm>
|
||||
</div>
|
||||
<Table
|
||||
scroll={{ x: true }}
|
||||
scroll={{ x: true}}
|
||||
dataSource={section.items}
|
||||
pagination={false}
|
||||
size="small"
|
||||
@@ -714,7 +728,6 @@ const ServicePage = () => {
|
||||
title: "模板名称",
|
||||
dataIndex: ["attributes", "templateName"],
|
||||
key: "templateName",
|
||||
className: "min-w-[200px]",
|
||||
},
|
||||
{
|
||||
title: "模板类型",
|
||||
@@ -766,6 +779,7 @@ const ServicePage = () => {
|
||||
title: "创建时间",
|
||||
dataIndex: "created_at",
|
||||
key: "created_at",
|
||||
width: 220,
|
||||
render: (text) => new Date(text).toLocaleString(),
|
||||
sorter: (a, b) => new Date(a.created_at) - new Date(b.created_at),
|
||||
},
|
||||
@@ -868,8 +882,19 @@ const ServicePage = () => {
|
||||
rowExpandable: (record) => record.attributes.sections?.length > 0,
|
||||
}}
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
...pagination,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`,
|
||||
onChange: (page, pageSize) => {
|
||||
// 页码或页大小改变时触发
|
||||
if (pageSize !== pagination.pageSize) {
|
||||
// 如果是页大小改变,重置到第一页
|
||||
fetchServices(1, pageSize);
|
||||
} else {
|
||||
fetchServices(page, pageSize);
|
||||
}
|
||||
},
|
||||
}}
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Table, Button, Form, Input, Space, message, Popconfirm, Drawer, Select, Segmented, Badge } from 'antd';
|
||||
import { PlusOutlined, FileTextOutlined, ProjectOutlined, CheckSquareOutlined } from '@ant-design/icons';
|
||||
import { Table, Button, Form, Input, Space, message, Popconfirm, Select, Segmented } from 'antd';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { supabaseService } from '@/hooks/supabaseService';
|
||||
const Classify = ({activeType,typeList}) => {
|
||||
const [data, setData] = useState([]);
|
||||
@@ -8,8 +8,13 @@ const Classify = ({activeType,typeList}) => {
|
||||
const [editingKey, setEditingKey] = useState('');
|
||||
const [form] = Form.useForm();
|
||||
const [filterType, setFilterType] = useState('all'); // 'all', 'common', 'current'
|
||||
const [pagination, setPagination] = useState({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const fetchCategories = async (type = activeType, filterTypeValue = filterType) => {
|
||||
const fetchCategories = async (type = activeType, filterTypeValue = filterType, page = pagination.current, pageSize = pagination.pageSize) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
let filterCondition;
|
||||
@@ -25,7 +30,7 @@ const Classify = ({activeType,typeList}) => {
|
||||
filterCondition = { in: `(${type},common)` };
|
||||
}
|
||||
|
||||
const { data: categories } = await supabaseService.select('resources', {
|
||||
const { data: categories, total } = await supabaseService.select('resources', {
|
||||
filter: {
|
||||
'type': { eq: 'categories' },
|
||||
'attributes->>template_type': filterCondition
|
||||
@@ -33,10 +38,18 @@ const Classify = ({activeType,typeList}) => {
|
||||
order: {
|
||||
column: 'created_at',
|
||||
ascending: false
|
||||
}
|
||||
},
|
||||
page,
|
||||
pageSize
|
||||
});
|
||||
|
||||
setData(categories || []);
|
||||
setPagination(prev => ({
|
||||
...prev,
|
||||
current: page,
|
||||
pageSize,
|
||||
total
|
||||
}));
|
||||
} catch (error) {
|
||||
message.error('获取分类数据失败');
|
||||
console.error(error);
|
||||
@@ -46,8 +59,9 @@ const Classify = ({activeType,typeList}) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchCategories(activeType, filterType);
|
||||
}, [activeType]);
|
||||
setPagination(prev => ({ ...prev, current: 1 }));
|
||||
fetchCategories(activeType, filterType, 1, pagination.pageSize);
|
||||
}, [activeType, filterType]);
|
||||
|
||||
|
||||
// 新增分类
|
||||
@@ -267,15 +281,23 @@ const Classify = ({activeType,typeList}) => {
|
||||
<div className="rounded-lg shadow-sm">
|
||||
<Form form={form}>
|
||||
<Table
|
||||
scroll={{ x: true }}
|
||||
scroll={{ x: true}}
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
className: "px-4"
|
||||
...pagination,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`,
|
||||
onChange: (page, pageSize) => {
|
||||
if (pageSize !== pagination.pageSize) {
|
||||
fetchCategories(activeType, filterType, 1, pageSize);
|
||||
} else {
|
||||
fetchCategories(activeType, filterType, page, pageSize);
|
||||
}
|
||||
}
|
||||
}}
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
@@ -9,8 +9,10 @@ export default function SectionComponent({ activeType }) {
|
||||
return <QuataSections />;
|
||||
case "task":
|
||||
return <TaskSections />;
|
||||
case "project":
|
||||
return <Project/>
|
||||
default:
|
||||
return <Project></Project>;
|
||||
return <div>暂无数据</div>
|
||||
}
|
||||
};
|
||||
return <>{renderFn(activeType)}</>;
|
||||
|
||||
@@ -24,7 +24,6 @@ import { supabaseService } from '@/hooks/supabaseService';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { supabase } from "@/config/supabase";
|
||||
|
||||
const { Text } = Typography;
|
||||
const TYPE = 'project';
|
||||
|
||||
const ProjectSections = () => {
|
||||
@@ -37,11 +36,15 @@ const ProjectSections = () => {
|
||||
const [formValues, setFormValues] = useState({});
|
||||
const [uploadModalVisible, setUploadModalVisible] = useState(false);
|
||||
const [currentAddItem, setCurrentAddItem] = useState(null);
|
||||
|
||||
const fetchSections = async () => {
|
||||
const [pagination, setPagination] = useState({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
const fetchSections = async (page = pagination.current, pageSize = pagination.pageSize) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data: sections } = await supabaseService.select('resources', {
|
||||
const { data: sections, total } = await supabaseService.select('resources', {
|
||||
filter: {
|
||||
'type': { eq: 'sections' },
|
||||
'attributes->>template_type': { eq: TYPE }
|
||||
@@ -49,9 +52,18 @@ const ProjectSections = () => {
|
||||
order: {
|
||||
column: 'created_at',
|
||||
ascending: false
|
||||
}
|
||||
},
|
||||
page,
|
||||
pageSize
|
||||
});
|
||||
|
||||
setData(sections || []);
|
||||
setPagination(prev => ({
|
||||
...prev,
|
||||
current: page,
|
||||
pageSize,
|
||||
total
|
||||
}));
|
||||
} catch (error) {
|
||||
message.error('获取模块数据失败');
|
||||
console.error(error);
|
||||
@@ -83,9 +95,9 @@ const ProjectSections = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchSections();
|
||||
fetchSections(1, pagination.pageSize);
|
||||
fetchUnits();
|
||||
}, []);
|
||||
}, [TYPE]);
|
||||
|
||||
const handleAdd = () => {
|
||||
const newData = {
|
||||
@@ -151,7 +163,7 @@ const ProjectSections = () => {
|
||||
|
||||
message.success('保存成功');
|
||||
setEditingKey('');
|
||||
fetchSections();
|
||||
fetchSections(pagination.current, pagination.pageSize);
|
||||
} catch (error) {
|
||||
message.error('保存失败');
|
||||
console.error(error);
|
||||
@@ -162,7 +174,11 @@ const ProjectSections = () => {
|
||||
try {
|
||||
await supabaseService.delete('resources', { id: record.id });
|
||||
message.success('删除成功');
|
||||
fetchSections();
|
||||
if (data.length === 1 && pagination.current > 1) {
|
||||
fetchSections(pagination.current - 1, pagination.pageSize);
|
||||
} else {
|
||||
fetchSections(pagination.current, pagination.pageSize);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
console.error(error);
|
||||
@@ -528,9 +544,19 @@ const ProjectSections = () => {
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
className: "px-4"
|
||||
...pagination,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`,
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
onChange: (page, pageSize) => {
|
||||
if (pageSize !== pagination.pageSize) {
|
||||
setPagination(prev => ({ ...prev, pageSize }));
|
||||
fetchSections(1, pageSize);
|
||||
} else {
|
||||
fetchSections(page, pageSize);
|
||||
}
|
||||
}
|
||||
}}
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
@@ -27,22 +27,34 @@ const SectionsManagement = () => {
|
||||
const [loadingUnits,setLoadingUnits]=useState(false)
|
||||
const [units,setUnit]=useState([])
|
||||
const [formValues, setFormValues] = useState({});
|
||||
const fetchSections = async () => {
|
||||
const [pagination, setPagination] = useState({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
const fetchSections = async (page = pagination.current, pageSize = pagination.pageSize) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
|
||||
const { data: sections } = await supabaseService.select('resources', {
|
||||
const { data: sections, total } = await supabaseService.select('resources', {
|
||||
filter: {
|
||||
'type': { eq: 'sections' },
|
||||
'attributes->>template_type': {eq:TYPE}
|
||||
'attributes->>template_type': { eq: TYPE }
|
||||
},
|
||||
order: {
|
||||
column: 'created_at',
|
||||
ascending: false
|
||||
}
|
||||
},
|
||||
page,
|
||||
pageSize
|
||||
});
|
||||
|
||||
setData(sections || []);
|
||||
setPagination(prev => ({
|
||||
...prev,
|
||||
current: page,
|
||||
pageSize,
|
||||
total
|
||||
}));
|
||||
} catch (error) {
|
||||
message.error('获取模块数据失败');
|
||||
console.error(error);
|
||||
@@ -72,8 +84,7 @@ const [formValues, setFormValues] = useState({});
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
fetchSections();
|
||||
|
||||
fetchSections(1, pagination.pageSize);
|
||||
}, [TYPE]);
|
||||
|
||||
const handleAdd = () => {
|
||||
@@ -115,7 +126,7 @@ const [formValues, setFormValues] = useState({});
|
||||
attributes: {
|
||||
name: values.name,
|
||||
template_type: TYPE,
|
||||
items: items.filter(item => item.name), // 只保存有名称的项目
|
||||
items: items.filter(item => item.name),
|
||||
},
|
||||
schema_version: 1
|
||||
});
|
||||
@@ -135,7 +146,7 @@ const [formValues, setFormValues] = useState({});
|
||||
|
||||
message.success('保存成功');
|
||||
setEditingKey('');
|
||||
fetchSections();
|
||||
fetchSections(pagination.current, pagination.pageSize);
|
||||
} catch (error) {
|
||||
message.error('保存失败');
|
||||
console.error(error);
|
||||
@@ -146,7 +157,11 @@ const [formValues, setFormValues] = useState({});
|
||||
try {
|
||||
await supabaseService.delete('resources', { id: record.id });
|
||||
message.success('删除成功');
|
||||
fetchSections();
|
||||
if (data.length === 1 && pagination.current > 1) {
|
||||
fetchSections(pagination.current - 1, pagination.pageSize);
|
||||
} else {
|
||||
fetchSections(pagination.current, pagination.pageSize);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
console.error(error);
|
||||
@@ -450,9 +465,17 @@ const [formValues, setFormValues] = useState({});
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
className: "px-4"
|
||||
...pagination,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`,
|
||||
onChange: (page, pageSize) => {
|
||||
if (pageSize !== pagination.pageSize) {
|
||||
fetchSections(1, pageSize);
|
||||
} else {
|
||||
fetchSections(page, pageSize);
|
||||
}
|
||||
}
|
||||
}}
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
@@ -18,7 +18,6 @@ import { supabaseService } from '@/hooks/supabaseService';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const { Text } = Typography;
|
||||
const TYPE = 'task';
|
||||
|
||||
const TaskSections = () => {
|
||||
@@ -29,11 +28,16 @@ const TaskSections = () => {
|
||||
const [loadingUnits, setLoadingUnits] = useState(false);
|
||||
const [units, setUnit] = useState([]);
|
||||
const [formValues, setFormValues] = useState({});
|
||||
const [pagination, setPagination] = useState({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const fetchSections = async () => {
|
||||
const fetchSections = async (page = pagination.current, pageSize = pagination.pageSize) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data: sections } = await supabaseService.select('resources', {
|
||||
const { data: sections, total } = await supabaseService.select('resources', {
|
||||
filter: {
|
||||
'type': { eq: 'sections' },
|
||||
'attributes->>template_type': { eq: TYPE }
|
||||
@@ -41,9 +45,18 @@ const TaskSections = () => {
|
||||
order: {
|
||||
column: 'created_at',
|
||||
ascending: false
|
||||
}
|
||||
},
|
||||
page,
|
||||
pageSize
|
||||
});
|
||||
|
||||
setData(sections || []);
|
||||
setPagination(prev => ({
|
||||
...prev,
|
||||
current: page,
|
||||
pageSize,
|
||||
total
|
||||
}));
|
||||
} catch (error) {
|
||||
message.error('获取模块数据失败');
|
||||
console.error(error);
|
||||
@@ -75,9 +88,9 @@ const TaskSections = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchSections();
|
||||
fetchSections(1, pagination.pageSize);
|
||||
fetchUnits();
|
||||
}, []);
|
||||
}, [TYPE]);
|
||||
|
||||
const handleAdd = () => {
|
||||
const newData = {
|
||||
@@ -144,7 +157,7 @@ const TaskSections = () => {
|
||||
|
||||
message.success('保存成功');
|
||||
setEditingKey('');
|
||||
fetchSections();
|
||||
fetchSections(pagination.current, pagination.pageSize);
|
||||
} catch (error) {
|
||||
message.error('保存失败');
|
||||
console.error(error);
|
||||
@@ -155,7 +168,11 @@ const TaskSections = () => {
|
||||
try {
|
||||
await supabaseService.delete('resources', { id: record.id });
|
||||
message.success('删除成功');
|
||||
fetchSections();
|
||||
if (data.length === 1 && pagination.current > 1) {
|
||||
fetchSections(pagination.current - 1, pagination.pageSize);
|
||||
} else {
|
||||
fetchSections(pagination.current, pagination.pageSize);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
console.error(error);
|
||||
@@ -440,9 +457,19 @@ const TaskSections = () => {
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
className: "px-4"
|
||||
...pagination,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`,
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
onChange: (page, pageSize) => {
|
||||
if (pageSize !== pagination.pageSize) {
|
||||
setPagination(prev => ({ ...prev, pageSize }));
|
||||
fetchSections(1, pageSize);
|
||||
} else {
|
||||
fetchSections(page, pageSize);
|
||||
}
|
||||
}
|
||||
}}
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
@@ -9,8 +9,13 @@ const UnitManagement = ({ activeType, typeList }) => {
|
||||
const [editingKey, setEditingKey] = useState('');
|
||||
const [form] = Form.useForm();
|
||||
const [filterType, setFilterType] = useState('all');
|
||||
const [pagination, setPagination] = useState({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const fetchUnits = async (type = activeType, filterTypeValue = filterType) => {
|
||||
const fetchUnits = async (type = activeType, filterTypeValue = filterType, page = pagination.current, pageSize = pagination.pageSize) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
let filterCondition;
|
||||
@@ -26,7 +31,7 @@ const UnitManagement = ({ activeType, typeList }) => {
|
||||
filterCondition = { in: `(${type},common)` };
|
||||
}
|
||||
|
||||
const { data: units } = await supabaseService.select('resources', {
|
||||
const { data: units, total } = await supabaseService.select('resources', {
|
||||
filter: {
|
||||
'type': { eq: 'units' },
|
||||
'attributes->>template_type': filterCondition
|
||||
@@ -34,10 +39,18 @@ const UnitManagement = ({ activeType, typeList }) => {
|
||||
order: {
|
||||
column: 'created_at',
|
||||
ascending: false
|
||||
}
|
||||
},
|
||||
page,
|
||||
pageSize
|
||||
});
|
||||
|
||||
setData(units || []);
|
||||
setPagination(prev => ({
|
||||
...prev,
|
||||
current: page,
|
||||
pageSize,
|
||||
total
|
||||
}));
|
||||
} catch (error) {
|
||||
message.error('获取单位数据失败');
|
||||
console.error(error);
|
||||
@@ -47,8 +60,9 @@ const UnitManagement = ({ activeType, typeList }) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchUnits(activeType, filterType);
|
||||
}, [activeType]);
|
||||
setPagination(prev => ({ ...prev, current: 1 }));
|
||||
fetchUnits(activeType, filterType, 1, pagination.pageSize);
|
||||
}, [activeType, filterType]);
|
||||
|
||||
const handleAdd = () => {
|
||||
const newData = {
|
||||
@@ -263,15 +277,23 @@ const UnitManagement = ({ activeType, typeList }) => {
|
||||
<div className="rounded-lg shadow-sm">
|
||||
<Form form={form}>
|
||||
<Table
|
||||
scroll={{ x: true }}
|
||||
scroll={{ x: true}}
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
className: "px-4"
|
||||
...pagination,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`,
|
||||
onChange: (page, pageSize) => {
|
||||
if (pageSize !== pagination.pageSize) {
|
||||
fetchUnits(activeType, filterType, 1, pageSize);
|
||||
} else {
|
||||
fetchUnits(activeType, filterType, page, pageSize);
|
||||
}
|
||||
}
|
||||
}}
|
||||
className="rounded-lg"
|
||||
/>
|
||||
|
||||
@@ -144,7 +144,7 @@ const SupplierPage = () => {
|
||||
}
|
||||
>
|
||||
<Table
|
||||
scroll={{ x: true }}
|
||||
scroll={{ x: true}}
|
||||
columns={columns}
|
||||
dataSource={suppliers}
|
||||
rowKey="id"
|
||||
|
||||
@@ -264,7 +264,7 @@ export default function TaskForm() {
|
||||
}, [id, templateId]);
|
||||
|
||||
return (
|
||||
<div className="bg-gradient-to-b from-gray-50 to-white dark:from-gray-800 dark:to-gray-900/90 min-h-screen p-2">
|
||||
<div className="bg-gradient-to-b min-h-screen p-2">
|
||||
<Card
|
||||
className="shadow-lg rounded-lg border-0"
|
||||
title={
|
||||
|
||||
@@ -344,7 +344,7 @@ const TaskPage = () => {
|
||||
</Tag>
|
||||
</Space>
|
||||
}
|
||||
className="h-full w-full overflow-auto dark:bg-gray-800 dark:border-gray-700"
|
||||
className="h-full w-full overflow-auto "
|
||||
extra={
|
||||
<Space>
|
||||
<Select
|
||||
@@ -385,7 +385,7 @@ const TaskPage = () => {
|
||||
columns={columns}
|
||||
dataSource={tasks}
|
||||
rowKey="id"
|
||||
scroll={{ x: true }}
|
||||
scroll={{ x: true}}
|
||||
loading={loadingTasks}
|
||||
onChange={handleTableChange}
|
||||
pagination={{
|
||||
@@ -423,7 +423,7 @@ const TaskPage = () => {
|
||||
type="primary"
|
||||
disabled={!selectedTemplateId}
|
||||
onClick={handleConfirm}
|
||||
className="gap-2 px-6 rounded-full bg-blue-600 hover:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-700"
|
||||
className="gap-2 px-6 rounded-full "
|
||||
>
|
||||
<AppstoreOutlined /> 使用选中模板
|
||||
</Button>,
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
const MarketingCampaign = () => {
|
||||
return <Outlet />;
|
||||
};
|
||||
|
||||
export default MarketingCampaign;
|
||||
@@ -1,34 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Card, DatePicker } from 'antd';
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||||
|
||||
const CampaignPerformance = () => {
|
||||
const data = [
|
||||
{ name: '1月', 点击率: 400, 转化率: 240 },
|
||||
{ name: '2月', 点击率: 300, 转化率: 139 },
|
||||
{ name: '3月', 点击率: 200, 转化率: 980 },
|
||||
];
|
||||
|
||||
return (
|
||||
<Card title="活动成效分析">
|
||||
<div className="mb-4">
|
||||
<DatePicker.RangePicker />
|
||||
</div>
|
||||
<div style={{ width: '100%', height: 400 }}>
|
||||
<ResponsiveContainer>
|
||||
<LineChart data={data}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Line type="monotone" dataKey="点击率" stroke="#8884d8" />
|
||||
<Line type="monotone" dataKey="转化率" stroke="#82ca9d" />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default CampaignPerformance;
|
||||
@@ -1,48 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Card, Table, Button, Tag } from 'antd';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
|
||||
const CampaignPlan = () => {
|
||||
const columns = [
|
||||
{
|
||||
title: '计划名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '开始时间',
|
||||
dataIndex: 'startTime',
|
||||
key: 'startTime',
|
||||
},
|
||||
{
|
||||
title: '结束时间',
|
||||
dataIndex: 'endTime',
|
||||
key: 'endTime',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
render: status => (
|
||||
<Tag color={status === '进行中' ? 'green' : 'default'}>
|
||||
{status}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Card
|
||||
title="活动计划"
|
||||
extra={
|
||||
<Button type="primary" icon={<PlusOutlined />}>
|
||||
新增计划
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Table columns={columns} dataSource={[]} rowKey="id" />
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default CampaignPlan;
|
||||
@@ -1,45 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Form, Input, DatePicker, Select } from 'antd';
|
||||
import { STATUS } from '@/constants/status';
|
||||
|
||||
export const ProjectForm = ({ form }) => (
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="专案名称"
|
||||
rules={[{ required: true, message: '请输入专案名称' }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="manager"
|
||||
label="负责人"
|
||||
rules={[{ required: true, message: '请选择负责人' }]}
|
||||
>
|
||||
<Select placeholder="请选择负责人" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="startDate"
|
||||
label="开始日期"
|
||||
rules={[{ required: true, message: '请选择开始日期' }]}
|
||||
>
|
||||
<DatePicker />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="status"
|
||||
label="状态"
|
||||
rules={[{ required: true, message: '请选择状态' }]}
|
||||
>
|
||||
<Select>
|
||||
{Object.values(STATUS).map(status => (
|
||||
<Select.Option key={status} value={status}>
|
||||
{status}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
@@ -1,71 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Card, Modal, Form } from 'antd';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { BaseTable } from '@/components/common/BaseTable';
|
||||
import { ProjectForm } from './components/ProjectForm';
|
||||
import { useProjectData } from './hooks/useProjectData';
|
||||
import { getStatusColumn, getDateColumn } from '@/utils/tableColumns';
|
||||
|
||||
const CampaignProject = () => {
|
||||
const [form] = Form.useForm();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { loading, data, pagination, loadData } = useProjectData();
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '专案名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '负责人',
|
||||
dataIndex: 'manager',
|
||||
key: 'manager',
|
||||
},
|
||||
getDateColumn('开始日期', 'startDate'),
|
||||
getStatusColumn(),
|
||||
];
|
||||
|
||||
const handleAdd = () => {
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
console.log('Success:', values);
|
||||
setVisible(false);
|
||||
form.resetFields();
|
||||
loadData();
|
||||
} catch (error) {
|
||||
console.error('Failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<PageHeader
|
||||
title="行销活动专案"
|
||||
onAdd={handleAdd}
|
||||
addButtonText="新增专案"
|
||||
/>
|
||||
<BaseTable
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
loading={loading}
|
||||
pagination={pagination}
|
||||
onChange={loadData}
|
||||
/>
|
||||
<Modal
|
||||
title="新增专案"
|
||||
open={visible}
|
||||
onOk={handleSubmit}
|
||||
onCancel={() => setVisible(false)}
|
||||
>
|
||||
<ProjectForm form={form} />
|
||||
</Modal>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default CampaignProject;
|
||||
@@ -1,8 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
const Communication = () => {
|
||||
return <Outlet />;
|
||||
};
|
||||
|
||||
export default Communication;
|
||||
@@ -1,40 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Card, Timeline, Button } from 'antd';
|
||||
import { ClockCircleOutlined } from '@ant-design/icons';
|
||||
|
||||
const CommunicationJourney = () => {
|
||||
return (
|
||||
<Card
|
||||
title="沟通历程"
|
||||
extra={
|
||||
<Button type="primary">导出记录</Button>
|
||||
}
|
||||
>
|
||||
<Timeline
|
||||
mode="alternate"
|
||||
items={[
|
||||
{
|
||||
children: '创建营销活动 2023-10-25 10:00:00',
|
||||
},
|
||||
{
|
||||
children: '发送邮件通知 2023-10-25 10:30:00',
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
dot: <ClockCircleOutlined style={{ fontSize: '16px' }} />,
|
||||
children: '客户查看邮件 2023-10-25 11:00:00',
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
children: '系统提醒跟进 2023-10-25 14:00:00',
|
||||
},
|
||||
{
|
||||
children: '完成跟进 2023-10-25 16:00:00',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommunicationJourney;
|
||||
@@ -1,48 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Card, Table, Button, Input } from 'antd';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
|
||||
const { Search } = Input;
|
||||
|
||||
const CommunicationList = () => {
|
||||
const columns = [
|
||||
{
|
||||
title: '名单名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '联系人数量',
|
||||
dataIndex: 'contactCount',
|
||||
key: 'contactCount',
|
||||
},
|
||||
{
|
||||
title: '创建日期',
|
||||
dataIndex: 'createdAt',
|
||||
key: 'createdAt',
|
||||
},
|
||||
{
|
||||
title: '更新日期',
|
||||
dataIndex: 'updatedAt',
|
||||
key: 'updatedAt',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Card
|
||||
title="名单管理"
|
||||
extra={
|
||||
<Button type="primary" icon={<PlusOutlined />}>
|
||||
新增名单
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="mb-4">
|
||||
<Search placeholder="搜索名单" allowClear enterButton />
|
||||
</div>
|
||||
<Table columns={columns} dataSource={[]} rowKey="id" />
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommunicationList;
|
||||
@@ -1,42 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Card, Tabs, Button } from 'antd';
|
||||
import { SendOutlined } from '@ant-design/icons';
|
||||
|
||||
const CommunicationPreview = () => {
|
||||
const items = [
|
||||
{
|
||||
key: '1',
|
||||
label: '邮件预览',
|
||||
children: (
|
||||
<div className="p-4 border rounded">
|
||||
<h2>邮件主题</h2>
|
||||
<div className="mt-4">邮件内容预览区域</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: '短信预览',
|
||||
children: (
|
||||
<div className="p-4 border rounded">
|
||||
<div className="mt-4">短信内容预览区域</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Card
|
||||
title="消息预览"
|
||||
extra={
|
||||
<Button type="primary" icon={<SendOutlined />}>
|
||||
发送测试
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Tabs items={items} />
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommunicationPreview;
|
||||
@@ -1,8 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
const MarketingCenter = () => {
|
||||
return <Outlet />;
|
||||
};
|
||||
|
||||
export default MarketingCenter;
|
||||
@@ -1,43 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Card, Table, Button } from 'antd';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
|
||||
const TemplatePage = () => {
|
||||
const columns = [
|
||||
{
|
||||
title: '模版名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
},
|
||||
{
|
||||
title: '创建日期',
|
||||
dataIndex: 'createdAt',
|
||||
key: 'createdAt',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Card
|
||||
title="模版管理"
|
||||
extra={
|
||||
<Button type="primary" icon={<PlusOutlined />}>
|
||||
新增模版
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Table columns={columns} dataSource={[]} rowKey="id" />
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default TemplatePage;
|
||||
1
src/pages/quotation/index.jsx
Normal file
1
src/pages/quotation/index.jsx
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
TreeSelect
|
||||
} from 'antd';
|
||||
import { getAllRouteOptions, allRoutes } from '@/routes/routes';
|
||||
import { supabase } from '@/config/supabase';
|
||||
import { supabaseService } from '@/hooks/supabaseService';
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||
|
||||
const { Title } = Typography;
|
||||
@@ -25,6 +25,11 @@ export default function MenuManagement() {
|
||||
const [menuList, setMenuList] = useState([]);
|
||||
const [form] = Form.useForm();
|
||||
const [roles, setRoles] = useState([]);
|
||||
const [pagination, setPagination] = useState({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
// 获取所有路由选项
|
||||
const routeOptions = getAllRouteOptions();
|
||||
@@ -57,6 +62,7 @@ export default function MenuManagement() {
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed:'right',
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Button
|
||||
@@ -80,19 +86,31 @@ export default function MenuManagement() {
|
||||
];
|
||||
|
||||
// 获取菜单数据
|
||||
const fetchMenuList = async () => {
|
||||
try {
|
||||
const fetchMenuList = async (page = pagination.current, pageSize = pagination.pageSize) => {
|
||||
setLoading(true);
|
||||
const { data, error } = await supabase
|
||||
.from('resources')
|
||||
.select('*')
|
||||
.eq('type', TYPE)
|
||||
.order('created_at', { ascending: false });
|
||||
try {
|
||||
const { data: menus, total } = await supabaseService.select('resources', {
|
||||
filter: {
|
||||
'type': { eq: TYPE }
|
||||
},
|
||||
order: {
|
||||
column: 'created_at',
|
||||
ascending: false
|
||||
},
|
||||
page,
|
||||
pageSize
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
setMenuList(data);
|
||||
setMenuList(menus || []);
|
||||
setPagination(prev => ({
|
||||
...prev,
|
||||
current: page,
|
||||
pageSize,
|
||||
total
|
||||
}));
|
||||
} catch (error) {
|
||||
message.error('获取数据失败:' + error.message);
|
||||
message.error('获取菜单数据失败');
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -101,12 +119,12 @@ export default function MenuManagement() {
|
||||
// 获取角色列表
|
||||
const fetchRoles = async () => {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('roles')
|
||||
.select('*')
|
||||
.order('name');
|
||||
|
||||
if (error) throw error;
|
||||
const { data } = await supabaseService.select('roles', {
|
||||
order: {
|
||||
column: 'name',
|
||||
ascending: true
|
||||
}
|
||||
});
|
||||
setRoles(data || []);
|
||||
} catch (error) {
|
||||
message.error('获取角色数据失败');
|
||||
@@ -127,27 +145,20 @@ export default function MenuManagement() {
|
||||
}
|
||||
};
|
||||
|
||||
let result;
|
||||
if (form.getFieldValue('id')) {
|
||||
result = await supabase
|
||||
.from('resources')
|
||||
.update(menuData)
|
||||
.eq('id', form.getFieldValue('id'))
|
||||
.select();
|
||||
await supabaseService.update('resources',
|
||||
{ id: form.getFieldValue('id') },
|
||||
menuData
|
||||
);
|
||||
} else {
|
||||
result = await supabase
|
||||
.from('resources')
|
||||
.insert([menuData])
|
||||
.select();
|
||||
await supabaseService.insert('resources', menuData);
|
||||
}
|
||||
|
||||
if (result.error) throw result.error;
|
||||
|
||||
message.success('保存成功');
|
||||
setIsModalVisible(false);
|
||||
fetchMenuList();
|
||||
fetchMenuList(pagination.current, pagination.pageSize);
|
||||
} catch (error) {
|
||||
message.error('保存失败:' + error.message);
|
||||
message.error('保存失败:' + (error.message || '未知错误'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -168,19 +179,17 @@ export default function MenuManagement() {
|
||||
const handleDelete = async (id) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const {data, error } = await supabase
|
||||
.from('resources')
|
||||
.delete()
|
||||
.eq('id', id);
|
||||
if (error) throw error;
|
||||
if(data.length>0){
|
||||
await supabaseService.delete('resources', { id });
|
||||
message.success('删除成功');
|
||||
fetchMenuList();
|
||||
}else{
|
||||
message.error('删除失败');
|
||||
|
||||
// 如果当前页只有一条数据且不是第一页,删除后自动跳转到上一页
|
||||
if (menuList.length === 1 && pagination.current > 1) {
|
||||
fetchMenuList(pagination.current - 1, pagination.pageSize);
|
||||
} else {
|
||||
fetchMenuList(pagination.current, pagination.pageSize);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('删除失败:' + error.message);
|
||||
message.error('删除失败:' + (error.message || '未知错误'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -196,7 +205,7 @@ export default function MenuManagement() {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchMenuList();
|
||||
fetchMenuList(1, pagination.pageSize);
|
||||
fetchRoles();
|
||||
}, []);
|
||||
|
||||
@@ -221,6 +230,23 @@ export default function MenuManagement() {
|
||||
columns={columns}
|
||||
dataSource={menuList}
|
||||
loading={loading}
|
||||
pagination={{
|
||||
...pagination,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`,
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
onChange: (page, pageSize) => {
|
||||
if (pageSize !== pagination.pageSize) {
|
||||
// 如果是改变每页条数,重置到第一页
|
||||
setPagination(prev => ({ ...prev, pageSize }));
|
||||
fetchMenuList(1, pageSize);
|
||||
} else {
|
||||
// 如果是改变页码,获取对应页数据
|
||||
fetchMenuList(page, pageSize);
|
||||
}
|
||||
}
|
||||
}}
|
||||
rowKey="id"
|
||||
/>
|
||||
|
||||
|
||||
@@ -261,7 +261,7 @@ const ResourceTaskForm = () => {
|
||||
bordered={false}
|
||||
>
|
||||
<Table
|
||||
scroll={{ x: true }}
|
||||
scroll={{ x: true}}
|
||||
dataSource={dataSource}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
|
||||
@@ -148,7 +148,7 @@ const ResourceTask = () => {
|
||||
}
|
||||
>
|
||||
<Table
|
||||
scroll={{ x: true }}
|
||||
scroll={{ x: true}}
|
||||
columns={columns}
|
||||
dataSource={quotations}
|
||||
rowKey="id"
|
||||
|
||||
@@ -52,7 +52,6 @@ export default function PermissionManagement() {
|
||||
resources:resource_id(*)
|
||||
`, { count: 'exact' });
|
||||
|
||||
// 添加排序
|
||||
if (params.field && params.order) {
|
||||
const ascending = params.order === 'ascend';
|
||||
query = query.order(params.field, { ascending });
|
||||
@@ -366,17 +365,18 @@ export default function PermissionManagement() {
|
||||
{
|
||||
title: '操作',
|
||||
key: 'operation',
|
||||
fixed: 'right',
|
||||
render: (_, record) => (
|
||||
<div className="space-x-2">
|
||||
<Button
|
||||
type="text"
|
||||
type="link"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => {
|
||||
setModalType('edit');
|
||||
form.setFieldsValue(record);
|
||||
setModalVisible(true);
|
||||
}}
|
||||
/>
|
||||
>编辑</Button>
|
||||
<Popconfirm
|
||||
title="确认删除"
|
||||
description="确定要删除这条权限记录吗?"
|
||||
@@ -385,10 +385,10 @@ export default function PermissionManagement() {
|
||||
onConfirm={() => handleDelete(record.id)}
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
type="link"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
/>
|
||||
>删除</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
),
|
||||
@@ -522,7 +522,7 @@ export default function PermissionManagement() {
|
||||
|
||||
return (
|
||||
<App>
|
||||
<Card title="权限管理" bordered={false}>
|
||||
<Card title="权限管理" >
|
||||
<RoleHeader
|
||||
onAdd={() => {
|
||||
setModalType('add');
|
||||
@@ -538,12 +538,23 @@ export default function PermissionManagement() {
|
||||
/>
|
||||
|
||||
<Table
|
||||
scroll={{ x: true}}
|
||||
columns={columns}
|
||||
dataSource={permissions}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={pagination}
|
||||
pagination={{
|
||||
...pagination,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`,
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
onChange: (page, pageSize) => {
|
||||
setPagination(prev => ({ ...prev,page, pageSize }));
|
||||
}
|
||||
}}
|
||||
onChange={handleTableChange}
|
||||
className='w-full'
|
||||
/>
|
||||
|
||||
<Modal
|
||||
|
||||
@@ -212,7 +212,7 @@ export const MembershipTable = ({ memberships, onUpdate, onDelete, onAdd }) => {
|
||||
</Button>
|
||||
<Form form={form} component={false}>
|
||||
<Table
|
||||
scroll={{ x: true }}
|
||||
scroll={{ x: true}}
|
||||
components={{
|
||||
body: {
|
||||
cell: EditableMembershipCell,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Table, Form, Button, Space, Popconfirm, Tag, message } from 'antd';
|
||||
import { EditOutlined, DeleteOutlined, SaveOutlined, CloseOutlined } from '@ant-design/icons';
|
||||
import { EditableCell } from './EditableCell';
|
||||
import { ExpandedMemberships } from './ExpandedMemberships';
|
||||
export const TeamTable = ({ tableLoading,pagination,dataSource, onTableChange,onDelete ,onUpdate}) => {
|
||||
export const TeamTable = ({ tableLoading,pagination,dataSource,setPagination, onTableChange,onDelete ,onUpdate}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [editingKey, setEditingKey] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -106,6 +106,7 @@ export const TeamTable = ({ tableLoading,pagination,dataSource, onTableChange,on
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
render: (_, record) => {
|
||||
const editable = isEditing(record);
|
||||
return editable ? (
|
||||
@@ -180,8 +181,7 @@ export const TeamTable = ({ tableLoading,pagination,dataSource, onTableChange,on
|
||||
return (
|
||||
<Form form={form} component={false}>
|
||||
<Table
|
||||
scroll={{ x: true }}
|
||||
pagination={pagination}
|
||||
scroll={{ x: true}}
|
||||
loading={loading||tableLoading}
|
||||
components={{
|
||||
body: {
|
||||
@@ -192,6 +192,16 @@ export const TeamTable = ({ tableLoading,pagination,dataSource, onTableChange,on
|
||||
dataSource={dataSource}
|
||||
columns={mergedColumns}
|
||||
rowKey="id"
|
||||
pagination={{
|
||||
...pagination,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`,
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
onChange: (page, pageSize) => {
|
||||
setPagination(prev => ({ ...prev,page, pageSize }));
|
||||
}
|
||||
}}
|
||||
expandable={{
|
||||
expandedRowRender: (record) => (
|
||||
<ExpandedMemberships
|
||||
|
||||
@@ -183,9 +183,10 @@ const TeamManagement = () => {
|
||||
|
||||
return (
|
||||
<App>
|
||||
<Card title="团队管理" bordered={false}>
|
||||
<Card title="团队管理" >
|
||||
<TeamHeader onSearch={handleSearch} onAdd={handleAdd} />
|
||||
<TeamTable
|
||||
setPagination={setPagination}
|
||||
loading={loading}
|
||||
dataSource={teams}
|
||||
pagination={pagination}
|
||||
|
||||
@@ -69,7 +69,7 @@ const AppRoutes = () => {
|
||||
path="/login"
|
||||
element={
|
||||
user?.id ? (
|
||||
<Navigate to="/home" replace />
|
||||
<Navigate to="/dashboard" replace />
|
||||
) : (
|
||||
<Login />
|
||||
)
|
||||
@@ -86,7 +86,7 @@ const AppRoutes = () => {
|
||||
>
|
||||
<Route
|
||||
index
|
||||
element={<Navigate to="/home" replace />}
|
||||
element={<Navigate to="/dashboard" replace />}
|
||||
/>
|
||||
{renderRoutes(allRoutes)}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
|
||||
:root {
|
||||
// Light mode colors
|
||||
--primary-color: #1677ff;
|
||||
--primary-gradient: linear-gradient(45deg, #1677ff, #36cff0);
|
||||
--success-color: #52c41a;
|
||||
@@ -12,6 +13,25 @@
|
||||
--error-color: #ff4d4f;
|
||||
--font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
|
||||
'Noto Sans', sans-serif;
|
||||
|
||||
// Google-style scrollbar colors (Light mode)
|
||||
--scrollbar-thumb: rgba(0, 0, 0, .2);
|
||||
--scrollbar-thumb-hover: rgba(0, 0, 0, .4);
|
||||
--scrollbar-track: transparent;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
// Dark mode colors
|
||||
--primary-color: #4f9eff; // 更亮的蓝色适配暗色模式
|
||||
--primary-gradient: linear-gradient(45deg, #4f9eff, #60deff);
|
||||
--success-color: #6ede3b;
|
||||
--warning-color: #ffc53d;
|
||||
--error-color: #ff7875;
|
||||
|
||||
// Google-style scrollbar colors (Dark mode)
|
||||
--scrollbar-thumb: rgba(255, 255, 255, .2);
|
||||
--scrollbar-thumb-hover: rgba(255, 255, 255, .4);
|
||||
--scrollbar-track: transparent;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -152,42 +172,36 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
/* 滚动条基础样式 */
|
||||
/* 自定义滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
/* 垂直滚动条宽度 */
|
||||
height: 8px;
|
||||
/* 水平滚动条高度 */
|
||||
}
|
||||
|
||||
/* 亮色模式滚动条样式 */
|
||||
::-webkit-scrollbar-track {
|
||||
@apply bg-gray-100;
|
||||
/* 轨道背景色 */
|
||||
background: var(--scrollbar-track);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
@apply bg-gray-300 rounded-full hover:bg-gray-400 transition-colors;
|
||||
/* 滑块样式 */
|
||||
background-color: var(--scrollbar-thumb);
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
/* 暗色模式滚动条样式 */
|
||||
.dark {
|
||||
::-webkit-scrollbar-track {
|
||||
@apply bg-gray-800;
|
||||
/* 暗色模式轨道背景 */
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
@apply bg-gray-600 hover:bg-gray-500;
|
||||
/* 暗色模式滑块样式 */
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--scrollbar-thumb-hover);
|
||||
}
|
||||
|
||||
/* Firefox 滚动条样式 */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: theme('colors.gray.300') theme('colors.gray.100');
|
||||
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
* {
|
||||
scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.dark * {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
const withMT = require("@material-tailwind/react/utils/withMT");
|
||||
|
||||
module.exports = withMT({
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{js,ts,jsx,tsx}",
|
||||
@@ -133,4 +135,4 @@ export default {
|
||||
corePlugins: {
|
||||
preflight: false,
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -1,9 +1,8 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import path from 'path';
|
||||
|
||||
// 使用 __dirname 需要添加 import.meta.url 的支持
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
@@ -19,13 +18,13 @@ export default defineConfig({
|
||||
'@contexts': path.resolve(__dirname, 'src/contexts'),
|
||||
'@assets': path.resolve(__dirname, 'src/assets'),
|
||||
},
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'] // 添加文件扩展名自动解析
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json']
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ['react-apexcharts', 'apexcharts'],
|
||||
esbuildOptions: {
|
||||
target: 'es2020',
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
open: true,
|
||||
host: true,
|
||||
strictPort: true,
|
||||
},
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
@@ -34,8 +33,19 @@ export default defineConfig({
|
||||
output: {
|
||||
manualChunks: {
|
||||
vendor: ['react', 'react-dom', 'react-router-dom', 'antd'],
|
||||
charts: ['react-apexcharts', 'apexcharts'],
|
||||
},
|
||||
},
|
||||
},
|
||||
target: 'es2020',
|
||||
commonjsOptions: {
|
||||
include: [/node_modules/],
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
open: true,
|
||||
host: true,
|
||||
strictPort: true,
|
||||
},
|
||||
});
|
||||
273
yarn.lock
273
yarn.lock
@@ -537,6 +537,18 @@
|
||||
dependencies:
|
||||
"@emotion/memoize" "^0.8.1"
|
||||
|
||||
"@emotion/is-prop-valid@^0.8.2":
|
||||
version "0.8.8"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
|
||||
integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
|
||||
dependencies:
|
||||
"@emotion/memoize" "0.7.4"
|
||||
|
||||
"@emotion/memoize@0.7.4":
|
||||
version "0.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
|
||||
integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
|
||||
|
||||
"@emotion/memoize@^0.8.1":
|
||||
version "0.8.1"
|
||||
resolved "https://registry.npmmirror.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17"
|
||||
@@ -709,6 +721,21 @@
|
||||
"@floating-ui/core" "^1.6.0"
|
||||
"@floating-ui/utils" "^0.2.8"
|
||||
|
||||
"@floating-ui/dom@^1.2.1":
|
||||
version "1.6.13"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.13.tgz#a8a938532aea27a95121ec16e667a7cbe8c59e34"
|
||||
integrity sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==
|
||||
dependencies:
|
||||
"@floating-ui/core" "^1.6.0"
|
||||
"@floating-ui/utils" "^0.2.9"
|
||||
|
||||
"@floating-ui/react-dom@^1.2.2":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-1.3.0.tgz#4d35d416eb19811c2b0e9271100a6aa18c1579b3"
|
||||
integrity sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g==
|
||||
dependencies:
|
||||
"@floating-ui/dom" "^1.2.1"
|
||||
|
||||
"@floating-ui/react-dom@^2.1.2":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz#a1349bbf6a0e5cb5ded55d023766f20a4d439a31"
|
||||
@@ -716,6 +743,15 @@
|
||||
dependencies:
|
||||
"@floating-ui/dom" "^1.0.0"
|
||||
|
||||
"@floating-ui/react@0.19.0":
|
||||
version "0.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.19.0.tgz#d8e19a3fcfaa0684d5ec3f335232b4e0ac0c87e1"
|
||||
integrity sha512-fgYvN4ksCi5OvmPXkyOT8o5a8PSKHMzPHt+9mR6KYWdF16IAjWRLZPAAziI2sznaWT23drRFrYw64wdvYqqaQw==
|
||||
dependencies:
|
||||
"@floating-ui/react-dom" "^1.2.2"
|
||||
aria-hidden "^1.1.3"
|
||||
tabbable "^6.0.1"
|
||||
|
||||
"@floating-ui/react@^0.26.28":
|
||||
version "0.26.28"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.28.tgz#93f44ebaeb02409312e9df9507e83aab4a8c0dc7"
|
||||
@@ -730,6 +766,16 @@
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62"
|
||||
integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==
|
||||
|
||||
"@floating-ui/utils@^0.2.9":
|
||||
version "0.2.9"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.9.tgz#50dea3616bc8191fb8e112283b49eaff03e78429"
|
||||
integrity sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==
|
||||
|
||||
"@heroicons/react@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-2.2.0.tgz#0c05124af50434a800773abec8d3af6a297d904b"
|
||||
integrity sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==
|
||||
|
||||
"@humanwhocodes/config-array@^0.13.0":
|
||||
version "0.13.0"
|
||||
resolved "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748"
|
||||
@@ -818,6 +864,21 @@
|
||||
resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-7.15.2.tgz#f583ecfb7b92cad17c67f7825f20f23b72e6d7ff"
|
||||
integrity sha512-p8dsW0fdJxzYhULbm1noFYRHuBvJHleYviC0BlwbkVySC8AsvFI8AmC3sMssWV3dQ3yQ/SidYo9U+K/czpDpZw==
|
||||
|
||||
"@material-tailwind/react@^2.1.10":
|
||||
version "2.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@material-tailwind/react/-/react-2.1.10.tgz#e4ff8b8a5cf1a39209d408dda3217f632c080e9a"
|
||||
integrity sha512-xGU/mLDKDBp/qZ8Dp2XR7fKcTpDuFeZEBqoL9Bk/29kakKxNxjUGYSRHEFLsyOFf4VIhU6WGHdIS7tOA3QGJHA==
|
||||
dependencies:
|
||||
"@floating-ui/react" "0.19.0"
|
||||
classnames "2.3.2"
|
||||
deepmerge "4.2.2"
|
||||
framer-motion "6.5.1"
|
||||
material-ripple-effects "2.0.1"
|
||||
prop-types "15.8.1"
|
||||
react "18.2.0"
|
||||
react-dom "18.2.0"
|
||||
tailwind-merge "1.8.1"
|
||||
|
||||
"@monaco-editor/loader@^1.4.0":
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npmmirror.com/@monaco-editor/loader/-/loader-1.4.0.tgz#f08227057331ec890fa1e903912a5b711a2ad558"
|
||||
@@ -832,6 +893,59 @@
|
||||
dependencies:
|
||||
"@monaco-editor/loader" "^1.4.0"
|
||||
|
||||
"@motionone/animation@^10.12.0":
|
||||
version "10.18.0"
|
||||
resolved "https://registry.yarnpkg.com/@motionone/animation/-/animation-10.18.0.tgz#868d00b447191816d5d5cf24b1cafa144017922b"
|
||||
integrity sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw==
|
||||
dependencies:
|
||||
"@motionone/easing" "^10.18.0"
|
||||
"@motionone/types" "^10.17.1"
|
||||
"@motionone/utils" "^10.18.0"
|
||||
tslib "^2.3.1"
|
||||
|
||||
"@motionone/dom@10.12.0":
|
||||
version "10.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@motionone/dom/-/dom-10.12.0.tgz#ae30827fd53219efca4e1150a5ff2165c28351ed"
|
||||
integrity sha512-UdPTtLMAktHiqV0atOczNYyDd/d8Cf5fFsd1tua03PqTwwCe/6lwhLSQ8a7TbnQ5SN0gm44N1slBfj+ORIhrqw==
|
||||
dependencies:
|
||||
"@motionone/animation" "^10.12.0"
|
||||
"@motionone/generators" "^10.12.0"
|
||||
"@motionone/types" "^10.12.0"
|
||||
"@motionone/utils" "^10.12.0"
|
||||
hey-listen "^1.0.8"
|
||||
tslib "^2.3.1"
|
||||
|
||||
"@motionone/easing@^10.18.0":
|
||||
version "10.18.0"
|
||||
resolved "https://registry.yarnpkg.com/@motionone/easing/-/easing-10.18.0.tgz#7b82f6010dfee3a1bb0ee83abfbaff6edae0c708"
|
||||
integrity sha512-VcjByo7XpdLS4o9T8t99JtgxkdMcNWD3yHU/n6CLEz3bkmKDRZyYQ/wmSf6daum8ZXqfUAgFeCZSpJZIMxaCzg==
|
||||
dependencies:
|
||||
"@motionone/utils" "^10.18.0"
|
||||
tslib "^2.3.1"
|
||||
|
||||
"@motionone/generators@^10.12.0":
|
||||
version "10.18.0"
|
||||
resolved "https://registry.yarnpkg.com/@motionone/generators/-/generators-10.18.0.tgz#fe09ab5cfa0fb9a8884097feb7eb60abeb600762"
|
||||
integrity sha512-+qfkC2DtkDj4tHPu+AFKVfR/C30O1vYdvsGYaR13W/1cczPrrcjdvYCj0VLFuRMN+lP1xvpNZHCRNM4fBzn1jg==
|
||||
dependencies:
|
||||
"@motionone/types" "^10.17.1"
|
||||
"@motionone/utils" "^10.18.0"
|
||||
tslib "^2.3.1"
|
||||
|
||||
"@motionone/types@^10.12.0", "@motionone/types@^10.17.1":
|
||||
version "10.17.1"
|
||||
resolved "https://registry.yarnpkg.com/@motionone/types/-/types-10.17.1.tgz#cf487badbbdc9da0c2cb86ffc1e5d11147c6e6fb"
|
||||
integrity sha512-KaC4kgiODDz8hswCrS0btrVrzyU2CSQKO7Ps90ibBVSQmjkrt2teqta6/sOG59v7+dPnKMAg13jyqtMKV2yJ7A==
|
||||
|
||||
"@motionone/utils@^10.12.0", "@motionone/utils@^10.18.0":
|
||||
version "10.18.0"
|
||||
resolved "https://registry.yarnpkg.com/@motionone/utils/-/utils-10.18.0.tgz#a59ff8932ed9009624bca07c56b28ef2bb2f885e"
|
||||
integrity sha512-3XVF7sgyTSI2KWvTf6uLlBJ5iAgRgmvp3bpuOiQJvInd4nZ19ET8lX5unn30SlmRH7hXbBbH+Gxd0m0klJ3Xtw==
|
||||
dependencies:
|
||||
"@motionone/types" "^10.17.1"
|
||||
hey-listen "^1.0.8"
|
||||
tslib "^2.3.1"
|
||||
|
||||
"@nodelib/fs.scandir@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
||||
@@ -1096,6 +1210,33 @@
|
||||
"@supabase/realtime-js" "2.11.2"
|
||||
"@supabase/storage-js" "2.7.1"
|
||||
|
||||
"@svgdotjs/svg.draggable.js@^3.0.4":
|
||||
version "3.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@svgdotjs/svg.draggable.js/-/svg.draggable.js-3.0.5.tgz#804fa627cbe850a137350009bd03d2c8423b39a7"
|
||||
integrity sha512-ljL/fB0tAjRfFOJGhXpr7rEx9DJ6D7Pxt3AXvgxjEM17g6wK3Ho9nXhntraOMx8JLZdq4NBMjokeXMvnQzJVYA==
|
||||
|
||||
"@svgdotjs/svg.filter.js@^3.0.8":
|
||||
version "3.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@svgdotjs/svg.filter.js/-/svg.filter.js-3.0.8.tgz#998cb2481a871fa70d7dbaa891c886b335c562d7"
|
||||
integrity sha512-YshF2YDaeRA2StyzAs5nUPrev7npQ38oWD0eTRwnsciSL2KrRPMoUw8BzjIXItb3+dccKGTX3IQOd2NFzmHkog==
|
||||
dependencies:
|
||||
"@svgdotjs/svg.js" "^3.1.1"
|
||||
|
||||
"@svgdotjs/svg.js@^3.1.1", "@svgdotjs/svg.js@^3.2.4":
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/@svgdotjs/svg.js/-/svg.js-3.2.4.tgz#4716be92a64c66b29921b63f7235fcfb953fb13a"
|
||||
integrity sha512-BjJ/7vWNowlX3Z8O4ywT58DqbNRyYlkk6Yz/D13aB7hGmfQTvGX4Tkgtm/ApYlu9M7lCQi15xUEidqMUmdMYwg==
|
||||
|
||||
"@svgdotjs/svg.resize.js@^2.0.2":
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@svgdotjs/svg.resize.js/-/svg.resize.js-2.0.5.tgz#732e4cae15d09ad3021adeac63bc9fad0dc7255a"
|
||||
integrity sha512-4heRW4B1QrJeENfi7326lUPYBCevj78FJs8kfeDxn5st0IYPIRXoTtOSYvTzFWgaWWXd3YCDE6ao4fmv91RthA==
|
||||
|
||||
"@svgdotjs/svg.select.js@^4.0.1":
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@svgdotjs/svg.select.js/-/svg.select.js-4.0.2.tgz#80a10409e6c73206218690eac5c9f94f8c8909b5"
|
||||
integrity sha512-5gWdrvoQX3keo03SCmgaBbD+kFftq0F/f2bzCbNnpkkvW6tk4rl4MakORzFuNjvXPWwB4az9GwuvVxQVnjaK2g==
|
||||
|
||||
"@types/babel__core@^7.20.5":
|
||||
version "7.20.5"
|
||||
resolved "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
|
||||
@@ -1268,6 +1409,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d"
|
||||
integrity sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==
|
||||
|
||||
"@yr/monotone-cubic-spline@^1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz#7272d89f8e4f6fb7a1600c28c378cc18d3b577b9"
|
||||
integrity sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==
|
||||
|
||||
acorn-jsx@^5.3.2:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
||||
@@ -1398,6 +1544,18 @@ anymatch@~3.1.2:
|
||||
normalize-path "^3.0.0"
|
||||
picomatch "^2.0.4"
|
||||
|
||||
apexcharts@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-4.3.0.tgz#eccf28e830ce1b5e018cfc0e99d1c6af0076c9c7"
|
||||
integrity sha512-PfvZQpv91T68hzry9l5zP3Gip7sQvF0nFK91uCBrswIKX7rbIdbVNS4fOks9m9yP3Ppgs6LHgU2M/mjoG4NM0A==
|
||||
dependencies:
|
||||
"@svgdotjs/svg.draggable.js" "^3.0.4"
|
||||
"@svgdotjs/svg.filter.js" "^3.0.8"
|
||||
"@svgdotjs/svg.js" "^3.2.4"
|
||||
"@svgdotjs/svg.resize.js" "^2.0.2"
|
||||
"@svgdotjs/svg.select.js" "^4.0.1"
|
||||
"@yr/monotone-cubic-spline" "^1.0.3"
|
||||
|
||||
arg@^5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c"
|
||||
@@ -1408,6 +1566,13 @@ argparse@^2.0.1:
|
||||
resolved "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
|
||||
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
||||
|
||||
aria-hidden@^1.1.3:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.4.tgz#b78e383fdbc04d05762c78b4a25a501e736c4522"
|
||||
integrity sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==
|
||||
dependencies:
|
||||
tslib "^2.0.0"
|
||||
|
||||
array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmmirror.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b"
|
||||
@@ -1655,6 +1820,11 @@ chokidar@^4.0.0:
|
||||
dependencies:
|
||||
readdirp "^4.0.1"
|
||||
|
||||
classnames@2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
|
||||
integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
|
||||
|
||||
classnames@2.x, classnames@^2.2.1, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2, classnames@^2.5.1:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
|
||||
@@ -1889,6 +2059,11 @@ deep-is@^0.1.3:
|
||||
resolved "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
||||
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
|
||||
|
||||
deepmerge@4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
|
||||
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
|
||||
|
||||
define-data-property@^1.0.1, define-data-property@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
|
||||
@@ -2403,6 +2578,27 @@ fraction.js@^4.3.7:
|
||||
resolved "https://registry.npmmirror.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
|
||||
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
|
||||
|
||||
framer-motion@6.5.1:
|
||||
version "6.5.1"
|
||||
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-6.5.1.tgz#802448a16a6eb764124bf36d8cbdfa6dd6b931a7"
|
||||
integrity sha512-o1BGqqposwi7cgDrtg0dNONhkmPsUFDaLcKXigzuTFC5x58mE8iyTazxSudFzmT6MEyJKfjjU8ItoMe3W+3fiw==
|
||||
dependencies:
|
||||
"@motionone/dom" "10.12.0"
|
||||
framesync "6.0.1"
|
||||
hey-listen "^1.0.8"
|
||||
popmotion "11.0.3"
|
||||
style-value-types "5.0.0"
|
||||
tslib "^2.1.0"
|
||||
optionalDependencies:
|
||||
"@emotion/is-prop-valid" "^0.8.2"
|
||||
|
||||
framesync@6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/framesync/-/framesync-6.0.1.tgz#5e32fc01f1c42b39c654c35b16440e07a25d6f20"
|
||||
integrity sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
@@ -2581,6 +2777,16 @@ hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2:
|
||||
dependencies:
|
||||
function-bind "^1.1.2"
|
||||
|
||||
heroicons@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/heroicons/-/heroicons-2.2.0.tgz#f1f554155152b4ec4d1b7165363d7c583690f77d"
|
||||
integrity sha512-yOwvztmNiBWqR946t+JdgZmyzEmnRMC2nxvHFC90bF1SUttwB6yJKYeme1JeEcBfobdOs827nCyiWBS2z/brog==
|
||||
|
||||
hey-listen@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
|
||||
integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==
|
||||
|
||||
highlight.js@^11.10.0:
|
||||
version "11.11.1"
|
||||
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.11.1.tgz#fca06fa0e5aeecf6c4d437239135fabc15213585"
|
||||
@@ -3033,6 +3239,11 @@ lru-cache@^5.1.1:
|
||||
dependencies:
|
||||
yallist "^3.0.2"
|
||||
|
||||
material-ripple-effects@2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/material-ripple-effects/-/material-ripple-effects-2.0.1.tgz#47803d2ab1561698d930e2524a7a9a19fb2829b7"
|
||||
integrity sha512-hHlUkZAuXbP94lu02VgrPidbZ3hBtgXBtjlwR8APNqOIgDZMV8MCIcsclL8FmGJQHvnORyvoQgC965vPsiyXLQ==
|
||||
|
||||
math-intrinsics@^1.0.0, math-intrinsics@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
|
||||
@@ -3302,6 +3513,16 @@ pirates@^4.0.1:
|
||||
resolved "https://registry.npmmirror.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9"
|
||||
integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==
|
||||
|
||||
popmotion@11.0.3:
|
||||
version "11.0.3"
|
||||
resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-11.0.3.tgz#565c5f6590bbcddab7a33a074bb2ba97e24b0cc9"
|
||||
integrity sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==
|
||||
dependencies:
|
||||
framesync "6.0.1"
|
||||
hey-listen "^1.0.8"
|
||||
style-value-types "5.0.0"
|
||||
tslib "^2.1.0"
|
||||
|
||||
possible-typed-array-names@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f"
|
||||
@@ -3374,7 +3595,7 @@ prelude-ls@^1.2.1:
|
||||
resolved "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||
|
||||
prop-types@^15.5.10, prop-types@^15.6.2, prop-types@^15.8.1:
|
||||
prop-types@15.8.1, prop-types@^15.5.10, prop-types@^15.6.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
@@ -3771,6 +3992,21 @@ rc-virtual-list@^3.14.2, rc-virtual-list@^3.5.1, rc-virtual-list@^3.5.2:
|
||||
rc-resize-observer "^1.0.0"
|
||||
rc-util "^5.36.0"
|
||||
|
||||
react-apexcharts@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/react-apexcharts/-/react-apexcharts-1.7.0.tgz#bbd08425674224adb27c9f2c62477d43bd5de539"
|
||||
integrity sha512-03oScKJyNLRf0Oe+ihJxFZliBQM9vW3UWwomVn4YVRTN1jsIR58dLWt0v1sb8RwJVHDMbeHiKQueM0KGpn7nOA==
|
||||
dependencies:
|
||||
prop-types "^15.8.1"
|
||||
|
||||
react-dom@18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
|
||||
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
scheduler "^0.23.0"
|
||||
|
||||
react-dom@^18.2.0:
|
||||
version "18.3.1"
|
||||
resolved "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
|
||||
@@ -3911,6 +4147,13 @@ react-use@^17.6.0:
|
||||
ts-easing "^0.2.0"
|
||||
tslib "^2.1.0"
|
||||
|
||||
react@18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
|
||||
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
react@^18.2.0:
|
||||
version "18.3.1"
|
||||
resolved "https://registry.npmmirror.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"
|
||||
@@ -3951,9 +4194,9 @@ recharts-scale@^0.4.4:
|
||||
dependencies:
|
||||
decimal.js-light "^2.4.1"
|
||||
|
||||
recharts@^2.9.0:
|
||||
recharts@^2.15.0:
|
||||
version "2.15.0"
|
||||
resolved "https://registry.npmmirror.com/recharts/-/recharts-2.15.0.tgz#0b77bff57a43885df9769ae649a14cb1a7fe19aa"
|
||||
resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.15.0.tgz#0b77bff57a43885df9769ae649a14cb1a7fe19aa"
|
||||
integrity sha512-cIvMxDfpAmqAmVgc4yb7pgm/O1tmmkl/CjrvXuW+62/+7jj/iF9Ykm+hb/UJt42TREHMyd3gb+pkgoa2MxgDIw==
|
||||
dependencies:
|
||||
clsx "^2.0.0"
|
||||
@@ -3999,6 +4242,11 @@ regexp.prototype.flags@^1.5.3:
|
||||
es-errors "^1.3.0"
|
||||
set-function-name "^2.0.2"
|
||||
|
||||
remixicon@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/remixicon/-/remixicon-4.6.0.tgz#f2adafd18aaa983d61f8c9f7274fa17706da4ddc"
|
||||
integrity sha512-bKM5odjqE1yzVxEZGJE7F79WHhNrJFIKHXR+GG+P1IWXn8AnJZhl8SbIRDJsNAvIqx4VPkNwjuHfc42tutMDpQ==
|
||||
|
||||
resize-observer-polyfill@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
||||
@@ -4101,7 +4349,7 @@ sass@^1.69.5:
|
||||
optionalDependencies:
|
||||
"@parcel/watcher" "^2.4.1"
|
||||
|
||||
scheduler@^0.23.2:
|
||||
scheduler@^0.23.0, scheduler@^0.23.2:
|
||||
version "0.23.2"
|
||||
resolved "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3"
|
||||
integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==
|
||||
@@ -4390,6 +4638,14 @@ strip-json-comments@^3.1.1:
|
||||
resolved "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
|
||||
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
|
||||
|
||||
style-value-types@5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-5.0.0.tgz#76c35f0e579843d523187989da866729411fc8ad"
|
||||
integrity sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==
|
||||
dependencies:
|
||||
hey-listen "^1.0.8"
|
||||
tslib "^2.1.0"
|
||||
|
||||
styled-components@^6.1.0:
|
||||
version "6.1.13"
|
||||
resolved "https://registry.npmmirror.com/styled-components/-/styled-components-6.1.13.tgz#2d777750b773b31469bd79df754a32479e9f475e"
|
||||
@@ -4453,11 +4709,16 @@ swr@^2.0.0, swr@^2.2.5:
|
||||
dequal "^2.0.3"
|
||||
use-sync-external-store "^1.4.0"
|
||||
|
||||
tabbable@^6.0.0:
|
||||
tabbable@^6.0.0, tabbable@^6.0.1:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97"
|
||||
integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==
|
||||
|
||||
tailwind-merge@1.8.1:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-1.8.1.tgz#0e56c8afbab2491f72e06381043ffec8b720ba04"
|
||||
integrity sha512-+fflfPxvHFr81hTJpQ3MIwtqgvefHZFUHFiIHpVIRXvG/nX9+gu2P7JNlFu2bfDMJ+uHhi/pUgzaYacMoXv+Ww==
|
||||
|
||||
tailwindcss@^3.3.5:
|
||||
version "3.4.17"
|
||||
resolved "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.17.tgz#ae8406c0f96696a631c790768ff319d46d5e5a63"
|
||||
@@ -4574,7 +4835,7 @@ tslib@2.6.2:
|
||||
resolved "https://registry.npmmirror.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
|
||||
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
|
||||
|
||||
tslib@^2.0.0, tslib@^2.1.0:
|
||||
tslib@^2.0.0, tslib@^2.1.0, tslib@^2.3.1:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
|
||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||
|
||||
Reference in New Issue
Block a user