Переглянути джерело

修改chore: 删除所有 UI 组件文件

- 移除了 Accordion、Alert、AlertDialog、AspectRatio、Avatar
nahida 10 місяців тому
батько
коміт
b444013289
83 змінених файлів з 1430 додано та 8065 видалено
  1. 13 32
      app/(business)/monitor/job-log/index/[jobid]/page.tsx
  2. 16 57
      app/(business)/monitor/job/page.tsx
  3. 13 21
      app/(business)/monitor/online/page.tsx
  4. 10 19
      app/(business)/system/config/page.tsx
  5. 10 19
      app/(business)/system/dept/page.tsx
  6. 11 27
      app/(business)/system/dict-data/index/[dictid]/page.tsx
  7. 10 19
      app/(business)/system/dict/page.tsx
  8. 11 27
      app/(business)/system/log/logininfor/page.tsx
  9. 13 33
      app/(business)/system/log/operlog/page.tsx
  10. 11 20
      app/(business)/system/menu/page.tsx
  11. 3 3
      app/(business)/system/notice/page.tsx
  12. 11 27
      app/(business)/system/post/page.tsx
  13. 16 72
      app/(business)/system/role/auth/[roleid]/page.tsx
  14. 12 33
      app/(business)/system/role/page.tsx
  15. 9 13
      app/(business)/system/user/page.tsx
  16. 0 11
      app/(business)/test1/page.tsx
  17. 1069 0
      app/(other)/test5/page.tsx
  18. BIN
      app/favicon.ico
  19. 22 32
      app/login/page.tsx
  20. 0 457
      components/alerts/alerts-dashboard.tsx
  21. 87 0
      components/echarts.tsx
  22. 0 203
      components/infra/infra-dashboard.tsx
  23. 0 99
      components/map/map-distribution.tsx
  24. 0 169
      components/monitoring/monitoring-dashboard.tsx
  25. 0 261
      components/overview/overview-dashboard.tsx
  26. 0 51
      components/shared/metric-card.tsx
  27. 0 27
      components/shared/section.tsx
  28. 0 8
      components/theme-provider.tsx
  29. 0 66
      components/ui/accordion.tsx
  30. 0 157
      components/ui/alert-dialog.tsx
  31. 0 66
      components/ui/alert.tsx
  32. 0 11
      components/ui/aspect-ratio.tsx
  33. 0 53
      components/ui/avatar.tsx
  34. 0 46
      components/ui/badge.tsx
  35. 0 109
      components/ui/breadcrumb.tsx
  36. 0 59
      components/ui/button.tsx
  37. 0 209
      components/ui/calendar.tsx
  38. 0 92
      components/ui/card.tsx
  39. 0 239
      components/ui/carousel.tsx
  40. 0 353
      components/ui/chart.tsx
  41. 0 32
      components/ui/checkbox.tsx
  42. 0 33
      components/ui/collapsible.tsx
  43. 0 178
      components/ui/command.tsx
  44. 0 252
      components/ui/context-menu.tsx
  45. 0 143
      components/ui/dialog.tsx
  46. 0 135
      components/ui/drawer.tsx
  47. 0 257
      components/ui/dropdown-menu.tsx
  48. 0 167
      components/ui/form.tsx
  49. 0 44
      components/ui/hover-card.tsx
  50. 0 77
      components/ui/input-otp.tsx
  51. 0 21
      components/ui/input.tsx
  52. 0 24
      components/ui/label.tsx
  53. 0 276
      components/ui/menubar.tsx
  54. 0 168
      components/ui/navigation-menu.tsx
  55. 0 123
      components/ui/pagination.tsx
  56. 0 48
      components/ui/popover.tsx
  57. 0 31
      components/ui/progress.tsx
  58. 0 45
      components/ui/radio-group.tsx
  59. 0 56
      components/ui/resizable.tsx
  60. 0 58
      components/ui/scroll-area.tsx
  61. 0 185
      components/ui/select.tsx
  62. 0 28
      components/ui/separator.tsx
  63. 0 139
      components/ui/sheet.tsx
  64. 0 715
      components/ui/sidebar.tsx
  65. 0 13
      components/ui/skeleton.tsx
  66. 0 63
      components/ui/slider.tsx
  67. 0 25
      components/ui/sonner.tsx
  68. 0 31
      components/ui/switch.tsx
  69. 0 116
      components/ui/table.tsx
  70. 0 66
      components/ui/tabs.tsx
  71. 0 18
      components/ui/textarea.tsx
  72. 0 129
      components/ui/toast.tsx
  73. 0 28
      components/ui/toaster.tsx
  74. 0 73
      components/ui/toggle-group.tsx
  75. 0 47
      components/ui/toggle.tsx
  76. 0 61
      components/ui/tooltip.tsx
  77. 0 19
      components/ui/use-mobile.tsx
  78. 0 191
      components/ui/use-toast.ts
  79. 0 19
      hooks/use-mobile.ts
  80. 0 191
      hooks/use-toast.ts
  81. 0 6
      lib/utils.ts
  82. 77 530
      package-lock.json
  83. 6 4
      package.json

+ 13 - 32
app/(business)/monitor/job-log/index/[jobid]/page.tsx

@@ -1,35 +1,16 @@
 "use client";
 
-import { fetchApi, fetchFile } from "@/app/_modules/func";
-import {
-  DeleteOutlined,
-  ExclamationCircleFilled,
-  EyeOutlined,
-  ReloadOutlined,
-} from "@ant-design/icons";
-import type {
-  ActionType,
-  ProColumns,
-  ProFormInstance,
-} from "@ant-design/pro-components";
-import {
-  PageContainer,
-  ProDescriptions,
-  ProTable,
-} from "@ant-design/pro-components";
-import { Button, message, Modal, Space, Tag } from "antd";
-import { useRouter } from "next/navigation";
-
-import {
-  faCheck,
-  faDownload,
-  faToggleOff,
-  faToggleOn,
-  faXmark,
-} from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-
-import { useRef, useState } from "react";
+import {fetchApi, fetchFile} from "@/app/_modules/func";
+import {DeleteOutlined, ExclamationCircleFilled, EyeOutlined, ReloadOutlined,} from "@ant-design/icons";
+import type {ActionType, ProColumns, ProFormInstance,} from "@ant-design/pro-components";
+import {PageContainer, ProDescriptions, ProTable,} from "@ant-design/pro-components";
+import {Button, message, Modal, Space, Tag} from "antd";
+import {useRouter} from "next/navigation";
+
+import {faCheck, faDownload, faToggleOff, faToggleOn, faXmark,} from "@fortawesome/free-solid-svg-icons";
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
+
+import {useRef, useState} from "react";
 
 //查询Job详情
 const queryJobAPI = "/api/monitor/job";
@@ -355,9 +336,9 @@ export default function JobLog({ params }: { params: { jobid: string } }) {
   //搜索栏显示状态
   const [showSearch, setShowSearch] = useState(true);
   //action对象引用
-  const actionTableRef = useRef<ActionType>();
+  const actionTableRef = useRef<ActionType>(null);
   //搜索表单对象引用
-  const searchTableFormRef = useRef<ProFormInstance>();
+  const searchTableFormRef = useRef<ProFormInstance>(null!);
   //当前页数和每页条数
   const [page, setPage] = useState(1);
   const defaultPageSize = 10;

+ 16 - 57
app/(business)/monitor/job/page.tsx

@@ -1,83 +1,42 @@
 "use client";
 
-import { fetchApi, fetchFile } from "@/app/_modules/func";
+import {fetchApi, fetchFile} from "@/app/_modules/func";
 import {
   CaretDownOutlined,
   CheckOutlined,
+  ClockCircleOutlined,
   CloseOutlined,
   DeleteOutlined,
   ExclamationCircleFilled,
   EyeOutlined,
+  PlayCircleOutlined,
   PlusOutlined,
   ReloadOutlined,
-  SearchOutlined,
-  KeyOutlined,
-  LoadingOutlined,
-  CloudUploadOutlined,
-  FileAddOutlined,
-  ClockCircleOutlined,
   ScheduleOutlined,
-  PlayCircleOutlined,
 } from "@ant-design/icons";
-import type {
-  ProColumns,
-  ProFormInstance,
-  ActionType,
-} from "@ant-design/pro-components";
+import type {ActionType, ProColumns, ProFormInstance,} from "@ant-design/pro-components";
 import {
   ModalForm,
   PageContainer,
-  ProCard,
+  ProDescriptions,
   ProForm,
   ProFormRadio,
   ProFormSelect,
   ProFormText,
-  ProFormTextArea,
-  ProFormTreeSelect,
   ProTable,
-  ProDescriptions,
 } from "@ant-design/pro-components";
-import type { TreeDataNode, MenuProps, UploadProps, GetProp } from "antd";
-import {
-  Button,
-  Col,
-  Flex,
-  Input,
-  message,
-  Modal,
-  Row,
-  Space,
-  Spin,
-  Switch,
-  Tree,
-  Dropdown,
-  Form,
-  Upload,
-  Typography,
-  Checkbox,
-  Tag,
-} from "antd";
-import { useRouter } from "next/navigation";
+import {Button, Dropdown, Input, message, Modal, Space, Switch,} from "antd";
+import {useRouter} from "next/navigation";
 
-import {
-  faDownload,
-  faPenToSquare,
-  faToggleOff,
-  faToggleOn,
-  faUpload,
-  faUsers,
-  faCheck,
-  faXmark,
-} from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {faDownload, faPenToSquare, faToggleOff, faToggleOn,} from "@fortawesome/free-solid-svg-icons";
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
 
 import "./styles.scss";
 
-import { ReQuartzCron, ReUnixCron, CronLocalization } from "@sbzen/re-cron";
+import {ReQuartzCron} from "@sbzen/re-cron";
 
-import { useEffect, useMemo, useRef, useState } from "react";
-import { Divider } from "@/node_modules/antd/es/index";
-import { MortnonCronLocalization } from "@/app/_modules/definies";
+import {useRef, useState} from "react";
+import {MortnonCronLocalization} from "@/app/_modules/definies";
 
 //查询表格数据API
 const queryAPI = "/api/monitor/job/list";
@@ -283,7 +242,7 @@ export default function Job() {
   //1.新建
 
   //新建对话框表单引用
-  const addFormRef = useRef<ProFormInstance>();
+  const addFormRef = useRef<ProFormInstance>(null);
 
   //确定新建数据
   const executeAddData = async (values: any) => {
@@ -374,7 +333,7 @@ export default function Job() {
   };
 
   //修改数据表单引用
-  const modifyFormRef = useRef<ProFormInstance>();
+  const modifyFormRef = useRef<ProFormInstance>(null);
 
   //操作当前数据的附加数据
   const [operatRowData, setOperateRowData] = useState<{
@@ -547,9 +506,9 @@ export default function Job() {
   //搜索栏显示状态
   const [showSearch, setShowSearch] = useState(true);
   //action对象引用
-  const actionTableRef = useRef<ActionType>();
+  const actionTableRef = useRef<ActionType>(null);
   //搜索表单对象引用
-  const searchTableFormRef = useRef<ProFormInstance>();
+  const searchTableFormRef = useRef<ProFormInstance>(null!);
   //当前页数和每页条数
   const [page, setPage] = useState(1);
   const defaultPageSize = 10;

+ 13 - 21
app/(business)/monitor/online/page.tsx

@@ -1,24 +1,16 @@
 "use client";
 
-import { fetchApi } from "@/app/_modules/func";
-import {
-  DeleteOutlined,
-  ExclamationCircleFilled,
-  ReloadOutlined,
-} from "@ant-design/icons";
-import type {
-  ActionType,
-  ProColumns,
-  ProFormInstance,
-} from "@ant-design/pro-components";
-import { PageContainer, ProTable } from "@ant-design/pro-components";
-import { Button, Modal, message } from "antd";
-import { useRouter } from "next/navigation";
-
-import { faToggleOff, faToggleOn } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-
-import { useRef, useState } from "react";
+import {fetchApi} from "@/app/_modules/func";
+import {DeleteOutlined, ExclamationCircleFilled, ReloadOutlined,} from "@ant-design/icons";
+import type {ActionType, ProColumns, ProFormInstance,} from "@ant-design/pro-components";
+import {PageContainer, ProTable} from "@ant-design/pro-components";
+import {Button, message, Modal} from "antd";
+import {useRouter} from "next/navigation";
+
+import {faToggleOff, faToggleOn} from "@fortawesome/free-solid-svg-icons";
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
+
+import {useRef, useState} from "react";
 
 //查询表格数据API
 const queryAPI = "/api/monitor/online/list";
@@ -162,9 +154,9 @@ export default function Online() {
   //搜索栏显示状态
   const [showSearch, setShowSearch] = useState(true);
   //action对象引用
-  const actionTableRef = useRef<ActionType>();
+  const actionTableRef = useRef<ActionType>(null);
   //搜索表单对象引用
-  const searchTableFormRef = useRef<ProFormInstance>();
+  const searchTableFormRef = useRef<ProFormInstance>(null!);
   //当前页数和每页条数
   const [page, setPage] = useState(1);
   const defaultPageSize = 10;

+ 10 - 19
app/(business)/system/config/page.tsx

@@ -1,17 +1,8 @@
 "use client";
 
-import { fetchApi, fetchFile } from "@/app/_modules/func";
-import {
-  DeleteOutlined,
-  ExclamationCircleFilled,
-  PlusOutlined,
-  ReloadOutlined,
-} from "@ant-design/icons";
-import type {
-  ActionType,
-  ProColumns,
-  ProFormInstance,
-} from "@ant-design/pro-components";
+import {fetchApi, fetchFile} from "@/app/_modules/func";
+import {DeleteOutlined, ExclamationCircleFilled, PlusOutlined, ReloadOutlined,} from "@ant-design/icons";
+import type {ActionType, ProColumns, ProFormInstance,} from "@ant-design/pro-components";
 import {
   ModalForm,
   PageContainer,
@@ -21,8 +12,8 @@ import {
   ProFormTextArea,
   ProTable,
 } from "@ant-design/pro-components";
-import { Button, message, Modal, Space, Tag } from "antd";
-import { useRouter } from "next/navigation";
+import {Button, message, Modal, Space, Tag} from "antd";
+import {useRouter} from "next/navigation";
 
 import {
   faCheck,
@@ -33,9 +24,9 @@ import {
   faToggleOn,
   faXmark,
 } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
 
-import { useRef, useState } from "react";
+import {useRef, useState} from "react";
 
 //查询表格数据API
 const queryAPI = "/api/system/config/list";
@@ -246,7 +237,7 @@ export default function Config() {
   };
 
   //修改数据表单引用
-  const modifyFormRef = useRef<ProFormInstance>();
+  const modifyFormRef = useRef<ProFormInstance>(null);
 
   //操作当前数据的附加数据
   const [operatRowData, setOperateRowData] = useState<{
@@ -416,9 +407,9 @@ export default function Config() {
   //搜索栏显示状态
   const [showSearch, setShowSearch] = useState(true);
   //action对象引用
-  const actionTableRef = useRef<ActionType>();
+  const actionTableRef = useRef<ActionType>(null);
   //搜索表单对象引用
-  const searchTableFormRef = useRef<ProFormInstance>();
+  const searchTableFormRef = useRef<ProFormInstance>(null!);
   //当前页数和每页条数
   const [page, setPage] = useState(1);
   const defaultPageSize = 10;

+ 10 - 19
app/(business)/system/dept/page.tsx

@@ -1,17 +1,8 @@
 "use client";
 
-import { fetchApi } from "@/app/_modules/func";
-import {
-  DeleteOutlined,
-  ExclamationCircleFilled,
-  PlusOutlined,
-  ReloadOutlined,
-} from "@ant-design/icons";
-import type {
-  ActionType,
-  ProColumns,
-  ProFormInstance,
-} from "@ant-design/pro-components";
+import {fetchApi} from "@/app/_modules/func";
+import {DeleteOutlined, ExclamationCircleFilled, PlusOutlined, ReloadOutlined,} from "@ant-design/icons";
+import type {ActionType, ProColumns, ProFormInstance,} from "@ant-design/pro-components";
 import {
   ModalForm,
   PageContainer,
@@ -22,8 +13,8 @@ import {
   ProFormTreeSelect,
   ProTable,
 } from "@ant-design/pro-components";
-import { Button, message, Modal, Space, Tag } from "antd";
-import { useRouter } from "next/navigation";
+import {Button, message, Modal, Space, Tag} from "antd";
+import {useRouter} from "next/navigation";
 
 import {
   faArrowsUpDown,
@@ -33,9 +24,9 @@ import {
   faToggleOn,
   faXmark,
 } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
 
-import { useRef, useState } from "react";
+import {useRef, useState} from "react";
 
 //查询表格数据API
 const queryAPI = "/api/system/dept/list";
@@ -296,7 +287,7 @@ export default function Dept() {
   };
 
   //修改数据表单引用
-  const modifyFormRef = useRef<ProFormInstance>();
+  const modifyFormRef = useRef<ProFormInstance>(null);
 
   //操作当前数据的附加数据
   const [operatRowData, setOperateRowData] = useState<{
@@ -392,9 +383,9 @@ export default function Dept() {
   //搜索栏显示状态
   const [showSearch, setShowSearch] = useState(true);
   //action对象引用
-  const actionTableRef = useRef<ActionType>();
+  const actionTableRef = useRef<ActionType>(null);
   //搜索表单对象引用
-  const searchTableFormRef = useRef<ProFormInstance>();
+  const searchTableFormRef = useRef<ProFormInstance>(null!);
 
   const getDeptList = async () => {
     const body = await fetchApi(queryAPI, push);

+ 11 - 27
app/(business)/system/dict-data/index/[dictid]/page.tsx

@@ -1,17 +1,8 @@
 "use client";
 
-import { fetchApi, fetchFile } from "@/app/_modules/func";
-import {
-  DeleteOutlined,
-  ExclamationCircleFilled,
-  PlusOutlined,
-  ReloadOutlined,
-} from "@ant-design/icons";
-import type {
-  ActionType,
-  ProColumns,
-  ProFormInstance,
-} from "@ant-design/pro-components";
+import {fetchApi, fetchFile} from "@/app/_modules/func";
+import {DeleteOutlined, ExclamationCircleFilled, PlusOutlined, ReloadOutlined,} from "@ant-design/icons";
+import type {ActionType, ProColumns, ProFormInstance,} from "@ant-design/pro-components";
 import {
   ModalForm,
   PageContainer,
@@ -23,20 +14,13 @@ import {
   ProFormTextArea,
   ProTable,
 } from "@ant-design/pro-components";
-import { Button, message, Modal, Space, Tag } from "antd";
-import { useRouter } from "next/navigation";
+import {Button, message, Modal, Space, Tag} from "antd";
+import {useRouter} from "next/navigation";
 
-import {
-  faCheck,
-  faDownload,
-  faPenToSquare,
-  faToggleOff,
-  faToggleOn,
-  faXmark,
-} from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {faCheck, faDownload, faPenToSquare, faToggleOff, faToggleOn, faXmark,} from "@fortawesome/free-solid-svg-icons";
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
 
-import { useRef, useState } from "react";
+import {useRef, useState} from "react";
 
 //查询类型详情
 const queryTypeAPI = "/api/system/dict/type";
@@ -313,7 +297,7 @@ export default function DictData({ params }: { params: { dictid: string } }) {
   };
 
   //修改数据表单引用
-  const modifyFormRef = useRef<ProFormInstance>();
+  const modifyFormRef = useRef<ProFormInstance>(null);
 
   //操作当前数据的附加数据
   const [operatRowData, setOperateRowData] = useState<{
@@ -486,9 +470,9 @@ export default function DictData({ params }: { params: { dictid: string } }) {
   //搜索栏显示状态
   const [showSearch, setShowSearch] = useState(true);
   //action对象引用
-  const actionTableRef = useRef<ActionType>();
+  const actionTableRef = useRef<ActionType>(null);
   //搜索表单对象引用
-  const searchTableFormRef = useRef<ProFormInstance>();
+  const searchTableFormRef = useRef<ProFormInstance>(null!);
   //当前页数和每页条数
   const [page, setPage] = useState(1);
   const defaultPageSize = 10;

+ 10 - 19
app/(business)/system/dict/page.tsx

@@ -1,17 +1,8 @@
 "use client";
 
-import { fetchApi, fetchFile } from "@/app/_modules/func";
-import {
-  DeleteOutlined,
-  ExclamationCircleFilled,
-  PlusOutlined,
-  ReloadOutlined,
-} from "@ant-design/icons";
-import type {
-  ActionType,
-  ProColumns,
-  ProFormInstance,
-} from "@ant-design/pro-components";
+import {fetchApi, fetchFile} from "@/app/_modules/func";
+import {DeleteOutlined, ExclamationCircleFilled, PlusOutlined, ReloadOutlined,} from "@ant-design/icons";
+import type {ActionType, ProColumns, ProFormInstance,} from "@ant-design/pro-components";
 import {
   ModalForm,
   PageContainer,
@@ -21,8 +12,8 @@ import {
   ProFormTextArea,
   ProTable,
 } from "@ant-design/pro-components";
-import { Button, message, Modal, Space, Tag } from "antd";
-import { useRouter } from "next/navigation";
+import {Button, message, Modal, Space, Tag} from "antd";
+import {useRouter} from "next/navigation";
 
 import {
   faCheck,
@@ -33,9 +24,9 @@ import {
   faToggleOn,
   faXmark,
 } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
 
-import { useRef, useState } from "react";
+import {useRef, useState} from "react";
 
 //查询表格数据API
 const queryAPI = "/api/system/dict/type/list";
@@ -247,7 +238,7 @@ export default function Dict() {
   };
 
   //修改数据表单引用
-  const modifyFormRef = useRef<ProFormInstance>();
+  const modifyFormRef = useRef<ProFormInstance>(null);
 
   //操作当前数据的附加数据
   const [operatRowData, setOperateRowData] = useState<{
@@ -415,9 +406,9 @@ export default function Dict() {
   //搜索栏显示状态
   const [showSearch, setShowSearch] = useState(true);
   //action对象引用
-  const actionTableRef = useRef<ActionType>();
+  const actionTableRef = useRef<ActionType>(null);
   //搜索表单对象引用
-  const searchTableFormRef = useRef<ProFormInstance>();
+  const searchTableFormRef = useRef<ProFormInstance>(null!);
   //当前页数和每页条数
   const [page, setPage] = useState(1);
   const defaultPageSize = 10;

+ 11 - 27
app/(business)/system/log/logininfor/page.tsx

@@ -1,37 +1,21 @@
 "use client";
 
-import { fetchApi, fetchFile } from "@/app/_modules/func";
+import {fetchApi, fetchFile} from "@/app/_modules/func";
 import {
   ClearOutlined,
   DeleteOutlined,
-  EyeOutlined,
-  ImportOutlined,
-  ReloadOutlined,
   ExclamationCircleFilled,
+  ReloadOutlined,
   UnlockOutlined,
 } from "@ant-design/icons";
-import type {
-  ProColumns,
-  ProFormInstance,
-  ActionType,
-} from "@ant-design/pro-components";
-import {
-  PageContainer,
-  ProDescriptions,
-  ProTable,
-} from "@ant-design/pro-components";
-import { Button, Modal, Space, Tag, message } from "antd";
-import { useRouter } from "next/navigation";
-import {
-  faCheck,
-  faToggleOff,
-  faToggleOn,
-  faXmark,
-  faDownload,
-} from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import type {ActionType, ProColumns, ProFormInstance,} from "@ant-design/pro-components";
+import {PageContainer, ProTable,} from "@ant-design/pro-components";
+import {Button, message, Modal, Space, Tag} from "antd";
+import {useRouter} from "next/navigation";
+import {faCheck, faDownload, faToggleOff, faToggleOn, faXmark,} from "@fortawesome/free-solid-svg-icons";
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
 
-import { useRef, useState } from "react";
+import {useRef, useState} from "react";
 
 export default function OperLog() {
   const { push } = useRouter();
@@ -310,9 +294,9 @@ export default function OperLog() {
   //搜索栏显示状态
   const [showSearch, setShowSearch] = useState(true);
   //action对象引用
-  const actionRef = useRef<ActionType>();
+  const actionRef = useRef<ActionType>(null);
   //表单对象引用
-  const formRef = useRef<ProFormInstance>();
+  const formRef = useRef<ProFormInstance>(null!);
 
   //当前页数和每页条数
   const [page, setPage] = useState(1);

+ 13 - 33
app/(business)/system/log/operlog/page.tsx

@@ -1,36 +1,16 @@
 "use client";
 
-import { fetchApi, fetchFile } from "@/app/_modules/func";
-import {
-  ClearOutlined,
-  DeleteOutlined,
-  ExclamationCircleFilled,
-  EyeOutlined,
-  ReloadOutlined,
-} from "@ant-design/icons";
-import type {
-  ActionType,
-  ProColumns,
-  ProFormInstance,
-} from "@ant-design/pro-components";
-import {
-  PageContainer,
-  ProDescriptions,
-  ProTable,
-} from "@ant-design/pro-components";
-import { Button, message, Modal, Space, Tag } from "antd";
-import { useRouter } from "next/navigation";
-
-import {
-  faCheck,
-  faDownload,
-  faToggleOff,
-  faToggleOn,
-  faXmark,
-} from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-
-import { useRef, useState } from "react";
+import {fetchApi, fetchFile} from "@/app/_modules/func";
+import {ClearOutlined, DeleteOutlined, ExclamationCircleFilled, EyeOutlined, ReloadOutlined,} from "@ant-design/icons";
+import type {ActionType, ProColumns, ProFormInstance,} from "@ant-design/pro-components";
+import {PageContainer, ProDescriptions, ProTable,} from "@ant-design/pro-components";
+import {Button, message, Modal, Space, Tag} from "antd";
+import {useRouter} from "next/navigation";
+
+import {faCheck, faDownload, faToggleOff, faToggleOn, faXmark,} from "@fortawesome/free-solid-svg-icons";
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
+
+import {useRef, useState} from "react";
 
 export default function OperLog() {
   const { push } = useRouter();
@@ -343,9 +323,9 @@ export default function OperLog() {
   //搜索栏显示状态
   const [showSearch, setShowSearch] = useState(true);
   //action对象引用
-  const actionRef = useRef<ActionType>();
+  const actionRef = useRef<ActionType>(null);
   //表单对象引用
-  const formRef = useRef<ProFormInstance>();
+  const formRef = useRef<ProFormInstance>(null!);
 
   //当前页数和每页条数
   const [page, setPage] = useState(1);

+ 11 - 20
app/(business)/system/menu/page.tsx

@@ -1,17 +1,8 @@
 "use client";
 
-import { fetchApi } from "@/app/_modules/func";
-import {
-  DeleteOutlined,
-  ExclamationCircleFilled,
-  PlusOutlined,
-  ReloadOutlined,
-} from "@ant-design/icons";
-import type {
-  ActionType,
-  ProColumns,
-  ProFormInstance,
-} from "@ant-design/pro-components";
+import {fetchApi} from "@/app/_modules/func";
+import {DeleteOutlined, ExclamationCircleFilled, PlusOutlined, ReloadOutlined,} from "@ant-design/icons";
+import type {ActionType, ProColumns, ProFormInstance,} from "@ant-design/pro-components";
 import {
   ModalForm,
   PageContainer,
@@ -23,8 +14,8 @@ import {
   ProFormTreeSelect,
   ProTable,
 } from "@ant-design/pro-components";
-import { Button, message, Modal, Space, Tag } from "antd";
-import { useRouter } from "next/navigation";
+import {Button, message, Modal, Space, Tag} from "antd";
+import {useRouter} from "next/navigation";
 
 import {
   faArrowsUpDown,
@@ -34,10 +25,10 @@ import {
   faToggleOn,
   faXmark,
 } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
 
-import { IconMap } from "@/app/_modules/definies";
-import { useRef, useState } from "react";
+import {IconMap} from "@/app/_modules/definies";
+import {useRef, useState} from "react";
 
 //查询表格数据API
 const queryAPI = "/api/system/menu/list";
@@ -287,7 +278,7 @@ export default function Menu() {
   };
 
   //修改数据表单引用
-  const modifyFormRef = useRef<ProFormInstance>();
+  const modifyFormRef = useRef<ProFormInstance>(null);
 
   //操作当前数据的附加数据
   const [operatRowData, setOperateRowData] = useState<{
@@ -388,9 +379,9 @@ export default function Menu() {
   //搜索栏显示状态
   const [showSearch, setShowSearch] = useState(true);
   //action对象引用
-  const actionTableRef = useRef<ActionType>();
+  const actionTableRef = useRef<ActionType>(null);
   //搜索表单对象引用
-  const searchTableFormRef = useRef<ProFormInstance>();
+  const searchTableFormRef = useRef<ProFormInstance>(null!);
 
   const getMenuList = async () => {
     const body = await fetchApi(queryAPI, push);

+ 3 - 3
app/(business)/system/notice/page.tsx

@@ -226,7 +226,7 @@ export default function Notice() {
   };
 
   //修改数据表单引用
-  const modifyFormRef = useRef<ProFormInstance>();
+  const modifyFormRef = useRef<ProFormInstance>(null);
 
   //操作当前数据的附加数据
   const [operatRowData, setOperateRowData] = useState<{
@@ -366,9 +366,9 @@ export default function Notice() {
   //搜索栏显示状态
   const [showSearch, setShowSearch] = useState(true);
   //action对象引用
-  const actionTableRef = useRef<ActionType>();
+  const actionTableRef = useRef<ActionType>(null);
   //搜索表单对象引用
-  const searchTableFormRef = useRef<ProFormInstance>();
+  const searchTableFormRef = useRef<ProFormInstance>(null!);
   //当前页数和每页条数
   const [page, setPage] = useState(1);
   const defaultPageSize = 10;

+ 11 - 27
app/(business)/system/post/page.tsx

@@ -1,17 +1,8 @@
 "use client";
 
-import { fetchApi, fetchFile } from "@/app/_modules/func";
-import {
-  DeleteOutlined,
-  ExclamationCircleFilled,
-  PlusOutlined,
-  ReloadOutlined,
-} from "@ant-design/icons";
-import type {
-  ActionType,
-  ProColumns,
-  ProFormInstance,
-} from "@ant-design/pro-components";
+import {fetchApi, fetchFile} from "@/app/_modules/func";
+import {DeleteOutlined, ExclamationCircleFilled, PlusOutlined, ReloadOutlined,} from "@ant-design/icons";
+import type {ActionType, ProColumns, ProFormInstance,} from "@ant-design/pro-components";
 import {
   ModalForm,
   PageContainer,
@@ -22,20 +13,13 @@ import {
   ProFormTextArea,
   ProTable,
 } from "@ant-design/pro-components";
-import { Button, message, Modal, Space, Tag } from "antd";
-import { useRouter } from "next/navigation";
+import {Button, message, Modal, Space, Tag} from "antd";
+import {useRouter} from "next/navigation";
 
-import {
-  faCheck,
-  faDownload,
-  faPenToSquare,
-  faToggleOff,
-  faToggleOn,
-  faXmark,
-} from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {faCheck, faDownload, faPenToSquare, faToggleOff, faToggleOn, faXmark,} from "@fortawesome/free-solid-svg-icons";
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
 
-import React, { useRef, useState } from "react";
+import React, {useRef, useState} from "react";
 
 //查询表格数据API
 const queryAPI = "/api/system/post/list";
@@ -221,7 +205,7 @@ export default function Post() {
   };
 
   //修改数据表单引用
-  const modifyFormRef = useRef<ProFormInstance>();
+  const modifyFormRef = useRef<ProFormInstance>(null);
 
   //操作当前数据的附加数据
   const [operatRowData, setOperateRowData] = useState<{
@@ -390,9 +374,9 @@ export default function Post() {
   //搜索栏显示状态
   const [showSearch, setShowSearch] = useState(true);
   //action对象引用
-  const actionTableRef = useRef<ActionType>();
+  const actionTableRef = useRef<ActionType>(null);
   //搜索表单对象引用
-  const searchTableFormRef = useRef<ProFormInstance>();
+  const searchTableFormRef = useRef<ProFormInstance>(null!);
   //当前页数和每页条数
   const [page, setPage] = useState(1);
   const defaultPageSize = 10;

+ 16 - 72
app/(business)/system/role/auth/[roleid]/page.tsx

@@ -1,74 +1,18 @@
 "use client";
 
-import { fetchApi, fetchFile } from "@/app/_modules/func";
-import {
-  CaretDownOutlined,
-  CheckOutlined,
-  CloseOutlined,
-  DeleteOutlined,
-  ExclamationCircleFilled,
-  EyeOutlined,
-  PlusOutlined,
-  ReloadOutlined,
-  SearchOutlined,
-  KeyOutlined,
-  LoadingOutlined,
-  CloudUploadOutlined,
-  FileAddOutlined,
-} from "@ant-design/icons";
-import type {
-  ProColumns,
-  ProFormInstance,
-  ActionType,
-} from "@ant-design/pro-components";
-import {
-  ModalForm,
-  PageContainer,
-  ProCard,
-  ProForm,
-  ProFormRadio,
-  ProFormSelect,
-  ProFormText,
-  ProFormTextArea,
-  ProFormTreeSelect,
-  ProTable,
-} from "@ant-design/pro-components";
-
-import type { TreeDataNode, MenuProps, UploadProps, GetProp } from "antd";
-import {
-  Button,
-  Col,
-  Flex,
-  Input,
-  message,
-  Modal,
-  Row,
-  Space,
-  Spin,
-  Switch,
-  Tree,
-  Dropdown,
-  Form,
-  Upload,
-  Typography,
-  Checkbox,
-  Tag,
-} from "antd";
-import { useRouter } from "next/navigation";
-
-import {
-  faDownload,
-  faPenToSquare,
-  faToggleOff,
-  faToggleOn,
-  faUpload,
-  faUsers,
-  faCheck,
-  faXmark,
-} from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-
-import { useEffect, useMemo, useRef, useState } from "react";
+import {fetchApi} from "@/app/_modules/func";
+import {DeleteOutlined, ExclamationCircleFilled, PlusOutlined, ReloadOutlined,} from "@ant-design/icons";
+import type {ActionType, ProColumns, ProFormInstance,} from "@ant-design/pro-components";
+import {PageContainer, ProTable,} from "@ant-design/pro-components";
+
+import type {GetProp, UploadProps} from "antd";
+import {Button, message, Modal, Space, Tag, Upload,} from "antd";
+import {useRouter} from "next/navigation";
+
+import {faCheck, faToggleOff, faToggleOn, faXmark,} from "@fortawesome/free-solid-svg-icons";
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
+
+import {useRef, useState} from "react";
 
 type FileType = Parameters<GetProp<UploadProps, "beforeUpload">>[0];
 
@@ -462,12 +406,12 @@ export default function RoleAuth({ params }: { params: { roleid: string } }) {
   //搜索栏显示状态
   const [showSearch, setShowSearch] = useState(true);
   //action对象引用
-  const actionRef = useRef<ActionType>();
+  const actionRef = useRef<ActionType>(null);
   //表单对象引用
-  const formRef = useRef<ProFormInstance>();
+  const formRef = useRef<ProFormInstance>(null!);
 
   //未分配用户列表action对象引用
-  const unallocateActionRef = useRef<ActionType>();
+  const unallocateActionRef = useRef<ActionType>(null);
 
   //当前默认条数
   const defaultPageSize = 10;

+ 12 - 33
app/(business)/system/role/page.tsx

@@ -1,6 +1,6 @@
 "use client";
 
-import { fetchApi, fetchFile } from "@/app/_modules/func";
+import {fetchApi, fetchFile} from "@/app/_modules/func";
 import {
   CaretDownOutlined,
   CheckOutlined,
@@ -11,11 +11,7 @@ import {
   PlusOutlined,
   ReloadOutlined,
 } from "@ant-design/icons";
-import type {
-  ActionType,
-  ProColumns,
-  ProFormInstance,
-} from "@ant-design/pro-components";
+import type {ActionType, ProColumns, ProFormInstance,} from "@ant-design/pro-components";
 import {
   ModalForm,
   PageContainer,
@@ -27,32 +23,15 @@ import {
   ProFormTreeSelect,
   ProTable,
 } from "@ant-design/pro-components";
-import type { GetProp, UploadProps } from "antd";
-import {
-  Button,
-  Dropdown,
-  Form,
-  Input,
-  message,
-  Modal,
-  Select,
-  Space,
-  Switch,
-  Upload,
-} from "antd";
-import { useRouter } from "next/navigation";
+import type {GetProp, UploadProps} from "antd";
+import {Button, Dropdown, Form, Input, message, Modal, Select, Space, Switch, Upload,} from "antd";
+import {useRouter} from "next/navigation";
 
-import {
-  faDownload,
-  faPenToSquare,
-  faToggleOff,
-  faToggleOn,
-  faUsers,
-} from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {faDownload, faPenToSquare, faToggleOff, faToggleOn, faUsers,} from "@fortawesome/free-solid-svg-icons";
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
 
-import { TreeSelect } from "@/node_modules/antd/es/index";
-import { useRef, useState } from "react";
+import {TreeSelect} from "@/node_modules/antd/es/index";
+import {useRef, useState} from "react";
 
 type FileType = Parameters<GetProp<UploadProps, "beforeUpload">>[0];
 
@@ -473,7 +452,7 @@ export default function Role() {
   };
 
   //修改角色表单引用
-  const modifyFormRef = useRef<ProFormInstance>();
+  const modifyFormRef = useRef<ProFormInstance>(null);
 
   //操作角色的附加数据
   const [attachRowdata, setAttachRowdata] = useState<{ [key: string]: any }>(
@@ -606,9 +585,9 @@ export default function Role() {
   //搜索栏显示状态
   const [showSearch, setShowSearch] = useState(true);
   //action对象引用
-  const actionRef = useRef<ActionType>();
+  const actionRef = useRef<ActionType>(null);
   //表单对象引用
-  const formRef = useRef<ProFormInstance>();
+  const formRef = useRef<ProFormInstance>(null!);
 
   //当前页数和每页条数
   const [page, setPage] = useState(1);

+ 9 - 13
app/(business)/system/user/page.tsx

@@ -1,6 +1,6 @@
 "use client";
 
-import { fetchApi, fetchFile } from "@/app/_modules/func";
+import {fetchApi, fetchFile} from "@/app/_modules/func";
 import {
   CaretDownOutlined,
   CheckOutlined,
@@ -14,11 +14,7 @@ import {
   ReloadOutlined,
   SearchOutlined,
 } from "@ant-design/icons";
-import type {
-  ActionType,
-  ProColumns,
-  ProFormInstance,
-} from "@ant-design/pro-components";
+import type {ActionType, ProColumns, ProFormInstance,} from "@ant-design/pro-components";
 import {
   ModalForm,
   PageContainer,
@@ -31,7 +27,7 @@ import {
   ProFormTreeSelect,
   ProTable,
 } from "@ant-design/pro-components";
-import type { GetProp, TreeDataNode, UploadProps } from "antd";
+import type {GetProp, TreeDataNode, UploadProps} from "antd";
 import {
   Button,
   Checkbox,
@@ -50,7 +46,7 @@ import {
   Typography,
   Upload,
 } from "antd";
-import { useRouter } from "next/navigation";
+import {useRouter} from "next/navigation";
 
 import {
   faDownload,
@@ -60,9 +56,9 @@ import {
   faUpload,
   faUsers,
 } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
 
-import { useEffect, useMemo, useRef, useState } from "react";
+import {useEffect, useMemo, useRef, useState} from "react";
 
 type FileType = Parameters<GetProp<UploadProps, "beforeUpload">>[0];
 
@@ -607,7 +603,7 @@ export default function User() {
   };
 
   //修改用户表单引用
-  const modifyFormRef = useRef<ProFormInstance>();
+  const modifyFormRef = useRef<ProFormInstance>(null);
 
   //待修改用户的岗位可选数据
   const [modifyPositionValue, setModifyPositionValue] = useState(
@@ -844,9 +840,9 @@ export default function User() {
   //搜索栏显示状态
   const [showSearch, setShowSearch] = useState(true);
   //action对象引用
-  const actionRef = useRef<ActionType>();
+  const actionRef = useRef<ActionType>(null);
   //表单对象引用
-  const formRef = useRef<ProFormInstance>();
+  const formRef = useRef<ProFormInstance>(null!);
 
   //当前页数和每页条数
   const [page, setPage] = useState(1);

+ 0 - 11
app/(business)/test1/page.tsx

@@ -1,11 +0,0 @@
-import React from 'react';
-
-function Page() {
-  return (
-    <>
-      城市生命线驾驶舱
-    </>
-  );
-}
-
-export default Page;

+ 1069 - 0
app/(other)/test5/page.tsx

@@ -0,0 +1,1069 @@
+"use client"
+
+import React, {useEffect, useMemo, useState} from "react"
+import type {MenuProps, TableColumnsType} from "antd"
+import {
+  Badge,
+  Button,
+  Card,
+  Col,
+  ConfigProvider,
+  DatePicker,
+  Descriptions,
+  Divider,
+  Drawer,
+  Layout,
+  Menu,
+  Progress,
+  Row,
+  Select,
+  Space,
+  Statistic,
+  Switch,
+  Table,
+  Tabs,
+  Tag,
+  Timeline,
+  Tooltip as AntdTooltip
+} from "antd"
+import {
+  Activity,
+  AlertTriangle,
+  BellRing,
+  Database,
+  Droplets,
+  Factory,
+  Flame,
+  Gauge,
+  Layers3,
+  Map,
+  Moon,
+  Settings2,
+  ShieldCheck,
+  Sun,
+  TrendingUp,
+  Waves
+} from 'lucide-react'
+import EChart from "@/components/echarts"
+import dayjs from "dayjs"
+import {EChartsOption} from "echarts";
+
+const { Header, Sider, Content } = Layout
+const { RangePicker } = DatePicker
+
+// Helpers
+const primary = "#10b981" // teal, avoid blue
+const danger = "#ef4444"
+const warn = "#f59e0b"
+const ok = "#22c55e"
+const muted = "#6b7280"
+
+type Facility = {
+  id: string
+  industry: "燃气" | "供水" | "排水"
+  type: string
+  count: number
+  age: number
+  region: string
+}
+
+type Device = {
+  id: string
+  type: string
+  status: "在线" | "离线"
+  industry: Facility["industry"]
+  risk: "高" | "中" | "低"
+  lat: number
+  lng: number
+}
+
+type Alert = {
+  id: string
+  level: "I级" | "II级" | "III级" | "IV级"
+  industry: Facility["industry"]
+  type: string
+  time: string
+  status: "待处置" | "处置中" | "已闭环"
+  location: string
+  desc: string
+}
+
+function useMockData() {
+  const [facilities] = useState<Facility[]>(() => {
+    const regions = ["中心城区", "东区", "西区", "南区", "北区"]
+    const types = {
+      燃气: ["高压管道", "调压站", "门站", "阀门井"],
+      供水: ["原水管", "净水厂", "二次供水", "监测点"],
+      排水: ["主干管", "泵站", "检查井", "溢流口"],
+    } as const
+    let i = 0
+    return (["燃气", "供水", "排水"] as Facility["industry"][]).flatMap((ind) =>
+      types[ind].map((t) => ({
+        id: `f-${i++}`,
+        industry: ind,
+        type: t,
+        count: Math.floor(Math.random() * 900 + 100),
+        age: Math.floor(Math.random() * 30 + 1),
+        region: regions[Math.floor(Math.random() * regions.length)],
+      }))
+    )
+  })
+
+  const [devices, setDevices] = useState<Device[]>(() => {
+    const types = ["压力", "流量", "液位", "阀位", "水质", "气体浓度"]
+    let i = 0
+    return Array.from({ length: 250 }).map(() => ({
+      id: `d-${i++}`,
+      type: types[Math.floor(Math.random() * types.length)],
+      status: Math.random() > 0.12 ? "在线" : "离线",
+      industry: (["燃气", "供水", "排水"] as const)[Math.floor(Math.random() * 3)],
+      risk: Math.random() > 0.8 ? "高" : Math.random() > 0.5 ? "中" : "低",
+      lat: Math.random(),
+      lng: Math.random(),
+    }))
+  })
+
+  // Simulate device status fluctuation
+  useEffect(() => {
+    const t = setInterval(() => {
+      setDevices((prev) =>
+        prev.map((d) =>
+          Math.random() > 0.97 ? { ...d, status: d.status === "在线" ? "离线" : "在线" } : d
+        )
+      )
+    }, 4000)
+    return () => clearInterval(t)
+  }, [])
+
+  const [alerts, setAlerts] = useState<Alert[]>(() => {
+    const levels: Alert["level"][] = ["I级", "II级", "III级", "IV级"]
+    const types = ["泄漏", "压力异常", "流量突变", "停电", "液位过低", "浊度异常"]
+    let i = 0
+    return Array.from({ length: 30 }).map(() => ({
+      id: `a-${i++}`,
+      level: levels[Math.floor(Math.random() * levels.length)],
+      industry: (["燃气", "供水", "排水"] as const)[Math.floor(Math.random() * 3)],
+      type: types[Math.floor(Math.random() * types.length)],
+      time: dayjs().subtract(Math.floor(Math.random() * 200), "minute").format("YYYY-MM-DD HH:mm"),
+      status: Math.random() > 0.6 ? "已闭环" : Math.random() > 0.3 ? "处置中" : "待处置",
+      location: ["中心城区", "东区", "西区", "南区", "北区"][Math.floor(Math.random() * 5)],
+      desc: "自动监测发现异常,已推送至管理单位核实。",
+    }))
+  })
+
+  // Simulate new alerts
+  useEffect(() => {
+    const t = setInterval(() => {
+      setAlerts((prev) => {
+        const n: Alert = {
+          id: `a-${prev.length + 1}`,
+          level: Math.random() > 0.85 ? "I级" : Math.random() > 0.6 ? "II级" : Math.random() > 0.3 ? "III级" : "IV级",
+          industry: (["燃气", "供水", "排水"] as const)[Math.floor(Math.random() * 3)],
+          type: ["泄漏", "压力异常", "流量突变", "停电", "液位过低", "浊度异常"][Math.floor(Math.random() * 6)],
+          time: dayjs().format("YYYY-MM-DD HH:mm"),
+          status: "待处置",
+          location: ["中心城区", "东区", "西区", "南区", "北区"][Math.floor(Math.random() * 5)],
+          desc: "前端设备上报新预警,请尽快核查。",
+        }
+        return [n, ...prev].slice(0, 60)
+      })
+    }, 20000)
+    return () => clearInterval(t)
+  }, [])
+
+  return { facilities, devices, alerts }
+}
+
+function LevelTag({ level }: { level: Alert["level"] }) {
+  const map = {
+    "I级": { color: danger },
+    "II级": { color: warn },
+    "III级": { color: "#f97316" },
+    "IV级": { color: ok },
+  } as const
+  return <Tag color={map[level].color}>{level}</Tag>
+}
+
+function StatusBadge({ s }: { s: Alert["status"] }) {
+  const status = {
+    待处置: "error",
+    处置中: "processing",
+    已闭环: "success",
+  } as const
+  return <Badge status={status[s]} text={s} />
+}
+
+export default function Page() {
+  const [collapsed, setCollapsed] = useState(false)
+  const { facilities, devices, alerts } = useMockData()
+  const [selectedMenu, setSelectedMenu] = useState("overview")
+  const [drawerOpen, setDrawerOpen] = useState(false)
+  const [selectedAlert, setSelectedAlert] = useState<Alert | null>(null)
+  const [isDark, setIsDark] = useState(true)
+
+  const totals = useMemo(() => {
+    const sum = { 燃气: 0, 供水: 0, 排水: 0 } as Record<Facility["industry"], number>
+    facilities.forEach((f) => (sum[f.industry] += f.count))
+    return sum
+  }, [facilities])
+
+  const onlineRate = useMemo(() => {
+    const total = devices.length
+    const online = devices.filter((d) => d.status === "在线").length
+    return Math.round((online / Math.max(total, 1)) * 100)
+  }, [devices])
+
+  const riskDist = useMemo(() => {
+    const dist = { 高: 0, 中: 0, 低: 0 } as Record<Device["risk"], number>
+    devices.forEach((d) => (dist[d.risk] += 1))
+    return dist
+  }, [devices])
+
+  // Menu
+  const menuItems: MenuProps["items"] = [
+    { key: "overview", icon: <Layers3 size={18} />, label: "总体概览" },
+    { key: "asset", icon: <Database size={18} />, label: "基础设施管理" },
+    { key: "monitor", icon: <Activity size={18} />, label: "运行监测管理" },
+    { key: "alert", icon: <BellRing size={18} />, label: "预警处置管理" },
+    { type: "divider" as const },
+    { key: "settings", icon: <Settings2 size={18} />, label: "设置" },
+  ]
+
+  // Charts Options
+  const infraPieOption = useMemo(
+    () => ({
+      title: { text: "设施类型占比", left: "center", textStyle: { fontSize: 14 } },
+      tooltip: { trigger: "item" },
+      legend: { bottom: 0 },
+      color: [primary, "#f59e0b", "#6366f1", "#ef4444", "#14b8a6", "#a855f7"],
+      series: [
+        {
+          type: "pie",
+          radius: ["35%", "60%"],
+          avoidLabelOverlap: false,
+          itemStyle: { borderRadius: 6, borderColor: "#fff", borderWidth: 2 },
+          data: ["燃气", "供水", "排水"].map((ind) => ({
+            name: ind,
+            value: Object.values(facilities)
+              .filter((f) => (f as Facility).industry === ind)
+              .reduce((acc, f) => acc + (f as Facility).count, 0),
+          })),
+        },
+      ],
+    }),
+    [facilities]
+  )
+
+  const deviceBarOption = useMemo(
+    () => {
+      const types = Array.from(new Set(devices.map((d) => d.type)))
+      const byType = types.map((t) => devices.filter((d) => d.type === t).length)
+      return {
+        title: { text: "监测设备类型规模", left: "center", textStyle: { fontSize: 14 } },
+        tooltip: { trigger: "axis" },
+        xAxis: { type: "category", data: types, axisLabel: { rotate: 20 } },
+        yAxis: { type: "value" },
+        grid: { left: 40, right: 10, bottom: 50, top: 40 },
+        color: [primary],
+        series: [{ type: "bar", data: byType, barWidth: "50%", itemStyle: { borderRadius: [4, 4, 0, 0] } }],
+      } as const
+    },
+    [devices]
+  )
+
+  const alertTrendOption = useMemo(() => {
+    const days = 14
+    const labels = Array.from({ length: days }).map((_, i) => dayjs().subtract(days - i - 1, "day").format("MM-DD"))
+    const series = (["燃气", "供水", "排水"] as const).map((ind, idx) => ({
+      name: ind,
+      type: "line",
+      smooth: true,
+      data: labels.map(() => Math.floor(Math.random() * (idx === 0 ? 12 : idx === 1 ? 9 : 7)) + (idx === 0 ? 3 : 1)),
+    }))
+    return {
+      title: { text: "预警趋势(近14天)", left: "center", textStyle: { fontSize: 14 } },
+      tooltip: { trigger: "axis" },
+      legend: { bottom: 0 },
+      grid: { left: 40, right: 10, bottom: 40, top: 40 },
+      xAxis: { type: "category", data: labels },
+      yAxis: { type: "value" },
+      color: [primary, "#f59e0b", "#a855f7"],
+      series,
+    } as const
+  }, [])
+
+  const onlineGaugeOption = useMemo(
+    () => ({
+      title: { text: "设备在线率", left: "center", top: 10, textStyle: { fontSize: 14 } },
+      series: [
+        {
+          type: "gauge",
+          startAngle: 200,
+          endAngle: -20,
+          radius: "90%",
+          pointer: { show: false },
+          progress: { show: true, width: 16, itemStyle: { color: onlineRate > 95 ? ok : onlineRate > 85 ? "#84cc16" : warn } },
+          axisLine: { lineStyle: { width: 16 } },
+          axisTick: { show: false },
+          splitLine: { show: false },
+          axisLabel: { show: false },
+          detail: { valueAnimation: true, formatter: "{value}%", fontSize: 22, offsetCenter: [0, "10%"] },
+          data: [{ value: onlineRate }],
+        },
+      ],
+    }),
+    [onlineRate]
+  )
+
+  const riskBarOption = useMemo(
+    () => ({
+      title: { text: "风险等级分布", left: "center", textStyle: { fontSize: 14 } },
+      tooltip: { trigger: "axis" },
+      grid: { left: 40, right: 10, top: 40, bottom: 20 },
+      xAxis: { type: "category", data: ["高", "中", "低"] },
+      yAxis: { type: "value" },
+      color: [danger, warn, ok],
+      series: [{ type: "bar", data: [riskDist["高"], riskDist["中"], riskDist["低"]], barWidth: "50%" }],
+    }),
+    [riskDist]
+  )
+
+  const efficiencyPieOption = useMemo(
+    () => {
+      const total = alerts.length
+      const closed = alerts.filter((a) => a.status === "已闭环").length
+      const correct = Math.floor(closed * (0.85 + Math.random() * 0.1)) // 假定人工复核正确率
+      const wrong = Math.max(closed - correct, 0)
+      return {
+        title: { text: "预警正确性(闭环内)", left: "center", textStyle: { fontSize: 14 } },
+        tooltip: { trigger: "item", formatter: "{b}: {c} ({d}%)" },
+        legend: { bottom: 0 },
+        color: [ok, danger],
+        series: [
+          {
+            type: "pie",
+            radius: ["35%", "60%"],
+            data: [
+              { name: "正确预警", value: correct },
+              { name: "误报", value: wrong },
+            ],
+          },
+        ],
+      } as const
+    },
+    [alerts]
+  )
+
+  const radarCapacityOption = useMemo(
+    () => ({
+      title: { text: "应急保障能力雷达", left: "center", textStyle: { fontSize: 14 } },
+      tooltip: {},
+      radar: {
+        indicator: [
+          { name: "抢修速度", max: 100 },
+          { name: "物资充足", max: 100 },
+          { name: "跨部门联动", max: 100 },
+          { name: "覆盖广度", max: 100 },
+          { name: "应急演练", max: 100 },
+        ],
+      },
+      color: [primary],
+      series: [
+        {
+          type: "radar",
+          data: [{ value: [78, 85, 72, 88, 76], name: "当前" }],
+          areaStyle: { color: primary + "33" },
+        },
+      ],
+    }),
+    []
+  )
+
+  // Tables
+  const facilityColumns: TableColumnsType<Facility> = [
+    { title: "行业", dataIndex: "industry", key: "industry", width: 90 },
+    { title: "类型", dataIndex: "type", key: "type" },
+    { title: "数量", dataIndex: "count", key: "count", width: 100 },
+    { title: "平均年限", dataIndex: "age", key: "age", width: 120, render: (v) => `${v} 年` },
+    { title: "分布区域", dataIndex: "region", key: "region", width: 120 },
+  ]
+
+  const alertColumns: TableColumnsType<Alert> = [
+    { title: "等级", dataIndex: "level", key: "level", width: 90, render: (v) => <LevelTag level={v} /> },
+    { title: "行业", dataIndex: "industry", key: "industry", width: 90 },
+    { title: "类型", dataIndex: "type", key: "type", width: 140 },
+    { title: "时间", dataIndex: "time", key: "time", width: 160 },
+    { title: "状态", dataIndex: "status", key: "status", width: 120, render: (s) => <StatusBadge s={s} /> },
+    { title: "位置", dataIndex: "location", key: "location", width: 120 },
+    {
+      title: "操作",
+      key: "action",
+      render: (_, record) => (
+        <Space size="small">
+          <a
+            onClick={() => {
+              setSelectedAlert(record)
+              setDrawerOpen(true)
+            }}
+          >
+            查看
+          </a>
+          <a>派单</a>
+          <a>联动</a>
+        </Space>
+      ),
+      width: 160,
+      fixed: "right",
+    },
+  ]
+
+  const alertLevelColor = (lvl: Alert["level"]) =>
+    lvl === "I级" ? danger : lvl === "II级" ? warn : lvl === "III级" ? "#f97316" : ok
+
+  // Derived numbers
+  const stats = [
+    {
+      title: "燃气总规模",
+      value: totals["燃气"],
+      icon: <Flame className="text-white" size={18} />,
+      bg: "bg-emerald-500",
+    },
+    {
+      title: "供水总规模",
+      value: totals["供水"],
+      icon: <Droplets className="text-white" size={18} />,
+      bg: "bg-teal-500",
+    },
+    {
+      title: "排水总规模",
+      value: totals["排水"],
+      icon: <Waves className="text-white" size={18} />,
+      bg: "bg-cyan-500",
+    },
+    {
+      title: "在线设备",
+      value: devices.filter((d) => d.status === "在线").length,
+      icon: <Gauge className="text-white" size={18} />,
+      bg: "bg-lime-500",
+    },
+  ]
+
+  // Map markers for device distribution
+  const markers = devices.slice(0, 120).map((d, i) => ({
+    id: d.id,
+    top: `${Math.floor(d.lat * 85) + 5}%`,
+    left: `${Math.floor(d.lng * 90) + 5}%`,
+    color: d.risk === "高" ? danger : d.risk === "中" ? warn : ok,
+    title: `${d.industry}/${d.type}(${d.status})`,
+  }))
+
+  const themeTokens = useMemo(
+    () => ({
+      token: {
+        colorPrimary: primary,
+        colorInfo: primary,
+        borderRadius: 8,
+        fontSize: 13,
+      },
+      components: {
+        Layout: {
+          headerBg: isDark ? "#0f172a" : "#ffffff",
+          siderBg: isDark ? "#0b1220" : "#ffffff",
+          bodyBg: isDark ? "#0b1220" : "#f6f7f9",
+        },
+        Menu: {
+          itemSelectedBg: isDark ? "#0ea5a8" : "#d1fae5",
+          itemSelectedColor: isDark ? "#fff" : "#0f172a",
+          itemActiveBg: "#0ea5a822",
+        },
+      },
+    }),
+    [isDark]
+  )
+
+  return (
+    <ConfigProvider theme={themeTokens}>
+      <Layout style={{ minHeight: "100vh" }}>
+        <Sider collapsible collapsed={collapsed} onCollapse={setCollapsed} width={240} theme={isDark ? "dark" : "light"}>
+          <div className="flex items-center gap-2 px-4 py-3">
+            <ShieldCheck className="text-emerald-400" size={22} />
+            {!collapsed && (
+              <div className={isDark ? "text-emerald-100 font-medium" : "text-emerald-700 font-medium"}>
+                城市生命线驾驶舱
+              </div>
+            )}
+          </div>
+          <Menu
+            theme={isDark ? "dark" : "light"}
+            mode="inline"
+            selectedKeys={[selectedMenu]}
+            onClick={(e) => setSelectedMenu(e.key)}
+            items={menuItems}
+          />
+        </Sider>
+        <Layout>
+          <Header className="flex items-center justify-between px-4">
+            <div className={`flex items-center gap-3 ${isDark ? "text-white" : "text-slate-800"}`}>
+              <Factory size={18} />
+              <span className="font-medium">综合运行态势</span>
+              <span className={`text-xs hidden md:inline ${isDark ? "text-slate-300" : "text-slate-500"}`}>
+                更新时间 {dayjs().format("YYYY-MM-DD HH:mm:ss")}
+              </span>
+            </div>
+            <div className="flex items-center gap-3">
+              <Select
+                size="small"
+                defaultValue="全市"
+                options={[
+                  { value: "全市", label: "全市" },
+                  { value: "中心城区", label: "中心城区" },
+                  { value: "东区", label: "东区" },
+                  { value: "西区", label: "西区" },
+                  { value: "南区", label: "南区" },
+                  { value: "北区", label: "北区" },
+                ]}
+                style={{ width: 120 }}
+              />
+              <RangePicker size="small" />
+              <Button
+                size="small"
+                onClick={() => setIsDark((v) => !v)}
+                icon={isDark ? <Sun size={14} /> : <Moon size={14} />}
+              >
+                {isDark ? "暗色" : "白色"}
+              </Button>
+            </div>
+          </Header>
+          <Content className="p-4 md:p-6">
+            {selectedMenu === "overview" && (
+              <section className="space-y-4">
+                <Row gutter={[16, 16]}>
+                  {stats.map((s) => (
+                    <Col xs={24} sm={12} md={12} lg={6} key={s.title}>
+                      <Card>
+                        <div className="flex items-center gap-3">
+                          <div className={`w-9 h-9 rounded-md flex items-center justify-center ${s.bg}`}>{s.icon}</div>
+                          <div className="flex-1">
+                            <div className="text-xs text-slate-500">{s.title}</div>
+                            <div className="text-xl font-semibold">{s.value.toLocaleString()}</div>
+                          </div>
+                          <AntdTooltip title="环比增长">
+                            <TrendingUp className="text-emerald-500" size={18} />
+                          </AntdTooltip>
+                        </div>
+                      </Card>
+                    </Col>
+                  ))}
+                </Row>
+
+                <Row gutter={[16, 16]}>
+                  <Col xs={24} md={12} lg={8}>
+                    <Card>
+                      <EChart option={infraPieOption} />
+                    </Card>
+                  </Col>
+                  <Col xs={24} md={12} lg={8}>
+                    <Card>
+                      <EChart option={deviceBarOption as EChartsOption} />
+                    </Card>
+                  </Col>
+                  <Col xs={24} md={24} lg={8}>
+                    <Card>
+                      <EChart option={onlineGaugeOption} />
+                    </Card>
+                  </Col>
+                </Row>
+
+                <Row gutter={[16, 16]}>
+                  <Col xs={24} md={12}>
+                    <Card>
+                      <EChart option={alertTrendOption as EChartsOption} />
+                    </Card>
+                  </Col>
+                  <Col xs={24} md={12}>
+                    <Card>
+                      <EChart option={riskBarOption} />
+                    </Card>
+                  </Col>
+                </Row>
+
+                <Card title="监测分布(示意)" extra={<span className="text-xs text-slate-500">标注颜色代表风险等级</span>}>
+                  <div className="relative w-full h-[380px] rounded-md overflow-hidden bg-slate-100">
+                    <img
+                      src="/images/city-map.png"
+                      alt="城市简化地图"
+                      className="absolute inset-0 w-full h-full object-cover opacity-90"
+                    />
+                    {/* Markers */}
+                    {markers.map((m) => (
+                      <AntdTooltip key={m.id} title={m.title}>
+                        <div
+                          className="absolute w-2.5 h-2.5 rounded-full ring-2 ring-white/70"
+                          style={{ top: m.top, left: m.left, backgroundColor: m.color }}
+                        />
+                      </AntdTooltip>
+                    ))}
+
+                    {/* Legend */}
+                    <div className="absolute right-3 top-3 bg-white/90 backdrop-blur rounded-md p-2 text-xs shadow">
+                      <div className="font-medium mb-1 flex items-center gap-1">
+                        <Map size={14} /> 覆盖与风险
+                      </div>
+                      <div className="flex items-center gap-3">
+                        <div className="flex items-center gap-1">
+                          <span className="inline-block w-2.5 h-2.5 rounded-full" style={{ background: danger }} /> 高
+                        </div>
+                        <div className="flex items-center gap-1">
+                          <span className="inline-block w-2.5 h-2.5 rounded-full" style={{ background: warn }} /> 中
+                        </div>
+                        <div className="flex items-center gap-1">
+                          <span className="inline-block w-2.5 h-2.5 rounded-full" style={{ background: ok }} /> 低
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </Card>
+              </section>
+            )}
+
+            {selectedMenu === "asset" && (
+              <section className="space-y-4">
+                <Tabs
+                  defaultActiveKey="archive"
+                  items={[
+                    { key: "archive", label: "基础档案" },
+                    { key: "run", label: "运行信息" },
+                    { key: "mgmt", label: "管理信息" },
+                    { key: "emergency", label: "应急保障" },
+                  ]}
+                />
+                <Row gutter={[16, 16]}>
+                  <Col xs={24} lg={14}>
+                    <Card title="设施基础档案">
+                      <Table
+                        size="small"
+                        rowKey="id"
+                        columns={facilityColumns}
+                        dataSource={facilities}
+                        pagination={{ pageSize: 8 }}
+                        scroll={{ x: 700 }}
+                      />
+                    </Card>
+                  </Col>
+                  <Col xs={24} lg={10}>
+                    <Card title="行业重点指标">
+                      <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
+                        <div>
+                          <div className="text-xs text-slate-500 mb-1">平均设施年限</div>
+                          <Statistic value={Math.round(facilities.reduce((a, b) => a + b.age, 0) / facilities.length)} suffix="年" />
+                          <Progress percent={Math.min(100, Math.round((facilities.filter((f) => f.age >= 20).length / facilities.length) * 100))} size="small" />
+                          <div className="text-xs text-slate-500 mt-1">20年以上占比</div>
+                        </div>
+                        <div>
+                          <div className="text-xs text-slate-500 mb-1">重点设施数量</div>
+                          <Statistic value={facilities.filter((f) => f.type.includes("主") || f.type.includes("厂")).reduce((a, b) => a + b.count, 0)} />
+                          <div className="text-xs text-slate-500 mt-1">主干/厂站等关键设施总量</div>
+                        </div>
+                      </div>
+                    </Card>
+                    <Card className="mt-4" title="分布热点(示意)">
+                      <EChart
+                        option={{
+                          title: { text: "区域设施数量", left: "center", textStyle: { fontSize: 14 } },
+                          xAxis: { type: "category", data: ["中心城区", "东区", "西区", "南区", "北区"] },
+                          yAxis: { type: "value" },
+                          grid: { left: 40, right: 10, bottom: 20, top: 40 },
+                          color: [primary],
+                          series: [
+                            {
+                              type: "bar",
+                              data: ["中心城区", "东区", "西区", "南区", "北区"].map(
+                                (r) => facilities.filter((f) => f.region === r).reduce((a, b) => a + b.count, 0)
+                              ),
+                              barWidth: "50%",
+                            },
+                          ],
+                        }}
+                      />
+                    </Card>
+                  </Col>
+                </Row>
+
+                <Row gutter={[16, 16]}>
+                  <Col xs={24} md={12}>
+                    <Card title="日常供应能力(示意)">
+                      <EChart
+                        option={{
+                          tooltip: { trigger: "axis" },
+                          legend: { bottom: 0 },
+                          grid: { left: 40, right: 10, top: 20, bottom: 40 },
+                          xAxis: { type: "category", data: Array.from({ length: 24 }).map((_, i) => `${i}:00`) },
+                          yAxis: { type: "value" },
+                          color: [primary, "#f59e0b"],
+                          series: [
+                            { name: "供水(万m³/h)", type: "line", smooth: true, data: Array.from({ length: 24 }).map(() => 50 + Math.random() * 20) },
+                            { name: "燃气(万m³/h)", type: "line", smooth: true, data: Array.from({ length: 24 }).map(() => 30 + Math.random() * 15) },
+                          ],
+                        }}
+                      />
+                    </Card>
+                  </Col>
+                  <Col xs={24} md={12}>
+                    <Card title="管理单位信息">
+                      <Descriptions size="small" column={1} bordered>
+                        <Descriptions.Item label="燃气管理单位">市燃气集团、区属燃气公司等(共 6 家)</Descriptions.Item>
+                        <Descriptions.Item label="供水管理单位">市自来水公司、二供物业等(共 9 家)</Descriptions.Item>
+                        <Descriptions.Item label="排水管理单位">城排中心、各区运营公司(共 7 家)</Descriptions.Item>
+                        <Descriptions.Item label="产权分布">市属 60%,区属 30%,社会 10%</Descriptions.Item>
+                      </Descriptions>
+                      <Divider className="my-3" />
+                      <Timeline
+                        items={[
+                          { color: "green", children: "制度更新:设施巡检标准(本月)" },
+                          { color: "blue", children: "完成年度普查 80%" },
+                          { color: "orange", children: "外包维保单位进场(上周)" },
+                        ]}
+                      />
+                    </Card>
+                  </Col>
+                </Row>
+
+                <Row gutter={[16, 16]}>
+                  <Col xs={24} md={12}>
+                    <Card title="应急资源配置">
+                      <div className="grid grid-cols-2 gap-4">
+                        <Card size="small">
+                          <div className="text-xs text-slate-500">物资仓库</div>
+                          <div className="text-xl font-semibold mt-1">12 处</div>
+                          <Progress percent={78} status="active" size="small" />
+                          <div className="text-xs text-slate-500 mt-1">库存充足度</div>
+                        </Card>
+                        <Card size="small">
+                          <div className="text-xs text-slate-500">抢修队伍</div>
+                          <div className="text-xl font-semibold mt-1">26 支</div>
+                          <Progress percent={86} status="active" size="small" />
+                          <div className="text-xs text-slate-500 mt-1">到岗率</div>
+                        </Card>
+                        <Card size="small">
+                          <div className="text-xs text-slate-500">重点防护目标</div>
+                          <div className="text-xl font-semibold mt-1">143 处</div>
+                          <Progress percent={92} status="active" size="small" />
+                          <div className="text-xs text-slate-500 mt-1">纳入台账比例</div>
+                        </Card>
+                        <Card size="small">
+                          <div className="text-xs text-slate-500">联动单位</div>
+                          <div className="text-xl font-semibold mt-1">19 家</div>
+                          <Progress percent={74} status="active" size="small" />
+                          <div className="text-xs text-slate-500 mt-1">联动成熟度</div>
+                        </Card>
+                      </div>
+                    </Card>
+                  </Col>
+                  <Col xs={24} md={12}>
+                    <Card>
+                      <EChart option={radarCapacityOption} />
+                    </Card>
+                  </Col>
+                </Row>
+              </section>
+            )}
+
+            {selectedMenu === "monitor" && (
+              <section className="space-y-4">
+                <Tabs
+                  defaultActiveKey="summary"
+                  items={[
+                    { key: "summary", label: "监测概览" },
+                    { key: "distribution", label: "监测分布" },
+                    { key: "running", label: "运行概况" },
+                  ]}
+                />
+
+                <Row gutter={[16, 16]}>
+                  <Col xs={24} md={12} lg={8}>
+                    <Card>
+                      <EChart option={onlineGaugeOption} />
+                    </Card>
+                  </Col>
+                  <Col xs={24} md={12} lg={8}>
+                    <Card>
+                      <EChart option={deviceBarOption as EChartsOption} />
+                    </Card>
+                  </Col>
+                  <Col xs={24} md={24} lg={8}>
+                    <Card>
+                      <EChart option={riskBarOption} />
+                    </Card>
+                  </Col>
+                </Row>
+
+                <Card title="监测分布(按行业与风险)">
+                  <Row gutter={[16, 16]}>
+                    {(["燃气", "供水", "排水"] as const).map((ind) => (
+                      <Col xs={24} md={8} key={ind}>
+                        <Card size="small" title={ind}>
+                          <div className="flex items-center gap-4">
+                            <div className="flex-1">
+                              <div className="text-xs text-slate-500">设备在线</div>
+                              <div className="text-lg font-semibold">
+                                {devices.filter((d) => d.industry === ind && d.status === "在线").length}
+                              </div>
+                              <div className="text-xs text-slate-500">总量 {devices.filter((d) => d.industry === ind).length}</div>
+                            </div>
+                            <div className="flex-1">
+                              <div className="text-xs text-slate-500">高风险</div>
+                              <div className="text-lg font-semibold">{devices.filter((d) => d.industry === ind && d.risk === "高").length}</div>
+                              <Progress percent={Math.round((devices.filter((d) => d.industry === ind && d.risk === "高").length / Math.max(devices.filter((d) => d.industry === ind).length, 1)) * 100)} size="small" />
+                            </div>
+                          </div>
+                        </Card>
+                      </Col>
+                    ))}
+                  </Row>
+                </Card>
+
+                <Card title="运行概况(报警覆盖)">
+                  <EChart
+                    option={{
+                      tooltip: { trigger: "axis" },
+                      legend: { bottom: 0 },
+                      grid: { left: 40, right: 10, top: 20, bottom: 40 },
+                      xAxis: { type: "category", data: Array.from({ length: 12 }).map((_, i) => dayjs().subtract(11 - i, "month").format("YYYY-MM")) },
+                      yAxis: { type: "value" },
+                      color: [primary, "#f59e0b", "#a855f7"],
+                      series: (["燃气", "供水", "排水"] as const).map((ind, idx) => ({
+                        name: ind,
+                        type: "bar",
+                        stack: "sum",
+                        emphasis: { focus: "series" },
+                        data: Array.from({ length: 12 }).map(() => Math.floor(Math.random() * (idx === 0 ? 50 : idx === 1 ? 40 : 30))),
+                      })),
+                    }}
+                  />
+                </Card>
+              </section>
+            )}
+
+            {selectedMenu === "alert" && (
+              <section className="space-y-4">
+                <Tabs
+                  defaultActiveKey="current"
+                  items={[
+                    { key: "current", label: "当前预警" },
+                    { key: "history", label: "历史分析" },
+                    { key: "efficiency", label: "预警效率" },
+                    { key: "analysis", label: "预警分析" },
+                  ]}
+                />
+
+                <Row gutter={[16, 16]}>
+                  <Col xs={24} lg={16}>
+                    <Card title="总体预警处置情况">
+                      <Table
+                        size="small"
+                        rowKey="id"
+                        columns={alertColumns}
+                        dataSource={alerts}
+                        pagination={{ pageSize: 8 }}
+                        onRow={(record) => ({
+                          onClick: () => {
+                            setSelectedAlert(record)
+                            setDrawerOpen(true)
+                          },
+                        })}
+                        scroll={{ x: 900 }}
+                      />
+                    </Card>
+                  </Col>
+                  <Col xs={24} lg={8}>
+                    <Card title="处置效率">
+                      <div className="grid grid-cols-2 gap-4">
+                        <div>
+                          <div className="text-xs text-slate-500">平均处置时效</div>
+                          <div className="text-xl font-semibold mt-1">{(45 + Math.random() * 30).toFixed(0)} 分钟</div>
+                          <Progress percent={78} status="active" size="small" />
+                          <div className="text-xs text-slate-500 mt-1">较上月 +6%</div>
+                        </div>
+                        <div>
+                          <div className="text-xs text-slate-500">闭环率</div>
+                          <div className="text-xl font-semibold mt-1">
+                            {Math.round((alerts.filter((a) => a.status === "已闭环").length / Math.max(alerts.length, 1)) * 100)}%
+                          </div>
+                          <Progress percent={86} status="active" size="small" />
+                          <div className="text-xs text-slate-500 mt-1">较上周 +2%</div>
+                        </div>
+                      </div>
+                    </Card>
+                    <Card className="mt-4">
+                      <EChart option={efficiencyPieOption as EChartsOption} />
+                    </Card>
+                  </Col>
+                </Row>
+
+                <Row gutter={[16, 16]}>
+                  <Col xs={24} md={12}>
+                    <Card title="各行业预警数量(近6月)">
+                      <EChart
+                        option={{
+                          tooltip: { trigger: "axis" },
+                          legend: { bottom: 0 },
+                          grid: { left: 40, right: 10, top: 20, bottom: 40 },
+                          xAxis: { type: "category", data: Array.from({ length: 6 }).map((_, i) => dayjs().subtract(5 - i, "month").format("YYYY-MM")) },
+                          yAxis: { type: "value" },
+                          color: [primary, "#f59e0b", "#a855f7"],
+                          series: (["燃气", "供水", "排水"] as const).map((ind, idx) => ({
+                            name: ind,
+                            type: "line",
+                            smooth: true,
+                            data: Array.from({ length: 6 }).map(() => Math.floor(Math.random() * (idx === 0 ? 60 : idx === 1 ? 45 : 35)) + 5),
+                          })),
+                        }}
+                      />
+                    </Card>
+                  </Col>
+                  <Col xs={24} md={12}>
+                    <Card title="预警类型与成因分布">
+                      <EChart
+                        option={{
+                          tooltip: { trigger: "axis" },
+                          legend: { bottom: 0 },
+                          grid: { left: 40, right: 10, top: 20, bottom: 40 },
+                          xAxis: {
+                            type: "category",
+                            data: ["泄漏", "压力异常", "流量突变", "停电", "液位过低", "浊度异常"],
+                            axisLabel: { rotate: 20 },
+                          },
+                          yAxis: { type: "value" },
+                          color: [primary, "#f59e0b"],
+                          series: [
+                            { name: "设备原因", type: "bar", data: [30, 22, 18, 12, 9, 7] },
+                            { name: "外部原因", type: "bar", data: [18, 15, 14, 28, 11, 10] },
+                          ],
+                        }}
+                      />
+                    </Card>
+                  </Col>
+                </Row>
+
+                <Drawer
+                  title={
+                    <div className="flex items-center gap-2">
+                      <AlertTriangle size={18} color={selectedAlert ? alertLevelColor(selectedAlert.level) : primary} />
+                      <span>预警详情</span>
+                    </div>
+                  }
+                  placement="right"
+                  width={520}
+                  open={drawerOpen}
+                  onClose={() => setDrawerOpen(false)}
+                >
+                  {selectedAlert ? (
+                    <div className="space-y-4">
+                      <Descriptions size="small" column={1} bordered>
+                        <Descriptions.Item label="预警编号">{selectedAlert.id}</Descriptions.Item>
+                        <Descriptions.Item label="等级">
+                          <LevelTag level={selectedAlert.level} />
+                        </Descriptions.Item>
+                        <Descriptions.Item label="行业">{selectedAlert.industry}</Descriptions.Item>
+                        <Descriptions.Item label="类型">{selectedAlert.type}</Descriptions.Item>
+                        <Descriptions.Item label="时间">{selectedAlert.time}</Descriptions.Item>
+                        <Descriptions.Item label="位置">{selectedAlert.location}</Descriptions.Item>
+                        <Descriptions.Item label="状态">
+                          <StatusBadge s={selectedAlert.status} />
+                        </Descriptions.Item>
+                        <Descriptions.Item label="描述">{selectedAlert.desc}</Descriptions.Item>
+                      </Descriptions>
+                      <Card size="small" title="事件时序">
+                        <EChart
+                          option={{
+                            grid: { left: 30, right: 10, top: 20, bottom: 20 },
+                            xAxis: { type: "category", data: ["发现", "确认", "处置", "复盘"] },
+                            yAxis: { type: "value" },
+                            color: [primary],
+                            series: [{ type: "line", smooth: true, data: [0, 12, 38, 60] }],
+                          }}
+                        />
+                      </Card>
+                      <Card size="small" title="一张图联动(示意)">
+                        <div className="relative w-full h-[200px] rounded-md overflow-hidden">
+                          <img src="/images/city-map.png" alt="地图联动示意图" className="absolute inset-0 w-full h-full object-cover opacity-90" />
+                          <div className="absolute inset-0">
+                            <div
+                              className="absolute w-3 h-3 rounded-full ring-2 ring-white animate-pulse"
+                              style={{ top: "42%", left: "56%", background: alertLevelColor(selectedAlert.level) }}
+                              title="预警位置"
+                            />
+                          </div>
+                        </div>
+                      </Card>
+                      <Space>
+                        <a className="text-emerald-600">查看工单</a>
+                        <a className="text-emerald-600">下达指令</a>
+                        <a className="text-emerald-600">通知联动</a>
+                      </Space>
+                    </div>
+                  ) : (
+                    <div className="text-slate-500 text-sm">请选择左侧预警查看详情。</div>
+                  )}
+                </Drawer>
+              </section>
+            )}
+
+            {selectedMenu === "settings" && (
+              <section className="space-y-4">
+                <Card title="预警设置">
+                  <Row gutter={[16, 16]}>
+                    <Col xs={24} md={12}>
+                      <Card size="small" title="阈值设置">
+                        <div className="grid grid-cols-2 gap-4">
+                          <div>
+                            <div className="text-xs text-slate-500 mb-1">压力异常阈值</div>
+                            <Select defaultValue="P≤0.3MPa" options={[{ value: "P≤0.3MPa", label: "P≤0.3MPa" }, { value: "P≤0.2MPa", label: "P≤0.2MPa" }]} />
+                          </div>
+                          <div>
+                            <div className="text-xs text-slate-500 mb-1">流量突变阈值</div>
+                            <Select defaultValue="ΔQ≥20%" options={[{ value: "ΔQ≥20%", label: "ΔQ≥20%" }, { value: "ΔQ≥30%", label: "ΔQ≥30%" }]} />
+                          </div>
+                          <div>
+                            <div className="text-xs text-slate-500 mb-1">水质指标(浊度)</div>
+                            <Select defaultValue="≥5 NTU" options={[{ value: "≥5 NTU", label: "≥5 NTU" }, { value: "≥3 NTU", label: "≥3 NTU" }]} />
+                          </div>
+                          <div>
+                            <div className="text-xs text-slate-500 mb-1">液位过低</div>
+                            <Select defaultValue="≤20%" options={[{ value: "≤20%", label: "≤20%" }, { value: "≤15%", label: "≤15%" }]} />
+                          </div>
+                        </div>
+                      </Card>
+                    </Col>
+                    <Col xs={24} md={12}>
+                      <Card size="small" title="通知策略">
+                        <div className="grid grid-cols-2 gap-4">
+                          <div className="flex items-center justify-between">
+                            <span>高等级预警短信</span>
+                            <Switch defaultChecked />
+                          </div>
+                          <div className="flex items-center justify-between">
+                            <span>邮件抄送管理层</span>
+                            <Switch />
+                          </div>
+                          <div className="flex items-center justify-between">
+                            <span>自动派单</span>
+                            <Switch />
+                          </div>
+                          <div className="flex items-center justify-between">
+                            <span>与值守大屏联动</span>
+                            <Switch defaultChecked />
+                          </div>
+                        </div>
+                      </Card>
+                    </Col>
+                  </Row>
+                </Card>
+              </section>
+            )}
+          </Content>
+        </Layout>
+      </Layout>
+    </ConfigProvider>
+  )
+}

BIN
app/favicon.ico


+ 22 - 32
app/login/page.tsx

@@ -1,28 +1,16 @@
 "use client";
-import { LockOutlined, UserOutlined } from "@ant-design/icons";
-import {
-  LoginFormPage,
-  ProConfigProvider,
-  ProFormCheckbox,
-  ProFormText,
-} from "@ant-design/pro-components";
-import { Divider, message, Spin, theme, ConfigProvider } from "antd";
-import type { ConfigProviderProps } from "antd";
-import { setCookie, getCookie, deleteCookie } from "cookies-next";
-import { useRouter } from "next/navigation";
-
-import type { ProFormInstance } from "@ant-design/pro-components";
+import {LockOutlined, UserOutlined} from "@ant-design/icons";
+import type {ProFormInstance} from "@ant-design/pro-components";
+import {LoginFormPage, ProConfigProvider, ProFormCheckbox, ProFormText,} from "@ant-design/pro-components";
+import {Divider, message, Spin, theme} from "antd";
+import {deleteCookie, getCookie, setCookie} from "cookies-next";
+import {useRouter} from "next/navigation";
 
 import Image from "next/image";
 
-import { useEffect, useState, useRef } from "react";
-import { LoginReq } from "../_modules/definies";
-import {
-  encrypt,
-  decrypt,
-  displayModeIsDark,
-  watchDarkModeChange,
-} from "../_modules/func";
+import {useEffect, useRef, useState} from "react";
+import {LoginReq} from "../_modules/definies";
+import {decrypt, displayModeIsDark, encrypt, watchDarkModeChange,} from "../_modules/func";
 
 type Captcha = {
   img: string;
@@ -174,7 +162,7 @@ export default function Login() {
     deleteCookie(cookie_password_key);
   };
 
-  const loginFormRef = useRef<ProFormInstance>();
+  const loginFormRef = useRef<ProFormInstance>(null);
 
   //读取cookie中用户名密码,并填写到表单中
   const readUserNamePassword = () => {
@@ -183,16 +171,18 @@ export default function Login() {
 
     if (username !== undefined && password !== undefined) {
       if (loginFormRef) {
-        loginFormRef.current?.setFieldsValue({
-          username: decrypt(username),
-          password: decrypt(password),
-          autoLogin: true,
-        });
+        if (typeof username === "string" && password === "string") {
+          loginFormRef.current?.setFieldsValue({
+            username: decrypt(username),
+            password: decrypt(password),
+            autoLogin: true,
+          });
+        }
       }
     }
   };
 
-  const { token } = theme.useToken();
+  const {token} = theme.useToken();
 
   return (
     <ProConfigProvider dark={isDark}>
@@ -212,7 +202,7 @@ export default function Login() {
             backdropFilter: "blur(4px)",
           }}
           subTitle={
-            <span style={{ color: "rgba(255,255,255,1)" }}>
+            <span style={{color: "rgba(255,255,255,1)"}}>
               MorTnon,高质量的快速开发框架
             </span>
           }
@@ -224,7 +214,7 @@ export default function Login() {
                 alignItems: "center",
               }}
             >
-              <p style={{ color: "rgba(255,255,255,.6)" }}>
+              <p style={{color: "rgba(255,255,255,.6)"}}>
                 ©{new Date().getFullYear()} Mortnon.
               </p>
             </div>
@@ -305,10 +295,10 @@ export default function Login() {
                   ]}
                 />
 
-                <div style={{ margin: "0 0 0 8px" }}>
+                <div style={{margin: "0 0 0 8px"}}>
                   <Spin spinning={isLoadingImg}>
                     {captcha.img === undefined ? (
-                      <div style={{ width: 80, height: 40 }}></div>
+                      <div style={{width: 80, height: 40}}></div>
                     ) : (
                       <Image
                         src={captcha.img}

+ 0 - 457
components/alerts/alerts-dashboard.tsx

@@ -1,457 +0,0 @@
-"use client"
-
-import {useMemo, useState} from "react"
-import Section from "@/components/shared/section"
-import {Button} from "@/components/ui/button"
-import {Badge} from "@/components/ui/badge"
-import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card"
-import {
-  type ChartConfig,
-  ChartContainer,
-  ChartLegend,
-  ChartLegendContent,
-  ChartTooltip,
-  ChartTooltipContent,
-} from "@/components/ui/chart"
-import {
-  Bar,
-  BarChart,
-  CartesianGrid,
-  Cell,
-  Line,
-  LineChart,
-  Pie,
-  PieChart,
-  ResponsiveContainer,
-  XAxis,
-  YAxis,
-} from "recharts"
-import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@/components/ui/table"
-import {Dialog, DialogContent, DialogHeader, DialogTitle} from "@/components/ui/dialog"
-import {Separator} from "@/components/ui/separator"
-import {AlarmClock, ShieldCheck} from 'lucide-react'
-
-type Warning = {
-  id: string
-  type: "泄漏" | "水压异常" | "液位过高" | "阀门故障"
-  industry: "燃气" | "供水" | "排水"
-  severity: "高" | "中" | "低"
-  district: string
-  time: string
-  status: "待处置" | "处置中" | "已完成"
-}
-
-function useWarnings() {
-  const now = new Date()
-  const items: Warning[] = Array.from({ length: 12 }).map((_, i) => {
-    const types: Warning["type"][] = ["泄漏", "水压异常", "液位过高", "阀门故障"]
-    const industries: Warning["industry"][] = ["燃气", "供水", "排水"]
-    const severities: Warning["severity"][] = ["高", "中", "低"]
-    const statuses: Warning["status"][] = ["待处置", "处置中", "已完成"]
-    const t = new Date(now.getTime() - i * 36 * 60 * 1000)
-    return {
-      id: `ALM-${1000 + i}`,
-      type: types[i % types.length]!,
-      industry: industries[(i + 1) % industries.length]!,
-      severity: severities[i % severities.length]!,
-      district: ["天河区", "越秀区", "黄埔区", "海珠区"][i % 4]!,
-      time: t.toLocaleString(),
-      status: statuses[i % statuses.length]!,
-    }
-  })
-  return items
-}
-
-const trendConfig = {
-  alarms: { label: "报警数", color: "hsl(var(--chart-5))" },
-} satisfies ChartConfig
-
-export default function AlertsDashboard() {
-  const warnings = useWarnings()
-  const [selected, setSelected] = useState<Warning | null>(null)
-
-  const trend = useMemo(
-    () =>
-      Array.from({ length: 10 }).map((_, i) => ({
-        name: `${i + 1}日`,
-        alarms: Math.round(80 + 20 * Math.sin(i / 2) + (i % 3) * 6),
-      })),
-    [],
-  )
-
-  const byIndustry = useMemo(() => {
-    const data = [
-      { name: "燃气", value: 0 },
-      { name: "供水", value: 0 },
-      { name: "排水", value: 0 },
-    ]
-    warnings.forEach((w) => {
-      const idx = data.findIndex((d) => d.name === w.industry)
-      data[idx]!.value++
-    })
-    return data
-  }, [warnings])
-
-  const efficiency = { correct: 86, avgMinutes: 42 }
-
-  return (
-    <div className="space-y-8">
-      <Section
-        title="当前预警"
-        description="综合展示总体预警处置情况,支持与一张图联动展示具体预警事件信息。"
-        action={<Button variant="outline" onClick={() => location.reload()}>模拟刷新</Button>}
-      >
-        <div className="grid gap-4 lg:grid-cols-3">
-          <Card className="lg:col-span-2">
-            <CardHeader>
-              <CardTitle className="text-sm">预警列表</CardTitle>
-            </CardHeader>
-            <CardContent className="overflow-x-auto">
-              <Table>
-                <TableHeader>
-                  <TableRow>
-                    <TableHead>编号</TableHead>
-                    <TableHead>类型</TableHead>
-                    <TableHead>行业</TableHead>
-                    <TableHead>等级</TableHead>
-                    <TableHead>区域</TableHead>
-                    <TableHead>时间</TableHead>
-                    <TableHead>状态</TableHead>
-                  </TableRow>
-                </TableHeader>
-                <TableBody>
-                  {warnings.map((w) => (
-                    <TableRow key={w.id} className="cursor-pointer" onClick={() => setSelected(w)}>
-                      <TableCell className="font-medium">{w.id}</TableCell>
-                      <TableCell>{w.type}</TableCell>
-                      <TableCell>{w.industry}</TableCell>
-                      <TableCell>
-                        <Badge
-                          className={
-                            w.severity === "高"
-                              ? "bg-rose-500 hover:bg-rose-500"
-                              : w.severity === "中"
-                              ? "bg-amber-500 hover:bg-amber-500"
-                              : "bg-emerald-500 hover:bg-emerald-500"
-                          }
-                        >
-                          {w.severity}
-                        </Badge>
-                      </TableCell>
-                      <TableCell>{w.district}</TableCell>
-                      <TableCell className="whitespace-nowrap">{w.time}</TableCell>
-                      <TableCell>
-                        <Badge variant="outline">{w.status}</Badge>
-                      </TableCell>
-                    </TableRow>
-                  ))}
-                </TableBody>
-              </Table>
-            </CardContent>
-          </Card>
-
-          <Card>
-            <CardHeader>
-              <CardTitle className="text-sm">近10日报警趋势</CardTitle>
-            </CardHeader>
-            <CardContent>
-              <ChartContainer config={trendConfig} className="h-[240px]">
-                <ResponsiveContainer width="100%" height="100%">
-                  <LineChart data={trend}>
-                    <CartesianGrid strokeDasharray="3 3" vertical={false} />
-                    <XAxis dataKey="name" tickLine={false} axisLine={false} />
-                    <YAxis tickLine={false} axisLine={false} />
-                    <ChartTooltip content={<ChartTooltipContent />} />
-                    <Line type="monotone" dataKey="alarms" stroke="var(--color-alarms)" strokeWidth={2} dot={false} />
-                  </LineChart>
-                </ResponsiveContainer>
-              </ChartContainer>
-            </CardContent>
-          </Card>
-        </div>
-      </Section>
-
-      <Section title="历史分析" description="按行业统计预警数量,分析处置效率变化趋势。">
-        <div className="grid gap-4 lg:grid-cols-3">
-          <Card>
-            <CardHeader>
-              <CardTitle className="text-sm">按行业分布</CardTitle>
-            </CardHeader>
-            <CardContent>
-              <ChartContainer
-                config={{
-                  gas: { label: "燃气", color: "hsl(var(--chart-1))" },
-                  water: { label: "供水", color: "hsl(var(--chart-2))" },
-                  drain: { label: "排水", color: "hsl(var(--chart-3))" },
-                }}
-                className="h-[240px]"
-              >
-                <ResponsiveContainer width="100%" height="100%">
-                  <PieChart>
-                    <ChartTooltip content={<ChartTooltipContent />} />
-                    <ChartLegend content={<ChartLegendContent />} />
-                    <Pie data={byIndustry} dataKey="value" nameKey="name" innerRadius={45} outerRadius={80}>
-                      {byIndustry.map((d) => (
-                        <Cell
-                          key={d.name}
-                          fill={
-                            d.name === "燃气"
-                              ? "var(--color-gas)"
-                              : d.name === "供水"
-                              ? "var(--color-water)"
-                              : "var(--color-drain)"
-                          }
-                        />
-                      ))}
-                    </Pie>
-                  </PieChart>
-                </ResponsiveContainer>
-              </ChartContainer>
-            </CardContent>
-          </Card>
-
-          <Card className="lg:col-span-2">
-            <CardHeader>
-              <CardTitle className="text-sm">平均处置时效(分钟)</CardTitle>
-            </CardHeader>
-            <CardContent>
-              <ChartContainer
-                config={{ minutes: { label: "分钟", color: "hsl(var(--chart-4))" } }}
-                className="h-[240px]"
-              >
-                <ResponsiveContainer width="100%" height="100%">
-                  <BarChart
-                    data={Array.from({ length: 8 }).map((_, i) => ({
-                      name: `${i + 1}周`,
-                      minutes: Math.round(50 - i * 2 + (i % 3) * 4),
-                    }))}
-                  >
-                    <CartesianGrid vertical={false} strokeDasharray="3 3" />
-                    <XAxis dataKey="name" tickLine={false} axisLine={false} />
-                    <YAxis tickLine={false} axisLine={false} />
-                    <ChartTooltip content={<ChartTooltipContent />} />
-                    <Bar dataKey="minutes" radius={6} fill="var(--color-minutes)" />
-                  </BarChart>
-                </ResponsiveContainer>
-              </ChartContainer>
-            </CardContent>
-          </Card>
-        </div>
-      </Section>
-
-      <Section title="预警效率" description="统计正确预警数量,展示整体预警效率。">
-        <div className="grid gap-4 md:grid-cols-2">
-          <Card>
-            <CardHeader>
-              <CardTitle className="text-sm">正确预警比例</CardTitle>
-            </CardHeader>
-            <CardContent className="flex items-center gap-6">
-              <div className="relative grid place-items-center">
-                <svg width="140" height="140" viewBox="0 0 36 36" aria-label="正确预警比例">
-                  <path
-                    d="M18 2.0845
-                       a 15.9155 15.9155 0 0 1 0 31.831
-                       a 15.9155 15.9155 0 0 1 0 -31.831"
-                    fill="none"
-                    stroke="hsl(var(--muted-foreground))"
-                    strokeWidth="2"
-                    opacity="0.2"
-                  />
-                  <path
-                    d="M18 2.0845
-                       a 15.9155 15.9155 0 0 1 0 31.831
-                       a 15.9155 15.9155 0 0 1 0 -31.831"
-                    fill="none"
-                    stroke="hsl(var(--chart-1))"
-                    strokeWidth="2.5"
-                    strokeDasharray={`${efficiency.correct}, 100`}
-                    strokeLinecap="round"
-                  />
-                  <text x="18" y="20.35" className="fill-foreground" textAnchor="middle" fontSize="6">
-                    {efficiency.correct}%
-                  </text>
-                </svg>
-              </div>
-              <div className="text-sm">
-                <div className="flex items-center gap-2">
-                  <ShieldCheck className="h-4 w-4 text-emerald-600" />
-                  正确预警
-                </div>
-                <p className="text-muted-foreground mt-1">
-                  结合人工复核等处置数据,统计系统预警准确度。
-                </p>
-              </div>
-            </CardContent>
-          </Card>
-
-          <Card>
-            <CardHeader>
-              <CardTitle className="text-sm">平均处置用时</CardTitle>
-            </CardHeader>
-            <CardContent className="flex items-center gap-6">
-              <AlarmClock className="h-8 w-8 text-emerald-600" />
-              <div>
-                <div className="text-2xl font-semibold">{efficiency.avgMinutes} 分钟</div>
-                <p className="text-xs text-muted-foreground mt-1">较上周提升 8%</p>
-              </div>
-            </CardContent>
-          </Card>
-        </div>
-      </Section>
-
-      <Section title="预警分析" description="按类型与成因维度分析预警分布情况。">
-        <div className="grid gap-4 md:grid-cols-2">
-          <Card>
-            <CardHeader>
-              <CardTitle className="text-sm">按类型(样例)</CardTitle>
-            </CardHeader>
-            <CardContent>
-              <ChartContainer
-                config={{
-                  gas: { label: "泄漏", color: "hsl(var(--chart-1))" },
-                  water: { label: "水压异常", color: "hsl(var(--chart-2))" },
-                  tank: { label: "液位过高", color: "hsl(var(--chart-3))" },
-                  valve: { label: "阀门故障", color: "hsl(var(--chart-4))" },
-                }}
-                className="h-[240px]"
-              >
-                <ResponsiveContainer width="100%" height="100%">
-                  <BarChart
-                    data={[
-                      { name: "一月", 泄漏: 42, 水压异常: 58, 液位过高: 32, 阀门故障: 20 },
-                      { name: "二月", 泄漏: 36, 水压异常: 62, 液位过高: 27, 阀门故障: 24 },
-                      { name: "三月", 泄漏: 50, 水压异常: 54, 液位过高: 31, 阀门故障: 22 },
-                    ]}
-                  >
-                    <CartesianGrid vertical={false} strokeDasharray="3 3" />
-                    <XAxis dataKey="name" tickLine={false} axisLine={false} />
-                    <YAxis tickLine={false} axisLine={false} />
-                    <ChartTooltip content={<ChartTooltipContent />} />
-                    <Bar dataKey="泄漏" stackId="a" fill="var(--color-gas)" radius={4} />
-                    <Bar dataKey="水压异常" stackId="a" fill="var(--color-water)" radius={4} />
-                    <Bar dataKey="液位过高" stackId="a" fill="var(--color-tank)" radius={4} />
-                    <Bar dataKey="阀门故障" stackId="a" fill="var(--color-valve)" radius={4} />
-                  </BarChart>
-                </ResponsiveContainer>
-              </ChartContainer>
-            </CardContent>
-          </Card>
-
-          <Card>
-            <CardHeader>
-              <CardTitle className="text-sm">成因分布(样例)</CardTitle>
-            </CardHeader>
-            <CardContent>
-              <ChartContainer
-                config={{
-                  human: { label: "人为", color: "hsl(var(--chart-5))" },
-                  aging: { label: "老化", color: "hsl(var(--chart-3))" },
-                  env: { label: "环境", color: "hsl(var(--chart-2))" },
-                }}
-                className="h-[240px]"
-              >
-                <ResponsiveContainer width="100%" height="100%">
-                  <PieChart>
-                    <ChartTooltip content={<ChartTooltipContent />} />
-                    <ChartLegend content={<ChartLegendContent />} />
-                    <Pie
-                      data={[
-                        { name: "人为", value: 36 },
-                        { name: "老化", value: 44 },
-                        { name: "环境", value: 20 },
-                      ]}
-                      dataKey="value"
-                      nameKey="name"
-                      innerRadius={45}
-                      outerRadius={80}
-                      paddingAngle={2}
-                    >
-                      <Cell key="人为" fill="var(--color-human)" />
-                      <Cell key="老化" fill="var(--color-aging)" />
-                      <Cell key="环境" fill="var(--color-env)" />
-                    </Pie>
-                  </PieChart>
-                </ResponsiveContainer>
-              </ChartContainer>
-            </CardContent>
-          </Card>
-        </div>
-      </Section>
-
-      <WarningDialog warning={selected} onOpenChange={(open) => !open && setSelected(null)} />
-    </div>
-  )
-}
-
-function WarningDialog({
-  warning,
-  onOpenChange,
-}: {
-  warning: Warning | null
-  onOpenChange: (open: boolean) => void
-}) {
-  return (
-    <Dialog open={!!warning} onOpenChange={onOpenChange}>
-      <DialogContent className="max-w-2xl">
-        <DialogHeader>
-          <DialogTitle>预警详情</DialogTitle>
-        </DialogHeader>
-        {warning ? (
-          <div className="text-sm">
-            <div className="grid grid-cols-2 gap-3">
-              <div>
-                <span className="text-muted-foreground">编号:</span>
-                {warning.id}
-              </div>
-              <div>
-                <span className="text-muted-foreground">时间:</span>
-                {warning.time}
-              </div>
-              <div>
-                <span className="text-muted-foreground">类型:</span>
-                {warning.type}
-              </div>
-              <div>
-                <span className="text-muted-foreground">行业:</span>
-                {warning.industry}
-              </div>
-              <div>
-                <span className="text-muted-foreground">等级:</span>
-                <Badge
-                  className={
-                    warning.severity === "高"
-                      ? "bg-rose-500 hover:bg-rose-500"
-                      : warning.severity === "中"
-                      ? "bg-amber-500 hover:bg-amber-500"
-                      : "bg-emerald-500 hover:bg-emerald-500"
-                  }
-                >
-                  {warning.severity}
-                </Badge>
-              </div>
-              <div>
-                <span className="text-muted-foreground">状态:</span>
-                <Badge variant="outline">{warning.status}</Badge>
-              </div>
-              <div className="col-span-2">
-                <span className="text-muted-foreground">区域:</span>
-                {warning.district}
-              </div>
-            </div>
-            <Separator className="my-4" />
-            <div className="rounded-md border p-3">
-              <div className="text-xs text-muted-foreground mb-2">定位(示意)</div>
-              <div className="relative h-48 w-full overflow-hidden rounded">
-                <svg viewBox="0 0 100 100" className="absolute inset-0 h-full w-full">
-                  <rect width="100" height="100" fill="hsl(var(--muted))" opacity="0.3" />
-                  <path d="M 0 50 L 100 50" stroke="hsl(var(--border))" strokeWidth="1" />
-                  <circle cx="62" cy="42" r="3" fill="hsl(var(--primary))" />
-                </svg>
-              </div>
-            </div>
-          </div>
-        ) : null}
-      </DialogContent>
-    </Dialog>
-  )
-}

+ 87 - 0
components/echarts.tsx

@@ -0,0 +1,87 @@
+"use client"
+
+import React, {useEffect, useRef} from "react"
+import {
+  type ECharts,
+  type EChartsCoreOption,
+  getInstanceByDom,
+  init as echartsInit,
+  type SetOptionOpts,
+  use as echartsUse
+} from "echarts/core"
+import {BarChart, GaugeChart, LineChart, PieChart, RadarChart, ScatterChart} from "echarts/charts"
+import {
+  DatasetComponent,
+  GraphicComponent,
+  GridComponent,
+  LegendComponent,
+  TitleComponent,
+  ToolboxComponent,
+  TooltipComponent,
+  VisualMapComponent,
+} from "echarts/components"
+import {CanvasRenderer} from "echarts/renderers"
+
+echartsUse([
+  BarChart,
+  LineChart,
+  PieChart,
+  GaugeChart,
+  RadarChart,
+  ScatterChart,
+  GridComponent,
+  TooltipComponent,
+  LegendComponent,
+  TitleComponent,
+  ToolboxComponent,
+  VisualMapComponent,
+  DatasetComponent,
+  GraphicComponent,
+  CanvasRenderer,
+])
+
+export type EChartProps = {
+  option: EChartsCoreOption
+  className?: string
+  style?: React.CSSProperties
+  theme?: "light" | "dark"
+  opts?: SetOptionOpts
+}
+
+export default function EChart({ option, className, style, theme = "light", opts }: EChartProps) {
+  const ref = useRef<HTMLDivElement | null>(null)
+  const chartRef = useRef<ECharts | null>(null)
+
+  useEffect(() => {
+    if (!ref.current) return
+    const el = ref.current
+
+    let chart = getInstanceByDom(el)
+    if (!chart) {
+      chart = echartsInit(el, theme, { renderer: "canvas", locale: "ZH" })
+    }
+    chartRef.current = chart
+
+    // Apply option
+    chart.setOption(option, opts)
+
+    const resize = () => chart && chart.resize()
+    window.addEventListener("resize", resize)
+
+    // Resize observer for container changes
+    const ro = new ResizeObserver(() => resize())
+    ro.observe(el)
+
+    return () => {
+      window.removeEventListener("resize", resize)
+      ro.disconnect()
+      // dispose only if element is being removed to avoid issues in fast refresh
+      if (chart && !el.isConnected) {
+        chart.dispose()
+        chartRef.current = null
+      }
+    }
+  }, [option, theme, opts])
+
+  return <div ref={ref} className={className} style={{ width: "100%", height: 320, ...style }} />
+}

+ 0 - 203
components/infra/infra-dashboard.tsx

@@ -1,203 +0,0 @@
-"use client"
-
-import Section from "@/components/shared/section"
-import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card"
-import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@/components/ui/table"
-import {ChartContainer, ChartTooltip, ChartTooltipContent,} from "@/components/ui/chart"
-import {
-  Area,
-  AreaChart,
-  CartesianGrid,
-  PolarAngleAxis,
-  PolarGrid,
-  PolarRadiusAxis,
-  Radar,
-  RadarChart,
-  ResponsiveContainer,
-  XAxis,
-  YAxis,
-} from "recharts"
-
-const baseArchive = [
-  { industry: "燃气", 规模: 1240, 平均年限: 9.4, 类型数: 12, 覆盖区县: 12 },
-  { industry: "供水", 规模: 980, 平均年限: 8.1, 类型数: 9, 覆盖区县: 12 },
-  { industry: "排水", 规模: 760, 平均年限: 10.2, 类型数: 7, 覆盖区县: 12 },
-]
-
-export default function InfraDashboard() {
-  return (
-    <div className="space-y-8">
-      <Section
-        title="基础档案"
-        description="构建设施基础档案,数字化呈现规模、年限、类型与分布。"
-      >
-        <Card>
-          <CardContent className="pt-4 overflow-x-auto">
-            <Table>
-              <TableHeader>
-                <TableRow>
-                  <TableHead>行业</TableHead>
-                  <TableHead>规模(处)</TableHead>
-                  <TableHead>平均年限(年)</TableHead>
-                  <TableHead>类型数</TableHead>
-                  <TableHead>覆盖区县</TableHead>
-                </TableRow>
-              </TableHeader>
-              <TableBody>
-                {baseArchive.map((row) => (
-                  <TableRow key={row.industry}>
-                    <TableCell className="font-medium">{row.industry}</TableCell>
-                    <TableCell>{row.规模}</TableCell>
-                    <TableCell>{row.平均年限}</TableCell>
-                    <TableCell>{row.类型数}</TableCell>
-                    <TableCell>{row.覆盖区县}</TableCell>
-                  </TableRow>
-                ))}
-              </TableBody>
-            </Table>
-          </CardContent>
-        </Card>
-      </Section>
-
-      <Section
-        title="运行信息"
-        description="展示日常供应能力与承载能力,辅助掌握运行能力、指导设施建设。"
-      >
-        <div className="grid gap-4 md:grid-cols-2">
-          <Card>
-            <CardHeader>
-              <CardTitle className="text-sm">供水能力(万m³/日)</CardTitle>
-            </CardHeader>
-            <CardContent>
-              <ChartContainer
-                config={{ supply: { label: "供水", color: "hsl(var(--chart-2))" } }}
-                className="h-[220px]"
-              >
-                <ResponsiveContainer width="100%" height="100%">
-                  <AreaChart
-                    data={Array.from({ length: 12 }).map((_, i) => ({
-                      m: `${i + 1}月`,
-                      supply: Math.round(290 + 5 * i + 10 * Math.sin(i / 2)),
-                    }))}
-                  >
-                    <defs>
-                      <linearGradient id="supplyFill" x1="0" y1="0" x2="0" y2="1">
-                        <stop offset="5%" stopColor="var(--color-supply)" stopOpacity={0.35} />
-                        <stop offset="95%" stopColor="var(--color-supply)" stopOpacity={0.05} />
-                      </linearGradient>
-                    </defs>
-                    <CartesianGrid strokeDasharray="3 3" vertical={false} />
-                    <XAxis dataKey="m" tickLine={false} axisLine={false} />
-                    <YAxis tickLine={false} axisLine={false} />
-                    <ChartTooltip content={<ChartTooltipContent />} />
-                    <Area type="monotone" dataKey="supply" stroke="var(--color-supply)" fill="url(#supplyFill)" />
-                  </AreaChart>
-                </ResponsiveContainer>
-              </ChartContainer>
-            </CardContent>
-          </Card>
-
-          <Card>
-            <CardHeader>
-              <CardTitle className="text-sm">承载能力雷达图(样例)</CardTitle>
-            </CardHeader>
-            <CardContent>
-              <ChartContainer
-                config={{
-                  score: { label: "能力分", color: "hsl(var(--chart-3))" },
-                }}
-                className="h-[220px]"
-              >
-                <ResponsiveContainer width="100%" height="100%">
-                  <RadarChart
-                    data={[
-                      { name: "管网", score: 76 },
-                      { name: "水厂", score: 84 },
-                      { name: "泵站", score: 72 },
-                      { name: "阀门", score: 68 },
-                      { name: "智能化", score: 80 },
-                    ]}
-                  >
-                    <PolarGrid />
-                    <PolarAngleAxis dataKey="name" />
-                    <PolarRadiusAxis angle={30} domain={[0, 100]} />
-                    <Radar
-                      name="能力"
-                      dataKey="score"
-                      stroke="var(--color-score)"
-                      fill="var(--color-score)"
-                      fillOpacity={0.2}
-                    />
-                  </RadarChart>
-                </ResponsiveContainer>
-              </ChartContainer>
-            </CardContent>
-          </Card>
-        </div>
-      </Section>
-
-      <Section title="管理信息" description="分类展示管理单位信息,辅助了解权属分布。">
-        <Card>
-          <CardContent className="pt-4">
-            <div className="grid gap-4 md:grid-cols-3">
-              <UnitCard name="市燃气集团" owners={5} regions={8} />
-              <UnitCard name="市自来水公司" owners={4} regions={10} />
-              <UnitCard name="市排水管理中心" owners={3} regions={12} />
-            </div>
-          </CardContent>
-        </Card>
-      </Section>
-
-      <Section title="应急保障" description="展示危险源、防护目标及应急资源配置情况。">
-        <div className="grid gap-4 md:grid-cols-3">
-          <Card>
-            <CardHeader>
-              <CardTitle className="text-sm">危险源</CardTitle>
-            </CardHeader>
-            <CardContent>
-              <ul className="text-sm list-disc pl-4 space-y-1">
-                <li>高压燃气管线(重点段) 26 处</li>
-                <li>大型储气罐区 8 处</li>
-                <li>易涝点 14 处</li>
-              </ul>
-            </CardContent>
-          </Card>
-          <Card>
-            <CardHeader>
-              <CardTitle className="text-sm">防护目标</CardTitle>
-            </CardHeader>
-            <CardContent>
-              <ul className="text-sm list-disc pl-4 space-y-1">
-                <li>学校、医院等重点单位 120 所</li>
-                <li>大型商圈 12 个</li>
-                <li>交通枢纽 6 处</li>
-              </ul>
-            </CardContent>
-          </Card>
-          <Card>
-            <CardHeader>
-              <CardTitle className="text-sm">应急资源</CardTitle>
-            </CardHeader>
-            <CardContent>
-              <ul className="text-sm list-disc pl-4 space-y-1">
-                <li>应急队伍 18 支</li>
-                <li>应急车辆 64 台</li>
-                <li>物资库 9 处(发电机、沙袋、移动泵等)</li>
-              </ul>
-            </CardContent>
-          </Card>
-        </div>
-      </Section>
-    </div>
-  )
-}
-
-function UnitCard({ name, owners, regions }: { name: string; owners: number; regions: number }) {
-  return (
-    <div className="rounded-lg border p-4">
-      <h3 className="font-medium">{name}</h3>
-      <p className="text-sm text-muted-foreground mt-1">权属单位:{owners} 个</p>
-      <p className="text-sm text-muted-foreground">覆盖区县:{regions} 个</p>
-    </div>
-  )
-}

+ 0 - 99
components/map/map-distribution.tsx

@@ -1,99 +0,0 @@
-"use client"
-
-import {Card} from "@/components/ui/card"
-import {Badge} from "@/components/ui/badge"
-import {useMemo} from "react"
-
-type MapDistributionProps = {
-  industry?: string
-  risk?: string
-}
-type DistrictPoint = {
-  id: string
-  x: number // 0..1 relative
-  y: number // 0..1 relative
-  risk: "low" | "mid" | "high"
-  industry: "gas" | "water" | "drain"
-}
-
-function seededRandom(seed: number) {
-  let x = Math.sin(seed) * 10000
-  return x - Math.floor(x)
-}
-
-function genPoints(seed = 42, count = 120): DistrictPoint[] {
-  const inds: Array<DistrictPoint["industry"]> = ["gas", "water", "drain"]
-  const risks: Array<DistrictPoint["risk"]> = ["low", "mid", "high"]
-  return Array.from({ length: count }).map((_, i) => {
-    const r1 = seededRandom(seed + i * 1.3)
-    const r2 = seededRandom(seed + i * 2.7)
-    const r3 = seededRandom(seed + i * 3.9)
-    return {
-      id: `pt-${i}`,
-      x: r1,
-      y: r2,
-      risk: r3 > 0.75 ? "high" : r3 > 0.45 ? "mid" : "low",
-      industry: inds[Math.floor(r1 * inds.length)]!,
-    }
-  })
-}
-
-export default function MapDistribution({ industry = "all", risk = "all" }: MapDistributionProps) {
-  const points = useMemo(() => genPoints(7, 150), [])
-  const filtered = points.filter((p) => (industry === "all" || p.industry === industry) && (risk === "all" || p.risk === risk))
-
-  const riskColor = (r: DistrictPoint["risk"]) =>
-    r === "high" ? "bg-rose-500" : r === "mid" ? "bg-amber-500" : "bg-emerald-500"
-
-  return (
-    <Card className="relative h-[420px] w-full overflow-hidden">
-      {/* Background grid as simplified "city map" */}
-      <svg viewBox="0 0 100 100" className="absolute inset-0 h-full w-full">
-        <defs>
-          <pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse">
-            <path d="M 10 0 L 0 0 0 10" fill="none" stroke="hsl(var(--muted-foreground))" opacity="0.2" strokeWidth="0.5" />
-          </pattern>
-        </defs>
-        <rect width="100" height="100" fill="url(#grid)" />
-        {/* Water bodies / parks (decorative) */}
-        <rect x="5" y="12" width="30" height="10" rx="2" fill="hsl(var(--muted))" opacity="0.6" />
-        <rect x="60" y="70" width="30" height="12" rx="2" fill="hsl(var(--muted))" opacity="0.6" />
-        {/* Main roads */}
-        <path d="M 0 50 L 100 50" stroke="hsl(var(--border))" strokeWidth="1.5" opacity="0.5" />
-        <path d="M 25 0 L 75 100" stroke="hsl(var(--border))" strokeWidth="1.5" opacity="0.5" />
-      </svg>
-
-      {/* Points */}
-      <div className="absolute inset-0">
-        {filtered.map((p) => (
-          <div
-            key={p.id}
-            className={`absolute h-2.5 w-2.5 rounded-full ring-2 ring-white/70 shadow ${riskColor(p.risk)}`}
-            style={{
-              left: `calc(${p.x * 100}% - 5px)`,
-              top: `calc(${p.y * 100}% - 5px)`,
-            }}
-            title={`${p.industry === "gas" ? "燃气" : p.industry === "water" ? "供水" : "排水"} · ${
-              p.risk === "high" ? "高" : p.risk === "mid" ? "中" : "低"
-            }风险`}
-            aria-label="监测点"
-          />
-        ))}
-      </div>
-
-      {/* Legend */}
-      <div className="absolute bottom-3 left-3 flex items-center gap-2 rounded-md bg-background/80 p-2 text-xs shadow">
-        <span className="inline-flex h-2.5 w-2.5 rounded-full bg-emerald-500" />
-        低
-        <span className="inline-flex h-2.5 w-2.5 rounded-full bg-amber-500 ml-2" />
-        中
-        <span className="inline-flex h-2.5 w-2.5 rounded-full bg-rose-500 ml-2" />
-        高
-        <div className="ml-3 flex items-center gap-1">
-          <Badge variant="outline">{industry === "all" ? "全部行业" : industry === "gas" ? "燃气" : industry === "water" ? "供水" : "排水"}</Badge>
-          <Badge variant="outline">{risk === "all" ? "全部风险" : risk === "high" ? "高风险" : risk === "mid" ? "中风险" : "低风险"}</Badge>
-        </div>
-      </div>
-    </Card>
-  )
-}

+ 0 - 169
components/monitoring/monitoring-dashboard.tsx

@@ -1,169 +0,0 @@
-"use client"
-
-import {useMemo, useState} from "react"
-import Section from "@/components/shared/section"
-import MetricCard from "@/components/shared/metric-card"
-import {Activity, Cpu, Filter, Satellite, Wifi} from 'lucide-react'
-import {Button} from "@/components/ui/button"
-import {type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent,} from "@/components/ui/chart"
-import {Bar, BarChart, CartesianGrid, Line, LineChart, ResponsiveContainer, XAxis, YAxis,} from "recharts"
-import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card"
-import MapDistribution from "@/components/map/map-distribution"
-import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select"
-
-function useMonitoringData() {
-  const devices = 18860
-  const types = [
-    { name: "流量计", value: 5200 },
-    { name: "压力传感", value: 4300 },
-    { name: "液位传感", value: 3720 },
-    { name: "阀门状态", value: 2640 },
-  ]
-  const onlineRate = 96.8
-  const trend = Array.from({ length: 14 }).map((_, i) => ({
-    t: `${i + 1}时`,
-    online: 95 + Math.sin(i / 2) * 2 + (i % 3) * 0.2,
-    alarms: Math.max(0, Math.round(60 + 8 * Math.sin(i / 1.5) + (i % 4) * 2)),
-  }))
-  return { devices, types, onlineRate, trend }
-}
-
-const onlineConfig = {
-  online: { label: "在线率", color: "hsl(var(--chart-1))" },
-} satisfies ChartConfig
-
-const alarmConfig = {
-  alarms: { label: "报警数", color: "hsl(var(--chart-5))" },
-} satisfies ChartConfig
-
-export default function MonitoringDashboard() {
-  const { devices, types, onlineRate, trend } = useMonitoringData()
-  const [industry, setIndustry] = useState<string>("all")
-  const [risk, setRisk] = useState<string>("all")
-  const [refreshing, setRefreshing] = useState(false)
-
-  const typesBar = useMemo(
-    () => types.map((t) => ({ name: t.name, value: t.value })),
-    [types],
-  )
-
-  return (
-    <div className="space-y-8">
-      <Section
-        title="监测概览"
-        description="通过监测规模、类型、在线率等指标展示设备整体运行情况。"
-        action={
-          <Button
-            variant="outline"
-            onClick={() => {
-              setRefreshing(true)
-              setTimeout(() => setRefreshing(false), 800)
-            }}
-          >
-            {refreshing ? "刷新中..." : "刷新数据"}
-          </Button>
-        }
-      >
-        <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
-          <MetricCard title="监测设备数" value={devices} icon={<Satellite className="h-4 w-4 text-emerald-600" />} />
-          <MetricCard
-            title="设备类型数"
-            value={types.length}
-            icon={<Cpu className="h-4 w-4 text-emerald-600" />}
-          />
-          <MetricCard
-            title="在线率"
-            value={onlineRate}
-            suffix="%"
-            icon={<Wifi className="h-4 w-4 text-emerald-600" />}
-            trend={{ delta: 0.4, label: "近24h" }}
-          />
-          <MetricCard
-            title="今日报警"
-            value={trend.reduce((acc, cur) => acc + cur.alarms, 0)}
-            icon={<Activity className="h-4 w-4 text-emerald-600" />}
-          />
-        </div>
-      </Section>
-
-      <Section title="监测类型分布与在线率">
-        <div className="grid gap-4 lg:grid-cols-3">
-          <Card className="lg:col-span-2">
-            <CardHeader>
-              <CardTitle className="text-sm">在线率(近14小时)</CardTitle>
-            </CardHeader>
-            <CardContent>
-              <ChartContainer config={onlineConfig} className="h-[240px]">
-                <ResponsiveContainer width="100%" height="100%">
-                  <LineChart data={trend}>
-                    <CartesianGrid strokeDasharray="3 3" vertical={false} />
-                    <XAxis dataKey="t" tickLine={false} axisLine={false} />
-                    <YAxis domain={[92, 100]} tickLine={false} axisLine={false} unit="%" />
-                    <ChartTooltip content={<ChartTooltipContent />} />
-                    <Line type="monotone" dataKey="online" stroke="var(--color-online)" dot={false} strokeWidth={2} />
-                  </LineChart>
-                </ResponsiveContainer>
-              </ChartContainer>
-            </CardContent>
-          </Card>
-
-          <Card>
-            <CardHeader>
-              <CardTitle className="text-sm">设备类型分布</CardTitle>
-            </CardHeader>
-            <CardContent>
-              <ChartContainer
-                config={{ value: { label: "数量", color: "hsl(var(--chart-2))" } }}
-                className="h-[240px]"
-              >
-                <ResponsiveContainer width="100%" height="100%">
-                  <BarChart data={typesBar}>
-                    <CartesianGrid vertical={false} strokeDasharray="3 3" />
-                    <XAxis dataKey="name" tickLine={false} axisLine={false} />
-                    <YAxis tickLine={false} axisLine={false} />
-                    <ChartTooltip content={<ChartTooltipContent />} />
-                    <Bar dataKey="value" radius={6} fill="var(--color-value)" />
-                  </BarChart>
-                </ResponsiveContainer>
-              </ChartContainer>
-            </CardContent>
-          </Card>
-        </div>
-      </Section>
-
-      <Section
-        title="监测分布"
-        description="基于设备地理位置信息,展示不同风险等级的覆盖情况。"
-        action={
-          <div className="flex items-center gap-2">
-            <Filter className="h-4 w-4 text-muted-foreground" />
-            <Select defaultValue="all" onValueChange={setIndustry}>
-              <SelectTrigger className="w-[140px]">
-                <SelectValue placeholder="行业" />
-              </SelectTrigger>
-              <SelectContent>
-                <SelectItem value="all">全部行业</SelectItem>
-                <SelectItem value="gas">燃气</SelectItem>
-                <SelectItem value="water">供水</SelectItem>
-                <SelectItem value="drain">排水</SelectItem>
-              </SelectContent>
-            </Select>
-            <Select defaultValue="all" onValueChange={setRisk}>
-              <SelectTrigger className="w-[140px]">
-                <SelectValue placeholder="风险等级" />
-              </SelectTrigger>
-              <SelectContent>
-                <SelectItem value="all">全部风险</SelectItem>
-                <SelectItem value="low">低风险</SelectItem>
-                <SelectItem value="mid">中风险</SelectItem>
-                <SelectItem value="high">高风险</SelectItem>
-              </SelectContent>
-            </Select>
-          </div>
-        }
-      >
-        <MapDistribution industry={industry} risk={risk} />
-      </Section>
-    </div>
-  )
-}

+ 0 - 261
components/overview/overview-dashboard.tsx

@@ -1,261 +0,0 @@
-"use client"
-
-import {Activity, Cpu, Droplets, Flame, Waves} from 'lucide-react'
-import MetricCard from "@/components/shared/metric-card"
-import Section from "@/components/shared/section"
-import {Button} from "@/components/ui/button"
-import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card"
-import {
-  type ChartConfig,
-  ChartContainer,
-  ChartLegend,
-  ChartLegendContent,
-  ChartTooltip,
-  ChartTooltipContent,
-} from "@/components/ui/chart"
-import {
-  Area,
-  Bar,
-  BarChart,
-  CartesianGrid,
-  Legend,
-  Line,
-  LineChart,
-  Pie,
-  PieChart,
-  RadialBar,
-  RadialBarChart,
-  ResponsiveContainer,
-  XAxis,
-  YAxis,
-} from "recharts"
-import {useMemo, useState} from "react"
-
-const industries = [
-  { key: "gas", label: "燃气", icon: <Flame className="h-4 w-4 text-amber-600" /> },
-  { key: "water", label: "供水", icon: <Droplets className="h-4 w-4 text-cyan-600" /> },
-  { key: "drain", label: "排水", icon: <Waves className="h-4 w-4 text-emerald-600" /> },
-] as const
-
-function useMockOverviewData() {
-  // Deterministic mock numbers
-  const base = {
-    gas: { facilities: 1240, devices: 8420, onlineRate: 96.2, supply: 52.4 },
-    water: { facilities: 980, devices: 6230, onlineRate: 97.8, supply: 315.6 },
-    drain: { facilities: 760, devices: 4210, onlineRate: 94.1, supply: 288.3 },
-  }
-  const totalFacilities = base.gas.facilities + base.water.facilities + base.drain.facilities
-  const totalDevices = base.gas.devices + base.water.devices + base.drain.devices
-  const avgOnline =
-    Math.round(((base.gas.onlineRate + base.water.onlineRate + base.drain.onlineRate) / 3) * 10) / 10
-
-  const typeShare = [
-    { name: "燃气", value: base.gas.facilities, key: "gas" },
-    { name: "供水", value: base.water.facilities, key: "water" },
-    { name: "排水", value: base.drain.facilities, key: "drain" },
-  ]
-
-  const trend = Array.from({ length: 12 }).map((_, i) => ({
-    month: `${i + 1}月`,
-    // mock报警/事件趋势
-    alarms: Math.round(140 + 30 * Math.sin((i / 12) * Math.PI * 2) + (i % 3) * 8),
-    supply: base.water.supply * (0.9 + 0.02 * i), // example: 供水能力趋势
-  }))
-
-  return { base, totalFacilities, totalDevices, avgOnline, typeShare, trend }
-}
-
-const shareConfig = {
-  gas: { label: "燃气", color: "hsl(var(--chart-1))" },
-  water: { label: "供水", color: "hsl(var(--chart-2))" },
-  drain: { label: "排水", color: "hsl(var(--chart-3))" },
-} satisfies ChartConfig
-
-const trendConfig = {
-  alarms: { label: "报警数", color: "hsl(var(--chart-4))" },
-  supply: { label: "供水能力", color: "hsl(var(--chart-2))" },
-} satisfies ChartConfig
-
-export default function OverviewDashboard() {
-  const { base, totalFacilities, totalDevices, avgOnline, typeShare, trend } = useMockOverviewData()
-  const [loading, setLoading] = useState(false)
-
-  const onlineRadial = useMemo(
-    () => [{ name: "在线率", online: avgOnline, fill: "hsl(var(--chart-1))" }],
-    [avgOnline],
-  )
-
-  return (
-    <div className="space-y-8">
-      <Section
-        title="总体概览"
-        description="汇聚燃气、供水、排水等生命线基础设施的底数与运行情况。"
-        action={
-          <Button
-            variant="outline"
-            onClick={() => {
-              setLoading(true)
-              setTimeout(() => setLoading(false), 800)
-            }}
-          >
-            {loading ? "刷新中..." : "刷新数据"}
-          </Button>
-        }
-      >
-        <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
-          <MetricCard
-            title="设施总量"
-            value={totalFacilities}
-            icon={<Cpu className="h-4 w-4 text-emerald-600" aria-hidden />}
-            trend={{ delta: 2.4, label: "同比" }}
-          />
-          <MetricCard
-            title="监测设备"
-            value={totalDevices}
-            icon={<Activity className="h-4 w-4 text-emerald-600" aria-hidden />}
-            trend={{ delta: 1.2, label: "环比" }}
-          />
-          <MetricCard
-            title="平均在线率"
-            value={avgOnline}
-            suffix="%"
-            icon={<Activity className="h-4 w-4 text-emerald-600" aria-hidden />}
-            trend={{ delta: 0.6, label: "近7天" }}
-          />
-          <MetricCard
-            title="供水能力"
-            value={Math.round(base.water.supply)}
-            suffix="万m³/日"
-            icon={<Droplets className="h-4 w-4 text-cyan-600" aria-hidden />}
-          />
-        </div>
-      </Section>
-
-      <Section title="设施底数结构" description="构建设施底数指标体系,展示设施类型占比与规模变化。">
-        <div className="grid gap-4 lg:grid-cols-3">
-          <Card className="lg:col-span-1">
-            <CardHeader>
-              <CardTitle className="text-sm">设施类型占比</CardTitle>
-            </CardHeader>
-            <CardContent>
-              <ChartContainer config={shareConfig} className="h-[260px]">
-                <ResponsiveContainer width="100%" height="100%">
-                  <PieChart>
-                    <ChartTooltip content={<ChartTooltipContent />} />
-                    <ChartLegend content={<ChartLegendContent />} />
-                    <Pie
-                      data={typeShare}
-                      dataKey="value"
-                      nameKey="name"
-                      innerRadius={55}
-                      outerRadius={90}
-                      paddingAngle={2}
-                    >
-                      {typeShare.map((entry) => (
-                        <Cell key={entry.key} fill={`var(--color-${entry.key})`} />
-                      ))}
-                    </Pie>
-                  </PieChart>
-                </ResponsiveContainer>
-              </ChartContainer>
-              <div className="mt-2 grid grid-cols-3 gap-2 text-center text-xs text-muted-foreground">
-                {industries.map((it) => (
-                  <div key={it.key} className="flex items-center justify-center gap-1">
-                    {it.icon}
-                    <span>{it.label}</span>
-                  </div>
-                ))}
-              </div>
-            </CardContent>
-          </Card>
-
-          <Card className="lg:col-span-2">
-            <CardHeader>
-              <CardTitle className="text-sm">报警趋势与供水能力</CardTitle>
-            </CardHeader>
-            <CardContent>
-              <ChartContainer config={trendConfig} className="h-[260px]">
-                <ResponsiveContainer width="100%" height="100%">
-                  <ComposedTrend data={trend} />
-                </ResponsiveContainer>
-              </ChartContainer>
-            </CardContent>
-          </Card>
-        </div>
-      </Section>
-
-      <Section title="运行稳定性" description="展示在线率等关键稳定性指标。">
-        <div className="grid gap-4 md:grid-cols-2">
-          <Card>
-            <CardHeader>
-              <CardTitle className="text-sm">平均在线率</CardTitle>
-            </CardHeader>
-            <CardContent>
-              <ChartContainer
-                config={{ online: { label: "在线率", color: "hsl(var(--chart-1))" } }}
-                className="mx-auto h-[220px] w-full max-w-[380px]"
-              >
-                <ResponsiveContainer width="100%" height="100%">
-                  <RadialBarChart innerRadius="75%" outerRadius="100%" data={[{ name: "在线率", online: avgOnline }]}>
-                    <RadialBar dataKey="online" cornerRadius={8} fill="var(--color-online)" />
-                    <ChartTooltip content={<ChartTooltipContent hideLabel />} />
-                  </RadialBarChart>
-                </ResponsiveContainer>
-              </ChartContainer>
-              <p className="text-center text-xs text-muted-foreground mt-2">目标 ≥ 95%</p>
-            </CardContent>
-          </Card>
-
-          <Card>
-            <CardHeader>
-              <CardTitle className="text-sm">设施规模变化(样例)</CardTitle>
-            </CardHeader>
-            <CardContent>
-              <ChartContainer
-                config={{ facilities: { label: "设施数量", color: "hsl(var(--chart-3))" } }}
-                className="h-[220px]"
-              >
-                <ResponsiveContainer width="100%" height="100%">
-                  <BarChart
-                    data={Array.from({ length: 8 }).map((_, i) => ({
-                      name: `${i + 1}期`,
-                      facilities: 2200 + (i % 3) * 120 + Math.round(50 * Math.sin(i)),
-                    }))}
-                  >
-                    <CartesianGrid vertical={false} strokeDasharray="3 3" />
-                    <XAxis dataKey="name" tickLine={false} axisLine={false} />
-                    <YAxis tickLine={false} axisLine={false} />
-                    <ChartTooltip content={<ChartTooltipContent />} />
-                    <Bar dataKey="facilities" fill="var(--color-facilities)" radius={6} />
-                  </BarChart>
-                </ResponsiveContainer>
-              </ChartContainer>
-            </CardContent>
-          </Card>
-        </div>
-      </Section>
-    </div>
-  )
-}
-
-function ComposedTrend({ data }: { data: Array<{ month: string; alarms: number; supply: number }> }) {
-  return (
-    <LineChart data={data}>
-      <CartesianGrid strokeDasharray="3 3" vertical={false} />
-      <XAxis dataKey="month" tickLine={false} axisLine={false} />
-      <YAxis yAxisId="left" tickLine={false} axisLine={false} />
-      <YAxis yAxisId="right" orientation="right" tickLine={false} axisLine={false} />
-      <ChartTooltip content={<ChartTooltipContent />} />
-      <Legend />
-      <Area
-        yAxisId="right"
-        type="monotone"
-        dataKey="supply"
-        fill="var(--color-supply)"
-        stroke="var(--color-supply)"
-        fillOpacity={0.2}
-      />
-      <Line yAxisId="left" type="monotone" dataKey="alarms" stroke="var(--color-alarms)" strokeWidth={2} dot={false} />
-    </LineChart>
-  )
-}

+ 0 - 51
components/shared/metric-card.tsx

@@ -1,51 +0,0 @@
-"use client"
-
-import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card"
-import CountUp from "react-countup"
-import {cn} from "@/lib/utils"
-import type {ReactNode} from "react"
-
-type MetricCardProps = {
-  title: string
-  value: number
-  suffix?: string
-  icon?: ReactNode
-  className?: string
-  trend?: { delta: number; label?: string } // delta in percent
-}
-export default function MetricCard({
-  title,
-  value,
-  suffix,
-  icon,
-  className,
-  trend,
-}: MetricCardProps) {
-  const trendColor =
-    trend && trend.delta !== 0
-      ? trend.delta > 0
-        ? "text-emerald-600"
-        : "text-rose-600"
-      : "text-muted-foreground"
-
-  return (
-    <Card className={cn("h-full", className)}>
-      <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
-        <CardTitle className="text-sm font-medium">{title}</CardTitle>
-        {icon}
-      </CardHeader>
-      <CardContent>
-        <div className="text-2xl font-bold">
-          <CountUp end={value} duration={1.2} separator="," />
-          {suffix ? <span className="ml-1 text-base font-medium text-muted-foreground">{suffix}</span> : null}
-        </div>
-        {trend ? (
-          <p className={cn("text-xs mt-1", trendColor)}>
-            {trend.delta > 0 ? "↑" : trend.delta < 0 ? "↓" : "—"} {Math.abs(trend.delta)}%{" "}
-            {trend.label ? <span className="text-muted-foreground">· {trend.label}</span> : null}
-          </p>
-        ) : null}
-      </CardContent>
-    </Card>
-  )
-}

+ 0 - 27
components/shared/section.tsx

@@ -1,27 +0,0 @@
-import {Separator} from "@/components/ui/separator"
-
-export default function Section({
-  title,
-  description,
-  action,
-  children,
-}: {
-  title: string
-  description?: string
-  action?: React.ReactNode
-  children: React.ReactNode
-}) {
-  return (
-    <section className="w-full">
-      <div className="flex flex-col md:flex-row md:items-end gap-2 md:gap-4">
-        <div className="flex-1">
-          <h2 className="text-lg md:text-xl font-semibold">{title}</h2>
-          {description ? <p className="text-sm text-muted-foreground mt-1">{description}</p> : null}
-        </div>
-        {action ? <div className="shrink-0">{action}</div> : null}
-      </div>
-      <Separator className="my-4" />
-      <div>{children}</div>
-    </section>
-  )
-}

+ 0 - 8
components/theme-provider.tsx

@@ -1,8 +0,0 @@
-'use client'
-
-import * as React from 'react'
-import {ThemeProvider as NextThemesProvider, type ThemeProviderProps,} from 'next-themes'
-
-export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
-  return <NextThemesProvider {...props}>{children}</NextThemesProvider>
-}

+ 0 - 66
components/ui/accordion.tsx

@@ -1,66 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as AccordionPrimitive from "@radix-ui/react-accordion"
-import {ChevronDownIcon} from "lucide-react"
-
-import {cn} from "@/lib/utils"
-
-function Accordion({
-  ...props
-}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
-  return <AccordionPrimitive.Root data-slot="accordion" {...props} />
-}
-
-function AccordionItem({
-  className,
-  ...props
-}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
-  return (
-    <AccordionPrimitive.Item
-      data-slot="accordion-item"
-      className={cn("border-b last:border-b-0", className)}
-      {...props}
-    />
-  )
-}
-
-function AccordionTrigger({
-  className,
-  children,
-  ...props
-}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
-  return (
-    <AccordionPrimitive.Header className="flex">
-      <AccordionPrimitive.Trigger
-        data-slot="accordion-trigger"
-        className={cn(
-          "focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
-          className
-        )}
-        {...props}
-      >
-        {children}
-        <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
-      </AccordionPrimitive.Trigger>
-    </AccordionPrimitive.Header>
-  )
-}
-
-function AccordionContent({
-  className,
-  children,
-  ...props
-}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
-  return (
-    <AccordionPrimitive.Content
-      data-slot="accordion-content"
-      className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
-      {...props}
-    >
-      <div className={cn("pt-0 pb-4", className)}>{children}</div>
-    </AccordionPrimitive.Content>
-  )
-}
-
-export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

+ 0 - 157
components/ui/alert-dialog.tsx

@@ -1,157 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
-
-import {cn} from "@/lib/utils"
-import {buttonVariants} from "@/components/ui/button"
-
-function AlertDialog({
-  ...props
-}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
-  return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
-}
-
-function AlertDialogTrigger({
-  ...props
-}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
-  return (
-    <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
-  )
-}
-
-function AlertDialogPortal({
-  ...props
-}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
-  return (
-    <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
-  )
-}
-
-function AlertDialogOverlay({
-  className,
-  ...props
-}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
-  return (
-    <AlertDialogPrimitive.Overlay
-      data-slot="alert-dialog-overlay"
-      className={cn(
-        "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function AlertDialogContent({
-  className,
-  ...props
-}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
-  return (
-    <AlertDialogPortal>
-      <AlertDialogOverlay />
-      <AlertDialogPrimitive.Content
-        data-slot="alert-dialog-content"
-        className={cn(
-          "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
-          className
-        )}
-        {...props}
-      />
-    </AlertDialogPortal>
-  )
-}
-
-function AlertDialogHeader({
-  className,
-  ...props
-}: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="alert-dialog-header"
-      className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
-      {...props}
-    />
-  )
-}
-
-function AlertDialogFooter({
-  className,
-  ...props
-}: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="alert-dialog-footer"
-      className={cn(
-        "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function AlertDialogTitle({
-  className,
-  ...props
-}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
-  return (
-    <AlertDialogPrimitive.Title
-      data-slot="alert-dialog-title"
-      className={cn("text-lg font-semibold", className)}
-      {...props}
-    />
-  )
-}
-
-function AlertDialogDescription({
-  className,
-  ...props
-}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
-  return (
-    <AlertDialogPrimitive.Description
-      data-slot="alert-dialog-description"
-      className={cn("text-muted-foreground text-sm", className)}
-      {...props}
-    />
-  )
-}
-
-function AlertDialogAction({
-  className,
-  ...props
-}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
-  return (
-    <AlertDialogPrimitive.Action
-      className={cn(buttonVariants(), className)}
-      {...props}
-    />
-  )
-}
-
-function AlertDialogCancel({
-  className,
-  ...props
-}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
-  return (
-    <AlertDialogPrimitive.Cancel
-      className={cn(buttonVariants({ variant: "outline" }), className)}
-      {...props}
-    />
-  )
-}
-
-export {
-  AlertDialog,
-  AlertDialogPortal,
-  AlertDialogOverlay,
-  AlertDialogTrigger,
-  AlertDialogContent,
-  AlertDialogHeader,
-  AlertDialogFooter,
-  AlertDialogTitle,
-  AlertDialogDescription,
-  AlertDialogAction,
-  AlertDialogCancel,
-}

+ 0 - 66
components/ui/alert.tsx

@@ -1,66 +0,0 @@
-import * as React from "react"
-import {cva, type VariantProps} from "class-variance-authority"
-
-import {cn} from "@/lib/utils"
-
-const alertVariants = cva(
-  "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
-  {
-    variants: {
-      variant: {
-        default: "bg-card text-card-foreground",
-        destructive:
-          "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
-      },
-    },
-    defaultVariants: {
-      variant: "default",
-    },
-  }
-)
-
-function Alert({
-  className,
-  variant,
-  ...props
-}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
-  return (
-    <div
-      data-slot="alert"
-      role="alert"
-      className={cn(alertVariants({ variant }), className)}
-      {...props}
-    />
-  )
-}
-
-function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="alert-title"
-      className={cn(
-        "col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function AlertDescription({
-  className,
-  ...props
-}: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="alert-description"
-      className={cn(
-        "text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-export { Alert, AlertTitle, AlertDescription }

+ 0 - 11
components/ui/aspect-ratio.tsx

@@ -1,11 +0,0 @@
-"use client"
-
-import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
-
-function AspectRatio({
-  ...props
-}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
-  return <AspectRatioPrimitive.Root data-slot="aspect-ratio" {...props} />
-}
-
-export { AspectRatio }

+ 0 - 53
components/ui/avatar.tsx

@@ -1,53 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as AvatarPrimitive from "@radix-ui/react-avatar"
-
-import {cn} from "@/lib/utils"
-
-function Avatar({
-  className,
-  ...props
-}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
-  return (
-    <AvatarPrimitive.Root
-      data-slot="avatar"
-      className={cn(
-        "relative flex size-8 shrink-0 overflow-hidden rounded-full",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function AvatarImage({
-  className,
-  ...props
-}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
-  return (
-    <AvatarPrimitive.Image
-      data-slot="avatar-image"
-      className={cn("aspect-square size-full", className)}
-      {...props}
-    />
-  )
-}
-
-function AvatarFallback({
-  className,
-  ...props
-}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
-  return (
-    <AvatarPrimitive.Fallback
-      data-slot="avatar-fallback"
-      className={cn(
-        "bg-muted flex size-full items-center justify-center rounded-full",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-export { Avatar, AvatarImage, AvatarFallback }

+ 0 - 46
components/ui/badge.tsx

@@ -1,46 +0,0 @@
-import * as React from "react"
-import {Slot} from "@radix-ui/react-slot"
-import {cva, type VariantProps} from "class-variance-authority"
-
-import {cn} from "@/lib/utils"
-
-const badgeVariants = cva(
-  "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
-  {
-    variants: {
-      variant: {
-        default:
-          "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
-        secondary:
-          "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
-        destructive:
-          "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
-        outline:
-          "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
-      },
-    },
-    defaultVariants: {
-      variant: "default",
-    },
-  }
-)
-
-function Badge({
-  className,
-  variant,
-  asChild = false,
-  ...props
-}: React.ComponentProps<"span"> &
-  VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
-  const Comp = asChild ? Slot : "span"
-
-  return (
-    <Comp
-      data-slot="badge"
-      className={cn(badgeVariants({ variant }), className)}
-      {...props}
-    />
-  )
-}
-
-export { Badge, badgeVariants }

+ 0 - 109
components/ui/breadcrumb.tsx

@@ -1,109 +0,0 @@
-import * as React from "react"
-import {Slot} from "@radix-ui/react-slot"
-import {ChevronRight, MoreHorizontal} from "lucide-react"
-
-import {cn} from "@/lib/utils"
-
-function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
-  return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
-}
-
-function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
-  return (
-    <ol
-      data-slot="breadcrumb-list"
-      className={cn(
-        "text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
-  return (
-    <li
-      data-slot="breadcrumb-item"
-      className={cn("inline-flex items-center gap-1.5", className)}
-      {...props}
-    />
-  )
-}
-
-function BreadcrumbLink({
-  asChild,
-  className,
-  ...props
-}: React.ComponentProps<"a"> & {
-  asChild?: boolean
-}) {
-  const Comp = asChild ? Slot : "a"
-
-  return (
-    <Comp
-      data-slot="breadcrumb-link"
-      className={cn("hover:text-foreground transition-colors", className)}
-      {...props}
-    />
-  )
-}
-
-function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
-  return (
-    <span
-      data-slot="breadcrumb-page"
-      role="link"
-      aria-disabled="true"
-      aria-current="page"
-      className={cn("text-foreground font-normal", className)}
-      {...props}
-    />
-  )
-}
-
-function BreadcrumbSeparator({
-  children,
-  className,
-  ...props
-}: React.ComponentProps<"li">) {
-  return (
-    <li
-      data-slot="breadcrumb-separator"
-      role="presentation"
-      aria-hidden="true"
-      className={cn("[&>svg]:size-3.5", className)}
-      {...props}
-    >
-      {children ?? <ChevronRight />}
-    </li>
-  )
-}
-
-function BreadcrumbEllipsis({
-  className,
-  ...props
-}: React.ComponentProps<"span">) {
-  return (
-    <span
-      data-slot="breadcrumb-ellipsis"
-      role="presentation"
-      aria-hidden="true"
-      className={cn("flex size-9 items-center justify-center", className)}
-      {...props}
-    >
-      <MoreHorizontal className="size-4" />
-      <span className="sr-only">More</span>
-    </span>
-  )
-}
-
-export {
-  Breadcrumb,
-  BreadcrumbList,
-  BreadcrumbItem,
-  BreadcrumbLink,
-  BreadcrumbPage,
-  BreadcrumbSeparator,
-  BreadcrumbEllipsis,
-}

+ 0 - 59
components/ui/button.tsx

@@ -1,59 +0,0 @@
-import * as React from "react"
-import {Slot} from "@radix-ui/react-slot"
-import {cva, type VariantProps} from "class-variance-authority"
-
-import {cn} from "@/lib/utils"
-
-const buttonVariants = cva(
-  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
-  {
-    variants: {
-      variant: {
-        default:
-          "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
-        destructive:
-          "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
-        outline:
-          "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
-        secondary:
-          "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
-        ghost:
-          "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
-        link: "text-primary underline-offset-4 hover:underline",
-      },
-      size: {
-        default: "h-9 px-4 py-2 has-[>svg]:px-3",
-        sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
-        lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
-        icon: "size-9",
-      },
-    },
-    defaultVariants: {
-      variant: "default",
-      size: "default",
-    },
-  }
-)
-
-function Button({
-  className,
-  variant,
-  size,
-  asChild = false,
-  ...props
-}: React.ComponentProps<"button"> &
-  VariantProps<typeof buttonVariants> & {
-    asChild?: boolean
-  }) {
-  const Comp = asChild ? Slot : "button"
-
-  return (
-    <Comp
-      data-slot="button"
-      className={cn(buttonVariants({ variant, size, className }))}
-      {...props}
-    />
-  )
-}
-
-export { Button, buttonVariants }

+ 0 - 209
components/ui/calendar.tsx

@@ -1,209 +0,0 @@
-"use client"
-
-import * as React from "react"
-import {ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon,} from "lucide-react"
-import {DayButton, DayPicker, getDefaultClassNames} from "react-day-picker"
-
-import {cn} from "@/lib/utils"
-import {Button, buttonVariants} from "@/components/ui/button"
-
-function Calendar({
-  className,
-  classNames,
-  showOutsideDays = true,
-  captionLayout = "label",
-  buttonVariant = "ghost",
-  formatters,
-  components,
-  ...props
-}: React.ComponentProps<typeof DayPicker> & {
-  buttonVariant?: React.ComponentProps<typeof Button>["variant"]
-}) {
-  const defaultClassNames = getDefaultClassNames()
-
-  return (
-    <DayPicker
-      showOutsideDays={showOutsideDays}
-      className={cn(
-        "bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
-        String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
-        String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
-        className
-      )}
-      captionLayout={captionLayout}
-      formatters={{
-        formatMonthDropdown: (date) =>
-          date.toLocaleString("default", { month: "short" }),
-        ...formatters,
-      }}
-      classNames={{
-        root: cn("w-fit", defaultClassNames.root),
-        months: cn(
-          "flex gap-4 flex-col md:flex-row relative",
-          defaultClassNames.months
-        ),
-        month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
-        nav: cn(
-          "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
-          defaultClassNames.nav
-        ),
-        button_previous: cn(
-          buttonVariants({ variant: buttonVariant }),
-          "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
-          defaultClassNames.button_previous
-        ),
-        button_next: cn(
-          buttonVariants({ variant: buttonVariant }),
-          "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
-          defaultClassNames.button_next
-        ),
-        month_caption: cn(
-          "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
-          defaultClassNames.month_caption
-        ),
-        dropdowns: cn(
-          "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
-          defaultClassNames.dropdowns
-        ),
-        dropdown_root: cn(
-          "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md",
-          defaultClassNames.dropdown_root
-        ),
-        dropdown: cn(
-          "absolute bg-popover inset-0 opacity-0",
-          defaultClassNames.dropdown
-        ),
-        caption_label: cn(
-          "select-none font-medium",
-          captionLayout === "label"
-            ? "text-sm"
-            : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
-          defaultClassNames.caption_label
-        ),
-        table: "w-full border-collapse",
-        weekdays: cn("flex", defaultClassNames.weekdays),
-        weekday: cn(
-          "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
-          defaultClassNames.weekday
-        ),
-        week: cn("flex w-full mt-2", defaultClassNames.week),
-        week_number_header: cn(
-          "select-none w-(--cell-size)",
-          defaultClassNames.week_number_header
-        ),
-        week_number: cn(
-          "text-[0.8rem] select-none text-muted-foreground",
-          defaultClassNames.week_number
-        ),
-        day: cn(
-          "relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
-          defaultClassNames.day
-        ),
-        range_start: cn(
-          "rounded-l-md bg-accent",
-          defaultClassNames.range_start
-        ),
-        range_middle: cn("rounded-none", defaultClassNames.range_middle),
-        range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
-        today: cn(
-          "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
-          defaultClassNames.today
-        ),
-        outside: cn(
-          "text-muted-foreground aria-selected:text-muted-foreground",
-          defaultClassNames.outside
-        ),
-        disabled: cn(
-          "text-muted-foreground opacity-50",
-          defaultClassNames.disabled
-        ),
-        hidden: cn("invisible", defaultClassNames.hidden),
-        ...classNames,
-      }}
-      components={{
-        Root: ({ className, rootRef, ...props }) => {
-          return (
-            <div
-              data-slot="calendar"
-              ref={rootRef}
-              className={cn(className)}
-              {...props}
-            />
-          )
-        },
-        Chevron: ({ className, orientation, ...props }) => {
-          if (orientation === "left") {
-            return (
-              <ChevronLeftIcon className={cn("size-4", className)} {...props} />
-            )
-          }
-
-          if (orientation === "right") {
-            return (
-              <ChevronRightIcon
-                className={cn("size-4", className)}
-                {...props}
-              />
-            )
-          }
-
-          return (
-            <ChevronDownIcon className={cn("size-4", className)} {...props} />
-          )
-        },
-        DayButton: CalendarDayButton,
-        WeekNumber: ({ children, ...props }) => {
-          return (
-            <td {...props}>
-              <div className="flex size-(--cell-size) items-center justify-center text-center">
-                {children}
-              </div>
-            </td>
-          )
-        },
-        ...components,
-      }}
-      {...props}
-    />
-  )
-}
-
-function CalendarDayButton({
-  className,
-  day,
-  modifiers,
-  ...props
-}: React.ComponentProps<typeof DayButton>) {
-  const defaultClassNames = getDefaultClassNames()
-
-  const ref = React.useRef<HTMLButtonElement>(null)
-  React.useEffect(() => {
-    if (modifiers.focused) ref.current?.focus()
-  }, [modifiers.focused])
-
-  return (
-    <Button
-      ref={ref}
-      variant="ghost"
-      size="icon"
-      data-day={day.date.toLocaleDateString()}
-      data-selected-single={
-        modifiers.selected &&
-        !modifiers.range_start &&
-        !modifiers.range_end &&
-        !modifiers.range_middle
-      }
-      data-range-start={modifiers.range_start}
-      data-range-end={modifiers.range_end}
-      data-range-middle={modifiers.range_middle}
-      className={cn(
-        "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70",
-        defaultClassNames.day,
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-export { Calendar, CalendarDayButton }

+ 0 - 92
components/ui/card.tsx

@@ -1,92 +0,0 @@
-import * as React from "react"
-
-import {cn} from "@/lib/utils"
-
-function Card({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="card"
-      className={cn(
-        "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="card-header"
-      className={cn(
-        "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="card-title"
-      className={cn("leading-none font-semibold", className)}
-      {...props}
-    />
-  )
-}
-
-function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="card-description"
-      className={cn("text-muted-foreground text-sm", className)}
-      {...props}
-    />
-  )
-}
-
-function CardAction({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="card-action"
-      className={cn(
-        "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function CardContent({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="card-content"
-      className={cn("px-6", className)}
-      {...props}
-    />
-  )
-}
-
-function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="card-footer"
-      className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
-      {...props}
-    />
-  )
-}
-
-export {
-  Card,
-  CardHeader,
-  CardFooter,
-  CardTitle,
-  CardAction,
-  CardDescription,
-  CardContent,
-}

+ 0 - 239
components/ui/carousel.tsx

@@ -1,239 +0,0 @@
-"use client"
-
-import * as React from "react"
-import useEmblaCarousel, {type UseEmblaCarouselType,} from "embla-carousel-react"
-import {ArrowLeft, ArrowRight} from "lucide-react"
-
-import {cn} from "@/lib/utils"
-import {Button} from "@/components/ui/button"
-
-type CarouselApi = UseEmblaCarouselType[1]
-type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
-type CarouselOptions = UseCarouselParameters[0]
-type CarouselPlugin = UseCarouselParameters[1]
-
-type CarouselProps = {
-  opts?: CarouselOptions
-  plugins?: CarouselPlugin
-  orientation?: "horizontal" | "vertical"
-  setApi?: (api: CarouselApi) => void
-}
-
-type CarouselContextProps = {
-  carouselRef: ReturnType<typeof useEmblaCarousel>[0]
-  api: ReturnType<typeof useEmblaCarousel>[1]
-  scrollPrev: () => void
-  scrollNext: () => void
-  canScrollPrev: boolean
-  canScrollNext: boolean
-} & CarouselProps
-
-const CarouselContext = React.createContext<CarouselContextProps | null>(null)
-
-function useCarousel() {
-  const context = React.useContext(CarouselContext)
-
-  if (!context) {
-    throw new Error("useCarousel must be used within a <Carousel />")
-  }
-
-  return context
-}
-
-function Carousel({
-  orientation = "horizontal",
-  opts,
-  setApi,
-  plugins,
-  className,
-  children,
-  ...props
-}: React.ComponentProps<"div"> & CarouselProps) {
-  const [carouselRef, api] = useEmblaCarousel(
-    {
-      ...opts,
-      axis: orientation === "horizontal" ? "x" : "y",
-    },
-    plugins
-  )
-  const [canScrollPrev, setCanScrollPrev] = React.useState(false)
-  const [canScrollNext, setCanScrollNext] = React.useState(false)
-
-  const onSelect = React.useCallback((api: CarouselApi) => {
-    if (!api) return
-    setCanScrollPrev(api.canScrollPrev())
-    setCanScrollNext(api.canScrollNext())
-  }, [])
-
-  const scrollPrev = React.useCallback(() => {
-    api?.scrollPrev()
-  }, [api])
-
-  const scrollNext = React.useCallback(() => {
-    api?.scrollNext()
-  }, [api])
-
-  const handleKeyDown = React.useCallback(
-    (event: React.KeyboardEvent<HTMLDivElement>) => {
-      if (event.key === "ArrowLeft") {
-        event.preventDefault()
-        scrollPrev()
-      } else if (event.key === "ArrowRight") {
-        event.preventDefault()
-        scrollNext()
-      }
-    },
-    [scrollPrev, scrollNext]
-  )
-
-  React.useEffect(() => {
-    if (!api || !setApi) return
-    setApi(api)
-  }, [api, setApi])
-
-  React.useEffect(() => {
-    if (!api) return
-    onSelect(api)
-    api.on("reInit", onSelect)
-    api.on("select", onSelect)
-
-    return () => {
-      api?.off("select", onSelect)
-    }
-  }, [api, onSelect])
-
-  return (
-    <CarouselContext.Provider
-      value={{
-        carouselRef,
-        api: api,
-        opts,
-        orientation:
-          orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
-        scrollPrev,
-        scrollNext,
-        canScrollPrev,
-        canScrollNext,
-      }}
-    >
-      <div
-        onKeyDownCapture={handleKeyDown}
-        className={cn("relative", className)}
-        role="region"
-        aria-roledescription="carousel"
-        data-slot="carousel"
-        {...props}
-      >
-        {children}
-      </div>
-    </CarouselContext.Provider>
-  )
-}
-
-function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
-  const { carouselRef, orientation } = useCarousel()
-
-  return (
-    <div
-      ref={carouselRef}
-      className="overflow-hidden"
-      data-slot="carousel-content"
-    >
-      <div
-        className={cn(
-          "flex",
-          orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
-          className
-        )}
-        {...props}
-      />
-    </div>
-  )
-}
-
-function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
-  const { orientation } = useCarousel()
-
-  return (
-    <div
-      role="group"
-      aria-roledescription="slide"
-      data-slot="carousel-item"
-      className={cn(
-        "min-w-0 shrink-0 grow-0 basis-full",
-        orientation === "horizontal" ? "pl-4" : "pt-4",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function CarouselPrevious({
-  className,
-  variant = "outline",
-  size = "icon",
-  ...props
-}: React.ComponentProps<typeof Button>) {
-  const { orientation, scrollPrev, canScrollPrev } = useCarousel()
-
-  return (
-    <Button
-      data-slot="carousel-previous"
-      variant={variant}
-      size={size}
-      className={cn(
-        "absolute size-8 rounded-full",
-        orientation === "horizontal"
-          ? "top-1/2 -left-12 -translate-y-1/2"
-          : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
-        className
-      )}
-      disabled={!canScrollPrev}
-      onClick={scrollPrev}
-      {...props}
-    >
-      <ArrowLeft />
-      <span className="sr-only">Previous slide</span>
-    </Button>
-  )
-}
-
-function CarouselNext({
-  className,
-  variant = "outline",
-  size = "icon",
-  ...props
-}: React.ComponentProps<typeof Button>) {
-  const { orientation, scrollNext, canScrollNext } = useCarousel()
-
-  return (
-    <Button
-      data-slot="carousel-next"
-      variant={variant}
-      size={size}
-      className={cn(
-        "absolute size-8 rounded-full",
-        orientation === "horizontal"
-          ? "top-1/2 -right-12 -translate-y-1/2"
-          : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
-        className
-      )}
-      disabled={!canScrollNext}
-      onClick={scrollNext}
-      {...props}
-    >
-      <ArrowRight />
-      <span className="sr-only">Next slide</span>
-    </Button>
-  )
-}
-
-export {
-  type CarouselApi,
-  Carousel,
-  CarouselContent,
-  CarouselItem,
-  CarouselPrevious,
-  CarouselNext,
-}

+ 0 - 353
components/ui/chart.tsx

@@ -1,353 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as RechartsPrimitive from "recharts"
-
-import {cn} from "@/lib/utils"
-
-// Format: { THEME_NAME: CSS_SELECTOR }
-const THEMES = { light: "", dark: ".dark" } as const
-
-export type ChartConfig = {
-  [k in string]: {
-    label?: React.ReactNode
-    icon?: React.ComponentType
-  } & (
-    | { color?: string; theme?: never }
-    | { color?: never; theme: Record<keyof typeof THEMES, string> }
-  )
-}
-
-type ChartContextProps = {
-  config: ChartConfig
-}
-
-const ChartContext = React.createContext<ChartContextProps | null>(null)
-
-function useChart() {
-  const context = React.useContext(ChartContext)
-
-  if (!context) {
-    throw new Error("useChart must be used within a <ChartContainer />")
-  }
-
-  return context
-}
-
-function ChartContainer({
-  id,
-  className,
-  children,
-  config,
-  ...props
-}: React.ComponentProps<"div"> & {
-  config: ChartConfig
-  children: React.ComponentProps<
-    typeof RechartsPrimitive.ResponsiveContainer
-  >["children"]
-}) {
-  const uniqueId = React.useId()
-  const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
-
-  return (
-    <ChartContext.Provider value={{ config }}>
-      <div
-        data-slot="chart"
-        data-chart={chartId}
-        className={cn(
-          "[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
-          className
-        )}
-        {...props}
-      >
-        <ChartStyle id={chartId} config={config} />
-        <RechartsPrimitive.ResponsiveContainer>
-          {children}
-        </RechartsPrimitive.ResponsiveContainer>
-      </div>
-    </ChartContext.Provider>
-  )
-}
-
-const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
-  const colorConfig = Object.entries(config).filter(
-    ([, config]) => config.theme || config.color
-  )
-
-  if (!colorConfig.length) {
-    return null
-  }
-
-  return (
-    <style
-      dangerouslySetInnerHTML={{
-        __html: Object.entries(THEMES)
-          .map(
-            ([theme, prefix]) => `
-${prefix} [data-chart=${id}] {
-${colorConfig
-  .map(([key, itemConfig]) => {
-    const color =
-      itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
-      itemConfig.color
-    return color ? `  --color-${key}: ${color};` : null
-  })
-  .join("\n")}
-}
-`
-          )
-          .join("\n"),
-      }}
-    />
-  )
-}
-
-const ChartTooltip = RechartsPrimitive.Tooltip
-
-function ChartTooltipContent({
-  active,
-  payload,
-  className,
-  indicator = "dot",
-  hideLabel = false,
-  hideIndicator = false,
-  label,
-  labelFormatter,
-  labelClassName,
-  formatter,
-  color,
-  nameKey,
-  labelKey,
-}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
-  React.ComponentProps<"div"> & {
-    hideLabel?: boolean
-    hideIndicator?: boolean
-    indicator?: "line" | "dot" | "dashed"
-    nameKey?: string
-    labelKey?: string
-  }) {
-  const { config } = useChart()
-
-  const tooltipLabel = React.useMemo(() => {
-    if (hideLabel || !payload?.length) {
-      return null
-    }
-
-    const [item] = payload
-    const key = `${labelKey || item?.dataKey || item?.name || "value"}`
-    const itemConfig = getPayloadConfigFromPayload(config, item, key)
-    const value =
-      !labelKey && typeof label === "string"
-        ? config[label as keyof typeof config]?.label || label
-        : itemConfig?.label
-
-    if (labelFormatter) {
-      return (
-        <div className={cn("font-medium", labelClassName)}>
-          {labelFormatter(value, payload)}
-        </div>
-      )
-    }
-
-    if (!value) {
-      return null
-    }
-
-    return <div className={cn("font-medium", labelClassName)}>{value}</div>
-  }, [
-    label,
-    labelFormatter,
-    payload,
-    hideLabel,
-    labelClassName,
-    config,
-    labelKey,
-  ])
-
-  if (!active || !payload?.length) {
-    return null
-  }
-
-  const nestLabel = payload.length === 1 && indicator !== "dot"
-
-  return (
-    <div
-      className={cn(
-        "border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl",
-        className
-      )}
-    >
-      {!nestLabel ? tooltipLabel : null}
-      <div className="grid gap-1.5">
-        {payload.map((item, index) => {
-          const key = `${nameKey || item.name || item.dataKey || "value"}`
-          const itemConfig = getPayloadConfigFromPayload(config, item, key)
-          const indicatorColor = color || item.payload.fill || item.color
-
-          return (
-            <div
-              key={item.dataKey}
-              className={cn(
-                "[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
-                indicator === "dot" && "items-center"
-              )}
-            >
-              {formatter && item?.value !== undefined && item.name ? (
-                formatter(item.value, item.name, item, index, item.payload)
-              ) : (
-                <>
-                  {itemConfig?.icon ? (
-                    <itemConfig.icon />
-                  ) : (
-                    !hideIndicator && (
-                      <div
-                        className={cn(
-                          "shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
-                          {
-                            "h-2.5 w-2.5": indicator === "dot",
-                            "w-1": indicator === "line",
-                            "w-0 border-[1.5px] border-dashed bg-transparent":
-                              indicator === "dashed",
-                            "my-0.5": nestLabel && indicator === "dashed",
-                          }
-                        )}
-                        style={
-                          {
-                            "--color-bg": indicatorColor,
-                            "--color-border": indicatorColor,
-                          } as React.CSSProperties
-                        }
-                      />
-                    )
-                  )}
-                  <div
-                    className={cn(
-                      "flex flex-1 justify-between leading-none",
-                      nestLabel ? "items-end" : "items-center"
-                    )}
-                  >
-                    <div className="grid gap-1.5">
-                      {nestLabel ? tooltipLabel : null}
-                      <span className="text-muted-foreground">
-                        {itemConfig?.label || item.name}
-                      </span>
-                    </div>
-                    {item.value && (
-                      <span className="text-foreground font-mono font-medium tabular-nums">
-                        {item.value.toLocaleString()}
-                      </span>
-                    )}
-                  </div>
-                </>
-              )}
-            </div>
-          )
-        })}
-      </div>
-    </div>
-  )
-}
-
-const ChartLegend = RechartsPrimitive.Legend
-
-function ChartLegendContent({
-  className,
-  hideIcon = false,
-  payload,
-  verticalAlign = "bottom",
-  nameKey,
-}: React.ComponentProps<"div"> &
-  Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
-    hideIcon?: boolean
-    nameKey?: string
-  }) {
-  const { config } = useChart()
-
-  if (!payload?.length) {
-    return null
-  }
-
-  return (
-    <div
-      className={cn(
-        "flex items-center justify-center gap-4",
-        verticalAlign === "top" ? "pb-3" : "pt-3",
-        className
-      )}
-    >
-      {payload.map((item) => {
-        const key = `${nameKey || item.dataKey || "value"}`
-        const itemConfig = getPayloadConfigFromPayload(config, item, key)
-
-        return (
-          <div
-            key={item.value}
-            className={cn(
-              "[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3"
-            )}
-          >
-            {itemConfig?.icon && !hideIcon ? (
-              <itemConfig.icon />
-            ) : (
-              <div
-                className="h-2 w-2 shrink-0 rounded-[2px]"
-                style={{
-                  backgroundColor: item.color,
-                }}
-              />
-            )}
-            {itemConfig?.label}
-          </div>
-        )
-      })}
-    </div>
-  )
-}
-
-// Helper to extract item config from a payload.
-function getPayloadConfigFromPayload(
-  config: ChartConfig,
-  payload: unknown,
-  key: string
-) {
-  if (typeof payload !== "object" || payload === null) {
-    return undefined
-  }
-
-  const payloadPayload =
-    "payload" in payload &&
-    typeof payload.payload === "object" &&
-    payload.payload !== null
-      ? payload.payload
-      : undefined
-
-  let configLabelKey: string = key
-
-  if (
-    key in payload &&
-    typeof payload[key as keyof typeof payload] === "string"
-  ) {
-    configLabelKey = payload[key as keyof typeof payload] as string
-  } else if (
-    payloadPayload &&
-    key in payloadPayload &&
-    typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
-  ) {
-    configLabelKey = payloadPayload[
-      key as keyof typeof payloadPayload
-    ] as string
-  }
-
-  return configLabelKey in config
-    ? config[configLabelKey]
-    : config[key as keyof typeof config]
-}
-
-export {
-  ChartContainer,
-  ChartTooltip,
-  ChartTooltipContent,
-  ChartLegend,
-  ChartLegendContent,
-  ChartStyle,
-}

+ 0 - 32
components/ui/checkbox.tsx

@@ -1,32 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
-import {CheckIcon} from "lucide-react"
-
-import {cn} from "@/lib/utils"
-
-function Checkbox({
-  className,
-  ...props
-}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
-  return (
-    <CheckboxPrimitive.Root
-      data-slot="checkbox"
-      className={cn(
-        "peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
-        className
-      )}
-      {...props}
-    >
-      <CheckboxPrimitive.Indicator
-        data-slot="checkbox-indicator"
-        className="flex items-center justify-center text-current transition-none"
-      >
-        <CheckIcon className="size-3.5" />
-      </CheckboxPrimitive.Indicator>
-    </CheckboxPrimitive.Root>
-  )
-}
-
-export { Checkbox }

+ 0 - 33
components/ui/collapsible.tsx

@@ -1,33 +0,0 @@
-"use client"
-
-import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
-
-function Collapsible({
-  ...props
-}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
-  return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
-}
-
-function CollapsibleTrigger({
-  ...props
-}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
-  return (
-    <CollapsiblePrimitive.CollapsibleTrigger
-      data-slot="collapsible-trigger"
-      {...props}
-    />
-  )
-}
-
-function CollapsibleContent({
-  ...props
-}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
-  return (
-    <CollapsiblePrimitive.CollapsibleContent
-      data-slot="collapsible-content"
-      {...props}
-    />
-  )
-}
-
-export { Collapsible, CollapsibleTrigger, CollapsibleContent }

+ 0 - 178
components/ui/command.tsx

@@ -1,178 +0,0 @@
-"use client"
-
-import * as React from "react"
-import {Command as CommandPrimitive} from "cmdk"
-import {SearchIcon} from "lucide-react"
-
-import {cn} from "@/lib/utils"
-import {Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle,} from "@/components/ui/dialog"
-
-function Command({
-  className,
-  ...props
-}: React.ComponentProps<typeof CommandPrimitive>) {
-  return (
-    <CommandPrimitive
-      data-slot="command"
-      className={cn(
-        "bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function CommandDialog({
-  title = "Command Palette",
-  description = "Search for a command to run...",
-  children,
-  className,
-  showCloseButton = true,
-  ...props
-}: React.ComponentProps<typeof Dialog> & {
-  title?: string
-  description?: string
-  className?: string
-  showCloseButton?: boolean
-}) {
-  return (
-    <Dialog {...props}>
-      <DialogHeader className="sr-only">
-        <DialogTitle>{title}</DialogTitle>
-        <DialogDescription>{description}</DialogDescription>
-      </DialogHeader>
-      <DialogContent
-        className={cn("overflow-hidden p-0", className)}
-        showCloseButton={showCloseButton}
-      >
-        <Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
-          {children}
-        </Command>
-      </DialogContent>
-    </Dialog>
-  )
-}
-
-function CommandInput({
-  className,
-  ...props
-}: React.ComponentProps<typeof CommandPrimitive.Input>) {
-  return (
-    <div
-      data-slot="command-input-wrapper"
-      className="flex h-9 items-center gap-2 border-b px-3"
-    >
-      <SearchIcon className="size-4 shrink-0 opacity-50" />
-      <CommandPrimitive.Input
-        data-slot="command-input"
-        className={cn(
-          "placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
-          className
-        )}
-        {...props}
-      />
-    </div>
-  )
-}
-
-function CommandList({
-  className,
-  ...props
-}: React.ComponentProps<typeof CommandPrimitive.List>) {
-  return (
-    <CommandPrimitive.List
-      data-slot="command-list"
-      className={cn(
-        "max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function CommandEmpty({
-  ...props
-}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
-  return (
-    <CommandPrimitive.Empty
-      data-slot="command-empty"
-      className="py-6 text-center text-sm"
-      {...props}
-    />
-  )
-}
-
-function CommandGroup({
-  className,
-  ...props
-}: React.ComponentProps<typeof CommandPrimitive.Group>) {
-  return (
-    <CommandPrimitive.Group
-      data-slot="command-group"
-      className={cn(
-        "text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function CommandSeparator({
-  className,
-  ...props
-}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
-  return (
-    <CommandPrimitive.Separator
-      data-slot="command-separator"
-      className={cn("bg-border -mx-1 h-px", className)}
-      {...props}
-    />
-  )
-}
-
-function CommandItem({
-  className,
-  ...props
-}: React.ComponentProps<typeof CommandPrimitive.Item>) {
-  return (
-    <CommandPrimitive.Item
-      data-slot="command-item"
-      className={cn(
-        "data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function CommandShortcut({
-  className,
-  ...props
-}: React.ComponentProps<"span">) {
-  return (
-    <span
-      data-slot="command-shortcut"
-      className={cn(
-        "text-muted-foreground ml-auto text-xs tracking-widest",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-export {
-  Command,
-  CommandDialog,
-  CommandInput,
-  CommandList,
-  CommandEmpty,
-  CommandGroup,
-  CommandItem,
-  CommandShortcut,
-  CommandSeparator,
-}

+ 0 - 252
components/ui/context-menu.tsx

@@ -1,252 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
-import {CheckIcon, ChevronRightIcon, CircleIcon} from "lucide-react"
-
-import {cn} from "@/lib/utils"
-
-function ContextMenu({
-  ...props
-}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
-  return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />
-}
-
-function ContextMenuTrigger({
-  ...props
-}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
-  return (
-    <ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />
-  )
-}
-
-function ContextMenuGroup({
-  ...props
-}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
-  return (
-    <ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
-  )
-}
-
-function ContextMenuPortal({
-  ...props
-}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
-  return (
-    <ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
-  )
-}
-
-function ContextMenuSub({
-  ...props
-}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
-  return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />
-}
-
-function ContextMenuRadioGroup({
-  ...props
-}: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {
-  return (
-    <ContextMenuPrimitive.RadioGroup
-      data-slot="context-menu-radio-group"
-      {...props}
-    />
-  )
-}
-
-function ContextMenuSubTrigger({
-  className,
-  inset,
-  children,
-  ...props
-}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
-  inset?: boolean
-}) {
-  return (
-    <ContextMenuPrimitive.SubTrigger
-      data-slot="context-menu-sub-trigger"
-      data-inset={inset}
-      className={cn(
-        "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
-        className
-      )}
-      {...props}
-    >
-      {children}
-      <ChevronRightIcon className="ml-auto" />
-    </ContextMenuPrimitive.SubTrigger>
-  )
-}
-
-function ContextMenuSubContent({
-  className,
-  ...props
-}: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {
-  return (
-    <ContextMenuPrimitive.SubContent
-      data-slot="context-menu-sub-content"
-      className={cn(
-        "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function ContextMenuContent({
-  className,
-  ...props
-}: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {
-  return (
-    <ContextMenuPrimitive.Portal>
-      <ContextMenuPrimitive.Content
-        data-slot="context-menu-content"
-        className={cn(
-          "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
-          className
-        )}
-        {...props}
-      />
-    </ContextMenuPrimitive.Portal>
-  )
-}
-
-function ContextMenuItem({
-  className,
-  inset,
-  variant = "default",
-  ...props
-}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
-  inset?: boolean
-  variant?: "default" | "destructive"
-}) {
-  return (
-    <ContextMenuPrimitive.Item
-      data-slot="context-menu-item"
-      data-inset={inset}
-      data-variant={variant}
-      className={cn(
-        "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function ContextMenuCheckboxItem({
-  className,
-  children,
-  checked,
-  ...props
-}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {
-  return (
-    <ContextMenuPrimitive.CheckboxItem
-      data-slot="context-menu-checkbox-item"
-      className={cn(
-        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
-        className
-      )}
-      checked={checked}
-      {...props}
-    >
-      <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
-        <ContextMenuPrimitive.ItemIndicator>
-          <CheckIcon className="size-4" />
-        </ContextMenuPrimitive.ItemIndicator>
-      </span>
-      {children}
-    </ContextMenuPrimitive.CheckboxItem>
-  )
-}
-
-function ContextMenuRadioItem({
-  className,
-  children,
-  ...props
-}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {
-  return (
-    <ContextMenuPrimitive.RadioItem
-      data-slot="context-menu-radio-item"
-      className={cn(
-        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
-        className
-      )}
-      {...props}
-    >
-      <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
-        <ContextMenuPrimitive.ItemIndicator>
-          <CircleIcon className="size-2 fill-current" />
-        </ContextMenuPrimitive.ItemIndicator>
-      </span>
-      {children}
-    </ContextMenuPrimitive.RadioItem>
-  )
-}
-
-function ContextMenuLabel({
-  className,
-  inset,
-  ...props
-}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {
-  inset?: boolean
-}) {
-  return (
-    <ContextMenuPrimitive.Label
-      data-slot="context-menu-label"
-      data-inset={inset}
-      className={cn(
-        "text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function ContextMenuSeparator({
-  className,
-  ...props
-}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {
-  return (
-    <ContextMenuPrimitive.Separator
-      data-slot="context-menu-separator"
-      className={cn("bg-border -mx-1 my-1 h-px", className)}
-      {...props}
-    />
-  )
-}
-
-function ContextMenuShortcut({
-  className,
-  ...props
-}: React.ComponentProps<"span">) {
-  return (
-    <span
-      data-slot="context-menu-shortcut"
-      className={cn(
-        "text-muted-foreground ml-auto text-xs tracking-widest",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-export {
-  ContextMenu,
-  ContextMenuTrigger,
-  ContextMenuContent,
-  ContextMenuItem,
-  ContextMenuCheckboxItem,
-  ContextMenuRadioItem,
-  ContextMenuLabel,
-  ContextMenuSeparator,
-  ContextMenuShortcut,
-  ContextMenuGroup,
-  ContextMenuPortal,
-  ContextMenuSub,
-  ContextMenuSubContent,
-  ContextMenuSubTrigger,
-  ContextMenuRadioGroup,
-}

+ 0 - 143
components/ui/dialog.tsx

@@ -1,143 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as DialogPrimitive from "@radix-ui/react-dialog"
-import {XIcon} from "lucide-react"
-
-import {cn} from "@/lib/utils"
-
-function Dialog({
-  ...props
-}: React.ComponentProps<typeof DialogPrimitive.Root>) {
-  return <DialogPrimitive.Root data-slot="dialog" {...props} />
-}
-
-function DialogTrigger({
-  ...props
-}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
-  return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
-}
-
-function DialogPortal({
-  ...props
-}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
-  return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
-}
-
-function DialogClose({
-  ...props
-}: React.ComponentProps<typeof DialogPrimitive.Close>) {
-  return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
-}
-
-function DialogOverlay({
-  className,
-  ...props
-}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
-  return (
-    <DialogPrimitive.Overlay
-      data-slot="dialog-overlay"
-      className={cn(
-        "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function DialogContent({
-  className,
-  children,
-  showCloseButton = true,
-  ...props
-}: React.ComponentProps<typeof DialogPrimitive.Content> & {
-  showCloseButton?: boolean
-}) {
-  return (
-    <DialogPortal data-slot="dialog-portal">
-      <DialogOverlay />
-      <DialogPrimitive.Content
-        data-slot="dialog-content"
-        className={cn(
-          "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
-          className
-        )}
-        {...props}
-      >
-        {children}
-        {showCloseButton && (
-          <DialogPrimitive.Close
-            data-slot="dialog-close"
-            className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
-          >
-            <XIcon />
-            <span className="sr-only">Close</span>
-          </DialogPrimitive.Close>
-        )}
-      </DialogPrimitive.Content>
-    </DialogPortal>
-  )
-}
-
-function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="dialog-header"
-      className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
-      {...props}
-    />
-  )
-}
-
-function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="dialog-footer"
-      className={cn(
-        "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function DialogTitle({
-  className,
-  ...props
-}: React.ComponentProps<typeof DialogPrimitive.Title>) {
-  return (
-    <DialogPrimitive.Title
-      data-slot="dialog-title"
-      className={cn("text-lg leading-none font-semibold", className)}
-      {...props}
-    />
-  )
-}
-
-function DialogDescription({
-  className,
-  ...props
-}: React.ComponentProps<typeof DialogPrimitive.Description>) {
-  return (
-    <DialogPrimitive.Description
-      data-slot="dialog-description"
-      className={cn("text-muted-foreground text-sm", className)}
-      {...props}
-    />
-  )
-}
-
-export {
-  Dialog,
-  DialogClose,
-  DialogContent,
-  DialogDescription,
-  DialogFooter,
-  DialogHeader,
-  DialogOverlay,
-  DialogPortal,
-  DialogTitle,
-  DialogTrigger,
-}

+ 0 - 135
components/ui/drawer.tsx

@@ -1,135 +0,0 @@
-"use client"
-
-import * as React from "react"
-import {Drawer as DrawerPrimitive} from "vaul"
-
-import {cn} from "@/lib/utils"
-
-function Drawer({
-  ...props
-}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
-  return <DrawerPrimitive.Root data-slot="drawer" {...props} />
-}
-
-function DrawerTrigger({
-  ...props
-}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
-  return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />
-}
-
-function DrawerPortal({
-  ...props
-}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
-  return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />
-}
-
-function DrawerClose({
-  ...props
-}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
-  return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />
-}
-
-function DrawerOverlay({
-  className,
-  ...props
-}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
-  return (
-    <DrawerPrimitive.Overlay
-      data-slot="drawer-overlay"
-      className={cn(
-        "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function DrawerContent({
-  className,
-  children,
-  ...props
-}: React.ComponentProps<typeof DrawerPrimitive.Content>) {
-  return (
-    <DrawerPortal data-slot="drawer-portal">
-      <DrawerOverlay />
-      <DrawerPrimitive.Content
-        data-slot="drawer-content"
-        className={cn(
-          "group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
-          "data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
-          "data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
-          "data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
-          "data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
-          className
-        )}
-        {...props}
-      >
-        <div className="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
-        {children}
-      </DrawerPrimitive.Content>
-    </DrawerPortal>
-  )
-}
-
-function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="drawer-header"
-      className={cn(
-        "flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="drawer-footer"
-      className={cn("mt-auto flex flex-col gap-2 p-4", className)}
-      {...props}
-    />
-  )
-}
-
-function DrawerTitle({
-  className,
-  ...props
-}: React.ComponentProps<typeof DrawerPrimitive.Title>) {
-  return (
-    <DrawerPrimitive.Title
-      data-slot="drawer-title"
-      className={cn("text-foreground font-semibold", className)}
-      {...props}
-    />
-  )
-}
-
-function DrawerDescription({
-  className,
-  ...props
-}: React.ComponentProps<typeof DrawerPrimitive.Description>) {
-  return (
-    <DrawerPrimitive.Description
-      data-slot="drawer-description"
-      className={cn("text-muted-foreground text-sm", className)}
-      {...props}
-    />
-  )
-}
-
-export {
-  Drawer,
-  DrawerPortal,
-  DrawerOverlay,
-  DrawerTrigger,
-  DrawerClose,
-  DrawerContent,
-  DrawerHeader,
-  DrawerFooter,
-  DrawerTitle,
-  DrawerDescription,
-}

+ 0 - 257
components/ui/dropdown-menu.tsx

@@ -1,257 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
-import {CheckIcon, ChevronRightIcon, CircleIcon} from "lucide-react"
-
-import {cn} from "@/lib/utils"
-
-function DropdownMenu({
-  ...props
-}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
-  return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
-}
-
-function DropdownMenuPortal({
-  ...props
-}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
-  return (
-    <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
-  )
-}
-
-function DropdownMenuTrigger({
-  ...props
-}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
-  return (
-    <DropdownMenuPrimitive.Trigger
-      data-slot="dropdown-menu-trigger"
-      {...props}
-    />
-  )
-}
-
-function DropdownMenuContent({
-  className,
-  sideOffset = 4,
-  ...props
-}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
-  return (
-    <DropdownMenuPrimitive.Portal>
-      <DropdownMenuPrimitive.Content
-        data-slot="dropdown-menu-content"
-        sideOffset={sideOffset}
-        className={cn(
-          "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
-          className
-        )}
-        {...props}
-      />
-    </DropdownMenuPrimitive.Portal>
-  )
-}
-
-function DropdownMenuGroup({
-  ...props
-}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
-  return (
-    <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
-  )
-}
-
-function DropdownMenuItem({
-  className,
-  inset,
-  variant = "default",
-  ...props
-}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
-  inset?: boolean
-  variant?: "default" | "destructive"
-}) {
-  return (
-    <DropdownMenuPrimitive.Item
-      data-slot="dropdown-menu-item"
-      data-inset={inset}
-      data-variant={variant}
-      className={cn(
-        "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function DropdownMenuCheckboxItem({
-  className,
-  children,
-  checked,
-  ...props
-}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
-  return (
-    <DropdownMenuPrimitive.CheckboxItem
-      data-slot="dropdown-menu-checkbox-item"
-      className={cn(
-        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
-        className
-      )}
-      checked={checked}
-      {...props}
-    >
-      <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
-        <DropdownMenuPrimitive.ItemIndicator>
-          <CheckIcon className="size-4" />
-        </DropdownMenuPrimitive.ItemIndicator>
-      </span>
-      {children}
-    </DropdownMenuPrimitive.CheckboxItem>
-  )
-}
-
-function DropdownMenuRadioGroup({
-  ...props
-}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
-  return (
-    <DropdownMenuPrimitive.RadioGroup
-      data-slot="dropdown-menu-radio-group"
-      {...props}
-    />
-  )
-}
-
-function DropdownMenuRadioItem({
-  className,
-  children,
-  ...props
-}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
-  return (
-    <DropdownMenuPrimitive.RadioItem
-      data-slot="dropdown-menu-radio-item"
-      className={cn(
-        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
-        className
-      )}
-      {...props}
-    >
-      <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
-        <DropdownMenuPrimitive.ItemIndicator>
-          <CircleIcon className="size-2 fill-current" />
-        </DropdownMenuPrimitive.ItemIndicator>
-      </span>
-      {children}
-    </DropdownMenuPrimitive.RadioItem>
-  )
-}
-
-function DropdownMenuLabel({
-  className,
-  inset,
-  ...props
-}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
-  inset?: boolean
-}) {
-  return (
-    <DropdownMenuPrimitive.Label
-      data-slot="dropdown-menu-label"
-      data-inset={inset}
-      className={cn(
-        "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function DropdownMenuSeparator({
-  className,
-  ...props
-}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
-  return (
-    <DropdownMenuPrimitive.Separator
-      data-slot="dropdown-menu-separator"
-      className={cn("bg-border -mx-1 my-1 h-px", className)}
-      {...props}
-    />
-  )
-}
-
-function DropdownMenuShortcut({
-  className,
-  ...props
-}: React.ComponentProps<"span">) {
-  return (
-    <span
-      data-slot="dropdown-menu-shortcut"
-      className={cn(
-        "text-muted-foreground ml-auto text-xs tracking-widest",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function DropdownMenuSub({
-  ...props
-}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
-  return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
-}
-
-function DropdownMenuSubTrigger({
-  className,
-  inset,
-  children,
-  ...props
-}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
-  inset?: boolean
-}) {
-  return (
-    <DropdownMenuPrimitive.SubTrigger
-      data-slot="dropdown-menu-sub-trigger"
-      data-inset={inset}
-      className={cn(
-        "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
-        className
-      )}
-      {...props}
-    >
-      {children}
-      <ChevronRightIcon className="ml-auto size-4" />
-    </DropdownMenuPrimitive.SubTrigger>
-  )
-}
-
-function DropdownMenuSubContent({
-  className,
-  ...props
-}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
-  return (
-    <DropdownMenuPrimitive.SubContent
-      data-slot="dropdown-menu-sub-content"
-      className={cn(
-        "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-export {
-  DropdownMenu,
-  DropdownMenuPortal,
-  DropdownMenuTrigger,
-  DropdownMenuContent,
-  DropdownMenuGroup,
-  DropdownMenuLabel,
-  DropdownMenuItem,
-  DropdownMenuCheckboxItem,
-  DropdownMenuRadioGroup,
-  DropdownMenuRadioItem,
-  DropdownMenuSeparator,
-  DropdownMenuShortcut,
-  DropdownMenuSub,
-  DropdownMenuSubTrigger,
-  DropdownMenuSubContent,
-}

+ 0 - 167
components/ui/form.tsx

@@ -1,167 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as LabelPrimitive from "@radix-ui/react-label"
-import {Slot} from "@radix-ui/react-slot"
-import {
-  Controller,
-  type ControllerProps,
-  type FieldPath,
-  type FieldValues,
-  FormProvider,
-  useFormContext,
-  useFormState,
-} from "react-hook-form"
-
-import {cn} from "@/lib/utils"
-import {Label} from "@/components/ui/label"
-
-const Form = FormProvider
-
-type FormFieldContextValue<
-  TFieldValues extends FieldValues = FieldValues,
-  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
-> = {
-  name: TName
-}
-
-const FormFieldContext = React.createContext<FormFieldContextValue>(
-  {} as FormFieldContextValue
-)
-
-const FormField = <
-  TFieldValues extends FieldValues = FieldValues,
-  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
->({
-  ...props
-}: ControllerProps<TFieldValues, TName>) => {
-  return (
-    <FormFieldContext.Provider value={{ name: props.name }}>
-      <Controller {...props} />
-    </FormFieldContext.Provider>
-  )
-}
-
-const useFormField = () => {
-  const fieldContext = React.useContext(FormFieldContext)
-  const itemContext = React.useContext(FormItemContext)
-  const { getFieldState } = useFormContext()
-  const formState = useFormState({ name: fieldContext.name })
-  const fieldState = getFieldState(fieldContext.name, formState)
-
-  if (!fieldContext) {
-    throw new Error("useFormField should be used within <FormField>")
-  }
-
-  const { id } = itemContext
-
-  return {
-    id,
-    name: fieldContext.name,
-    formItemId: `${id}-form-item`,
-    formDescriptionId: `${id}-form-item-description`,
-    formMessageId: `${id}-form-item-message`,
-    ...fieldState,
-  }
-}
-
-type FormItemContextValue = {
-  id: string
-}
-
-const FormItemContext = React.createContext<FormItemContextValue>(
-  {} as FormItemContextValue
-)
-
-function FormItem({ className, ...props }: React.ComponentProps<"div">) {
-  const id = React.useId()
-
-  return (
-    <FormItemContext.Provider value={{ id }}>
-      <div
-        data-slot="form-item"
-        className={cn("grid gap-2", className)}
-        {...props}
-      />
-    </FormItemContext.Provider>
-  )
-}
-
-function FormLabel({
-  className,
-  ...props
-}: React.ComponentProps<typeof LabelPrimitive.Root>) {
-  const { error, formItemId } = useFormField()
-
-  return (
-    <Label
-      data-slot="form-label"
-      data-error={!!error}
-      className={cn("data-[error=true]:text-destructive", className)}
-      htmlFor={formItemId}
-      {...props}
-    />
-  )
-}
-
-function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
-  const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
-
-  return (
-    <Slot
-      data-slot="form-control"
-      id={formItemId}
-      aria-describedby={
-        !error
-          ? `${formDescriptionId}`
-          : `${formDescriptionId} ${formMessageId}`
-      }
-      aria-invalid={!!error}
-      {...props}
-    />
-  )
-}
-
-function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
-  const { formDescriptionId } = useFormField()
-
-  return (
-    <p
-      data-slot="form-description"
-      id={formDescriptionId}
-      className={cn("text-muted-foreground text-sm", className)}
-      {...props}
-    />
-  )
-}
-
-function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
-  const { error, formMessageId } = useFormField()
-  const body = error ? String(error?.message ?? "") : props.children
-
-  if (!body) {
-    return null
-  }
-
-  return (
-    <p
-      data-slot="form-message"
-      id={formMessageId}
-      className={cn("text-destructive text-sm", className)}
-      {...props}
-    >
-      {body}
-    </p>
-  )
-}
-
-export {
-  useFormField,
-  Form,
-  FormItem,
-  FormLabel,
-  FormControl,
-  FormDescription,
-  FormMessage,
-  FormField,
-}

+ 0 - 44
components/ui/hover-card.tsx

@@ -1,44 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
-
-import {cn} from "@/lib/utils"
-
-function HoverCard({
-  ...props
-}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
-  return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />
-}
-
-function HoverCardTrigger({
-  ...props
-}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
-  return (
-    <HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
-  )
-}
-
-function HoverCardContent({
-  className,
-  align = "center",
-  sideOffset = 4,
-  ...props
-}: React.ComponentProps<typeof HoverCardPrimitive.Content>) {
-  return (
-    <HoverCardPrimitive.Portal data-slot="hover-card-portal">
-      <HoverCardPrimitive.Content
-        data-slot="hover-card-content"
-        align={align}
-        sideOffset={sideOffset}
-        className={cn(
-          "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
-          className
-        )}
-        {...props}
-      />
-    </HoverCardPrimitive.Portal>
-  )
-}
-
-export { HoverCard, HoverCardTrigger, HoverCardContent }

+ 0 - 77
components/ui/input-otp.tsx

@@ -1,77 +0,0 @@
-"use client"
-
-import * as React from "react"
-import {OTPInput, OTPInputContext} from "input-otp"
-import {MinusIcon} from "lucide-react"
-
-import {cn} from "@/lib/utils"
-
-function InputOTP({
-  className,
-  containerClassName,
-  ...props
-}: React.ComponentProps<typeof OTPInput> & {
-  containerClassName?: string
-}) {
-  return (
-    <OTPInput
-      data-slot="input-otp"
-      containerClassName={cn(
-        "flex items-center gap-2 has-disabled:opacity-50",
-        containerClassName
-      )}
-      className={cn("disabled:cursor-not-allowed", className)}
-      {...props}
-    />
-  )
-}
-
-function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="input-otp-group"
-      className={cn("flex items-center", className)}
-      {...props}
-    />
-  )
-}
-
-function InputOTPSlot({
-  index,
-  className,
-  ...props
-}: React.ComponentProps<"div"> & {
-  index: number
-}) {
-  const inputOTPContext = React.useContext(OTPInputContext)
-  const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}
-
-  return (
-    <div
-      data-slot="input-otp-slot"
-      data-active={isActive}
-      className={cn(
-        "data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex h-9 w-9 items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]",
-        className
-      )}
-      {...props}
-    >
-      {char}
-      {hasFakeCaret && (
-        <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
-          <div className="animate-caret-blink bg-foreground h-4 w-px duration-1000" />
-        </div>
-      )}
-    </div>
-  )
-}
-
-function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
-  return (
-    <div data-slot="input-otp-separator" role="separator" {...props}>
-      <MinusIcon />
-    </div>
-  )
-}
-
-export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }

+ 0 - 21
components/ui/input.tsx

@@ -1,21 +0,0 @@
-import * as React from "react"
-
-import {cn} from "@/lib/utils"
-
-function Input({ className, type, ...props }: React.ComponentProps<"input">) {
-  return (
-    <input
-      type={type}
-      data-slot="input"
-      className={cn(
-        "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
-        "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
-        "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-export { Input }

+ 0 - 24
components/ui/label.tsx

@@ -1,24 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as LabelPrimitive from "@radix-ui/react-label"
-
-import {cn} from "@/lib/utils"
-
-function Label({
-  className,
-  ...props
-}: React.ComponentProps<typeof LabelPrimitive.Root>) {
-  return (
-    <LabelPrimitive.Root
-      data-slot="label"
-      className={cn(
-        "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-export { Label }

+ 0 - 276
components/ui/menubar.tsx

@@ -1,276 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as MenubarPrimitive from "@radix-ui/react-menubar"
-import {CheckIcon, ChevronRightIcon, CircleIcon} from "lucide-react"
-
-import {cn} from "@/lib/utils"
-
-function Menubar({
-  className,
-  ...props
-}: React.ComponentProps<typeof MenubarPrimitive.Root>) {
-  return (
-    <MenubarPrimitive.Root
-      data-slot="menubar"
-      className={cn(
-        "bg-background flex h-9 items-center gap-1 rounded-md border p-1 shadow-xs",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function MenubarMenu({
-  ...props
-}: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
-  return <MenubarPrimitive.Menu data-slot="menubar-menu" {...props} />
-}
-
-function MenubarGroup({
-  ...props
-}: React.ComponentProps<typeof MenubarPrimitive.Group>) {
-  return <MenubarPrimitive.Group data-slot="menubar-group" {...props} />
-}
-
-function MenubarPortal({
-  ...props
-}: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
-  return <MenubarPrimitive.Portal data-slot="menubar-portal" {...props} />
-}
-
-function MenubarRadioGroup({
-  ...props
-}: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
-  return (
-    <MenubarPrimitive.RadioGroup data-slot="menubar-radio-group" {...props} />
-  )
-}
-
-function MenubarTrigger({
-  className,
-  ...props
-}: React.ComponentProps<typeof MenubarPrimitive.Trigger>) {
-  return (
-    <MenubarPrimitive.Trigger
-      data-slot="menubar-trigger"
-      className={cn(
-        "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex items-center rounded-sm px-2 py-1 text-sm font-medium outline-hidden select-none",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function MenubarContent({
-  className,
-  align = "start",
-  alignOffset = -4,
-  sideOffset = 8,
-  ...props
-}: React.ComponentProps<typeof MenubarPrimitive.Content>) {
-  return (
-    <MenubarPortal>
-      <MenubarPrimitive.Content
-        data-slot="menubar-content"
-        align={align}
-        alignOffset={alignOffset}
-        sideOffset={sideOffset}
-        className={cn(
-          "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[12rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-md",
-          className
-        )}
-        {...props}
-      />
-    </MenubarPortal>
-  )
-}
-
-function MenubarItem({
-  className,
-  inset,
-  variant = "default",
-  ...props
-}: React.ComponentProps<typeof MenubarPrimitive.Item> & {
-  inset?: boolean
-  variant?: "default" | "destructive"
-}) {
-  return (
-    <MenubarPrimitive.Item
-      data-slot="menubar-item"
-      data-inset={inset}
-      data-variant={variant}
-      className={cn(
-        "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function MenubarCheckboxItem({
-  className,
-  children,
-  checked,
-  ...props
-}: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem>) {
-  return (
-    <MenubarPrimitive.CheckboxItem
-      data-slot="menubar-checkbox-item"
-      className={cn(
-        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
-        className
-      )}
-      checked={checked}
-      {...props}
-    >
-      <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
-        <MenubarPrimitive.ItemIndicator>
-          <CheckIcon className="size-4" />
-        </MenubarPrimitive.ItemIndicator>
-      </span>
-      {children}
-    </MenubarPrimitive.CheckboxItem>
-  )
-}
-
-function MenubarRadioItem({
-  className,
-  children,
-  ...props
-}: React.ComponentProps<typeof MenubarPrimitive.RadioItem>) {
-  return (
-    <MenubarPrimitive.RadioItem
-      data-slot="menubar-radio-item"
-      className={cn(
-        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
-        className
-      )}
-      {...props}
-    >
-      <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
-        <MenubarPrimitive.ItemIndicator>
-          <CircleIcon className="size-2 fill-current" />
-        </MenubarPrimitive.ItemIndicator>
-      </span>
-      {children}
-    </MenubarPrimitive.RadioItem>
-  )
-}
-
-function MenubarLabel({
-  className,
-  inset,
-  ...props
-}: React.ComponentProps<typeof MenubarPrimitive.Label> & {
-  inset?: boolean
-}) {
-  return (
-    <MenubarPrimitive.Label
-      data-slot="menubar-label"
-      data-inset={inset}
-      className={cn(
-        "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function MenubarSeparator({
-  className,
-  ...props
-}: React.ComponentProps<typeof MenubarPrimitive.Separator>) {
-  return (
-    <MenubarPrimitive.Separator
-      data-slot="menubar-separator"
-      className={cn("bg-border -mx-1 my-1 h-px", className)}
-      {...props}
-    />
-  )
-}
-
-function MenubarShortcut({
-  className,
-  ...props
-}: React.ComponentProps<"span">) {
-  return (
-    <span
-      data-slot="menubar-shortcut"
-      className={cn(
-        "text-muted-foreground ml-auto text-xs tracking-widest",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function MenubarSub({
-  ...props
-}: React.ComponentProps<typeof MenubarPrimitive.Sub>) {
-  return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />
-}
-
-function MenubarSubTrigger({
-  className,
-  inset,
-  children,
-  ...props
-}: React.ComponentProps<typeof MenubarPrimitive.SubTrigger> & {
-  inset?: boolean
-}) {
-  return (
-    <MenubarPrimitive.SubTrigger
-      data-slot="menubar-sub-trigger"
-      data-inset={inset}
-      className={cn(
-        "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[inset]:pl-8",
-        className
-      )}
-      {...props}
-    >
-      {children}
-      <ChevronRightIcon className="ml-auto h-4 w-4" />
-    </MenubarPrimitive.SubTrigger>
-  )
-}
-
-function MenubarSubContent({
-  className,
-  ...props
-}: React.ComponentProps<typeof MenubarPrimitive.SubContent>) {
-  return (
-    <MenubarPrimitive.SubContent
-      data-slot="menubar-sub-content"
-      className={cn(
-        "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-export {
-  Menubar,
-  MenubarPortal,
-  MenubarMenu,
-  MenubarTrigger,
-  MenubarContent,
-  MenubarGroup,
-  MenubarSeparator,
-  MenubarLabel,
-  MenubarItem,
-  MenubarShortcut,
-  MenubarCheckboxItem,
-  MenubarRadioGroup,
-  MenubarRadioItem,
-  MenubarSub,
-  MenubarSubTrigger,
-  MenubarSubContent,
-}

+ 0 - 168
components/ui/navigation-menu.tsx

@@ -1,168 +0,0 @@
-import * as React from "react"
-import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
-import {cva} from "class-variance-authority"
-import {ChevronDownIcon} from "lucide-react"
-
-import {cn} from "@/lib/utils"
-
-function NavigationMenu({
-  className,
-  children,
-  viewport = true,
-  ...props
-}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
-  viewport?: boolean
-}) {
-  return (
-    <NavigationMenuPrimitive.Root
-      data-slot="navigation-menu"
-      data-viewport={viewport}
-      className={cn(
-        "group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
-        className
-      )}
-      {...props}
-    >
-      {children}
-      {viewport && <NavigationMenuViewport />}
-    </NavigationMenuPrimitive.Root>
-  )
-}
-
-function NavigationMenuList({
-  className,
-  ...props
-}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
-  return (
-    <NavigationMenuPrimitive.List
-      data-slot="navigation-menu-list"
-      className={cn(
-        "group flex flex-1 list-none items-center justify-center gap-1",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function NavigationMenuItem({
-  className,
-  ...props
-}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
-  return (
-    <NavigationMenuPrimitive.Item
-      data-slot="navigation-menu-item"
-      className={cn("relative", className)}
-      {...props}
-    />
-  )
-}
-
-const navigationMenuTriggerStyle = cva(
-  "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1"
-)
-
-function NavigationMenuTrigger({
-  className,
-  children,
-  ...props
-}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
-  return (
-    <NavigationMenuPrimitive.Trigger
-      data-slot="navigation-menu-trigger"
-      className={cn(navigationMenuTriggerStyle(), "group", className)}
-      {...props}
-    >
-      {children}{" "}
-      <ChevronDownIcon
-        className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
-        aria-hidden="true"
-      />
-    </NavigationMenuPrimitive.Trigger>
-  )
-}
-
-function NavigationMenuContent({
-  className,
-  ...props
-}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
-  return (
-    <NavigationMenuPrimitive.Content
-      data-slot="navigation-menu-content"
-      className={cn(
-        "data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto",
-        "group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function NavigationMenuViewport({
-  className,
-  ...props
-}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
-  return (
-    <div
-      className={cn(
-        "absolute top-full left-0 isolate z-50 flex justify-center"
-      )}
-    >
-      <NavigationMenuPrimitive.Viewport
-        data-slot="navigation-menu-viewport"
-        className={cn(
-          "origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]",
-          className
-        )}
-        {...props}
-      />
-    </div>
-  )
-}
-
-function NavigationMenuLink({
-  className,
-  ...props
-}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
-  return (
-    <NavigationMenuPrimitive.Link
-      data-slot="navigation-menu-link"
-      className={cn(
-        "data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function NavigationMenuIndicator({
-  className,
-  ...props
-}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
-  return (
-    <NavigationMenuPrimitive.Indicator
-      data-slot="navigation-menu-indicator"
-      className={cn(
-        "data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden",
-        className
-      )}
-      {...props}
-    >
-      <div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
-    </NavigationMenuPrimitive.Indicator>
-  )
-}
-
-export {
-  NavigationMenu,
-  NavigationMenuList,
-  NavigationMenuItem,
-  NavigationMenuContent,
-  NavigationMenuTrigger,
-  NavigationMenuLink,
-  NavigationMenuIndicator,
-  NavigationMenuViewport,
-  navigationMenuTriggerStyle,
-}

+ 0 - 123
components/ui/pagination.tsx

@@ -1,123 +0,0 @@
-import * as React from "react"
-import {ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon,} from "lucide-react"
-
-import {cn} from "@/lib/utils"
-import {Button, buttonVariants} from "@/components/ui/button"
-
-function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
-  return (
-    <nav
-      role="navigation"
-      aria-label="pagination"
-      data-slot="pagination"
-      className={cn("mx-auto flex w-full justify-center", className)}
-      {...props}
-    />
-  )
-}
-
-function PaginationContent({
-  className,
-  ...props
-}: React.ComponentProps<"ul">) {
-  return (
-    <ul
-      data-slot="pagination-content"
-      className={cn("flex flex-row items-center gap-1", className)}
-      {...props}
-    />
-  )
-}
-
-function PaginationItem({ ...props }: React.ComponentProps<"li">) {
-  return <li data-slot="pagination-item" {...props} />
-}
-
-type PaginationLinkProps = {
-  isActive?: boolean
-} & Pick<React.ComponentProps<typeof Button>, "size"> &
-  React.ComponentProps<"a">
-
-function PaginationLink({
-  className,
-  isActive,
-  size = "icon",
-  ...props
-}: PaginationLinkProps) {
-  return (
-    <a
-      aria-current={isActive ? "page" : undefined}
-      data-slot="pagination-link"
-      data-active={isActive}
-      className={cn(
-        buttonVariants({
-          variant: isActive ? "outline" : "ghost",
-          size,
-        }),
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function PaginationPrevious({
-  className,
-  ...props
-}: React.ComponentProps<typeof PaginationLink>) {
-  return (
-    <PaginationLink
-      aria-label="Go to previous page"
-      size="default"
-      className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
-      {...props}
-    >
-      <ChevronLeftIcon />
-      <span className="hidden sm:block">Previous</span>
-    </PaginationLink>
-  )
-}
-
-function PaginationNext({
-  className,
-  ...props
-}: React.ComponentProps<typeof PaginationLink>) {
-  return (
-    <PaginationLink
-      aria-label="Go to next page"
-      size="default"
-      className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
-      {...props}
-    >
-      <span className="hidden sm:block">Next</span>
-      <ChevronRightIcon />
-    </PaginationLink>
-  )
-}
-
-function PaginationEllipsis({
-  className,
-  ...props
-}: React.ComponentProps<"span">) {
-  return (
-    <span
-      aria-hidden
-      data-slot="pagination-ellipsis"
-      className={cn("flex size-9 items-center justify-center", className)}
-      {...props}
-    >
-      <MoreHorizontalIcon className="size-4" />
-      <span className="sr-only">More pages</span>
-    </span>
-  )
-}
-
-export {
-  Pagination,
-  PaginationContent,
-  PaginationLink,
-  PaginationItem,
-  PaginationPrevious,
-  PaginationNext,
-  PaginationEllipsis,
-}

+ 0 - 48
components/ui/popover.tsx

@@ -1,48 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as PopoverPrimitive from "@radix-ui/react-popover"
-
-import {cn} from "@/lib/utils"
-
-function Popover({
-  ...props
-}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
-  return <PopoverPrimitive.Root data-slot="popover" {...props} />
-}
-
-function PopoverTrigger({
-  ...props
-}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
-  return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
-}
-
-function PopoverContent({
-  className,
-  align = "center",
-  sideOffset = 4,
-  ...props
-}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
-  return (
-    <PopoverPrimitive.Portal>
-      <PopoverPrimitive.Content
-        data-slot="popover-content"
-        align={align}
-        sideOffset={sideOffset}
-        className={cn(
-          "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
-          className
-        )}
-        {...props}
-      />
-    </PopoverPrimitive.Portal>
-  )
-}
-
-function PopoverAnchor({
-  ...props
-}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
-  return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
-}
-
-export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }

+ 0 - 31
components/ui/progress.tsx

@@ -1,31 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as ProgressPrimitive from "@radix-ui/react-progress"
-
-import {cn} from "@/lib/utils"
-
-function Progress({
-  className,
-  value,
-  ...props
-}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
-  return (
-    <ProgressPrimitive.Root
-      data-slot="progress"
-      className={cn(
-        "bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
-        className
-      )}
-      {...props}
-    >
-      <ProgressPrimitive.Indicator
-        data-slot="progress-indicator"
-        className="bg-primary h-full w-full flex-1 transition-all"
-        style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
-      />
-    </ProgressPrimitive.Root>
-  )
-}
-
-export { Progress }

+ 0 - 45
components/ui/radio-group.tsx

@@ -1,45 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
-import {CircleIcon} from "lucide-react"
-
-import {cn} from "@/lib/utils"
-
-function RadioGroup({
-  className,
-  ...props
-}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
-  return (
-    <RadioGroupPrimitive.Root
-      data-slot="radio-group"
-      className={cn("grid gap-3", className)}
-      {...props}
-    />
-  )
-}
-
-function RadioGroupItem({
-  className,
-  ...props
-}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
-  return (
-    <RadioGroupPrimitive.Item
-      data-slot="radio-group-item"
-      className={cn(
-        "border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
-        className
-      )}
-      {...props}
-    >
-      <RadioGroupPrimitive.Indicator
-        data-slot="radio-group-indicator"
-        className="relative flex items-center justify-center"
-      >
-        <CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
-      </RadioGroupPrimitive.Indicator>
-    </RadioGroupPrimitive.Item>
-  )
-}
-
-export { RadioGroup, RadioGroupItem }

+ 0 - 56
components/ui/resizable.tsx

@@ -1,56 +0,0 @@
-"use client"
-
-import * as React from "react"
-import {GripVerticalIcon} from "lucide-react"
-import * as ResizablePrimitive from "react-resizable-panels"
-
-import {cn} from "@/lib/utils"
-
-function ResizablePanelGroup({
-  className,
-  ...props
-}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) {
-  return (
-    <ResizablePrimitive.PanelGroup
-      data-slot="resizable-panel-group"
-      className={cn(
-        "flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function ResizablePanel({
-  ...props
-}: React.ComponentProps<typeof ResizablePrimitive.Panel>) {
-  return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />
-}
-
-function ResizableHandle({
-  withHandle,
-  className,
-  ...props
-}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
-  withHandle?: boolean
-}) {
-  return (
-    <ResizablePrimitive.PanelResizeHandle
-      data-slot="resizable-handle"
-      className={cn(
-        "bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90",
-        className
-      )}
-      {...props}
-    >
-      {withHandle && (
-        <div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border">
-          <GripVerticalIcon className="size-2.5" />
-        </div>
-      )}
-    </ResizablePrimitive.PanelResizeHandle>
-  )
-}
-
-export { ResizablePanelGroup, ResizablePanel, ResizableHandle }

+ 0 - 58
components/ui/scroll-area.tsx

@@ -1,58 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
-
-import {cn} from "@/lib/utils"
-
-function ScrollArea({
-  className,
-  children,
-  ...props
-}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
-  return (
-    <ScrollAreaPrimitive.Root
-      data-slot="scroll-area"
-      className={cn("relative", className)}
-      {...props}
-    >
-      <ScrollAreaPrimitive.Viewport
-        data-slot="scroll-area-viewport"
-        className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
-      >
-        {children}
-      </ScrollAreaPrimitive.Viewport>
-      <ScrollBar />
-      <ScrollAreaPrimitive.Corner />
-    </ScrollAreaPrimitive.Root>
-  )
-}
-
-function ScrollBar({
-  className,
-  orientation = "vertical",
-  ...props
-}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
-  return (
-    <ScrollAreaPrimitive.ScrollAreaScrollbar
-      data-slot="scroll-area-scrollbar"
-      orientation={orientation}
-      className={cn(
-        "flex touch-none p-px transition-colors select-none",
-        orientation === "vertical" &&
-          "h-full w-2.5 border-l border-l-transparent",
-        orientation === "horizontal" &&
-          "h-2.5 flex-col border-t border-t-transparent",
-        className
-      )}
-      {...props}
-    >
-      <ScrollAreaPrimitive.ScrollAreaThumb
-        data-slot="scroll-area-thumb"
-        className="bg-border relative flex-1 rounded-full"
-      />
-    </ScrollAreaPrimitive.ScrollAreaScrollbar>
-  )
-}
-
-export { ScrollArea, ScrollBar }

+ 0 - 185
components/ui/select.tsx

@@ -1,185 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as SelectPrimitive from "@radix-ui/react-select"
-import {CheckIcon, ChevronDownIcon, ChevronUpIcon} from "lucide-react"
-
-import {cn} from "@/lib/utils"
-
-function Select({
-  ...props
-}: React.ComponentProps<typeof SelectPrimitive.Root>) {
-  return <SelectPrimitive.Root data-slot="select" {...props} />
-}
-
-function SelectGroup({
-  ...props
-}: React.ComponentProps<typeof SelectPrimitive.Group>) {
-  return <SelectPrimitive.Group data-slot="select-group" {...props} />
-}
-
-function SelectValue({
-  ...props
-}: React.ComponentProps<typeof SelectPrimitive.Value>) {
-  return <SelectPrimitive.Value data-slot="select-value" {...props} />
-}
-
-function SelectTrigger({
-  className,
-  size = "default",
-  children,
-  ...props
-}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
-  size?: "sm" | "default"
-}) {
-  return (
-    <SelectPrimitive.Trigger
-      data-slot="select-trigger"
-      data-size={size}
-      className={cn(
-        "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
-        className
-      )}
-      {...props}
-    >
-      {children}
-      <SelectPrimitive.Icon asChild>
-        <ChevronDownIcon className="size-4 opacity-50" />
-      </SelectPrimitive.Icon>
-    </SelectPrimitive.Trigger>
-  )
-}
-
-function SelectContent({
-  className,
-  children,
-  position = "popper",
-  ...props
-}: React.ComponentProps<typeof SelectPrimitive.Content>) {
-  return (
-    <SelectPrimitive.Portal>
-      <SelectPrimitive.Content
-        data-slot="select-content"
-        className={cn(
-          "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
-          position === "popper" &&
-            "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
-          className
-        )}
-        position={position}
-        {...props}
-      >
-        <SelectScrollUpButton />
-        <SelectPrimitive.Viewport
-          className={cn(
-            "p-1",
-            position === "popper" &&
-              "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
-          )}
-        >
-          {children}
-        </SelectPrimitive.Viewport>
-        <SelectScrollDownButton />
-      </SelectPrimitive.Content>
-    </SelectPrimitive.Portal>
-  )
-}
-
-function SelectLabel({
-  className,
-  ...props
-}: React.ComponentProps<typeof SelectPrimitive.Label>) {
-  return (
-    <SelectPrimitive.Label
-      data-slot="select-label"
-      className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
-      {...props}
-    />
-  )
-}
-
-function SelectItem({
-  className,
-  children,
-  ...props
-}: React.ComponentProps<typeof SelectPrimitive.Item>) {
-  return (
-    <SelectPrimitive.Item
-      data-slot="select-item"
-      className={cn(
-        "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
-        className
-      )}
-      {...props}
-    >
-      <span className="absolute right-2 flex size-3.5 items-center justify-center">
-        <SelectPrimitive.ItemIndicator>
-          <CheckIcon className="size-4" />
-        </SelectPrimitive.ItemIndicator>
-      </span>
-      <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
-    </SelectPrimitive.Item>
-  )
-}
-
-function SelectSeparator({
-  className,
-  ...props
-}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
-  return (
-    <SelectPrimitive.Separator
-      data-slot="select-separator"
-      className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
-      {...props}
-    />
-  )
-}
-
-function SelectScrollUpButton({
-  className,
-  ...props
-}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
-  return (
-    <SelectPrimitive.ScrollUpButton
-      data-slot="select-scroll-up-button"
-      className={cn(
-        "flex cursor-default items-center justify-center py-1",
-        className
-      )}
-      {...props}
-    >
-      <ChevronUpIcon className="size-4" />
-    </SelectPrimitive.ScrollUpButton>
-  )
-}
-
-function SelectScrollDownButton({
-  className,
-  ...props
-}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
-  return (
-    <SelectPrimitive.ScrollDownButton
-      data-slot="select-scroll-down-button"
-      className={cn(
-        "flex cursor-default items-center justify-center py-1",
-        className
-      )}
-      {...props}
-    >
-      <ChevronDownIcon className="size-4" />
-    </SelectPrimitive.ScrollDownButton>
-  )
-}
-
-export {
-  Select,
-  SelectContent,
-  SelectGroup,
-  SelectItem,
-  SelectLabel,
-  SelectScrollDownButton,
-  SelectScrollUpButton,
-  SelectSeparator,
-  SelectTrigger,
-  SelectValue,
-}

+ 0 - 28
components/ui/separator.tsx

@@ -1,28 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as SeparatorPrimitive from "@radix-ui/react-separator"
-
-import {cn} from "@/lib/utils"
-
-function Separator({
-  className,
-  orientation = "horizontal",
-  decorative = true,
-  ...props
-}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
-  return (
-    <SeparatorPrimitive.Root
-      data-slot="separator"
-      decorative={decorative}
-      orientation={orientation}
-      className={cn(
-        "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-export { Separator }

+ 0 - 139
components/ui/sheet.tsx

@@ -1,139 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as SheetPrimitive from "@radix-ui/react-dialog"
-import {XIcon} from "lucide-react"
-
-import {cn} from "@/lib/utils"
-
-function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
-  return <SheetPrimitive.Root data-slot="sheet" {...props} />
-}
-
-function SheetTrigger({
-  ...props
-}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
-  return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
-}
-
-function SheetClose({
-  ...props
-}: React.ComponentProps<typeof SheetPrimitive.Close>) {
-  return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
-}
-
-function SheetPortal({
-  ...props
-}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
-  return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
-}
-
-function SheetOverlay({
-  className,
-  ...props
-}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
-  return (
-    <SheetPrimitive.Overlay
-      data-slot="sheet-overlay"
-      className={cn(
-        "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function SheetContent({
-  className,
-  children,
-  side = "right",
-  ...props
-}: React.ComponentProps<typeof SheetPrimitive.Content> & {
-  side?: "top" | "right" | "bottom" | "left"
-}) {
-  return (
-    <SheetPortal>
-      <SheetOverlay />
-      <SheetPrimitive.Content
-        data-slot="sheet-content"
-        className={cn(
-          "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
-          side === "right" &&
-            "data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
-          side === "left" &&
-            "data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
-          side === "top" &&
-            "data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
-          side === "bottom" &&
-            "data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
-          className
-        )}
-        {...props}
-      >
-        {children}
-        <SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
-          <XIcon className="size-4" />
-          <span className="sr-only">Close</span>
-        </SheetPrimitive.Close>
-      </SheetPrimitive.Content>
-    </SheetPortal>
-  )
-}
-
-function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="sheet-header"
-      className={cn("flex flex-col gap-1.5 p-4", className)}
-      {...props}
-    />
-  )
-}
-
-function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="sheet-footer"
-      className={cn("mt-auto flex flex-col gap-2 p-4", className)}
-      {...props}
-    />
-  )
-}
-
-function SheetTitle({
-  className,
-  ...props
-}: React.ComponentProps<typeof SheetPrimitive.Title>) {
-  return (
-    <SheetPrimitive.Title
-      data-slot="sheet-title"
-      className={cn("text-foreground font-semibold", className)}
-      {...props}
-    />
-  )
-}
-
-function SheetDescription({
-  className,
-  ...props
-}: React.ComponentProps<typeof SheetPrimitive.Description>) {
-  return (
-    <SheetPrimitive.Description
-      data-slot="sheet-description"
-      className={cn("text-muted-foreground text-sm", className)}
-      {...props}
-    />
-  )
-}
-
-export {
-  Sheet,
-  SheetTrigger,
-  SheetClose,
-  SheetContent,
-  SheetHeader,
-  SheetFooter,
-  SheetTitle,
-  SheetDescription,
-}

+ 0 - 715
components/ui/sidebar.tsx

@@ -1,715 +0,0 @@
-"use client"
-
-import * as React from "react"
-import {Slot} from "@radix-ui/react-slot"
-import {cva, VariantProps} from "class-variance-authority"
-import {PanelLeftIcon} from "lucide-react"
-
-import {useIsMobile} from "@/hooks/use-mobile"
-import {cn} from "@/lib/utils"
-import {Button} from "@/components/ui/button"
-import {Input} from "@/components/ui/input"
-import {Separator} from "@/components/ui/separator"
-import {Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle,} from "@/components/ui/sheet"
-import {Skeleton} from "@/components/ui/skeleton"
-import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger,} from "@/components/ui/tooltip"
-
-const SIDEBAR_COOKIE_NAME = "sidebar_state"
-const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
-const SIDEBAR_WIDTH = "16rem"
-const SIDEBAR_WIDTH_MOBILE = "18rem"
-const SIDEBAR_WIDTH_ICON = "3rem"
-const SIDEBAR_KEYBOARD_SHORTCUT = "b"
-
-type SidebarContextProps = {
-  state: "expanded" | "collapsed"
-  open: boolean
-  setOpen: (open: boolean) => void
-  openMobile: boolean
-  setOpenMobile: (open: boolean) => void
-  isMobile: boolean
-  toggleSidebar: () => void
-}
-
-const SidebarContext = React.createContext<SidebarContextProps | null>(null)
-
-function useSidebar() {
-  const context = React.useContext(SidebarContext)
-  if (!context) {
-    throw new Error("useSidebar must be used within a SidebarProvider.")
-  }
-
-  return context
-}
-
-function SidebarProvider({
-  defaultOpen = true,
-  open: openProp,
-  onOpenChange: setOpenProp,
-  className,
-  style,
-  children,
-  ...props
-}: React.ComponentProps<"div"> & {
-  defaultOpen?: boolean
-  open?: boolean
-  onOpenChange?: (open: boolean) => void
-}) {
-  const isMobile = useIsMobile()
-  const [openMobile, setOpenMobile] = React.useState(false)
-
-  // This is the internal state of the sidebar.
-  // We use openProp and setOpenProp for control from outside the component.
-  const [_open, _setOpen] = React.useState(defaultOpen)
-  const open = openProp ?? _open
-  const setOpen = React.useCallback(
-    (value: boolean | ((value: boolean) => boolean)) => {
-      const openState = typeof value === "function" ? value(open) : value
-      if (setOpenProp) {
-        setOpenProp(openState)
-      } else {
-        _setOpen(openState)
-      }
-
-      // This sets the cookie to keep the sidebar state.
-      document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
-    },
-    [setOpenProp, open]
-  )
-
-  // Helper to toggle the sidebar.
-  const toggleSidebar = React.useCallback(() => {
-    return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
-  }, [isMobile, setOpen, setOpenMobile])
-
-  // Adds a keyboard shortcut to toggle the sidebar.
-  React.useEffect(() => {
-    const handleKeyDown = (event: KeyboardEvent) => {
-      if (
-        event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
-        (event.metaKey || event.ctrlKey)
-      ) {
-        event.preventDefault()
-        toggleSidebar()
-      }
-    }
-
-    window.addEventListener("keydown", handleKeyDown)
-    return () => window.removeEventListener("keydown", handleKeyDown)
-  }, [toggleSidebar])
-
-  // We add a state so that we can do data-state="expanded" or "collapsed".
-  // This makes it easier to style the sidebar with Tailwind classes.
-  const state = open ? "expanded" : "collapsed"
-
-  const contextValue = React.useMemo<SidebarContextProps>(
-    () => ({
-      state,
-      open,
-      setOpen,
-      isMobile,
-      openMobile,
-      setOpenMobile,
-      toggleSidebar,
-    }),
-    [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
-  )
-
-  return (
-    <SidebarContext.Provider value={contextValue}>
-      <TooltipProvider delayDuration={0}>
-        <div
-          data-slot="sidebar-wrapper"
-          style={
-            {
-              "--sidebar-width": SIDEBAR_WIDTH,
-              "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
-              ...style,
-            } as React.CSSProperties
-          }
-          className={cn(
-            "group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
-            className
-          )}
-          {...props}
-        >
-          {children}
-        </div>
-      </TooltipProvider>
-    </SidebarContext.Provider>
-  )
-}
-
-function Sidebar({
-  side = "left",
-  variant = "sidebar",
-  collapsible = "offcanvas",
-  className,
-  children,
-  ...props
-}: React.ComponentProps<"div"> & {
-  side?: "left" | "right"
-  variant?: "sidebar" | "floating" | "inset"
-  collapsible?: "offcanvas" | "icon" | "none"
-}) {
-  const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
-
-  if (collapsible === "none") {
-    return (
-      <div
-        data-slot="sidebar"
-        className={cn(
-          "bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col",
-          className
-        )}
-        {...props}
-      >
-        {children}
-      </div>
-    )
-  }
-
-  if (isMobile) {
-    return (
-      <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
-        <SheetContent
-          data-sidebar="sidebar"
-          data-slot="sidebar"
-          data-mobile="true"
-          className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
-          style={
-            {
-              "--sidebar-width": SIDEBAR_WIDTH_MOBILE,
-            } as React.CSSProperties
-          }
-          side={side}
-        >
-          <SheetHeader className="sr-only">
-            <SheetTitle>Sidebar</SheetTitle>
-            <SheetDescription>Displays the mobile sidebar.</SheetDescription>
-          </SheetHeader>
-          <div className="flex h-full w-full flex-col">{children}</div>
-        </SheetContent>
-      </Sheet>
-    )
-  }
-
-  return (
-    <div
-      className="group peer text-sidebar-foreground hidden md:block"
-      data-state={state}
-      data-collapsible={state === "collapsed" ? collapsible : ""}
-      data-variant={variant}
-      data-side={side}
-      data-slot="sidebar"
-    >
-      {/* This is what handles the sidebar gap on desktop */}
-      <div
-        data-slot="sidebar-gap"
-        className={cn(
-          "relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear",
-          "group-data-[collapsible=offcanvas]:w-0",
-          "group-data-[side=right]:rotate-180",
-          variant === "floating" || variant === "inset"
-            ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
-            : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)"
-        )}
-      />
-      <div
-        data-slot="sidebar-container"
-        className={cn(
-          "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex",
-          side === "left"
-            ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
-            : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
-          // Adjust the padding for floating and inset variants.
-          variant === "floating" || variant === "inset"
-            ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
-            : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
-          className
-        )}
-        {...props}
-      >
-        <div
-          data-sidebar="sidebar"
-          data-slot="sidebar-inner"
-          className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm"
-        >
-          {children}
-        </div>
-      </div>
-    </div>
-  )
-}
-
-function SidebarTrigger({
-  className,
-  onClick,
-  ...props
-}: React.ComponentProps<typeof Button>) {
-  const { toggleSidebar } = useSidebar()
-
-  return (
-    <Button
-      data-sidebar="trigger"
-      data-slot="sidebar-trigger"
-      variant="ghost"
-      size="icon"
-      className={cn("size-7", className)}
-      onClick={(event) => {
-        onClick?.(event)
-        toggleSidebar()
-      }}
-      {...props}
-    >
-      <PanelLeftIcon />
-      <span className="sr-only">Toggle Sidebar</span>
-    </Button>
-  )
-}
-
-function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
-  const { toggleSidebar } = useSidebar()
-
-  return (
-    <button
-      data-sidebar="rail"
-      data-slot="sidebar-rail"
-      aria-label="Toggle Sidebar"
-      tabIndex={-1}
-      onClick={toggleSidebar}
-      title="Toggle Sidebar"
-      className={cn(
-        "hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex",
-        "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
-        "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
-        "hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full",
-        "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
-        "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
-  return (
-    <main
-      data-slot="sidebar-inset"
-      className={cn(
-        "bg-background relative flex w-full flex-1 flex-col",
-        "md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function SidebarInput({
-  className,
-  ...props
-}: React.ComponentProps<typeof Input>) {
-  return (
-    <Input
-      data-slot="sidebar-input"
-      data-sidebar="input"
-      className={cn("bg-background h-8 w-full shadow-none", className)}
-      {...props}
-    />
-  )
-}
-
-function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="sidebar-header"
-      data-sidebar="header"
-      className={cn("flex flex-col gap-2 p-2", className)}
-      {...props}
-    />
-  )
-}
-
-function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="sidebar-footer"
-      data-sidebar="footer"
-      className={cn("flex flex-col gap-2 p-2", className)}
-      {...props}
-    />
-  )
-}
-
-function SidebarSeparator({
-  className,
-  ...props
-}: React.ComponentProps<typeof Separator>) {
-  return (
-    <Separator
-      data-slot="sidebar-separator"
-      data-sidebar="separator"
-      className={cn("bg-sidebar-border mx-2 w-auto", className)}
-      {...props}
-    />
-  )
-}
-
-function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="sidebar-content"
-      data-sidebar="content"
-      className={cn(
-        "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="sidebar-group"
-      data-sidebar="group"
-      className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
-      {...props}
-    />
-  )
-}
-
-function SidebarGroupLabel({
-  className,
-  asChild = false,
-  ...props
-}: React.ComponentProps<"div"> & { asChild?: boolean }) {
-  const Comp = asChild ? Slot : "div"
-
-  return (
-    <Comp
-      data-slot="sidebar-group-label"
-      data-sidebar="group-label"
-      className={cn(
-        "text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
-        "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function SidebarGroupAction({
-  className,
-  asChild = false,
-  ...props
-}: React.ComponentProps<"button"> & { asChild?: boolean }) {
-  const Comp = asChild ? Slot : "button"
-
-  return (
-    <Comp
-      data-slot="sidebar-group-action"
-      data-sidebar="group-action"
-      className={cn(
-        "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
-        // Increases the hit area of the button on mobile.
-        "after:absolute after:-inset-2 md:after:hidden",
-        "group-data-[collapsible=icon]:hidden",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function SidebarGroupContent({
-  className,
-  ...props
-}: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="sidebar-group-content"
-      data-sidebar="group-content"
-      className={cn("w-full text-sm", className)}
-      {...props}
-    />
-  )
-}
-
-function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
-  return (
-    <ul
-      data-slot="sidebar-menu"
-      data-sidebar="menu"
-      className={cn("flex w-full min-w-0 flex-col gap-1", className)}
-      {...props}
-    />
-  )
-}
-
-function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
-  return (
-    <li
-      data-slot="sidebar-menu-item"
-      data-sidebar="menu-item"
-      className={cn("group/menu-item relative", className)}
-      {...props}
-    />
-  )
-}
-
-const sidebarMenuButtonVariants = cva(
-  "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
-  {
-    variants: {
-      variant: {
-        default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
-        outline:
-          "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
-      },
-      size: {
-        default: "h-8 text-sm",
-        sm: "h-7 text-xs",
-        lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
-      },
-    },
-    defaultVariants: {
-      variant: "default",
-      size: "default",
-    },
-  }
-)
-
-function SidebarMenuButton({
-  asChild = false,
-  isActive = false,
-  variant = "default",
-  size = "default",
-  tooltip,
-  className,
-  ...props
-}: React.ComponentProps<"button"> & {
-  asChild?: boolean
-  isActive?: boolean
-  tooltip?: string | React.ComponentProps<typeof TooltipContent>
-} & VariantProps<typeof sidebarMenuButtonVariants>) {
-  const Comp = asChild ? Slot : "button"
-  const { isMobile, state } = useSidebar()
-
-  const button = (
-    <Comp
-      data-slot="sidebar-menu-button"
-      data-sidebar="menu-button"
-      data-size={size}
-      data-active={isActive}
-      className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
-      {...props}
-    />
-  )
-
-  if (!tooltip) {
-    return button
-  }
-
-  if (typeof tooltip === "string") {
-    tooltip = {
-      children: tooltip,
-    }
-  }
-
-  return (
-    <Tooltip>
-      <TooltipTrigger asChild>{button}</TooltipTrigger>
-      <TooltipContent
-        side="right"
-        align="center"
-        hidden={state !== "collapsed" || isMobile}
-        {...tooltip}
-      />
-    </Tooltip>
-  )
-}
-
-function SidebarMenuAction({
-  className,
-  asChild = false,
-  showOnHover = false,
-  ...props
-}: React.ComponentProps<"button"> & {
-  asChild?: boolean
-  showOnHover?: boolean
-}) {
-  const Comp = asChild ? Slot : "button"
-
-  return (
-    <Comp
-      data-slot="sidebar-menu-action"
-      data-sidebar="menu-action"
-      className={cn(
-        "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
-        // Increases the hit area of the button on mobile.
-        "after:absolute after:-inset-2 md:after:hidden",
-        "peer-data-[size=sm]/menu-button:top-1",
-        "peer-data-[size=default]/menu-button:top-1.5",
-        "peer-data-[size=lg]/menu-button:top-2.5",
-        "group-data-[collapsible=icon]:hidden",
-        showOnHover &&
-          "peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function SidebarMenuBadge({
-  className,
-  ...props
-}: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="sidebar-menu-badge"
-      data-sidebar="menu-badge"
-      className={cn(
-        "text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none",
-        "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
-        "peer-data-[size=sm]/menu-button:top-1",
-        "peer-data-[size=default]/menu-button:top-1.5",
-        "peer-data-[size=lg]/menu-button:top-2.5",
-        "group-data-[collapsible=icon]:hidden",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function SidebarMenuSkeleton({
-  className,
-  showIcon = false,
-  ...props
-}: React.ComponentProps<"div"> & {
-  showIcon?: boolean
-}) {
-  // Random width between 50 to 90%.
-  const width = React.useMemo(() => {
-    return `${Math.floor(Math.random() * 40) + 50}%`
-  }, [])
-
-  return (
-    <div
-      data-slot="sidebar-menu-skeleton"
-      data-sidebar="menu-skeleton"
-      className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
-      {...props}
-    >
-      {showIcon && (
-        <Skeleton
-          className="size-4 rounded-md"
-          data-sidebar="menu-skeleton-icon"
-        />
-      )}
-      <Skeleton
-        className="h-4 max-w-(--skeleton-width) flex-1"
-        data-sidebar="menu-skeleton-text"
-        style={
-          {
-            "--skeleton-width": width,
-          } as React.CSSProperties
-        }
-      />
-    </div>
-  )
-}
-
-function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
-  return (
-    <ul
-      data-slot="sidebar-menu-sub"
-      data-sidebar="menu-sub"
-      className={cn(
-        "border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5",
-        "group-data-[collapsible=icon]:hidden",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function SidebarMenuSubItem({
-  className,
-  ...props
-}: React.ComponentProps<"li">) {
-  return (
-    <li
-      data-slot="sidebar-menu-sub-item"
-      data-sidebar="menu-sub-item"
-      className={cn("group/menu-sub-item relative", className)}
-      {...props}
-    />
-  )
-}
-
-function SidebarMenuSubButton({
-  asChild = false,
-  size = "md",
-  isActive = false,
-  className,
-  ...props
-}: React.ComponentProps<"a"> & {
-  asChild?: boolean
-  size?: "sm" | "md"
-  isActive?: boolean
-}) {
-  const Comp = asChild ? Slot : "a"
-
-  return (
-    <Comp
-      data-slot="sidebar-menu-sub-button"
-      data-sidebar="menu-sub-button"
-      data-size={size}
-      data-active={isActive}
-      className={cn(
-        "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
-        "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
-        size === "sm" && "text-xs",
-        size === "md" && "text-sm",
-        "group-data-[collapsible=icon]:hidden",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-export {
-  Sidebar,
-  SidebarContent,
-  SidebarFooter,
-  SidebarGroup,
-  SidebarGroupAction,
-  SidebarGroupContent,
-  SidebarGroupLabel,
-  SidebarHeader,
-  SidebarInput,
-  SidebarInset,
-  SidebarMenu,
-  SidebarMenuAction,
-  SidebarMenuBadge,
-  SidebarMenuButton,
-  SidebarMenuItem,
-  SidebarMenuSkeleton,
-  SidebarMenuSub,
-  SidebarMenuSubButton,
-  SidebarMenuSubItem,
-  SidebarProvider,
-  SidebarRail,
-  SidebarSeparator,
-  SidebarTrigger,
-  useSidebar,
-}

+ 0 - 13
components/ui/skeleton.tsx

@@ -1,13 +0,0 @@
-import {cn} from "@/lib/utils"
-
-function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
-  return (
-    <div
-      data-slot="skeleton"
-      className={cn("bg-accent animate-pulse rounded-md", className)}
-      {...props}
-    />
-  )
-}
-
-export { Skeleton }

+ 0 - 63
components/ui/slider.tsx

@@ -1,63 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as SliderPrimitive from "@radix-ui/react-slider"
-
-import {cn} from "@/lib/utils"
-
-function Slider({
-  className,
-  defaultValue,
-  value,
-  min = 0,
-  max = 100,
-  ...props
-}: React.ComponentProps<typeof SliderPrimitive.Root>) {
-  const _values = React.useMemo(
-    () =>
-      Array.isArray(value)
-        ? value
-        : Array.isArray(defaultValue)
-          ? defaultValue
-          : [min, max],
-    [value, defaultValue, min, max]
-  )
-
-  return (
-    <SliderPrimitive.Root
-      data-slot="slider"
-      defaultValue={defaultValue}
-      value={value}
-      min={min}
-      max={max}
-      className={cn(
-        "relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
-        className
-      )}
-      {...props}
-    >
-      <SliderPrimitive.Track
-        data-slot="slider-track"
-        className={cn(
-          "bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"
-        )}
-      >
-        <SliderPrimitive.Range
-          data-slot="slider-range"
-          className={cn(
-            "bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
-          )}
-        />
-      </SliderPrimitive.Track>
-      {Array.from({ length: _values.length }, (_, index) => (
-        <SliderPrimitive.Thumb
-          data-slot="slider-thumb"
-          key={index}
-          className="border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
-        />
-      ))}
-    </SliderPrimitive.Root>
-  )
-}
-
-export { Slider }

+ 0 - 25
components/ui/sonner.tsx

@@ -1,25 +0,0 @@
-"use client"
-
-import {useTheme} from "next-themes"
-import {Toaster as Sonner, ToasterProps} from "sonner"
-
-const Toaster = ({ ...props }: ToasterProps) => {
-  const { theme = "system" } = useTheme()
-
-  return (
-    <Sonner
-      theme={theme as ToasterProps["theme"]}
-      className="toaster group"
-      style={
-        {
-          "--normal-bg": "var(--popover)",
-          "--normal-text": "var(--popover-foreground)",
-          "--normal-border": "var(--border)",
-        } as React.CSSProperties
-      }
-      {...props}
-    />
-  )
-}
-
-export { Toaster }

+ 0 - 31
components/ui/switch.tsx

@@ -1,31 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as SwitchPrimitive from "@radix-ui/react-switch"
-
-import {cn} from "@/lib/utils"
-
-function Switch({
-  className,
-  ...props
-}: React.ComponentProps<typeof SwitchPrimitive.Root>) {
-  return (
-    <SwitchPrimitive.Root
-      data-slot="switch"
-      className={cn(
-        "peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
-        className
-      )}
-      {...props}
-    >
-      <SwitchPrimitive.Thumb
-        data-slot="switch-thumb"
-        className={cn(
-          "bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
-        )}
-      />
-    </SwitchPrimitive.Root>
-  )
-}
-
-export { Switch }

+ 0 - 116
components/ui/table.tsx

@@ -1,116 +0,0 @@
-"use client"
-
-import * as React from "react"
-
-import {cn} from "@/lib/utils"
-
-function Table({ className, ...props }: React.ComponentProps<"table">) {
-  return (
-    <div
-      data-slot="table-container"
-      className="relative w-full overflow-x-auto"
-    >
-      <table
-        data-slot="table"
-        className={cn("w-full caption-bottom text-sm", className)}
-        {...props}
-      />
-    </div>
-  )
-}
-
-function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
-  return (
-    <thead
-      data-slot="table-header"
-      className={cn("[&_tr]:border-b", className)}
-      {...props}
-    />
-  )
-}
-
-function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
-  return (
-    <tbody
-      data-slot="table-body"
-      className={cn("[&_tr:last-child]:border-0", className)}
-      {...props}
-    />
-  )
-}
-
-function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
-  return (
-    <tfoot
-      data-slot="table-footer"
-      className={cn(
-        "bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
-  return (
-    <tr
-      data-slot="table-row"
-      className={cn(
-        "hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function TableHead({ className, ...props }: React.ComponentProps<"th">) {
-  return (
-    <th
-      data-slot="table-head"
-      className={cn(
-        "text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function TableCell({ className, ...props }: React.ComponentProps<"td">) {
-  return (
-    <td
-      data-slot="table-cell"
-      className={cn(
-        "p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function TableCaption({
-  className,
-  ...props
-}: React.ComponentProps<"caption">) {
-  return (
-    <caption
-      data-slot="table-caption"
-      className={cn("text-muted-foreground mt-4 text-sm", className)}
-      {...props}
-    />
-  )
-}
-
-export {
-  Table,
-  TableHeader,
-  TableBody,
-  TableFooter,
-  TableHead,
-  TableRow,
-  TableCell,
-  TableCaption,
-}

+ 0 - 66
components/ui/tabs.tsx

@@ -1,66 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as TabsPrimitive from "@radix-ui/react-tabs"
-
-import {cn} from "@/lib/utils"
-
-function Tabs({
-  className,
-  ...props
-}: React.ComponentProps<typeof TabsPrimitive.Root>) {
-  return (
-    <TabsPrimitive.Root
-      data-slot="tabs"
-      className={cn("flex flex-col gap-2", className)}
-      {...props}
-    />
-  )
-}
-
-function TabsList({
-  className,
-  ...props
-}: React.ComponentProps<typeof TabsPrimitive.List>) {
-  return (
-    <TabsPrimitive.List
-      data-slot="tabs-list"
-      className={cn(
-        "bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function TabsTrigger({
-  className,
-  ...props
-}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
-  return (
-    <TabsPrimitive.Trigger
-      data-slot="tabs-trigger"
-      className={cn(
-        "data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-function TabsContent({
-  className,
-  ...props
-}: React.ComponentProps<typeof TabsPrimitive.Content>) {
-  return (
-    <TabsPrimitive.Content
-      data-slot="tabs-content"
-      className={cn("flex-1 outline-none", className)}
-      {...props}
-    />
-  )
-}
-
-export { Tabs, TabsList, TabsTrigger, TabsContent }

+ 0 - 18
components/ui/textarea.tsx

@@ -1,18 +0,0 @@
-import * as React from "react"
-
-import {cn} from "@/lib/utils"
-
-function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
-  return (
-    <textarea
-      data-slot="textarea"
-      className={cn(
-        "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
-        className
-      )}
-      {...props}
-    />
-  )
-}
-
-export { Textarea }

+ 0 - 129
components/ui/toast.tsx

@@ -1,129 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as ToastPrimitives from "@radix-ui/react-toast"
-import {cva, type VariantProps} from "class-variance-authority"
-import {X} from "lucide-react"
-
-import {cn} from "@/lib/utils"
-
-const ToastProvider = ToastPrimitives.Provider
-
-const ToastViewport = React.forwardRef<
-  React.ElementRef<typeof ToastPrimitives.Viewport>,
-  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
->(({ className, ...props }, ref) => (
-  <ToastPrimitives.Viewport
-    ref={ref}
-    className={cn(
-      "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
-      className
-    )}
-    {...props}
-  />
-))
-ToastViewport.displayName = ToastPrimitives.Viewport.displayName
-
-const toastVariants = cva(
-  "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
-  {
-    variants: {
-      variant: {
-        default: "border bg-background text-foreground",
-        destructive:
-          "destructive group border-destructive bg-destructive text-destructive-foreground",
-      },
-    },
-    defaultVariants: {
-      variant: "default",
-    },
-  }
-)
-
-const Toast = React.forwardRef<
-  React.ElementRef<typeof ToastPrimitives.Root>,
-  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
-    VariantProps<typeof toastVariants>
->(({ className, variant, ...props }, ref) => {
-  return (
-    <ToastPrimitives.Root
-      ref={ref}
-      className={cn(toastVariants({ variant }), className)}
-      {...props}
-    />
-  )
-})
-Toast.displayName = ToastPrimitives.Root.displayName
-
-const ToastAction = React.forwardRef<
-  React.ElementRef<typeof ToastPrimitives.Action>,
-  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
->(({ className, ...props }, ref) => (
-  <ToastPrimitives.Action
-    ref={ref}
-    className={cn(
-      "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
-      className
-    )}
-    {...props}
-  />
-))
-ToastAction.displayName = ToastPrimitives.Action.displayName
-
-const ToastClose = React.forwardRef<
-  React.ElementRef<typeof ToastPrimitives.Close>,
-  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
->(({ className, ...props }, ref) => (
-  <ToastPrimitives.Close
-    ref={ref}
-    className={cn(
-      "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
-      className
-    )}
-    toast-close=""
-    {...props}
-  >
-    <X className="h-4 w-4" />
-  </ToastPrimitives.Close>
-))
-ToastClose.displayName = ToastPrimitives.Close.displayName
-
-const ToastTitle = React.forwardRef<
-  React.ElementRef<typeof ToastPrimitives.Title>,
-  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
->(({ className, ...props }, ref) => (
-  <ToastPrimitives.Title
-    ref={ref}
-    className={cn("text-sm font-semibold", className)}
-    {...props}
-  />
-))
-ToastTitle.displayName = ToastPrimitives.Title.displayName
-
-const ToastDescription = React.forwardRef<
-  React.ElementRef<typeof ToastPrimitives.Description>,
-  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
->(({ className, ...props }, ref) => (
-  <ToastPrimitives.Description
-    ref={ref}
-    className={cn("text-sm opacity-90", className)}
-    {...props}
-  />
-))
-ToastDescription.displayName = ToastPrimitives.Description.displayName
-
-type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
-
-type ToastActionElement = React.ReactElement<typeof ToastAction>
-
-export {
-  type ToastProps,
-  type ToastActionElement,
-  ToastProvider,
-  ToastViewport,
-  Toast,
-  ToastTitle,
-  ToastDescription,
-  ToastClose,
-  ToastAction,
-}

+ 0 - 28
components/ui/toaster.tsx

@@ -1,28 +0,0 @@
-"use client"
-
-import {useToast} from "@/hooks/use-toast"
-import {Toast, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport,} from "@/components/ui/toast"
-
-export function Toaster() {
-  const { toasts } = useToast()
-
-  return (
-    <ToastProvider>
-      {toasts.map(function ({ id, title, description, action, ...props }) {
-        return (
-          <Toast key={id} {...props}>
-            <div className="grid gap-1">
-              {title && <ToastTitle>{title}</ToastTitle>}
-              {description && (
-                <ToastDescription>{description}</ToastDescription>
-              )}
-            </div>
-            {action}
-            <ToastClose />
-          </Toast>
-        )
-      })}
-      <ToastViewport />
-    </ToastProvider>
-  )
-}

+ 0 - 73
components/ui/toggle-group.tsx

@@ -1,73 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
-import {type VariantProps} from "class-variance-authority"
-
-import {cn} from "@/lib/utils"
-import {toggleVariants} from "@/components/ui/toggle"
-
-const ToggleGroupContext = React.createContext<
-  VariantProps<typeof toggleVariants>
->({
-  size: "default",
-  variant: "default",
-})
-
-function ToggleGroup({
-  className,
-  variant,
-  size,
-  children,
-  ...props
-}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
-  VariantProps<typeof toggleVariants>) {
-  return (
-    <ToggleGroupPrimitive.Root
-      data-slot="toggle-group"
-      data-variant={variant}
-      data-size={size}
-      className={cn(
-        "group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs",
-        className
-      )}
-      {...props}
-    >
-      <ToggleGroupContext.Provider value={{ variant, size }}>
-        {children}
-      </ToggleGroupContext.Provider>
-    </ToggleGroupPrimitive.Root>
-  )
-}
-
-function ToggleGroupItem({
-  className,
-  children,
-  variant,
-  size,
-  ...props
-}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
-  VariantProps<typeof toggleVariants>) {
-  const context = React.useContext(ToggleGroupContext)
-
-  return (
-    <ToggleGroupPrimitive.Item
-      data-slot="toggle-group-item"
-      data-variant={context.variant || variant}
-      data-size={context.size || size}
-      className={cn(
-        toggleVariants({
-          variant: context.variant || variant,
-          size: context.size || size,
-        }),
-        "min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",
-        className
-      )}
-      {...props}
-    >
-      {children}
-    </ToggleGroupPrimitive.Item>
-  )
-}
-
-export { ToggleGroup, ToggleGroupItem }

+ 0 - 47
components/ui/toggle.tsx

@@ -1,47 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as TogglePrimitive from "@radix-ui/react-toggle"
-import {cva, type VariantProps} from "class-variance-authority"
-
-import {cn} from "@/lib/utils"
-
-const toggleVariants = cva(
-  "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
-  {
-    variants: {
-      variant: {
-        default: "bg-transparent",
-        outline:
-          "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
-      },
-      size: {
-        default: "h-9 px-2 min-w-9",
-        sm: "h-8 px-1.5 min-w-8",
-        lg: "h-10 px-2.5 min-w-10",
-      },
-    },
-    defaultVariants: {
-      variant: "default",
-      size: "default",
-    },
-  }
-)
-
-function Toggle({
-  className,
-  variant,
-  size,
-  ...props
-}: React.ComponentProps<typeof TogglePrimitive.Root> &
-  VariantProps<typeof toggleVariants>) {
-  return (
-    <TogglePrimitive.Root
-      data-slot="toggle"
-      className={cn(toggleVariants({ variant, size, className }))}
-      {...props}
-    />
-  )
-}
-
-export { Toggle, toggleVariants }

+ 0 - 61
components/ui/tooltip.tsx

@@ -1,61 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as TooltipPrimitive from "@radix-ui/react-tooltip"
-
-import {cn} from "@/lib/utils"
-
-function TooltipProvider({
-  delayDuration = 0,
-  ...props
-}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
-  return (
-    <TooltipPrimitive.Provider
-      data-slot="tooltip-provider"
-      delayDuration={delayDuration}
-      {...props}
-    />
-  )
-}
-
-function Tooltip({
-  ...props
-}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
-  return (
-    <TooltipProvider>
-      <TooltipPrimitive.Root data-slot="tooltip" {...props} />
-    </TooltipProvider>
-  )
-}
-
-function TooltipTrigger({
-  ...props
-}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
-  return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
-}
-
-function TooltipContent({
-  className,
-  sideOffset = 0,
-  children,
-  ...props
-}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
-  return (
-    <TooltipPrimitive.Portal>
-      <TooltipPrimitive.Content
-        data-slot="tooltip-content"
-        sideOffset={sideOffset}
-        className={cn(
-          "bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
-          className
-        )}
-        {...props}
-      >
-        {children}
-        <TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
-      </TooltipPrimitive.Content>
-    </TooltipPrimitive.Portal>
-  )
-}
-
-export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }

+ 0 - 19
components/ui/use-mobile.tsx

@@ -1,19 +0,0 @@
-import * as React from "react"
-
-const MOBILE_BREAKPOINT = 768
-
-export function useIsMobile() {
-  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
-
-  React.useEffect(() => {
-    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
-    const onChange = () => {
-      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
-    }
-    mql.addEventListener("change", onChange)
-    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
-    return () => mql.removeEventListener("change", onChange)
-  }, [])
-
-  return !!isMobile
-}

+ 0 - 191
components/ui/use-toast.ts

@@ -1,191 +0,0 @@
-"use client"
-
-// Inspired by react-hot-toast library
-import * as React from "react"
-
-import type {ToastActionElement, ToastProps,} from "@/components/ui/toast"
-
-const TOAST_LIMIT = 1
-const TOAST_REMOVE_DELAY = 1000000
-
-type ToasterToast = ToastProps & {
-  id: string
-  title?: React.ReactNode
-  description?: React.ReactNode
-  action?: ToastActionElement
-}
-
-const actionTypes = {
-  ADD_TOAST: "ADD_TOAST",
-  UPDATE_TOAST: "UPDATE_TOAST",
-  DISMISS_TOAST: "DISMISS_TOAST",
-  REMOVE_TOAST: "REMOVE_TOAST",
-} as const
-
-let count = 0
-
-function genId() {
-  count = (count + 1) % Number.MAX_SAFE_INTEGER
-  return count.toString()
-}
-
-type ActionType = typeof actionTypes
-
-type Action =
-  | {
-      type: ActionType["ADD_TOAST"]
-      toast: ToasterToast
-    }
-  | {
-      type: ActionType["UPDATE_TOAST"]
-      toast: Partial<ToasterToast>
-    }
-  | {
-      type: ActionType["DISMISS_TOAST"]
-      toastId?: ToasterToast["id"]
-    }
-  | {
-      type: ActionType["REMOVE_TOAST"]
-      toastId?: ToasterToast["id"]
-    }
-
-interface State {
-  toasts: ToasterToast[]
-}
-
-const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
-
-const addToRemoveQueue = (toastId: string) => {
-  if (toastTimeouts.has(toastId)) {
-    return
-  }
-
-  const timeout = setTimeout(() => {
-    toastTimeouts.delete(toastId)
-    dispatch({
-      type: "REMOVE_TOAST",
-      toastId: toastId,
-    })
-  }, TOAST_REMOVE_DELAY)
-
-  toastTimeouts.set(toastId, timeout)
-}
-
-export const reducer = (state: State, action: Action): State => {
-  switch (action.type) {
-    case "ADD_TOAST":
-      return {
-        ...state,
-        toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
-      }
-
-    case "UPDATE_TOAST":
-      return {
-        ...state,
-        toasts: state.toasts.map((t) =>
-          t.id === action.toast.id ? { ...t, ...action.toast } : t
-        ),
-      }
-
-    case "DISMISS_TOAST": {
-      const { toastId } = action
-
-      // ! Side effects ! - This could be extracted into a dismissToast() action,
-      // but I'll keep it here for simplicity
-      if (toastId) {
-        addToRemoveQueue(toastId)
-      } else {
-        state.toasts.forEach((toast) => {
-          addToRemoveQueue(toast.id)
-        })
-      }
-
-      return {
-        ...state,
-        toasts: state.toasts.map((t) =>
-          t.id === toastId || toastId === undefined
-            ? {
-                ...t,
-                open: false,
-              }
-            : t
-        ),
-      }
-    }
-    case "REMOVE_TOAST":
-      if (action.toastId === undefined) {
-        return {
-          ...state,
-          toasts: [],
-        }
-      }
-      return {
-        ...state,
-        toasts: state.toasts.filter((t) => t.id !== action.toastId),
-      }
-  }
-}
-
-const listeners: Array<(state: State) => void> = []
-
-let memoryState: State = { toasts: [] }
-
-function dispatch(action: Action) {
-  memoryState = reducer(memoryState, action)
-  listeners.forEach((listener) => {
-    listener(memoryState)
-  })
-}
-
-type Toast = Omit<ToasterToast, "id">
-
-function toast({ ...props }: Toast) {
-  const id = genId()
-
-  const update = (props: ToasterToast) =>
-    dispatch({
-      type: "UPDATE_TOAST",
-      toast: { ...props, id },
-    })
-  const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
-
-  dispatch({
-    type: "ADD_TOAST",
-    toast: {
-      ...props,
-      id,
-      open: true,
-      onOpenChange: (open) => {
-        if (!open) dismiss()
-      },
-    },
-  })
-
-  return {
-    id: id,
-    dismiss,
-    update,
-  }
-}
-
-function useToast() {
-  const [state, setState] = React.useState<State>(memoryState)
-
-  React.useEffect(() => {
-    listeners.push(setState)
-    return () => {
-      const index = listeners.indexOf(setState)
-      if (index > -1) {
-        listeners.splice(index, 1)
-      }
-    }
-  }, [state])
-
-  return {
-    ...state,
-    toast,
-    dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
-  }
-}
-
-export { useToast, toast }

+ 0 - 19
hooks/use-mobile.ts

@@ -1,19 +0,0 @@
-import * as React from "react"
-
-const MOBILE_BREAKPOINT = 768
-
-export function useIsMobile() {
-  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
-
-  React.useEffect(() => {
-    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
-    const onChange = () => {
-      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
-    }
-    mql.addEventListener("change", onChange)
-    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
-    return () => mql.removeEventListener("change", onChange)
-  }, [])
-
-  return !!isMobile
-}

+ 0 - 191
hooks/use-toast.ts

@@ -1,191 +0,0 @@
-"use client"
-
-// Inspired by react-hot-toast library
-import * as React from "react"
-
-import type {ToastActionElement, ToastProps,} from "@/components/ui/toast"
-
-const TOAST_LIMIT = 1
-const TOAST_REMOVE_DELAY = 1000000
-
-type ToasterToast = ToastProps & {
-  id: string
-  title?: React.ReactNode
-  description?: React.ReactNode
-  action?: ToastActionElement
-}
-
-const actionTypes = {
-  ADD_TOAST: "ADD_TOAST",
-  UPDATE_TOAST: "UPDATE_TOAST",
-  DISMISS_TOAST: "DISMISS_TOAST",
-  REMOVE_TOAST: "REMOVE_TOAST",
-} as const
-
-let count = 0
-
-function genId() {
-  count = (count + 1) % Number.MAX_SAFE_INTEGER
-  return count.toString()
-}
-
-type ActionType = typeof actionTypes
-
-type Action =
-  | {
-      type: ActionType["ADD_TOAST"]
-      toast: ToasterToast
-    }
-  | {
-      type: ActionType["UPDATE_TOAST"]
-      toast: Partial<ToasterToast>
-    }
-  | {
-      type: ActionType["DISMISS_TOAST"]
-      toastId?: ToasterToast["id"]
-    }
-  | {
-      type: ActionType["REMOVE_TOAST"]
-      toastId?: ToasterToast["id"]
-    }
-
-interface State {
-  toasts: ToasterToast[]
-}
-
-const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
-
-const addToRemoveQueue = (toastId: string) => {
-  if (toastTimeouts.has(toastId)) {
-    return
-  }
-
-  const timeout = setTimeout(() => {
-    toastTimeouts.delete(toastId)
-    dispatch({
-      type: "REMOVE_TOAST",
-      toastId: toastId,
-    })
-  }, TOAST_REMOVE_DELAY)
-
-  toastTimeouts.set(toastId, timeout)
-}
-
-export const reducer = (state: State, action: Action): State => {
-  switch (action.type) {
-    case "ADD_TOAST":
-      return {
-        ...state,
-        toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
-      }
-
-    case "UPDATE_TOAST":
-      return {
-        ...state,
-        toasts: state.toasts.map((t) =>
-          t.id === action.toast.id ? { ...t, ...action.toast } : t
-        ),
-      }
-
-    case "DISMISS_TOAST": {
-      const { toastId } = action
-
-      // ! Side effects ! - This could be extracted into a dismissToast() action,
-      // but I'll keep it here for simplicity
-      if (toastId) {
-        addToRemoveQueue(toastId)
-      } else {
-        state.toasts.forEach((toast) => {
-          addToRemoveQueue(toast.id)
-        })
-      }
-
-      return {
-        ...state,
-        toasts: state.toasts.map((t) =>
-          t.id === toastId || toastId === undefined
-            ? {
-                ...t,
-                open: false,
-              }
-            : t
-        ),
-      }
-    }
-    case "REMOVE_TOAST":
-      if (action.toastId === undefined) {
-        return {
-          ...state,
-          toasts: [],
-        }
-      }
-      return {
-        ...state,
-        toasts: state.toasts.filter((t) => t.id !== action.toastId),
-      }
-  }
-}
-
-const listeners: Array<(state: State) => void> = []
-
-let memoryState: State = { toasts: [] }
-
-function dispatch(action: Action) {
-  memoryState = reducer(memoryState, action)
-  listeners.forEach((listener) => {
-    listener(memoryState)
-  })
-}
-
-type Toast = Omit<ToasterToast, "id">
-
-function toast({ ...props }: Toast) {
-  const id = genId()
-
-  const update = (props: ToasterToast) =>
-    dispatch({
-      type: "UPDATE_TOAST",
-      toast: { ...props, id },
-    })
-  const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
-
-  dispatch({
-    type: "ADD_TOAST",
-    toast: {
-      ...props,
-      id,
-      open: true,
-      onOpenChange: (open) => {
-        if (!open) dismiss()
-      },
-    },
-  })
-
-  return {
-    id: id,
-    dismiss,
-    update,
-  }
-}
-
-function useToast() {
-  const [state, setState] = React.useState<State>(memoryState)
-
-  React.useEffect(() => {
-    listeners.push(setState)
-    return () => {
-      const index = listeners.indexOf(setState)
-      if (index > -1) {
-        listeners.splice(index, 1)
-      }
-    }
-  }, [state])
-
-  return {
-    ...state,
-    toast,
-    dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
-  }
-}
-
-export { useToast, toast }

+ 0 - 6
lib/utils.ts

@@ -1,6 +0,0 @@
-import {type ClassValue, clsx} from "clsx"
-import {twMerge} from "tailwind-merge"
-
-export function cn(...inputs: ClassValue[]) {
-  return twMerge(clsx(inputs))
-}

+ 77 - 530
package-lock.json

@@ -15,14 +15,17 @@
         "@fortawesome/fontawesome-svg-core": "^7.0.0",
         "@fortawesome/free-solid-svg-icons": "^7.0.0",
         "@fortawesome/react-fontawesome": "^0.2.3",
-        "@radix-ui/react-tabs": "^1.1.12",
         "@sbzen/re-cron": "^2.0.7",
         "@uiw/react-md-editor": "^4.0.8",
         "antd": "^5.26.7",
         "chart.js": "^4.5.0",
+        "class-variance-authority": "^0.7.1",
         "clsx": "^2.1.1",
         "cookies-next": "^6.1.0",
+        "echarts": "^5.0.0",
+        "echarts-for-react": "^3.0.2",
         "jsencrypt": "^3.3.2",
+        "lucide-react": "^0.537.0",
         "next": "15.4.6",
         "react": "19.1.0",
         "react-chartjs-2": "^5.3.0",
@@ -30,7 +33,6 @@
         "react-d3-speedometer": "^3.1.1",
         "react-dom": "19.1.0",
         "react-spinners": "^0.17.0",
-        "recharts": "^3.1.2",
         "sass": "^1.90.0",
         "tailwind-merge": "^3.3.1"
       },
@@ -2104,294 +2106,6 @@
         "node": ">=0.10"
       }
     },
-    "node_modules/@radix-ui/primitive": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.1.2.tgz",
-      "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==",
-      "license": "MIT"
-    },
-    "node_modules/@radix-ui/react-collection": {
-      "version": "1.1.7",
-      "resolved": "https://registry.npmmirror.com/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
-      "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
-      "license": "MIT",
-      "dependencies": {
-        "@radix-ui/react-compose-refs": "1.1.2",
-        "@radix-ui/react-context": "1.1.2",
-        "@radix-ui/react-primitive": "2.1.3",
-        "@radix-ui/react-slot": "1.2.3"
-      },
-      "peerDependencies": {
-        "@types/react": "*",
-        "@types/react-dom": "*",
-        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
-        "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        },
-        "@types/react-dom": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@radix-ui/react-compose-refs": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
-      "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
-      "license": "MIT",
-      "peerDependencies": {
-        "@types/react": "*",
-        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@radix-ui/react-context": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.1.2.tgz",
-      "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
-      "license": "MIT",
-      "peerDependencies": {
-        "@types/react": "*",
-        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@radix-ui/react-direction": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmmirror.com/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
-      "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
-      "license": "MIT",
-      "peerDependencies": {
-        "@types/react": "*",
-        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@radix-ui/react-id": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmmirror.com/@radix-ui/react-id/-/react-id-1.1.1.tgz",
-      "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
-      "license": "MIT",
-      "dependencies": {
-        "@radix-ui/react-use-layout-effect": "1.1.1"
-      },
-      "peerDependencies": {
-        "@types/react": "*",
-        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@radix-ui/react-presence": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.1.4.tgz",
-      "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==",
-      "license": "MIT",
-      "dependencies": {
-        "@radix-ui/react-compose-refs": "1.1.2",
-        "@radix-ui/react-use-layout-effect": "1.1.1"
-      },
-      "peerDependencies": {
-        "@types/react": "*",
-        "@types/react-dom": "*",
-        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
-        "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        },
-        "@types/react-dom": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@radix-ui/react-primitive": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
-      "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
-      "license": "MIT",
-      "dependencies": {
-        "@radix-ui/react-slot": "1.2.3"
-      },
-      "peerDependencies": {
-        "@types/react": "*",
-        "@types/react-dom": "*",
-        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
-        "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        },
-        "@types/react-dom": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@radix-ui/react-roving-focus": {
-      "version": "1.1.10",
-      "resolved": "https://registry.npmmirror.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.10.tgz",
-      "integrity": "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==",
-      "license": "MIT",
-      "dependencies": {
-        "@radix-ui/primitive": "1.1.2",
-        "@radix-ui/react-collection": "1.1.7",
-        "@radix-ui/react-compose-refs": "1.1.2",
-        "@radix-ui/react-context": "1.1.2",
-        "@radix-ui/react-direction": "1.1.1",
-        "@radix-ui/react-id": "1.1.1",
-        "@radix-ui/react-primitive": "2.1.3",
-        "@radix-ui/react-use-callback-ref": "1.1.1",
-        "@radix-ui/react-use-controllable-state": "1.2.2"
-      },
-      "peerDependencies": {
-        "@types/react": "*",
-        "@types/react-dom": "*",
-        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
-        "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        },
-        "@types/react-dom": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@radix-ui/react-slot": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
-      "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
-      "license": "MIT",
-      "dependencies": {
-        "@radix-ui/react-compose-refs": "1.1.2"
-      },
-      "peerDependencies": {
-        "@types/react": "*",
-        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@radix-ui/react-tabs": {
-      "version": "1.1.12",
-      "resolved": "https://registry.npmmirror.com/@radix-ui/react-tabs/-/react-tabs-1.1.12.tgz",
-      "integrity": "sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw==",
-      "license": "MIT",
-      "dependencies": {
-        "@radix-ui/primitive": "1.1.2",
-        "@radix-ui/react-context": "1.1.2",
-        "@radix-ui/react-direction": "1.1.1",
-        "@radix-ui/react-id": "1.1.1",
-        "@radix-ui/react-presence": "1.1.4",
-        "@radix-ui/react-primitive": "2.1.3",
-        "@radix-ui/react-roving-focus": "1.1.10",
-        "@radix-ui/react-use-controllable-state": "1.2.2"
-      },
-      "peerDependencies": {
-        "@types/react": "*",
-        "@types/react-dom": "*",
-        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
-        "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        },
-        "@types/react-dom": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@radix-ui/react-use-callback-ref": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
-      "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
-      "license": "MIT",
-      "peerDependencies": {
-        "@types/react": "*",
-        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@radix-ui/react-use-controllable-state": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
-      "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
-      "license": "MIT",
-      "dependencies": {
-        "@radix-ui/react-use-effect-event": "0.0.2",
-        "@radix-ui/react-use-layout-effect": "1.1.1"
-      },
-      "peerDependencies": {
-        "@types/react": "*",
-        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@radix-ui/react-use-effect-event": {
-      "version": "0.0.2",
-      "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
-      "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
-      "license": "MIT",
-      "dependencies": {
-        "@radix-ui/react-use-layout-effect": "1.1.1"
-      },
-      "peerDependencies": {
-        "@types/react": "*",
-        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@radix-ui/react-use-layout-effect": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
-      "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
-      "license": "MIT",
-      "peerDependencies": {
-        "@types/react": "*",
-        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
     "node_modules/@rc-component/async-validator": {
       "version": "5.0.4",
       "resolved": "https://registry.npmmirror.com/@rc-component/async-validator/-/async-validator-5.0.4.tgz",
@@ -2541,32 +2255,6 @@
         "react-dom": ">=16.9.0"
       }
     },
-    "node_modules/@reduxjs/toolkit": {
-      "version": "2.8.2",
-      "resolved": "https://registry.npmmirror.com/@reduxjs/toolkit/-/toolkit-2.8.2.tgz",
-      "integrity": "sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A==",
-      "license": "MIT",
-      "dependencies": {
-        "@standard-schema/spec": "^1.0.0",
-        "@standard-schema/utils": "^0.3.0",
-        "immer": "^10.0.3",
-        "redux": "^5.0.1",
-        "redux-thunk": "^3.1.0",
-        "reselect": "^5.1.0"
-      },
-      "peerDependencies": {
-        "react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
-        "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
-      },
-      "peerDependenciesMeta": {
-        "react": {
-          "optional": true
-        },
-        "react-redux": {
-          "optional": true
-        }
-      }
-    },
     "node_modules/@rtsao/scc": {
       "version": "1.1.0",
       "resolved": "https://registry.npmmirror.com/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -2600,18 +2288,6 @@
         "react-dom": "*"
       }
     },
-    "node_modules/@standard-schema/spec": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.0.0.tgz",
-      "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
-      "license": "MIT"
-    },
-    "node_modules/@standard-schema/utils": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmmirror.com/@standard-schema/utils/-/utils-0.3.0.tgz",
-      "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
-      "license": "MIT"
-    },
     "node_modules/@swc/helpers": {
       "version": "0.5.15",
       "resolved": "https://registry.npmmirror.com/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -2908,69 +2584,6 @@
         "tslib": "^2.4.0"
       }
     },
-    "node_modules/@types/d3-array": {
-      "version": "3.2.1",
-      "resolved": "https://registry.npmmirror.com/@types/d3-array/-/d3-array-3.2.1.tgz",
-      "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==",
-      "license": "MIT"
-    },
-    "node_modules/@types/d3-color": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmmirror.com/@types/d3-color/-/d3-color-3.1.3.tgz",
-      "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
-      "license": "MIT"
-    },
-    "node_modules/@types/d3-ease": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmmirror.com/@types/d3-ease/-/d3-ease-3.0.2.tgz",
-      "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
-      "license": "MIT"
-    },
-    "node_modules/@types/d3-interpolate": {
-      "version": "3.0.4",
-      "resolved": "https://registry.npmmirror.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
-      "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/d3-color": "*"
-      }
-    },
-    "node_modules/@types/d3-path": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmmirror.com/@types/d3-path/-/d3-path-3.1.1.tgz",
-      "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
-      "license": "MIT"
-    },
-    "node_modules/@types/d3-scale": {
-      "version": "4.0.9",
-      "resolved": "https://registry.npmmirror.com/@types/d3-scale/-/d3-scale-4.0.9.tgz",
-      "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/d3-time": "*"
-      }
-    },
-    "node_modules/@types/d3-shape": {
-      "version": "3.1.7",
-      "resolved": "https://registry.npmmirror.com/@types/d3-shape/-/d3-shape-3.1.7.tgz",
-      "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/d3-path": "*"
-      }
-    },
-    "node_modules/@types/d3-time": {
-      "version": "3.0.4",
-      "resolved": "https://registry.npmmirror.com/@types/d3-time/-/d3-time-3.0.4.tgz",
-      "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
-      "license": "MIT"
-    },
-    "node_modules/@types/d3-timer": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmmirror.com/@types/d3-timer/-/d3-timer-3.0.2.tgz",
-      "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
-      "license": "MIT"
-    },
     "node_modules/@types/debug": {
       "version": "4.1.12",
       "resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz",
@@ -3068,7 +2681,7 @@
       "version": "19.1.7",
       "resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-19.1.7.tgz",
       "integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==",
-      "devOptional": true,
+      "dev": true,
       "license": "MIT",
       "peerDependencies": {
         "@types/react": "^19.0.0"
@@ -3080,12 +2693,6 @@
       "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
       "license": "MIT"
     },
-    "node_modules/@types/use-sync-external-store": {
-      "version": "0.0.6",
-      "resolved": "https://registry.npmmirror.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
-      "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
-      "license": "MIT"
-    },
     "node_modules/@typescript-eslint/eslint-plugin": {
       "version": "8.39.0",
       "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.0.tgz",
@@ -4349,6 +3956,18 @@
         "node": ">=18"
       }
     },
+    "node_modules/class-variance-authority": {
+      "version": "0.7.1",
+      "resolved": "https://registry.npmmirror.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+      "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "clsx": "^2.1.1"
+      },
+      "funding": {
+        "url": "https://polar.sh/cva"
+      }
+    },
     "node_modules/classnames": {
       "version": "2.5.1",
       "resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz",
@@ -4776,12 +4395,6 @@
         }
       }
     },
-    "node_modules/decimal.js-light": {
-      "version": "2.5.1",
-      "resolved": "https://registry.npmmirror.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
-      "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
-      "license": "MIT"
-    },
     "node_modules/decode-named-character-reference": {
       "version": "1.2.0",
       "resolved": "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz",
@@ -4911,6 +4524,36 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/echarts": {
+      "version": "5.6.0",
+      "resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.6.0.tgz",
+      "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "tslib": "2.3.0",
+        "zrender": "5.6.1"
+      }
+    },
+    "node_modules/echarts-for-react": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmmirror.com/echarts-for-react/-/echarts-for-react-3.0.2.tgz",
+      "integrity": "sha512-DRwIiTzx8JfwPOVgGttDytBqdp5VzCSyMRIxubgU/g2n9y3VLUmF2FK7Icmg/sNVkv4+rktmrLN9w22U2yy3fA==",
+      "license": "MIT",
+      "dependencies": {
+        "fast-deep-equal": "^3.1.3",
+        "size-sensor": "^1.0.1"
+      },
+      "peerDependencies": {
+        "echarts": "^3.0.0 || ^4.0.0 || ^5.0.0",
+        "react": "^15.0.0 || >=16.0.0"
+      }
+    },
+    "node_modules/echarts/node_modules/tslib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
+      "license": "0BSD"
+    },
     "node_modules/emoji-regex": {
       "version": "9.2.2",
       "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -5136,16 +4779,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/es-toolkit": {
-      "version": "1.39.8",
-      "resolved": "https://registry.npmmirror.com/es-toolkit/-/es-toolkit-1.39.8.tgz",
-      "integrity": "sha512-A8QO9TfF+rltS8BXpdu8OS+rpGgEdnRhqIVxO/ZmNvnXBYgOdSsxukT55ELyP94gZIntWJ+Li9QRrT2u1Kitpg==",
-      "license": "MIT",
-      "workspaces": [
-        "docs",
-        "benchmarks"
-      ]
-    },
     "node_modules/escape-string-regexp": {
       "version": "4.0.0",
       "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -5594,12 +5227,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/eventemitter3": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.1.tgz",
-      "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
-      "license": "MIT"
-    },
     "node_modules/extend": {
       "version": "3.0.2",
       "resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz",
@@ -5610,7 +5237,6 @@
       "version": "3.1.3",
       "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
       "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/fast-glob": {
@@ -6326,16 +5952,6 @@
         "node": ">= 4"
       }
     },
-    "node_modules/immer": {
-      "version": "10.1.1",
-      "resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz",
-      "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
-      "license": "MIT",
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/immer"
-      }
-    },
     "node_modules/immutable": {
       "version": "5.1.3",
       "resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.3.tgz",
@@ -7351,6 +6967,15 @@
         "loose-envify": "cli.js"
       }
     },
+    "node_modules/lucide-react": {
+      "version": "0.537.0",
+      "resolved": "https://registry.npmmirror.com/lucide-react/-/lucide-react-0.537.0.tgz",
+      "integrity": "sha512-VxWsdxBGeFnlC+HwMg/l08HptN4YRU9o/lRog156jOmRxI1ERKqN+rJiNY/mPcKAdWdM0UbyO8ft1o0jq69SSQ==",
+      "license": "ISC",
+      "peerDependencies": {
+        "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+      }
+    },
     "node_modules/magic-string": {
       "version": "0.30.17",
       "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz",
@@ -9612,29 +9237,6 @@
         "react": ">=18"
       }
     },
-    "node_modules/react-redux": {
-      "version": "9.2.0",
-      "resolved": "https://registry.npmmirror.com/react-redux/-/react-redux-9.2.0.tgz",
-      "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/use-sync-external-store": "^0.0.6",
-        "use-sync-external-store": "^1.4.0"
-      },
-      "peerDependencies": {
-        "@types/react": "^18.2.25 || ^19",
-        "react": "^18.0 || ^19",
-        "redux": "^5.0.0"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        },
-        "redux": {
-          "optional": true
-        }
-      }
-    },
     "node_modules/react-spinners": {
       "version": "0.17.0",
       "resolved": "https://registry.npmmirror.com/react-spinners/-/react-spinners-0.17.0.tgz",
@@ -9667,48 +9269,6 @@
         "url": "https://paulmillr.com/funding/"
       }
     },
-    "node_modules/recharts": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmmirror.com/recharts/-/recharts-3.1.2.tgz",
-      "integrity": "sha512-vhNbYwaxNbk/IATK0Ki29k3qvTkGqwvCgyQAQ9MavvvBwjvKnMTswdbklJpcOAoMPN/qxF3Lyqob0zO+ZXkZ4g==",
-      "license": "MIT",
-      "dependencies": {
-        "@reduxjs/toolkit": "1.x.x || 2.x.x",
-        "clsx": "^2.1.1",
-        "decimal.js-light": "^2.5.1",
-        "es-toolkit": "^1.39.3",
-        "eventemitter3": "^5.0.1",
-        "immer": "^10.1.1",
-        "react-redux": "8.x.x || 9.x.x",
-        "reselect": "5.1.1",
-        "tiny-invariant": "^1.3.3",
-        "use-sync-external-store": "^1.2.2",
-        "victory-vendor": "^37.0.2"
-      },
-      "engines": {
-        "node": ">=18"
-      },
-      "peerDependencies": {
-        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
-        "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
-        "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
-      }
-    },
-    "node_modules/redux": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmmirror.com/redux/-/redux-5.0.1.tgz",
-      "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
-      "license": "MIT"
-    },
-    "node_modules/redux-thunk": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmmirror.com/redux-thunk/-/redux-thunk-3.1.0.tgz",
-      "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
-      "license": "MIT",
-      "peerDependencies": {
-        "redux": "^5.0.0"
-      }
-    },
     "node_modules/reflect.getprototypeof": {
       "version": "1.0.10",
       "resolved": "https://registry.npmmirror.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -10065,12 +9625,6 @@
         "url": "https://opencollective.com/unified"
       }
     },
-    "node_modules/reselect": {
-      "version": "5.1.1",
-      "resolved": "https://registry.npmmirror.com/reselect/-/reselect-5.1.1.tgz",
-      "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
-      "license": "MIT"
-    },
     "node_modules/resize-observer-polyfill": {
       "version": "1.5.1",
       "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
@@ -10470,6 +10024,12 @@
         "is-arrayish": "^0.3.1"
       }
     },
+    "node_modules/size-sensor": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/size-sensor/-/size-sensor-1.0.2.tgz",
+      "integrity": "sha512-2NCmWxY7A9pYKGXNBfteo4hy14gWu47rg5692peVMst6lQLPKrVjhY+UTEsPI5ceFRJSl3gVgMYaUi/hKuaiKw==",
+      "license": "ISC"
+    },
     "node_modules/source-map": {
       "version": "0.5.7",
       "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.5.7.tgz",
@@ -10814,12 +10374,6 @@
         "node": ">=12.22"
       }
     },
-    "node_modules/tiny-invariant": {
-      "version": "1.3.3",
-      "resolved": "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
-      "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
-      "license": "MIT"
-    },
     "node_modules/tinycolor2": {
       "version": "1.6.0",
       "resolved": "https://registry.npmmirror.com/tinycolor2/-/tinycolor2-1.6.0.tgz",
@@ -11267,28 +10821,6 @@
         "url": "https://opencollective.com/unified"
       }
     },
-    "node_modules/victory-vendor": {
-      "version": "37.3.6",
-      "resolved": "https://registry.npmmirror.com/victory-vendor/-/victory-vendor-37.3.6.tgz",
-      "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==",
-      "license": "MIT AND ISC",
-      "dependencies": {
-        "@types/d3-array": "^3.0.3",
-        "@types/d3-ease": "^3.0.0",
-        "@types/d3-interpolate": "^3.0.1",
-        "@types/d3-scale": "^4.0.2",
-        "@types/d3-shape": "^3.1.0",
-        "@types/d3-time": "^3.0.0",
-        "@types/d3-timer": "^3.0.0",
-        "d3-array": "^3.1.6",
-        "d3-ease": "^3.0.1",
-        "d3-interpolate": "^3.0.1",
-        "d3-scale": "^4.0.2",
-        "d3-shape": "^3.1.0",
-        "d3-time": "^3.0.0",
-        "d3-timer": "^3.0.1"
-      }
-    },
     "node_modules/warning": {
       "version": "4.0.3",
       "resolved": "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz",
@@ -11455,6 +10987,21 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/zrender": {
+      "version": "5.6.1",
+      "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz",
+      "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "tslib": "2.3.0"
+      }
+    },
+    "node_modules/zrender/node_modules/tslib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
+      "license": "0BSD"
+    },
     "node_modules/zwitch": {
       "version": "2.0.4",
       "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz",

+ 6 - 4
package.json

@@ -3,8 +3,8 @@
   "version": "0.1.0",
   "private": true,
   "scripts": {
-    "dev": "next dev",
-    "build": "next build",
+    "dev": "next dev --turbopack",
+    "build": "next build --turbopack",
     "start": "next start",
     "lint": "next lint"
   },
@@ -16,14 +16,17 @@
     "@fortawesome/fontawesome-svg-core": "^7.0.0",
     "@fortawesome/free-solid-svg-icons": "^7.0.0",
     "@fortawesome/react-fontawesome": "^0.2.3",
-    "@radix-ui/react-tabs": "^1.1.12",
     "@sbzen/re-cron": "^2.0.7",
     "@uiw/react-md-editor": "^4.0.8",
     "antd": "^5.26.7",
     "chart.js": "^4.5.0",
+    "class-variance-authority": "^0.7.1",
     "clsx": "^2.1.1",
     "cookies-next": "^6.1.0",
+    "echarts": "^5.0.0",
+    "echarts-for-react": "^3.0.2",
     "jsencrypt": "^3.3.2",
+    "lucide-react": "^0.537.0",
     "next": "15.4.6",
     "react": "19.1.0",
     "react-chartjs-2": "^5.3.0",
@@ -31,7 +34,6 @@
     "react-d3-speedometer": "^3.1.1",
     "react-dom": "19.1.0",
     "react-spinners": "^0.17.0",
-    "recharts": "^3.1.2",
     "sass": "^1.90.0",
     "tailwind-merge": "^3.3.1"
   },