Commit ef2b315a authored by Andy-bubu's avatar Andy-bubu

feat: unify audit UI with Xinyuan HeroUI design

parent c0e5d603
......@@ -10,6 +10,7 @@
/.umi-test
/dist
/.mfsu
/.playwright-cli
.swc
.pnpm-store
.DS_Store
......@@ -82,6 +82,9 @@ export default defineConfig({
}
},
esbuildMinifyIIFE: true,
jsMinifierOptions: {
target: ['chrome80', 'es2020'],
},
define: {
STREAMS_API_URL: '/api',
STREAMS_PARSER_URL: '/parser',
......
# 监察稽核管理 UI/UX 全局审计
审计日期:2026-08-14
审计基线:鑫元业务应用设计系统 1.2、HeroUI 3、HeroUI Pro
## 1. 审计范围
- 运行路由:登录、稽核项目、稽核报告、历史报告详情、外部填报。
- 全局结构:应用 Shell、Sidebar、Navbar、应用切换器、主题与用户菜单。
- 业务组件:项目切换、搜索、表格、状态、分页、表单、引用选择、文件上传、确认弹窗与反馈。
- 视口:1440x900、1280x720、768x1024、390x844。
- 数据状态:真实账号登录后的已填充列表、空态、加载态、错误态和弹层打开态。
- 非活动代码:`src/pages/process_monitor`、`src/pages/RecordDetailDrawer` 和 `src/components/record-workbench` 当前未进入 `.umirc.ts` 路由,不属于本次运行时页面,但仍纳入静态兼容性检查。
## 2. 技术现状
- React 18、Umi Max 4、Tailwind CSS 4。
- UI 主要由 Catalyst / Headless UI、Heroicons、原生表格与局部原生表单控件组成。
- 未安装 HeroUI / HeroUI Pro,缺少统一 Token、主题、Shell、DataGrid 和共享表单适配层。
- 应用基路径为 `/audit_manager_new/`,认证重定向和应用内导航必须保持 base-aware。
## 3. 关键审计结论
### 登录
- 使用横向产品字标,未使用批准的 36px 鑫元花形 Logo。
- 卡片边界和品牌区层级不明确,系统标题与站点副标题存在身份分裂。
- 输入框、验证码和按钮高度不统一;已有值和浏览器自动填充态未使用鑫元浅红表面。
- 登录主按钮仍为蓝色。
### Shell 与应用切换
- Sidebar 是固定白色分栏,缺少 HeroUI Pro offcanvas 折叠、透明 Navbar 和全局主题切换。
- 用户区把“退出登录”作为常驻导航项,未使用可点击用户菜单。
- Navbar 不拥有当前路由标题,内容区重复渲染页面大标题和说明。
- 应用切换器是接近全屏的渐变展示面板,包含应用数量、权限标签、外链装饰和重复标题,不符合批准的紧凑三列应用列表。
### 稽核项目
- 项目选择使用带统计数字的页面私有按钮组,轨道横向延伸且层级噪音较高。
- 搜索和操作区缺少统一筛选 Card;普通文字按钮含装饰图标。
- 旧表格无 20px DataGrid 表面、列宽契约、稳定截断、共享 Tooltip、分页和空态插图。
- 控制点创建弹窗偏窄,标题下存在通用说明;控制措施仍为原生/Catalyst Select。
- 成功反馈占据页面流,会推挤内容。
### 稽核报告与详情
- 多个旧表格按项目纵向堆叠,表头、状态、备注输入与操作列缺少统一密度和宽表滚动策略。
- 页面级“进行中 / 历史报告”使用手写下划线 Tab;统计区不是标准 KPI/紧凑摘要模块。
- 文件选择仍通过隐藏原生 input 和页面按钮驱动,缺少 HeroUI Pro DropZone。
- 历史列表没有共享 Pagination;报告操作按钮混用装饰图标。
- 删除、提交和重置确认未统一到鑫元 Modal / AlertDialog 合同。
### 颜色、图标与暗色主题
- 源码中存在蓝、sky、rose、pink、渐变和页面私有品牌色;主色未统一为 `#9E1B1B`。
- 同时存在 Heroicons 和局部自绘图标,缺少 Ant Design Icons 适配层。
- 没有根级暗色主题,Portal 中的 Dropdown、Popover、Modal、Tooltip 和上传面板无法保证一致暗色表面。
- 当前活动路由未渲染数据图表;图表规范作为后续功能的强制合同写入设计文档。
## 4. 迁移边界
本次只替换视觉结构和交互组件,不改变:
- API URL、请求方法、请求体和响应解析;
- Umi 路由、应用基路径和认证协议;
- 权限检查、状态机、报告快照、提交与归档逻辑;
- 表单字段、校验条件、引用 ID 写入规则和上传接口;
- 业务回调、保存时机、筛选语义和数据计算。
## 5. 验收门槛
- 登录、Shell、应用切换器、筛选、Tab、DataGrid、Pagination、Modal、上传和反馈均使用共享鑫元模式。
- 仅保留批准的鑫元红/白 Logo,应用显示名统一为“监察稽核管理”。
- 关闭态与打开态控件均通过浅色/暗色和移动端检查。
- 无页面级横向滚动、文字重叠、空按钮、重复空态或 Portal 白色孤岛。
- 生产构建、`git diff --check`、本地 HeroUI Pro 子路径导入验证通过。
# 监察稽核管理鑫元设计规范
本文件是本项目后续新增页面和功能的 UI 实现合同。业务功能开发必须复用共享组件和 Token,不得新增第二套局部样式体系。
## 1. 品牌与技术基线
- 应用显示名:`监察稽核管理`。登录、Sidebar、Navbar、应用切换当前项和浏览器标题必须一致。
- 主色:`#9E1B1B`。选中、焦点、Checkbox、日期、Pagination 和信息态统一使用品牌 Token。
- 浅色 Logo 使用 `src/assets/identity/xy-logo-red.png`;暗色 Logo 使用 `xy-logo-white.png`。不得滤镜、反色、加底板或替换为 RSM 标识。
- 基础组件使用 `@heroui/react`,高级导航、DataGrid、DropZone、ActionBar 和 EmptyState 使用本地镜像 `@heroui-pro/react`。
- 图标只从 `src/components/AppIcons.jsx` 导出,底层统一使用 Ant Design Icons。
- 样式导入顺序固定为 Tailwind、HeroUI、HeroUI Pro、鑫元 Token、项目页面样式。
## 2. Token 与尺寸
- 页面画布:浅色 `#F5F5F5`,暗色 `#18181B`。
- 页面左右 inset:桌面 68px,移动端 72px;真实滚动容器 `.rc-app-page` 直接持有该边距。
- 工作台模块和 KPI Card:24px 圆角;DataGrid:20px;普通控件:8px;Popover:12px;Modal:24px。
- 页面标题:20px/600/28px;模块标题:16px/600;普通正文:14px/400。
- DataGrid:表头 12px/500,正文 14px/400,主字段 14px/550,次字段 13px/400,Chip 12px/500。
- 常规按钮:桌面 32px、触屏 36px、24px 圆角、14px/500。主次按钮尺寸完全一致。
- 字距统一为 0,不使用随视口缩放的字号、重阴影、装饰渐变或大面积单色板。
## 3. Shell、登录与应用切换
- HeroUI Pro Sidebar 宽 240px,`collapsible="offcanvas"`,收起后宽度为 0,不保留图标轨道。
- Navbar 高 64px、透明无边框;使用 HeroUI Pro 默认样式的 filled `Sidebar.Trigger` 图标。
- Sidebar 底部用户行整体可点击,菜单内提供退出登录并复用现有 auth service。
- 主题切换位于 Navbar 最右侧,作用于文档根节点并保存到 `xinyuan_color_theme`。
- 登录采用居中认证 Card。花形 Logo 约 36px,标题约 32px/600;不重复显示“登录”标题。
- 账号、密码、验证码可见高度统一为 40px,字段组可见间距 24px,提交按钮上方保留 24px。
- 登录字段有值或自动填充时使用 `#fbf1f1`,Hover 使用 `#faeaea`,Focus 使用鑫元红描边和浅红焦点环。
- 应用切换器固定为居中的紧凑三列列表,标题“应用切换”、副标题“选择你可访问的应用”;最多一个“当前”标签,不显示数量、权限标签、外链图标或统计内容。
## 4. 页面、筛选与 Tab
- Navbar 是唯一可见页面标题;内容区只保留 `sr-only` 标题。
- 页面顺序:页面级 Tabs、筛选 Card、透明列表标题/操作行、DataGrid、Pagination。
- 实时筛选不显示“查询”按钮;首行最多三个字段,右侧只允许重置图标和文字型展开/收起。
- Input、Select、远程引用、多选和 DatePicker 使用共享 HeroUI 适配器,关闭态与打开态必须一致。
- 页面主数据集切换使用 `filter-tabs-wrap`;权限过滤后只有一个选项时隐藏整个轨道。
- 模块内部视角/模式/范围切换使用 `rc-segmented-control`,不得与页面级 Tabs 混用。
- 常规文字按钮保持纯文本;图标只用于真正的 icon-only 操作,并必须有 Tooltip 与 `aria-label`。
## 5. DataGrid、分页与反馈
- 业务列表使用 HeroUI Pro DataGrid。表格链路始终 `width: 100%`,仅在真实最小列宽超过视口时内部横向滚动。
- 横向滚动时固定首个业务列和操作列;不滚动时不固定。
- 操作按钮 32px 正圆、16px Ant 图标;1-3 个操作直接展示,4 个及以上保留两个高频操作,其余进入更多菜单。
- 状态和标签为单行紧凑 Chip,12px/500;不使用蓝、青、玫红或页面私有方角标签。
- 多选动作仅显示在当前视口底部居中的 ActionBar,右侧使用关闭图标清除选择。
- Pagination 提供 10/20/50 页容量,当前页使用圆形品牌描边和品牌文字,距离 DataGrid 约 8px。
- 成功反馈使用顶部居中的 3-4 秒 Toast,不得用页面流 Alert 推挤筛选或表格。
## 6. 表单、上传与弹窗
- Label 14px/500;Input、Textarea、Select 和引用控件只允许一个可见边框与一个字段阴影。
- 可见下拉禁止原生 `<select>`;日期/时间使用共享 HeroUI DatePicker,日期时间不得丢失时间编辑。
- 文件选择使用 HeroUI Pro DropZone,保留原 accept、单/多文件、disabled 和上传回调协议。
- Modal 居中显示,24px 圆角;标准内边距 28px,确认弹窗 24px;遮罩约 50% 黑并带 12px blur。
- 普通 CRUD 使用 `lg` 至 `3xl`,详情、上传、批量编辑和导入预览优先 `4xl`。
- Footer 顺序为取消在左、主操作在右;最终删除确认才使用 solid danger。
- 常规成功结果使用 Toast;需要用户处理的错误和警告可保留在对应上下文。
## 7. 看板与图表
- KPI 网格移动端 1 列、中屏 2 列;桌面 4 张为 4 列、5 张为 3+2、6 张为 3+3、8 张为 4+4。
- KPI Card 使用不透明中性表面、24px 圆角、细边框与共享轻阴影;不得使用渐变或每卡异色背景。
- 密集 KPI 图标面为 48px、16px 圆角、20px Ant 图标,容器与 SVG 中心偏差不得超过 0.5px。
- ECharts 必须从共享主题 Hook 读取浅色/暗色文字、网格、系列和 Tooltip Token。
- 图表 Tooltip 使用根主题 overlay surface,不能成为白色孤岛,也不能遮挡关键数据。
## 8. 响应式与交付检查
- 必测视口:1440x900、1280x720、768x1024、390x844。
- 必测状态:Sidebar 开/关、移动 offcanvas、用户菜单、应用切换、登录 filled/autofill、全部 Popover、选择态、空态、Modal 和暗色 Portal。
- 正式交付前运行生产构建、鑫元审计脚本、`git diff --check`,并确认浏览器控制台无新增错误。
# UI 架构合同
## 1. 直接引用原则
- 页面和新建共享组件必须直接从 `@heroui/react`、`@heroui-pro/react` 或已验证的 Pro 子路径导入。
- 禁止在 `catalyst-ui-kit`、旧 Button/Input/Checkbox/Dialog/Table/Select/DatePicker/Pagination/Upload 内嵌 HeroUI。
- 禁止继续扩展或修改 `src/catalyst-ui-kit`。新功能不得引用旧 UI 组件。
- 禁止创建复刻旧 props、DOM event、class hook 或 DOM 结构的兼容包装层。
- 一个功能面只能使用一套 UI 交互模型。允许的共享组件必须是业务中立、HeroUI-native 的复合组件,并暴露当前 HeroUI selection/value/event 合同。
## 2. 标准实现
- **DataGrid**:使用 HeroUI Pro `DataGrid` 原生列、排序、固定列、`selectedKeys`、`onSelectionChange`、`selectionMode` 与 `showSelectionCheckboxes`。分页使用 compound Pagination,批量操作使用 Pro ActionBar。禁止手写选择列或在 Cell 内嵌旧 Checkbox。
- **Modal**:使用 `Modal.Root > Modal.Backdrop > Modal.Container > Modal.Dialog`,业务页直接传受控 open/close、异步状态和提交回调。删除/重置使用受控 AlertDialog/Modal,不使用 `window.confirm`。
- **表单**:使用 HeroUI TextField/Input/TextArea/Checkbox/Select/DatePicker 的 compound 结构。Select 必须包含 ListBox;Checkbox、Select、Tabs、Modal 在 React Aria Context 中按官方 slot/compound 结构实现。
- **Tabs**:主数据集切换使用 HeroUI Tabs;只有一个可访问视图时隐藏轨道但保留原 key 与查询行为。模块级模式切换使用紧凑分段控件。
- **上传**:使用 `@heroui-pro/react/drop-zone`,向业务回调传真实 `File` 或 `File[]`,不伪造旧 input event。
- **图表**:ECharts 配置从鑫元 Token 读取颜色、文字、网格、Tooltip、图例和暗色表面;统计口径和数据转换留在现有业务层。
## 3. 分层
```text
页面/controller(请求、权限、状态、业务回调)
-> HeroUI/HeroUI Pro 官方组件
-> 少量 HeroUI-native 业务中立复合组件(设计、布局、无障碍)
-> 鑫元 Token 与项目语义样式
```
旧 Catalyst/Headless 组件不在这条链路中。`assets/portable` 只作为历史视觉参考,不作为代码 API。
## 4. HeroUI Pro 版本与交付
- 本机开发镜像:`@heroui-pro/react@1.0.0-beta.7`,来源为本机 `project_manager_new` 的完整安装。
- 本地 bootstrap 可执行 `npm install --legacy-peer-deps ../project_manager_new/node_modules/@heroui-pro/react`,但该 `file:` 路径不能作为跨机器交付方案。
- 团队正式安装必须使用私有 npm 或统一授权、带版本和校验信息的 `.tgz`。需要同时固定 `@heroui/react@3.2.2`、`@heroui/styles@3.2.2`、`react-aria-components@1.19.0` 和兼容 React 19 运行时。
- 正式同步前验证 `css`、`sidebar`、`navbar`、`data-grid`、`drop-zone`、`action-bar`、`empty-state` 子路径,并运行 `npm ls @heroui-pro/react @heroui/react @heroui/styles react-aria-components @react-aria/utils`。
- 当前 sibling `file:` 图存在 React 18/19 双实例风险,尚不能视为可复现交付。
## 5. 业务保护
UI 层不得修改 API 地址、请求方法/参数、响应映射、权限与禁用条件、校验规则、提交数据、登录退出协议、表格选择结果、分页排序筛选语义或图表统计口径。发现业务缺陷时只记录,另开业务变更。
## 6. 当前依赖状态
- 浏览器运行时通过 Umi alias 使用顶层 React/React DOM 19,活动页面没有出现重复 React Aria Context、slot 或 provider 异常。
- 用户指定的 `npm ls @heroui-pro/react @heroui/react @heroui/styles react-aria-components @react-aria/utils` 当前通过,并显示一套 HeroUI/React Aria 版本。加入 `react react-dom` 后仍会报告 `ELSPROBLEMS`:HeroUI 3 使用 React 19,而 Umi 构建依赖保留 React 16-18 peer 声明及嵌套 React 18。这不是可复现的干净完整依赖树。
- 当前 `file:../project_manager_new/node_modules/...` 只保证本机启动。团队未提供私有 npm 或授权 `.tgz` 前,不得提交为跨机器安装方案,也不得宣称依赖交付完成。
## 7. 代码审查门槛
每个迁移模块都要扫描 Catalyst/Headless/旧本地 UI import,真实浏览器检查打开态和控制台。活动范围旧引用必须为零;非活动旧模块单独列出。构建和浏览器验收通过可以确认本机运行质量,但不能覆盖当前依赖树和团队包源的可复现风险。
# UI 迁移清单
状态说明:`已迁移`表示活动运行时入口已经直接使用 HeroUI/HeroUI Pro;浏览器状态只记录真实数据和真实业务条件下实际执行过的交互,不用伪造数据补齐场景。
| 页面/模块 | 路由 | 迁移结果 | 保留的业务回调 | 状态 | 浏览器验收 |
| --- | --- | --- | --- | --- | --- |
| 登录 | `/login` | HeroUI Card/TextField/Input/Button/Alert | captcha、AES 加密、userLogin、redirect | 已迁移 | 通过:登录、跳转、统一 Logo/标题、输入状态和响应式 |
| AppShell | 活动鉴权路由 | HeroUI Pro Sidebar/Navbar + HeroUI Dropdown/Tooltip | history 路由、主题持久化 | 已迁移 | 通过:桌面完全收起/展开、移动 offcanvas、浅色/暗色 |
| 左侧用户/退出 | 活动鉴权路由 | HeroUI Dropdown compound API | userLogout、clearUser、base-aware 登录跳转 | 已迁移 | 通过:菜单打开、退出并回到登录页 |
| 应用切换 | 全局 | HeroUI Modal compound API | getApplications、URL 导航 | 已迁移 | 通过:打开/关闭、当前项唯一、浅色/暗色 Portal |
| 稽核项目 | `/audit_process` | HeroUI 表单/Modal/Tabs + HeroUI Pro DataGrid | 查询、筛选、增删改、引用选择 | 已迁移 | 通过:真实数据、Tab、实时筛选、新增/编辑/删除弹窗、枚举和远程引用关闭态回显/打开态搜索 |
| 稽核报告 | `/audit_report` | HeroUI Tabs/Modal + HeroUI Pro DataGrid | 当前/历史查询、填报链接、提交、删除 | 已迁移 | 通过:当前/历史 Tab、填报链接、历史详情跳转;提交按钮因 8 个控制点未完成而禁用,未绕过业务限制 |
| 报告内容 | 活动报告与历史详情复用 | HeroUI 表单 + HeroUI Pro DataGrid/DropZone | update、confirm point/process | 已迁移 | 通过:真实内容表、横向滚动、固定列、暗色表面;未提交真实变更 |
| 历史详情 | `/audit_report/:reportId` | HeroUI Button/Chip + HeroUI Pro DataGrid | 加载、返回、导出 | 已迁移 | 通过:真实历史报告加载和多组明细表 |
| 外部填报 | `/audit_report_fill` | HeroUI 表单/AlertDialog + HeroUI Pro DataGrid/DropZone | ticket 鉴权、保存、重置、beforeunload | 已迁移 | 通过:真实填报链接、DropZone、Select 打开态、根主题、标题和移动端;未选择文件,避免立即调用真实上传接口 |
| PageState | 多路由复用 | HeroUI Spinner/Button/Alert | retry | 已迁移 | 通过:活动页面加载/空状态可见,控制台无异常 |
## 浏览器验收记录
- 视口:`1440x900`、`1280x720`、`768x1024`、`390x844`。
- Shell:Sidebar 展开后 240px;桌面收起后移出视口且主内容扩展到完整宽度;移动 offcanvas 铺满面板。
- 响应式:四个视口均无页面级横向滚动;Tabs 和 DataGrid 在自己的容器内滚动。
- 表格:稽核项目、当前报告、历史详情和外部填报均使用真实记录验收;固定列和操作列在宽表内部滚动时保持可见。
- 表单与弹层:新增/编辑 Modal、删除 AlertDialog、应用切换、枚举 Select、远程引用 ListBox、外部填报 Select 已检查打开态和暗色表面。
- 选择回显:空值显示占位符;责任部门等单选关闭态显示真实名称;标签/制度多选关闭态显示已选名称并保留可移除标签;打开后切换为搜索词,不改变提交 ID/数组。
- 主题:浅色和暗色的 Sidebar、Navbar、Modal、Select/ListBox、DataGrid、DropZone 与移动 offcanvas 已检查;外部填报页同步根主题和浏览器标题。
- 控制台:最终代码修改后的全新认证会话为 `0 errors / 0 warnings`;仅有 Webpack 连接日志和 React DevTools 开发提示。
## 受真实业务状态限制的场景
- 当前报告仍有 8 个控制点未完成,提交按钮按原业务规则禁用,因此没有打开提交确认,也没有绕过禁用条件。
- DropZone 选中文件后会立即调用真实后端上传。本次只验证可见表面、打开态、暗色和响应式,没有选择文件或制造上传数据。
- 当前真实数据量不足一页,运行时没有出现 Pagination;当前活动流程也没有选择依赖的批量业务动作,因此 ActionBar 没有可触发入口。未伪造记录,也不把这两项写成已交互通过。
- 没有执行新增、编辑、删除、保存、提交或重置的最终确认,避免修改真实业务数据;只验证弹窗、字段、选择器和取消路径。
## 非活动旧模块
以下目录未进入当前 `.umirc.ts` 活动路由,仍含 Catalyst/Headless/Heroicons 引用,不计入已迁移范围:
- `src/pages/process_monitor`
- `src/pages/RecordDetailDrawer`
- `src/components/record-workbench`
恢复这些入口前必须先按同一 HeroUI 原生架构迁移,不能直接重新挂载。
## 最终门槛
- [x] 活动路由 Catalyst/Headless/Heroicons 引用为零。
- [x] `npm run build` 通过(Webpack 编译成功)。
- [x] `git diff --check` 通过。
- [x] 本地 Pro CSS 与六个 JS 子路径全部可解析/导入。
- [x] 指定 HeroUI/React Aria `npm ls` 命令通过并显示一套版本。
- [ ] 包含 React/Umi 的完整依赖树可跨机器复现。当前本地镜像、Umi peer 声明和嵌套 React 18 仍不满足此项。
- [x] 鑫元审计脚本完成最终重跑;非活动旧代码剩余引用已单独报告。
# 监察稽核管理鑫元设计规范
本文件是后续新增页面与功能的强制 UI 合同,视觉基线为鑫元设计系统 1.2。
## 1. 品牌与基础
- 应用显示名统一为“监察稽核管理”,用于登录、Sidebar、Navbar、应用切换当前项和浏览器标题。
- 主色为 `#9E1B1B`。选中、焦点、Checkbox、日期、Pagination 和信息状态通过 Token 使用品牌红。
- 浅色 Logo 使用 `src/assets/identity/xy-logo-red.png`,暗色使用 `xy-logo-white.png`;不使用滤镜、反色、色块或 RSM 标识。
- 样式顺序为 Tailwind、HeroUI、HeroUI Pro、鑫元 Token、项目语义样式。
- 字距固定为 0,不使用渐变、营销式大标题、装饰性多卡片或随视口缩放的字号。
## 2. 尺寸与层级
- 画布:浅色 `#F5F5F5`,暗色 `#18181B`;表面分别使用语义 surface Token。
- 页面左右 inset:桌面 68px、移动 72px;页面级横向溢出隐藏,宽表只在 DataGrid 内滚动。
- 工作台/KPI 模块 24px 圆角,DataGrid 20px,控件 8px,Popover 12px,Modal 24px。
- 页面标题 20/600/28;模块标题 16/600;正文 14/400;字距均为 0。
- DataGrid 表头 12/500,正文 14/400,主字段 14/550,次字段 13/400,Chip 12/500。
- 常规按钮桌面高 32px、触屏 36px、24px 圆角、14/500;主次按钮同尺寸,主操作最右。
## 3. 登录、Shell 与应用切换
- 登录使用居中认证 Card;36px 花形 Logo 与约 32/600 系统标题横向排列,并作为一个品牌组在 Card 内容区水平居中;表单标签和控件仍保持左对齐,不重复“登录”标题。
- 账号、密码、验证码可见高度统一 40px;字段组可见间距 24px,提交按钮上方 24px。
- filled/autofill 使用不透明浅红 `#fbf1f1`,Hover `#faeaea`,Focus 用品牌红;必须覆盖 Chrome autofill inset fill。
- HeroUI Pro Sidebar 宽 240px,`collapsible="offcanvas"`,收起后宽度为 0,不保留图标轨道。
- Sidebar 底部整行用户入口使用 HeroUI Dropdown compound API,显示头像、姓名、登录名,菜单内提供现有业务退出。
- “退出登录”统一使用 HeroUI `Dropdown.Item variant="danger"`:文字与退出图标在默认、Hover、Focus 状态均使用 `var(--danger)`,反馈背景使用主题语义表面,不得在不同应用间混用黑色与红色。
- Navbar 高 64px、透明无边框;Sidebar.Trigger 使用 Pro 默认 filled 图形。全局主题切换在最右并持久化。
- 应用切换使用居中 HeroUI Modal:约 46rem 宽、42rem 最大高、三列紧凑应用卡;最多一个“当前”,无统计、权限或装饰信息。
## 4. 页面、筛选和 Tabs
- Navbar 是唯一可见页面标题;内容区仅保留无障碍标题。
- 标准列表顺序:页面级 Tabs、筛选 Card、透明列表标题/操作行、DataGrid、Pagination。
- 实时筛选首行最多三个字段,不显示查询按钮;右侧使用刷新 icon button 和 text-only 展开/收起。
- Input、Select/ListBox、远程引用、DatePicker 的关闭态和打开态统一为 8px 控件、单边框、单阴影。
- 选择控件仅在空值时显示占位符。已有值时关闭态必须显示实际名称;远程名称尚在加载时显示“已选择 N 项”,不得退回搜索占位符。已选值使用主文本色与 500 字重,打开后再进入搜索状态。
- 主数据集切换使用 HeroUI Tabs;只有一个可访问视图时隐藏轨道。模块局部模式使用紧凑 segmented control。
- 普通业务按钮为纯文本;icon-only 只用于真实图标操作,必须有 Tooltip 与 `aria-label`。
## 5. DataGrid 与分页
- 所有业务列表使用 HeroUI Pro DataGrid 原生排序、选择、固定列和空状态;不手写 Checkbox 列。
- 表格始终填满内容宽度。仅在真实列最小宽度超过 viewport 时固定首个业务列和操作列并内部横向滚动。
- 操作列仅在当前可见数据至少有一行能够渲染真实操作时展示;无可用操作时隐藏整列并回收宽度,不使用空白单元格或 `-` 占位。
- 操作按钮为 32px 正圆、16px Ant Design icon。1-3 个直接显示,4 个以上显示两个高频项,其余进入更多菜单。
- 多选后在当前可视区底部显示 HeroUI Pro ActionBar,清除选择是最右 icon-only 关闭操作。
- Pagination 提供 10/20/50,切换页容量回到第一页并保留筛选、排序、权限范围。
- 空状态只显示一次“暂无数据”,表头和列宽在 loading/empty 时不能跳动。
## 6. 表单、上传、弹窗和反馈
- 页面直接使用 HeroUI TextField/Input/TextArea/Checkbox/Select/DatePicker compound API;禁止可见原生 select/date/file 控件。
- Checkbox 16px、4px 圆角;选中/半选的背景、边框和内填充均为 `#9E1B1B`,指标为白色。
- 上传使用 HeroUI Pro DropZone,保留 accept、disabled、单/多文件和原业务回调。
- Modal 所有视口居中,24px 圆角;标准 padding 28px、确认 24px,遮罩约 50% 黑和 12px blur。
- Footer 桌面右对齐,取消在前、主操作最右;移动端全宽反向堆叠。最终删除确认才使用 solid danger。
- 成功保存/删除/导入/导出用顶部居中 3-4 秒 Toast,不使用推挤页面布局的 Alert。
## 7. KPI 与图表
- KPI 网格:移动 1 列、中屏 2 列;桌面 4 张 4 列、5 张 3+2、6 张 3+3、8 张 4+4。
- KPI 使用不透明表面、24px 圆角、细边框和轻阴影,不使用每卡异色背景。
- 图标面 48px、16px 圆角、20px Ant icon,容器与 SVG 中心偏差不超过 0.5px。
- 同一 KPI 组的图标按业务语义使用不同 Token 前景/浅色表面,不能全部使用品牌红;卡片主体仍保持统一中性表面。监察稽核报告固定映射为:全部控制点 teal、未填写 danger、待确认 warning、已完成 success。
- ECharts 从 Token 读取系列、文字、网格、图例、Tooltip 和暗色表面;不修改业务统计口径。
## 8. 暗色、响应式与验收
- 根节点统一控制 light/dark,所有 Dropdown、Popover、Tooltip、Modal、DatePicker、DropZone、ActionBar 与图表 Portal 继承主题。
- 页面不得出现 white island、文字遮挡、页面级横向滚动或移动端按钮溢出。
- 必测视口:1440x900、1280x720、768x1024、390x844。
- 每个模块必须在真实浏览器验证打开态、键盘焦点、业务回调和控制台;构建成功不能替代页面验收。
# 监察稽核管理 UI 全局审计
审计日期:2026-08-14
审计分支:`codex/ui-ux-xinyuan`
审计基线:鑫元设计系统 1.2、HeroUI 3、HeroUI Pro 1.0.0-beta.7
> 本文记录 UI 修改前的全局事实。迁移只替换展示与交互组件,不修改接口、请求参数、权限、状态机、表单提交、统计口径或路由。
## 1. 框架与构建
- Umi Max 4,构建命令 `npm run build`,开发命令 `npm run dev`。
- React 应用,Tailwind CSS 4,项目样式入口为 `src/tailwind.css` 与 `src/global.less`。
- `base` 与 `publicPath` 均为 `/audit_manager_new/`;登录、401/403 与应用内跳转必须保持 base-aware。
- 代理保持不变:`/api` 指向 `https://xy.streams7.com/api`,`/parser` 指向 `https://xy.streams7.com`。
- 当前活动路由:
| 页面 | 路由 | 鉴权 |
| --- | --- | --- |
| 登录 | `/login` | Site |
| 稽核项目 | `/audit_process` | Site + Auth |
| 稽核报告 | `/audit_report` | Site + Auth |
| 历史报告详情 | `/audit_report/:reportId` | Site + Auth |
| 外部填报 | `/audit_report_fill` | Site + ticket |
## 2. 登录、鉴权与退出
- 登录调用 `userLogin`,请求体字段为 `identity`、加密后的 `password`、`captcha`、验证码 `token` 与 `type: password`。
- 登录成功后由 `authService` 保存 `current_user` 与 `auth_token`,并跳回查询参数 `redirect` 或 `/`。
- `AuthWrapper` 使用 `getUserInfo` 刷新用户资料;失败时清理本地登录信息。
- 退出调用现有 `userLogout(username)`,无论接口结果如何均清理本地登录信息并跳转登录页。
- 审计发现的 Shell 问题:左侧用户行虽然显示用户信息,但 Dropdown 触发结构未按 HeroUI v3 的 `Dropdown.Trigger` 复合结构实现;退出菜单可能不能稳定打开。退出跳转当前硬编码 `/login`,必须改为保留 Umi base 的应用路由行为,但不能改变退出 API 或清理逻辑。
## 3. 页面、弹窗与业务入口
| 功能面 | 主要入口 | 业务保护点 |
| --- | --- | --- |
| 稽核项目 | 项目切换、搜索、刷新、新增/编辑/删除控制点 | 项目查询、控制点增删改、负责人引用、分页/筛选条件、确认回调不变 |
| 稽核报告 | 当前报告、历史报告、导入、生成、提交、删除 | 报告快照、状态流转、上传解析、保存与提交顺序不变 |
| 报告内容 | 文本、附件、异常、备注、确认控制点/流程 | 字段值结构、附件数组、owner 限制、确认条件不变 |
| 历史详情 | 加载、导出、返回 | reportId、统计口径和导出回调不变 |
| 外部填报 | ticket 鉴权、保存、重置、导出 | report/owner/ticket 参数、草稿合并、并发读取和未保存提醒不变 |
| 应用切换 | 应用查询、当前应用识别、导航 | 应用响应归一化、可访问列表和目标 URL 不变 |
活动页面包含创建/编辑、删除确认、提交确认、重置确认和详情内容。未发现活动路由中的驾驶舱图表;依赖中保留 ECharts,图表主题合同仍写入设计规范供后续功能使用。
## 4. 旧组件与引用关系
活动范围的硬性违规:
- `AuditProcess.jsx`:Catalyst Alert、Badge、Button、Dialog、Field、Input、Select、Table、Textarea。
- `AuditReport.jsx`:Catalyst Alert、Badge、Button、Dialog、Input、Table。
- `AuditReportContent.jsx`:Catalyst Badge、Button、Select、Textarea,并通过旧式共享 API 使用表格/上传/行操作。
- `AuditReportDetail.jsx`:Catalyst Badge、Button。
- `AuditReportFill.jsx`:Catalyst Alert、Button。
- `PageState.jsx`:Catalyst Button。
- `ApplicationSwitcherDialog.jsx`:Headless UI Dialog。
- `DataTable.jsx`:内部虽使用 HeroUI Pro DataGrid,但对页面暴露旧 `Table/TableHead/TableRow/TableCell` 兼容 API,不符合目标架构。
- `TableActionButton.jsx`、`FileUploadField.jsx` 等需要确认只暴露 HeroUI 当前事件与选择模型,不能复刻旧 props/event API。
`src/catalyst-ui-kit` 是旧组件库目录。本次禁止进入、扩展或把 HeroUI 嵌入其中;已被先前兼容尝试修改的文件必须恢复为仓库原状。
未进入 `.umirc.ts` 的存量代码仍引用 Catalyst/Headless UI:`src/pages/process_monitor`、`src/pages/RecordDetailDrawer`、`src/components/record-workbench`。它们不在本次运行时验收范围,不能误报为已迁移;最终扫描必须单独列出剩余数量。若后续恢复这些路由,必须先按同一架构迁移。
## 5. 控件、表格与 Portal
- Button、Input、Checkbox、Select、Textarea、Dialog、Table 的活动页面来源主要是 Catalyst。
- 活动页面存在旧 `<option>` Select 结构;需要直接替换为 HeroUI `Select + ListBox` compound API。
- 文件上传业务通过本地 FileUploadField 传递旧 DOM event,需要改为 HeroUI Pro DropZone 的 `File[]`/选择回调边界,页面保留上传函数与 accept/disabled 规则。
- 旧表格包含页面级列定义、手写分页和旧选择语义;目标必须使用 HeroUI Pro DataGrid 原生 `selectedKeys`、`onSelectionChange`、排序、固定列、Pagination 与 ActionBar。
- Application switcher 使用 Headless Portal;其他 Dropdown/Popover/Tooltip/Modal 需验证 body Portal 能继承根节点 `.dark` 与 `data-theme`。
- 当前应用没有活动图表页面;后续 ECharts 必须使用 Token 驱动的 Tooltip、图例、网格与暗色表面。
## 6. 依赖解析审计
当前本机解析:
| 包 | 版本 | 来源 |
| --- | --- | --- |
| `@heroui-pro/react` | 1.0.0-beta.7 | sibling `project_manager_new` 本地镜像 |
| `@heroui/react` | 3.2.2 | sibling file dependency |
| `@heroui/styles` | 3.2.2 | sibling file dependency |
| `react-aria-components` | 1.19.0 | sibling file dependency |
| `react` / `react-dom` | 19.2.7 | sibling file dependency |
迁移后,`npm ls @heroui-pro/react @heroui/react @heroui/styles react-aria-components @react-aria/utils` 显示一套 HeroUI/React Aria 版本并通过。加入 `react react-dom` 的完整运行时树仍失败:Umi 依赖链包含 React 18.3.1 及只声明兼容 React 16-18 的 peer,而顶层 HeroUI 3 使用 React 19;本地 `file:` 依赖又解析到相邻项目。浏览器运行时已通过 alias 固定到顶层 React 19,但完整依赖树和跨机器交付仍不干净。
本机镜像仅允许用于启动开发。团队交付必须由私有 npm 或统一授权的版本化 `.tgz` 提供 `@heroui-pro/react@1.0.0-beta.7`;未获得授权制品前,不得把机器相关 `file:../project_manager_new/...` 当作最终安装合同。
## 7. 业务与 UI 耦合风险
- 旧 Select/Input 使用 DOM event;HeroUI 使用值/selection/onChange,需要在页面展示层改签名,不能改服务参数。
- 旧 Table JSX 同时承载列展示和业务按钮;迁移时必须先抽出列配置,保持回调闭包和权限条件。
- 报告附件上传依赖 `event.target.files`;迁移 DropZone 时必须显式传 `File`,不能伪造 DOM event。
- 删除、提交、重置弹窗同时控制异步状态;必须保留 disabled/loading、关闭顺序和错误展示。
- DataGrid 选择、分页、筛选变化会影响批量动作;必须在浏览器中验证受控 selection 的清理时机。
## 8. 审计结论
现状不满足原生 HeroUI 架构门槛,不能通过修改 Catalyst 内部或保留兼容表格接口完成验收。迁移顺序固定为依赖与 Token、Login、Shell/用户退出、应用切换、活动页面表单与 Tabs、DataGrid/Pagination/ActionBar、Modal/详情/上传、暗色与响应式,最后执行旧引用和依赖树扫描。
## 9. 迁移后复核
- 活动路由和本次新建共享组件已经直接使用 `@heroui/react`、`@heroui-pro/react` 或已验证 Pro 子路径;扫描结果没有 Catalyst、Headless UI 或 Heroicons 引用。
- `src/catalyst-ui-kit` 未被本次修改。非活动存量目录 `src/pages/process_monitor`、`src/pages/RecordDetailDrawer`、`src/components/record-workbench` 仍有旧引用,已从“已迁移”声明中排除。
- 登录、退出、鉴权路由、应用切换、稽核项目、当前/历史报告、历史详情和外部填报已用真实账号及真实记录完成浏览器验收。API 地址、参数、权限条件、禁用条件和提交回调没有改动。
- 四个目标视口均无页面级横向滚动;宽表由 DataGrid 自身滚动。浅色/暗色 Portal、Modal、Select/ListBox、DropZone、桌面 Sidebar 收起和移动 offcanvas 已验收。
- 真实数据没有触发 Pagination 或选择批量 ActionBar;当前报告提交因 8 个控制点未完成而禁用;DropZone 选文件会真实上传。上述交互没有通过伪造数据或绕过业务条件补测。
- 本机依赖版本为 Pro `1.0.0-beta.7`、HeroUI/Styles `3.2.2`、React Aria Components `1.19.0`、React/React DOM `19.2.7`。指定 HeroUI/React Aria 树和 HeroUI Pro 子路径可在本机验证;包含 React 的完整树仍受 Umi peer 与嵌套 React 18 影响,团队包源也未确定,因此跨机器可复现性继续是明确风险。
## 10. 选择控件回显复核
- 当前活动路由共有三类选择控件:`ReferenceSearchSelect` 远程引用、`FilterSelect` 枚举选择,以及 `DataTable` 的分页页容量选择。
- `AuditProcess` 中责任部门、相关标签、相关制度和量化指标共用远程引用控件。原实现始终以搜索词控制输入值,导致已选 ID 存在、选项行也带选中标记,但关闭态仍显示搜索占位符。
- 远程引用现已分离关闭态选中摘要与打开态搜索词:名称解析后显示真实名称,解析期间显示明确选择数量;多选标签继续保留原移除回调。
- 枚举 Select 对非空值统一使用主文本色和 500 字重;即使后端返回的值暂未出现在选项定义中,也显示原值而不是错误退回占位符。
- 分页页容量原本已直接显示 `${pageSize} 条/页`,保持现有值协议和视觉层级,不额外改造。
- 浏览器已验证浅色/暗色、桌面/`390x844`:关闭态显示“审计部”“审计、测试”“文件上传”,打开态责任部门保留当前项标记并支持搜索;临时改选后取消可恢复原值,页面与弹窗均无横向溢出。
{
"name": "oa_process_monitor",
"name": "audit_manager_new",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"hasInstallScript": true,
"dependencies": {
"@ant-design/icons": "file:../project_manager_new/node_modules/@ant-design/icons",
"@headlessui/react": "^2.2.10",
"@heroicons/react": "^2.2.0",
"@heroui-pro/react": "file:../project_manager_new/node_modules/@heroui-pro/react",
"@heroui/react": "file:../project_manager_new/node_modules/@heroui/react",
"@heroui/styles": "file:../project_manager_new/node_modules/@heroui/styles",
"@tailwindcss/postcss": "^4.2.0",
"@umijs/max": "^4.5.3",
"clsx": "^2.1.1",
......@@ -15,8 +19,12 @@
"dayjs": "^1.11.20",
"echarts": "^6.0.0",
"echarts-for-react": "^3.0.6",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"motion": "file:../project_manager_new/node_modules/motion",
"react": "file:../project_manager_new/node_modules/react",
"react-aria-components": "file:../project_manager_new/node_modules/react-aria-components",
"react-dom": "file:../project_manager_new/node_modules/react-dom",
"tailwind-merge": "file:../project_manager_new/node_modules/tailwind-merge",
"tailwind-variants": "file:../project_manager_new/node_modules/tailwind-variants",
"tailwindcss": "^4.2.0",
"umi-request": "^1.4.0",
"umi-request-progress": "^1.1.0",
......@@ -28,6 +36,284 @@
"typescript": "^5.0.3"
}
},
"../project_manager_new/node_modules/@ant-design/icons": {
"version": "6.3.2",
"license": "MIT",
"dependencies": {
"@ant-design/colors": "^8.0.1",
"@ant-design/icons-svg": "^4.5.0",
"@rc-component/util": "^1.11.0",
"clsx": "^2.1.1"
},
"devDependencies": {
"@eslint/js": "^9.39.4",
"@rc-component/father-plugin": "^2.1.3",
"@rc-component/np": "^1.0.4",
"@swc/core": "^1.15.30",
"@testing-library/dom": "^10.4.1",
"@testing-library/react": "^16.3.2",
"@types/lodash": "^4.17.24",
"@types/node": "^24.12.2",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"antd": "^4.24.16",
"dumi": "^1.1.54",
"eslint": "^9.39.4",
"eslint-plugin-jest": "^28.14.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"father": "^4.6.19",
"glob": "^7.2.3",
"history": "^4.10.1",
"jsdom": "^25.0.1",
"lodash": "^4.18.1",
"pkg-dir": "4.0.0",
"prettier": "^3.8.3",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"rimraf": "^3.0.2",
"styled-components": "^3.4.10",
"ts-node": "^8.10.2",
"typescript": "^5.9.3",
"typescript-eslint": "^8.59.0",
"vite": "^8.0.9",
"vitest": "^4.1.5"
},
"engines": {
"node": ">=8"
},
"peerDependencies": {
"react": ">=16.0.0",
"react-dom": ">=16.0.0"
}
},
"../project_manager_new/node_modules/@heroui-pro/react": {
"version": "1.0.0-beta.7",
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@internationalized/date": "3.12.2",
"@internationalized/number": "3.6.7",
"@react-aria/utils": "3.34.1",
"@react-stately/utils": "3.12.1"
},
"peerDependencies": {
"@heroui/react": ">=3.2.2",
"@heroui/styles": ">=3.2.2",
"@number-flow/react": ">=0.5.0",
"@tiptap/core": ">=3.23.6",
"@tiptap/extension-link": ">=3.23.6",
"@tiptap/extension-underline": ">=3.23.6",
"@tiptap/extensions": ">=3.23.6",
"@tiptap/pm": ">=3.23.6",
"@tiptap/react": ">=3.23.6",
"@tiptap/starter-kit": ">=3.23.6",
"@tiptap/suggestion": ">=3.23.6",
"embla-carousel": ">=8.6.0",
"embla-carousel-react": ">=8.6.0",
"maplibre-gl": ">=5.15.0",
"marked": ">=18.0.0",
"motion": ">=12.12.0",
"react": ">=19.0.0",
"react-aria-components": ">=1.19.0",
"react-dom": ">=19.0.0",
"react-markdown": ">=10.1.0",
"react-resizable-panels": ">=4.10.0",
"recharts": ">=2.0.0",
"remark-breaks": ">=4.0.0",
"remark-gfm": ">=4.0.0",
"shiki": ">=4.0.0",
"streamdown": ">=2.5.0",
"tailwind-merge": ">=3.4.0",
"tailwind-variants": ">=3.2.0",
"tailwindcss": ">=4.0.0"
}
},
"../project_manager_new/node_modules/@heroui/react": {
"version": "3.2.2",
"license": "MIT",
"dependencies": {
"@heroui/styles": "3.2.2",
"@radix-ui/react-avatar": "1.1.11",
"@react-aria/i18n": "3.13.1",
"@react-aria/ssr": "3.10.1",
"@react-aria/utils": "3.34.1",
"@react-stately/utils": "3.12.1",
"@react-types/color": "3.2.0",
"@react-types/shared": "3.36.0",
"input-otp": "1.4.2",
"react-aria": "^3.50.0",
"react-aria-components": "^1.19.0",
"tailwind-merge": "3.4.0",
"tailwind-variants": "3.2.2"
},
"peerDependencies": {
"react": ">=19.0.0",
"react-dom": ">=19.0.0",
"tailwindcss": ">=4.0.0"
}
},
"../project_manager_new/node_modules/@heroui/styles": {
"version": "3.2.2",
"license": "MIT",
"dependencies": {
"tailwind-variants": "3.2.2",
"tw-animate-css": "1.4.0"
},
"peerDependencies": {
"tailwindcss": ">=4.0.0"
}
},
"../project_manager_new/node_modules/motion": {
"version": "12.35.1",
"license": "MIT",
"dependencies": {
"framer-motion": "^12.35.1",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"../project_manager_new/node_modules/react": {
"version": "19.2.7",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"../project_manager_new/node_modules/react-aria-components": {
"version": "1.19.0",
"license": "Apache-2.0",
"dependencies": {
"@internationalized/date": "^3.12.2",
"@react-types/shared": "^3.36.0",
"@swc/helpers": "^0.5.0",
"client-only": "^0.0.1",
"react-aria": "3.50.0",
"react-stately": "3.48.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.0.0",
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"../project_manager_new/node_modules/react-dom": {
"version": "19.2.7",
"license": "MIT",
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
"react": "^19.2.7"
}
},
"../project_manager_new/node_modules/tailwind-merge": {
"version": "3.6.0",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.29.0",
"@babel/preset-env": "^7.29.3",
"@codspeed/vitest-plugin": "^5.4.0",
"@rollup/plugin-babel": "^7.0.0",
"@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-typescript": "^12.3.0",
"@types/node": "^24.12.2",
"@vitest/coverage-v8": "^4.1.5",
"@vitest/eslint-plugin": "^1.6.16",
"babel-plugin-annotate-pure-calls": "^0.5.0",
"babel-plugin-polyfill-regenerator": "^0.6.8",
"eslint": "^9.39.4",
"eslint-plugin-import": "^2.32.0",
"globby": "^16.2.0",
"prettier": "^3.8.3",
"rollup": "^4.60.2",
"rollup-plugin-delete": "^3.0.2",
"rollup-plugin-dts": "^6.4.1",
"tslib": "^2.8.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.59.1",
"vitest": "^4.1.5",
"zx": "^8.8.5"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
}
},
"../project_manager_new/node_modules/tailwind-variants": {
"version": "3.2.2",
"license": "MIT",
"devDependencies": {
"@commitlint/cli": "19.5.0",
"@commitlint/config-conventional": "19.5.0",
"@jest/globals": "29.7.0",
"@swc-node/jest": "1.8.12",
"@swc/cli": "0.5.0",
"@swc/core": "1.9.2",
"@swc/helpers": "0.5.15",
"@swc/jest": "0.2.37",
"@types/jest": "29.5.14",
"@types/node": "22.9.0",
"@typescript-eslint/eslint-plugin": "8.14.0",
"@typescript-eslint/parser": "8.14.0",
"benchmark": "2.1.4",
"bumpp": "10.2.0",
"changelogithub": "13.16.0",
"class-variance-authority": "0.7.0",
"clean-package": "2.2.0",
"conventional-changelog-cli": "5.0.0",
"eslint": "8.57.0",
"eslint-config-prettier": "9.1.0",
"eslint-config-ts-lambdas": "1.2.3",
"eslint-import-resolver-typescript": "3.6.3",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jest": "28.9.0",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-prettier": "5.2.1",
"eslint-plugin-promise": "7.1.0",
"expect": "29.7.0",
"jest": "29.7.0",
"postcss": "8.5.6",
"prettier": "3.3.3",
"prettier-eslint": "16.3.0",
"prettier-eslint-cli": "8.0.1",
"tailwindcss": "4.1.11",
"ts-node": "10.9.2",
"tsup": "8.5.0",
"typescript": "5.6.3"
},
"engines": {
"node": ">=16.x",
"pnpm": ">=7.x"
},
"peerDependencies": {
"tailwind-merge": ">=3.0.0",
"tailwindcss": "*"
},
"peerDependenciesMeta": {
"tailwind-merge": {
"optional": true
}
}
},
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
......@@ -90,25 +376,8 @@
}
},
"node_modules/@ant-design/icons": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.8.3.tgz",
"integrity": "sha512-HGlIQZzrEbAhpJR6+IGdzfbPym94Owr6JZkJ2QCCnOkPVIWMO2xgIVcOKnl8YcpijIo39V7l2qQL5fmtw56cMw==",
"license": "MIT",
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons-svg": "^4.3.0",
"@babel/runtime": "^7.11.2",
"classnames": "^2.2.6",
"lodash": "^4.17.15",
"rc-util": "^5.9.4"
},
"engines": {
"node": ">=8"
},
"peerDependencies": {
"react": ">=16.0.0",
"react-dom": ">=16.0.0"
}
"resolved": "../project_manager_new/node_modules/@ant-design/icons",
"link": true
},
"node_modules/@ant-design/icons-svg": {
"version": "4.4.2",
......@@ -641,7 +910,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz",
"integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.23.5",
......@@ -1490,7 +1758,6 @@
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@dnd-kit/accessibility": "^3.1.1",
"@dnd-kit/utilities": "^3.2.2",
......@@ -2606,12 +2873,23 @@
"react": ">= 16 || ^19.0.0-rc"
}
},
"node_modules/@heroui-pro/react": {
"resolved": "../project_manager_new/node_modules/@heroui-pro/react",
"link": true
},
"node_modules/@heroui/react": {
"resolved": "../project_manager_new/node_modules/@heroui/react",
"link": true
},
"node_modules/@heroui/styles": {
"resolved": "../project_manager_new/node_modules/@heroui/styles",
"link": true
},
"node_modules/@hono/node-server": {
"version": "1.19.14",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
"integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18.14.1"
},
......@@ -3774,7 +4052,6 @@
"resolved": "https://registry.npmjs.org/@svgr/core/-/core-6.5.1.tgz",
"integrity": "sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/core": "^7.19.6",
"@svgr/babel-preset": "^6.5.1",
......@@ -4221,32 +4498,6 @@
"@babel/types": "^7.28.2"
}
},
"node_modules/@types/eslint": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
"license": "MIT",
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
}
},
"node_modules/@types/eslint-scope": {
"version": "3.7.7",
"resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
"integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
"license": "MIT",
"dependencies": {
"@types/eslint": "*",
"@types/estree": "*"
}
},
"node_modules/@types/estree": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
"license": "MIT"
},
"node_modules/@types/graceful-fs": {
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
......@@ -4262,12 +4513,6 @@
"integrity": "sha512-oOMFT8vmCTFncsF1engrs04jatz8/Anwx3De9uxnOK4chgSEgWBvFtpSoJo8u3784JNO+ql5tzRR6phHoRnscQ==",
"license": "MIT"
},
"node_modules/@types/history": {
"version": "4.7.11",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
"integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==",
"license": "MIT"
},
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz",
......@@ -4292,12 +4537,6 @@
"integrity": "sha512-IwpIMieE55oGWiXkQPSBY1nw1nFs6bsKXTFskNY8sdS17K24vyEBRQZEwlRS7ZmXCWnJcQtbxWzly+cODWGs2A==",
"license": "MIT"
},
"node_modules/@types/isomorphic-fetch": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.34.tgz",
"integrity": "sha512-BmJKuPCZCR6pbYYgi5nKFJrPC4pLoBgsi/B1nFN64Ba+hLLGUcKPIh7eVlR2xG763Ap08hgQafq/Wx4RFb0omQ==",
"license": "MIT"
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
......@@ -4339,7 +4578,6 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.2.tgz",
"integrity": "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==",
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~7.19.0"
}
......@@ -4360,14 +4598,15 @@
"version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.28",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.2.2"
......@@ -4377,46 +4616,12 @@
"version": "18.3.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"devOptional": true,
"dev": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^18.0.0"
}
},
"node_modules/@types/react-router": {
"version": "5.1.20",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz",
"integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==",
"license": "MIT",
"dependencies": {
"@types/history": "^4.7.11",
"@types/react": "*"
}
},
"node_modules/@types/react-router-dom": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-4.3.5.tgz",
"integrity": "sha512-eFajSUASYbPHg2BDM1G8Btx+YqGgvROPIg6sBhl3O4kbDdYXdFdfrgQFf/pcBuQVObjfT9AL/dd15jilR5DIEA==",
"license": "MIT",
"dependencies": {
"@types/history": "*",
"@types/react": "*",
"@types/react-router": "*"
}
},
"node_modules/@types/react-router-redux": {
"version": "5.0.27",
"resolved": "https://registry.npmjs.org/@types/react-router-redux/-/react-router-redux-5.0.27.tgz",
"integrity": "sha512-qC5lbuP2K/kMR/HE3e5ltCJptyiQhmfV0wbklqcqWDbNdpJBDwUsBGP4f/0RDYJf09+OTbz43u6iG+8E0Zcwqw==",
"license": "MIT",
"dependencies": {
"@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router": "^5.1.0",
"redux": ">= 3.7.2"
}
},
"node_modules/@types/resolve": {
"version": "1.20.6",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.6.tgz",
......@@ -4507,7 +4712,6 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz",
"integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==",
"license": "BSD-2-Clause",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.62.0",
"@typescript-eslint/types": "5.62.0",
......@@ -5069,7 +5273,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
......@@ -5830,7 +6033,6 @@
"resolved": "https://registry.npmjs.org/less/-/less-4.6.4.tgz",
"integrity": "sha512-OJmO5+HxZLLw0RLzkqaNHzcgEAQG7C0y3aMbwtCzIUFZsLMNNq/1IdAdHEycQ58CwUO3jPTHmoN+tE5I7FQxNg==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"copy-anything": "^3.0.5",
"parse-node-version": "^1.0.1"
......@@ -6100,6 +6302,36 @@
"react-dom": ">=16.0.0"
}
},
"node_modules/@umijs/plugins/node_modules/@ant-design/icons": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.8.3.tgz",
"integrity": "sha512-HGlIQZzrEbAhpJR6+IGdzfbPym94Owr6JZkJ2QCCnOkPVIWMO2xgIVcOKnl8YcpijIo39V7l2qQL5fmtw56cMw==",
"license": "MIT",
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons-svg": "^4.3.0",
"@babel/runtime": "^7.11.2",
"classnames": "^2.2.6",
"lodash": "^4.17.15",
"rc-util": "^5.9.4"
},
"engines": {
"node": ">=8"
},
"peerDependencies": {
"react": ">=16.0.0",
"react-dom": ">=16.0.0"
}
},
"node_modules/@umijs/plugins/node_modules/@ant-design/icons/node_modules/@ant-design/colors": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz",
"integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==",
"license": "MIT",
"dependencies": {
"@ctrl/tinycolor": "^3.4.0"
}
},
"node_modules/@umijs/plugins/node_modules/@ant-design/pro-components": {
"version": "2.8.10",
"resolved": "https://registry.npmjs.org/@ant-design/pro-components/-/pro-components-2.8.10.tgz",
......@@ -6347,7 +6579,6 @@
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.44.0.tgz",
"integrity": "sha512-RuIqHYrS98LrK/8kJJOJMMSQ/BCpojwsXDh7p0fBmp38ZOz6dlk+uyFRRusH+V+t3POoCsDOQ2zhomEYOeReXw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@tanstack/query-core": "4.44.0",
"use-sync-external-store": "^1.6.0"
......@@ -6406,31 +6637,6 @@
"styled-components": ">= 2"
}
},
"node_modules/@umijs/plugins/node_modules/dva": {
"version": "2.5.0-beta.2",
"resolved": "https://registry.npmjs.org/dva/-/dva-2.5.0-beta.2.tgz",
"integrity": "sha512-kc2+CHhF1cNIU3Rg1miMhHgOKJ/VDrq9d6ynVBZf1EN2YKWU3MVFq/uTTBqMr2qkR0m9f8VKHOFmfKLtfMI93Q==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.0.0",
"@types/isomorphic-fetch": "^0.0.34",
"@types/react-router-dom": "^4.2.7",
"@types/react-router-redux": "^5.0.13",
"dva-core": "^1.5.0-beta.2",
"global": "^4.3.2",
"history": "^4.6.3",
"invariant": "^2.2.2",
"isomorphic-fetch": "^2.2.1",
"react-redux": "^5.0.5",
"react-router-dom": "^4.1.2",
"react-router-redux": "5.0.0-alpha.9",
"redux": "^3.7.2"
},
"peerDependencies": {
"react": "15.x || ^16.0.0-0",
"react-dom": "15.x || ^16.0.0-0"
}
},
"node_modules/@umijs/plugins/node_modules/dva-core": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/dva-core/-/dva-core-2.0.4.tgz",
......@@ -6471,110 +6677,6 @@
"dva": "^2.5.0-0"
}
},
"node_modules/@umijs/plugins/node_modules/dva/node_modules/dva-core": {
"version": "1.5.0-beta.2",
"resolved": "https://registry.npmjs.org/dva-core/-/dva-core-1.5.0-beta.2.tgz",
"integrity": "sha512-xmtr/J63EZXBdVXNBW+QCD7p9CaE8kAo2U1faRyv3PIGcy0G3Y6IBDNtoBB/Cj3nzk/jvX0dv96Hnh1kpSnI7Q==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.0.0",
"flatten": "^1.0.2",
"global": "^4.3.2",
"invariant": "^2.2.1",
"is-plain-object": "^2.0.3",
"redux": "^3.7.1",
"redux-saga": "^0.16.0",
"warning": "^3.0.0"
},
"peerDependencies": {
"redux": "3.x"
}
},
"node_modules/@umijs/plugins/node_modules/dva/node_modules/react-redux": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.2.tgz",
"integrity": "sha512-Ns1G0XXc8hDyH/OcBHOxNgQx9ayH3SPxBnFCOidGKSle8pKihysQw2rG/PmciUQRoclhVBO8HMhiRmGXnDja9Q==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.1.2",
"hoist-non-react-statics": "^3.3.0",
"invariant": "^2.2.4",
"loose-envify": "^1.1.0",
"prop-types": "^15.6.1",
"react-is": "^16.6.0",
"react-lifecycles-compat": "^3.0.0"
},
"peerDependencies": {
"react": "^0.14.0 || ^15.0.0-0 || ^16.0.0-0",
"redux": "^2.0.0 || ^3.0.0 || ^4.0.0-0"
}
},
"node_modules/@umijs/plugins/node_modules/dva/node_modules/redux": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
"integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==",
"license": "MIT",
"peer": true,
"dependencies": {
"lodash": "^4.2.1",
"lodash-es": "^4.2.1",
"loose-envify": "^1.1.0",
"symbol-observable": "^1.0.3"
}
},
"node_modules/@umijs/plugins/node_modules/dva/node_modules/warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
"integrity": "sha512-jMBt6pUrKn5I+OGgtQ4YZLdhIeJmObddh6CsibPxyQ5yPZm1XExSyzC1LCNX7BzhxWgiHmizBWJTHJIjMjTQYQ==",
"license": "BSD-3-Clause",
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/@umijs/plugins/node_modules/history": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.1.2",
"loose-envify": "^1.2.0",
"resolve-pathname": "^3.0.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0",
"value-equal": "^1.0.1"
}
},
"node_modules/@umijs/plugins/node_modules/react": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@umijs/plugins/node_modules/react-dom": {
"version": "16.14.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
"integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.19.1"
},
"peerDependencies": {
"react": "^16.14.0"
}
},
"node_modules/@umijs/plugins/node_modules/react-intl": {
"version": "3.12.1",
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-3.12.1.tgz",
......@@ -6643,63 +6745,11 @@
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"license": "MIT"
},
"node_modules/@umijs/plugins/node_modules/react-router": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz",
"integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==",
"license": "MIT",
"dependencies": {
"history": "^4.7.2",
"hoist-non-react-statics": "^2.5.0",
"invariant": "^2.2.4",
"loose-envify": "^1.3.1",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.6.1",
"warning": "^4.0.1"
},
"peerDependencies": {
"react": ">=15"
}
},
"node_modules/@umijs/plugins/node_modules/react-router-dom": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz",
"integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==",
"license": "MIT",
"dependencies": {
"history": "^4.7.2",
"invariant": "^2.2.4",
"loose-envify": "^1.3.1",
"prop-types": "^15.6.1",
"react-router": "^4.3.1",
"warning": "^4.0.1"
},
"peerDependencies": {
"react": ">=15"
}
},
"node_modules/@umijs/plugins/node_modules/react-router/node_modules/hoist-non-react-statics": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==",
"license": "BSD-3-Clause"
},
"node_modules/@umijs/plugins/node_modules/scheduler": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
"integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
}
},
"node_modules/@umijs/plugins/node_modules/styled-components": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.1.tgz",
"integrity": "sha512-cpZZP5RrKRIClBW5Eby4JM1wElLVP4NQrJbJ0h10TidTyJf4SIIwa3zLXOoPb4gJi8MsJ8mjq5mu2IrEhZIAcQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@emotion/is-prop-valid": "^1.2.1",
"@emotion/unitless": "^0.8.0",
......@@ -6814,6 +6864,31 @@
"node": ">=8.6.0"
}
},
"node_modules/@umijs/preset-umi/node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@umijs/preset-umi/node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
},
"peerDependencies": {
"react": "^18.3.1"
}
},
"node_modules/@umijs/preset-umi/node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
......@@ -6920,6 +6995,31 @@
"react-router-dom": "6.3.0"
}
},
"node_modules/@umijs/server/node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@umijs/server/node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
},
"peerDependencies": {
"react": "^18.3.1"
}
},
"node_modules/@umijs/test": {
"version": "4.6.51",
"resolved": "https://registry.npmjs.org/@umijs/test/-/test-4.6.51.tgz",
......@@ -7131,164 +7231,6 @@
"vite": "^4.2.0"
}
},
"node_modules/@webassemblyjs/ast": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
"integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
"license": "MIT",
"dependencies": {
"@webassemblyjs/helper-numbers": "1.13.2",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2"
}
},
"node_modules/@webassemblyjs/floating-point-hex-parser": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
"integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
"license": "MIT"
},
"node_modules/@webassemblyjs/helper-api-error": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
"integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
"license": "MIT"
},
"node_modules/@webassemblyjs/helper-buffer": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
"integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
"license": "MIT"
},
"node_modules/@webassemblyjs/helper-numbers": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz",
"integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
"license": "MIT",
"dependencies": {
"@webassemblyjs/floating-point-hex-parser": "1.13.2",
"@webassemblyjs/helper-api-error": "1.13.2",
"@xtuc/long": "4.2.2"
}
},
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
"integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
"license": "MIT"
},
"node_modules/@webassemblyjs/helper-wasm-section": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz",
"integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/wasm-gen": "1.14.1"
}
},
"node_modules/@webassemblyjs/ieee754": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz",
"integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
"license": "MIT",
"dependencies": {
"@xtuc/ieee754": "^1.2.0"
}
},
"node_modules/@webassemblyjs/leb128": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz",
"integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
"license": "Apache-2.0",
"dependencies": {
"@xtuc/long": "4.2.2"
}
},
"node_modules/@webassemblyjs/utf8": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
"integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
"license": "MIT"
},
"node_modules/@webassemblyjs/wasm-edit": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz",
"integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/helper-wasm-section": "1.14.1",
"@webassemblyjs/wasm-gen": "1.14.1",
"@webassemblyjs/wasm-opt": "1.14.1",
"@webassemblyjs/wasm-parser": "1.14.1",
"@webassemblyjs/wast-printer": "1.14.1"
}
},
"node_modules/@webassemblyjs/wasm-gen": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz",
"integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/ieee754": "1.13.2",
"@webassemblyjs/leb128": "1.13.2",
"@webassemblyjs/utf8": "1.13.2"
}
},
"node_modules/@webassemblyjs/wasm-opt": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz",
"integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
"@webassemblyjs/wasm-gen": "1.14.1",
"@webassemblyjs/wasm-parser": "1.14.1"
}
},
"node_modules/@webassemblyjs/wasm-parser": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz",
"integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-api-error": "1.13.2",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/ieee754": "1.13.2",
"@webassemblyjs/leb128": "1.13.2",
"@webassemblyjs/utf8": "1.13.2"
}
},
"node_modules/@webassemblyjs/wast-printer": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz",
"integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@xtuc/long": "4.2.2"
}
},
"node_modules/@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
"license": "BSD-3-Clause"
},
"node_modules/@xtuc/long": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
"license": "Apache-2.0"
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
......@@ -7316,7 +7258,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
......@@ -7324,18 +7265,6 @@
"node": ">=0.4.0"
}
},
"node_modules/acorn-import-phases": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
"integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
"license": "MIT",
"engines": {
"node": ">=10.13.0"
},
"peerDependencies": {
"acorn": "^8.14.0"
}
},
"node_modules/acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
......@@ -7395,7 +7324,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
"integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
......@@ -7407,45 +7335,6 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-formats": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
"license": "MIT",
"dependencies": {
"ajv": "^8.0.0"
},
"peerDependencies": {
"ajv": "^8.0.0"
},
"peerDependenciesMeta": {
"ajv": {
"optional": true
}
}
},
"node_modules/ajv-formats/node_modules/ajv": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-formats/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT"
},
"node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
......@@ -7496,7 +7385,6 @@
"resolved": "https://registry.npmjs.org/antd/-/antd-4.24.16.tgz",
"integrity": "sha512-zZrK4UYxHtU6tGOOf0uG/kBRx1kTvypfuSB3GqE/SBQxFhZ/TZ+yj7Z1qwI8vGfMtUUJdLeuoCAqGDa1zPsXnQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons": "^4.8.2",
......@@ -7560,6 +7448,27 @@
"dayjs": "*"
}
},
"node_modules/antd/node_modules/@ant-design/icons": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.8.3.tgz",
"integrity": "sha512-HGlIQZzrEbAhpJR6+IGdzfbPym94Owr6JZkJ2QCCnOkPVIWMO2xgIVcOKnl8YcpijIo39V7l2qQL5fmtw56cMw==",
"license": "MIT",
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons-svg": "^4.3.0",
"@babel/runtime": "^7.11.2",
"classnames": "^2.2.6",
"lodash": "^4.17.15",
"rc-util": "^5.9.4"
},
"engines": {
"node": ">=8"
},
"peerDependencies": {
"react": ">=16.0.0",
"react-dom": ">=16.0.0"
}
},
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
......@@ -8375,7 +8284,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782",
......@@ -8652,15 +8560,6 @@
"fsevents": "~2.3.2"
}
},
"node_modules/chrome-trace-event": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
"integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
"license": "MIT",
"engines": {
"node": ">=6.0"
}
},
"node_modules/ci-info": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
......@@ -8731,12 +8630,6 @@
"react": ">=16.8.0"
}
},
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
......@@ -9441,8 +9334,7 @@
"version": "1.11.20",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz",
"integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/debug": {
"version": "4.4.3",
......@@ -10108,36 +10000,9 @@
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.4.1",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1",
"stream-shift": "^1.0.2"
}
},
"node_modules/dva-core": {
"version": "1.6.0-beta.7",
"resolved": "https://registry.npmjs.org/dva-core/-/dva-core-1.6.0-beta.7.tgz",
"integrity": "sha512-e+0yOEWUK+XbnqflX+KXoLZDGxn+kLKgcT6XYT8GyRe0xcbmLEbwDZO0DXUkdsbxfqSOlLkIprMUoYJ3D5B4Gg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.0.0",
"flatten": "^1.0.2",
"global": "^4.3.2",
"invariant": "^2.2.1",
"is-plain-object": "^2.0.3",
"redux-saga": "^0.16.0",
"warning": "^3.0.0"
},
"peerDependencies": {
"redux": "4.x"
}
},
"node_modules/dva-core/node_modules/warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
"integrity": "sha512-jMBt6pUrKn5I+OGgtQ4YZLdhIeJmObddh6CsibPxyQ5yPZm1XExSyzC1LCNX7BzhxWgiHmizBWJTHJIjMjTQYQ==",
"license": "BSD-3-Clause",
"dependencies": {
"loose-envify": "^1.0.0"
"inherits": "^2.0.3",
"readable-stream": "^3.1.1",
"stream-shift": "^1.0.2"
}
},
"node_modules/dva-loading": {
......@@ -10473,12 +10338,6 @@
"node": ">= 0.4"
}
},
"node_modules/es-module-lexer": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz",
"integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
"license": "MIT"
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
......@@ -10658,7 +10517,6 @@
"integrity": "sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==",
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint/eslintrc": "^2.0.0",
"@eslint/js": "8.35.0",
......@@ -11806,12 +11664,6 @@
"node": ">= 6"
}
},
"node_modules/glob-to-regexp": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
"license": "BSD-2-Clause"
},
"node_modules/global": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
......@@ -12121,7 +11973,6 @@
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz",
"integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=16.9.0"
}
......@@ -13619,7 +13470,6 @@
"resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz",
"integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"copy-anything": "^2.0.1",
"parse-node-version": "^1.0.1",
......@@ -13646,7 +13496,6 @@
"resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.1.0.tgz",
"integrity": "sha512-C+uDBV7kS7W5fJlUjq5mPBeBVhYpTIm5gB09APT9o3n/ILeaXVsiSFTbZpTJCJwQ/Crczfn3DmfQFwxYusWFug==",
"license": "MIT",
"peer": true,
"dependencies": {
"klona": "^2.0.4"
},
......@@ -14394,6 +14243,10 @@
"node": "*"
}
},
"node_modules/motion": {
"resolved": "../project_manager_new/node_modules/motion",
"link": true
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
......@@ -15371,7 +15224,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
......@@ -16107,7 +15959,6 @@
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
"license": "MIT",
"peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
......@@ -16121,7 +15972,6 @@
"resolved": "https://registry.npmjs.org/postcss-syntax/-/postcss-syntax-0.36.2.tgz",
"integrity": "sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==",
"license": "MIT",
"peer": true,
"peerDependencies": {
"postcss": ">=5.0.0"
}
......@@ -16141,22 +15991,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/prettier": {
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz",
"integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-plugin-organize-imports": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz",
......@@ -16641,7 +16475,6 @@
"resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-1.38.2.tgz",
"integrity": "sha512-O83Oi1qPyEv31Sg+Jwvsj6pXc8uQI2BtIAkURr5lvEYHVggXJhdU/nynK8wY1gbw0qR48k731sN5ON4egRCROA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.18.0",
"async-validator": "^4.1.0",
......@@ -17151,17 +16984,8 @@
}
},
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
"resolved": "../project_manager_new/node_modules/react",
"link": true
},
"node_modules/react-aria": {
"version": "3.48.0",
......@@ -17184,6 +17008,10 @@
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/react-aria-components": {
"resolved": "../project_manager_new/node_modules/react-aria-components",
"link": true
},
"node_modules/react-aria/node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
......@@ -17194,18 +17022,8 @@
}
},
"node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
},
"peerDependencies": {
"react": "^18.3.1"
}
"resolved": "../project_manager_new/node_modules/react-dom",
"link": true
},
"node_modules/react-error-overlay": {
"version": "6.0.9",
......@@ -17263,7 +17081,6 @@
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
"integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
......@@ -17294,59 +17111,6 @@
"react-dom": ">=16.8"
}
},
"node_modules/react-router-redux": {
"version": "5.0.0-alpha.9",
"resolved": "https://registry.npmjs.org/react-router-redux/-/react-router-redux-5.0.0-alpha.9.tgz",
"integrity": "sha512-euSgNIANnRXr4GydIuwA7RZCefrLQzIw5WdXspS8NPYbV+FxrKSS9MKG7U9vb6vsKHONnA4VxrVNWfnMUnUQAw==",
"deprecated": "This project is no longer maintained.",
"license": "MIT",
"dependencies": {
"history": "^4.7.2",
"prop-types": "^15.6.0",
"react-router": "^4.2.0"
},
"peerDependencies": {
"react": ">=15"
}
},
"node_modules/react-router-redux/node_modules/history": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.1.2",
"loose-envify": "^1.2.0",
"resolve-pathname": "^3.0.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0",
"value-equal": "^1.0.1"
}
},
"node_modules/react-router-redux/node_modules/hoist-non-react-statics": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==",
"license": "BSD-3-Clause"
},
"node_modules/react-router-redux/node_modules/react-router": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz",
"integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==",
"license": "MIT",
"dependencies": {
"history": "^4.7.2",
"hoist-non-react-statics": "^2.5.0",
"invariant": "^2.2.4",
"loose-envify": "^1.3.1",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.6.1",
"warning": "^4.0.1"
},
"peerDependencies": {
"react": ">=15"
}
},
"node_modules/react-stately": {
"version": "3.46.0",
"resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.46.0.tgz",
......@@ -17585,7 +17349,6 @@
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.9.2"
}
......@@ -17764,12 +17527,6 @@
"node": ">=4"
}
},
"node_modules/resolve-pathname": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
"integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==",
"license": "MIT"
},
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
......@@ -17784,7 +17541,6 @@
"resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz",
"integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==",
"license": "MIT",
"peer": true,
"dependencies": {
"adjust-sourcemap-loader": "^4.0.0",
"convert-source-map": "^1.7.0",
......@@ -17926,7 +17682,6 @@
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.30.0.tgz",
"integrity": "sha512-kQvGasUgN+AlWGliFn2POSajRQEsULVYFGTvOZmK06d7vCD+YhZztt70kGk3qaeAXeWYL5eO7zx+rAubBc55eA==",
"license": "MIT",
"peer": true,
"bin": {
"rollup": "dist/bin/rollup"
},
......@@ -18094,7 +17849,6 @@
"resolved": "https://registry.npmjs.org/sass/-/sass-1.54.0.tgz",
"integrity": "sha512-C4zp79GCXZfK0yoHZg+GxF818/aclhp9F48XBu/+bm9vXEVAYov9iU3FBVRMq3Hx3OA4jfKL+p2K9180mEh0xQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
......@@ -18112,7 +17866,6 @@
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.0.tgz",
"integrity": "sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg==",
"license": "MIT",
"peer": true,
"dependencies": {
"klona": "^2.0.4",
"neo-async": "^2.6.2"
......@@ -19051,30 +18804,6 @@
"integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==",
"license": "ISC"
},
"node_modules/styled-jsx": {
"version": "5.1.7",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.7.tgz",
"integrity": "sha512-HPLmEIYprxCeWDMLYiaaAhsV3yGfIlCqzuVOybE6fjF3SUJmH67nCoMDO+nAvHNHo46OfvpCNu4Rcue82dMNFg==",
"license": "MIT",
"peer": true,
"dependencies": {
"client-only": "0.0.1"
},
"engines": {
"node": ">= 12.0.0"
},
"peerDependencies": {
"react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
},
"peerDependenciesMeta": {
"@babel/core": {
"optional": true
},
"babel-plugin-macros": {
"optional": true
}
}
},
"node_modules/stylelint": {
"version": "14.8.2",
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.8.2.tgz",
......@@ -19324,15 +19053,6 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/symbol-observable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/synckit": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz",
......@@ -19399,6 +19119,14 @@
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT"
},
"node_modules/tailwind-merge": {
"resolved": "../project_manager_new/node_modules/tailwind-merge",
"link": true
},
"node_modules/tailwind-variants": {
"resolved": "../project_manager_new/node_modules/tailwind-variants",
"link": true
},
"node_modules/tailwindcss": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz",
......@@ -19423,7 +19151,6 @@
"resolved": "https://registry.npmjs.org/terser/-/terser-5.47.1.tgz",
"integrity": "sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw==",
"license": "BSD-2-Clause",
"peer": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.15.0",
......@@ -19437,122 +19164,6 @@
"node": ">=10"
}
},
"node_modules/terser-webpack-plugin": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.5.0.tgz",
"integrity": "sha512-UYhptBwhWvfIjKd/UuFo6D8uq9xpGLDK+z8EDsj/zWhrTaH34cKEbrkMKfV5YWqGBvAYA3tlzZbs2R+qYrbQJA==",
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.25",
"jest-worker": "^27.4.5",
"schema-utils": "^4.3.0",
"terser": "^5.31.1"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "^5.1.0"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"esbuild": {
"optional": true
},
"uglify-js": {
"optional": true
}
}
},
"node_modules/terser-webpack-plugin/node_modules/ajv": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/terser-webpack-plugin/node_modules/ajv-keywords": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3"
},
"peerDependencies": {
"ajv": "^8.8.2"
}
},
"node_modules/terser-webpack-plugin/node_modules/jest-worker": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
"license": "MIT",
"dependencies": {
"@types/node": "*",
"merge-stream": "^2.0.0",
"supports-color": "^8.0.0"
},
"engines": {
"node": ">= 10.13.0"
}
},
"node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT"
},
"node_modules/terser-webpack-plugin/node_modules/schema-utils": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
"integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.9",
"ajv": "^8.9.0",
"ajv-formats": "^2.1.1",
"ajv-keywords": "^5.1.0"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/terser-webpack-plugin/node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/terser/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
......@@ -19609,18 +19220,6 @@
"node": ">=0.6.0"
}
},
"node_modules/tiny-invariant": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
"license": "MIT"
},
"node_modules/tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==",
"license": "MIT"
},
"node_modules/tinycolor2": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
......@@ -19881,8 +19480,8 @@
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
......@@ -19922,7 +19521,6 @@
"resolved": "https://registry.npmjs.org/umi-request/-/umi-request-1.4.0.tgz",
"integrity": "sha512-OknwtQZddZHi0Ggi+Vr/olJ7HNMx4AzlywyK0W3NZBT7B0stjeZ9lcztA85dBgdAj3KVk8uPJPZSnGaDjELhrA==",
"license": "MIT",
"peer": true,
"dependencies": {
"isomorphic-fetch": "^2.2.1",
"qs": "^6.9.1"
......@@ -20183,12 +19781,6 @@
}
}
},
"node_modules/value-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==",
"license": "MIT"
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
......@@ -20203,7 +19795,6 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.18.10",
"postcss": "^8.4.27",
......@@ -20667,19 +20258,6 @@
"loose-envify": "^1.0.0"
}
},
"node_modules/watchpack": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz",
"integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==",
"license": "MIT",
"dependencies": {
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/wbuf": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
......@@ -20698,54 +20276,6 @@
"node": ">= 8"
}
},
"node_modules/webpack": {
"version": "5.106.2",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.2.tgz",
"integrity": "sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.8",
"@types/json-schema": "^7.0.15",
"@webassemblyjs/ast": "^1.14.1",
"@webassemblyjs/wasm-edit": "^1.14.1",
"@webassemblyjs/wasm-parser": "^1.14.1",
"acorn": "^8.16.0",
"acorn-import-phases": "^1.0.3",
"browserslist": "^4.28.1",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.20.0",
"es-module-lexer": "^2.0.0",
"eslint-scope": "5.1.1",
"events": "^3.2.0",
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.2.11",
"loader-runner": "^4.3.1",
"mime-db": "^1.54.0",
"neo-async": "^2.6.2",
"schema-utils": "^4.3.3",
"tapable": "^2.3.0",
"terser-webpack-plugin": "^5.3.17",
"watchpack": "^2.5.1",
"webpack-sources": "^3.3.4"
},
"bin": {
"webpack": "bin/webpack.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependenciesMeta": {
"webpack-cli": {
"optional": true
}
}
},
"node_modules/webpack-5-chain": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/webpack-5-chain/-/webpack-5-chain-8.0.1.tgz",
......@@ -20768,78 +20298,6 @@
"node": ">=0.10.0"
}
},
"node_modules/webpack-sources": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.4.1.tgz",
"integrity": "sha512-eACpxRN02yaawnt+uUNIF7Qje6A9zArxBbcAJjK1PK3S9Ycg5jIuJ8pW4q8EMnwNZCEGltcjkRx1QzOxOkKD8A==",
"license": "MIT",
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/webpack/node_modules/ajv": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/webpack/node_modules/ajv-keywords": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3"
},
"peerDependencies": {
"ajv": "^8.8.2"
}
},
"node_modules/webpack/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT"
},
"node_modules/webpack/node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/webpack/node_modules/schema-utils": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
"integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.9",
"ajv": "^8.9.0",
"ajv-formats": "^2.1.1",
"ajv-keywords": "^5.1.0"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/whatwg-fetch": {
"version": "3.6.20",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
......@@ -21155,7 +20613,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
......
......@@ -8,8 +8,12 @@
"start": "npm run dev"
},
"dependencies": {
"@ant-design/icons": "file:../project_manager_new/node_modules/@ant-design/icons",
"@headlessui/react": "^2.2.10",
"@heroicons/react": "^2.2.0",
"@heroui-pro/react": "file:../project_manager_new/node_modules/@heroui-pro/react",
"@heroui/react": "file:../project_manager_new/node_modules/@heroui/react",
"@heroui/styles": "file:../project_manager_new/node_modules/@heroui/styles",
"@tailwindcss/postcss": "^4.2.0",
"@umijs/max": "^4.5.3",
"clsx": "^2.1.1",
......@@ -17,8 +21,12 @@
"dayjs": "^1.11.20",
"echarts": "^6.0.0",
"echarts-for-react": "^3.0.6",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"motion": "file:../project_manager_new/node_modules/motion",
"react": "file:../project_manager_new/node_modules/react",
"react-aria-components": "file:../project_manager_new/node_modules/react-aria-components",
"react-dom": "file:../project_manager_new/node_modules/react-dom",
"tailwind-merge": "file:../project_manager_new/node_modules/tailwind-merge",
"tailwind-variants": "file:../project_manager_new/node_modules/tailwind-variants",
"tailwindcss": "^4.2.0",
"umi-request": "^1.4.0",
"umi-request-progress": "^1.1.0",
......
import '@/global.less';
import '@/tailwind.css';
import '@/global.less';
import { defineApp } from 'umi';
export async function getInitialState() {
......
import BankOutlined from '@ant-design/icons/BankOutlined';
import BarChartOutlined from '@ant-design/icons/BarChartOutlined';
import AppstoreOutlined from '@ant-design/icons/AppstoreOutlined';
import CalendarOutlined from '@ant-design/icons/CalendarOutlined';
import CheckOutlined from '@ant-design/icons/CheckOutlined';
import CheckCircleOutlined from '@ant-design/icons/CheckCircleOutlined';
import DownloadOutlined from '@ant-design/icons/DownloadOutlined';
import DownOutlined from '@ant-design/icons/DownOutlined';
import ClockCircleOutlined from '@ant-design/icons/ClockCircleOutlined';
import CloseOutlined from '@ant-design/icons/CloseOutlined';
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
import DollarOutlined from '@ant-design/icons/DollarOutlined';
import DatabaseOutlined from '@ant-design/icons/DatabaseOutlined';
import EditOutlined from '@ant-design/icons/EditOutlined';
import EyeOutlined from '@ant-design/icons/EyeOutlined';
import ExportOutlined from '@ant-design/icons/ExportOutlined';
import FileTextOutlined from '@ant-design/icons/FileTextOutlined';
import FolderOpenOutlined from '@ant-design/icons/FolderOpenOutlined';
import PaperClipOutlined from '@ant-design/icons/PaperClipOutlined';
import LeftOutlined from '@ant-design/icons/LeftOutlined';
import LogoutOutlined from '@ant-design/icons/LogoutOutlined';
import InboxOutlined from '@ant-design/icons/InboxOutlined';
import InfoCircleOutlined from '@ant-design/icons/InfoCircleOutlined';
import MoreOutlined from '@ant-design/icons/MoreOutlined';
import MoonOutlined from '@ant-design/icons/MoonOutlined';
import OrderedListOutlined from '@ant-design/icons/OrderedListOutlined';
import PieChartOutlined from '@ant-design/icons/PieChartOutlined';
import PlusOutlined from '@ant-design/icons/PlusOutlined';
import ProjectOutlined from '@ant-design/icons/ProjectOutlined';
import ReloadOutlined from '@ant-design/icons/ReloadOutlined';
import RightOutlined from '@ant-design/icons/RightOutlined';
import RiseOutlined from '@ant-design/icons/RiseOutlined';
import SearchOutlined from '@ant-design/icons/SearchOutlined';
import SendOutlined from '@ant-design/icons/SendOutlined';
import SunOutlined from '@ant-design/icons/SunOutlined';
import TeamOutlined from '@ant-design/icons/TeamOutlined';
import UserOutlined from '@ant-design/icons/UserOutlined';
import UploadOutlined from '@ant-design/icons/UploadOutlined';
import WarningOutlined from '@ant-design/icons/WarningOutlined';
function iconStyle(size, style) {
return size === undefined ? style : { fontSize: size, ...style };
}
function withSize(Icon) {
return function AppIcon({ size, style, ...props }) {
return (
<Icon aria-hidden="true" {...props} style={iconStyle(size, style)} />
);
};
}
export const Bank = withSize(BankOutlined);
export const Archive = withSize(InboxOutlined);
export const AppGrid = withSize(AppstoreOutlined);
export const BarChart = withSize(BarChartOutlined);
export const Calendar = withSize(CalendarOutlined);
export const Check = withSize(CheckOutlined);
export const ChevronDown = withSize(DownOutlined);
export const ChevronRight = withSize(RightOutlined);
export const Clock = withSize(ClockCircleOutlined);
export const Close = withSize(CloseOutlined);
export const Delete = withSize(DeleteOutlined);
export const Dollar = withSize(DollarOutlined);
export const Database = withSize(DatabaseOutlined);
export const Edit = withSize(EditOutlined);
export const Eye = withSize(EyeOutlined);
export const OpenExternal = withSize(ExportOutlined);
export const FileText = withSize(FileTextOutlined);
export const Info = withSize(InfoCircleOutlined);
export const Folder = withSize(FolderOpenOutlined);
export const LogOut = withSize(LogoutOutlined);
export function SidebarPanel({ size = 20, style, ...props }) {
return (
<span
aria-hidden="true"
role="img"
{...props}
style={iconStyle(size, style)}
className={['anticon', props.className].filter(Boolean).join(' ')}
>
<svg
viewBox="0 0 16 16"
width="1em"
height="1em"
fill="none"
xmlns="http://www.w3.org/2000/svg"
focusable="false"
aria-hidden="true"
>
<path
clipRule="evenodd"
d="M6 3.5h6A1.5 1.5 0 0 1 13.5 5v6a1.5 1.5 0 0 1-1.5 1.5H6zm-1.5 0H4A1.5 1.5 0 0 0 2.5 5v6A1.5 1.5 0 0 0 4 12.5h.5zM1 5a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
</span>
);
}
export const More = withSize(MoreOutlined);
export const Moon = withSize(MoonOutlined);
export const Project = withSize(ProjectOutlined);
export const Search = withSize(SearchOutlined);
export const Send = withSize(SendOutlined);
export const Sun = withSize(SunOutlined);
export const Team = withSize(TeamOutlined);
export const User = withSize(UserOutlined);
export const Upload = withSize(UploadOutlined);
export const Back = withSize(LeftOutlined);
export const ArrowDownTrayIcon = withSize(DownloadOutlined);
export const ArrowPathIcon = withSize(ReloadOutlined);
export const ArrowTrendingUpIcon = withSize(RiseOutlined);
export const BanknotesIcon = withSize(DollarOutlined);
export const CalendarDaysIcon = Calendar;
export const ChartBarSquareIcon = BarChart;
export const ChartPieIcon = withSize(PieChartOutlined);
export const CheckCircleIcon = withSize(CheckCircleOutlined);
export const ChevronDownIcon = ChevronDown;
export const ChevronRightIcon = ChevronRight;
export const ClipboardDocumentListIcon = withSize(OrderedListOutlined);
export const ClockIcon = Clock;
export const ExclamationTriangleIcon = withSize(WarningOutlined);
export const FolderOpenIcon = Folder;
export const InformationCircleIcon = Info;
export const MagnifyingGlassIcon = Search;
export const PaperClipIcon = withSize(PaperClipOutlined);
export const PlusIcon = withSize(PlusOutlined);
export const UserGroupIcon = Team;
import xyLogo from '@/assets/identity/xy-logo-red.png';
import { Close } from '@/components/AppIcons';
import { APP_DISPLAY_NAME, APP_LOGIN_SUBTITLE, APP_ROUTE_NAME } from '@/constants/app';
import { getApplications } from '@/services/DataService';
import * as Headless from '@headlessui/react';
import {
ArrowTopRightOnSquareIcon,
ExclamationCircleIcon,
SparklesIcon,
} from '@heroicons/react/24/outline';
import clsx from 'clsx';
import { Modal, Spinner } from '@heroui/react';
import { useEffect, useMemo, useState } from 'react';
function normalizeLabelList(items) {
if (!Array.isArray(items)) {
return [];
}
return items
.map((item) => {
if (typeof item === 'string') {
return item;
}
return item?.display_name || item?.title || item?.name || '';
})
.filter(Boolean);
}
function resolveApplicationUrl(application) {
if (application?.name) {
return `/${application.name}`;
}
return '#';
function unwrapApplications(response) {
if (Array.isArray(response)) return response;
return response?.data || response?.applications || response?.result || [];
}
function normalizeApplication(application, fallback = {}) {
const name = application?.name || fallback?.name || '';
const current = name === APP_ROUTE_NAME;
return {
name,
title: application?.title || fallback?.title || application?.name || fallback?.name || '未命名应用',
description:
application?.description ||
fallback?.description ||
fallback?.title ||
application?.title ||
'进入对应应用继续处理业务',
logo: application?.logo || fallback?.logo || null,
url: resolveApplicationUrl({ name }),
access: application?.access || fallback?.access || '',
status: application?.status || fallback?.status || '',
type: application?.type || fallback?.type || '',
order: application?.order ?? fallback?.order ?? 0,
roles: normalizeLabelList(application?.roles?.length ? application.roles : fallback?.roles),
groups: normalizeLabelList(application?.groups?.length ? application.groups : fallback?.groups),
title: current ? APP_DISPLAY_NAME : application?.title || fallback?.title || name || '未命名应用',
description: current ? APP_LOGIN_SUBTITLE : application?.description || fallback?.description || application?.title || fallback?.title || name || '应用',
logo: current ? xyLogo : application?.logo || fallback?.logo || null,
url: application?.url || (name ? `/${name}` : '#'),
current,
};
}
function formatAccessLabel(access) {
if (access === 'public') return '公开';
if (access === 'protected') return '受限';
if (access === 'private') return '私有';
if (access === 'internal') return '内部';
return '';
}
function mergeApplications(fetchedApplications, fallbackApplications) {
const fallbackMap = new Map(
fallbackApplications.map((application) => [application.name, application]),
);
const normalizedFetched = fetchedApplications
.map((application) =>
normalizeApplication(application, fallbackMap.get(application?.name)),
)
.filter((application) => application.name);
const mergedMap = new Map(
normalizedFetched.map((application) => [application.name, application]),
);
fallbackApplications.forEach((application) => {
if (!application?.name || mergedMap.has(application.name)) {
return;
}
mergedMap.set(application.name, normalizeApplication(application, application));
});
return Array.from(mergedMap.values()).sort((left, right) => {
const orderDifference = Number(left.order || 0) - Number(right.order || 0);
if (orderDifference !== 0) {
return orderDifference;
}
return left.title.localeCompare(right.title, 'zh-CN');
});
}
function buildApplicationSections(applications, applicationGroups) {
if (!Array.isArray(applicationGroups) || applicationGroups.length === 0) {
return [
{
key: 'all',
label: '全部应用',
applications,
},
];
}
const applicationMap = new Map(
applications.map((application) => [application.name, application]),
);
const groupedApplicationNames = new Set();
const sections = applicationGroups
.map((group, index) => {
const groupApplications = Array.isArray(group?.applications)
? group.applications
.map((name) => {
const application = applicationMap.get(name);
if (application) {
groupedApplicationNames.add(name);
}
return application;
})
.filter(Boolean)
: [];
return {
key: group?.label || `group-${index}`,
label: group?.label || '应用分组',
applications: groupApplications,
};
})
.filter((section) => section.applications.length > 0);
const ungroupedApplications = applications.filter(
(application) => !groupedApplicationNames.has(application.name),
);
if (ungroupedApplications.length > 0) {
sections.push({
key: 'ungrouped',
label: '其他应用',
applications: ungroupedApplications,
function mergeApplications(fetched, fallback) {
const fallbackMap = new Map(fallback.map((item) => [item.name, item]));
const merged = new Map();
fetched.forEach((item) => {
const normalized = normalizeApplication(item, fallbackMap.get(item?.name));
if (normalized.name) merged.set(normalized.name, normalized);
});
}
return sections;
}
function isCurrentApplication(application) {
if (typeof window === 'undefined') {
return false;
}
const currentPath = window.location.pathname;
const applicationRoot = application?.name ? `/${application.name}` : '';
if (application?.url?.startsWith('http')) {
try {
const url = new URL(application.url);
return url.pathname === currentPath;
} catch (error) {
return false;
}
}
return (
application?.url === currentPath ||
(!!applicationRoot &&
(currentPath === applicationRoot || currentPath.startsWith(`${applicationRoot}/`)))
);
fallback.forEach((item) => { if (item.name && !merged.has(item.name)) merged.set(item.name, item); });
return Array.from(merged.values());
}
function ApplicationCard({ application }) {
const current = isCurrentApplication(application);
const target = application?.url?.startsWith('http') ? '_blank' : undefined;
const accessLabel = formatAccessLabel(application.access);
const firstRole = application.roles?.[0];
const firstGroup = application.groups?.[0];
const target = application.url?.startsWith('http') ? '_blank' : undefined;
return (
<a
href={application.url}
target={target}
rel={target ? 'noreferrer' : undefined}
className={clsx(
'group relative min-h-[84px] overflow-hidden rounded-2xl border border-zinc-200/80 bg-[linear-gradient(180deg,rgba(255,255,255,0.98),rgba(248,250,252,0.94))] p-2.5 shadow-[0_10px_24px_rgba(15,23,42,0.06)] transition duration-200 ease-out hover:-translate-y-0.5 hover:border-rose-200 hover:shadow-[0_16px_32px_rgba(244,63,94,0.12)] focus:outline-none focus-visible:ring-2 focus-visible:ring-rose-400/60',
current && 'border-rose-300 bg-[linear-gradient(180deg,rgba(255,241,242,0.95),rgba(255,255,255,0.96))]',
)}
>
<div className="absolute inset-x-0 top-0 h-14 bg-[radial-gradient(circle_at_top_right,rgba(251,113,133,0.14),transparent_50%)] opacity-0 transition duration-200 group-hover:opacity-100" />
<div className="relative flex items-start justify-between gap-3">
<div className="flex min-w-0 items-center gap-2.5">
{application.logo ? (
<img
src={application.logo}
alt={application.title}
className="size-9 rounded-xl border border-white/80 bg-white object-cover shadow-sm"
/>
) : (
<div className="flex size-9 shrink-0 items-center justify-center rounded-xl bg-[linear-gradient(135deg,#b42318,#f43f5e)] text-sm font-semibold text-white shadow-[0_8px_18px_rgba(180,35,24,0.22)]">
{application.title.slice(0, 1)}
</div>
)}
<div className="min-w-0">
<div className="truncate text-sm font-semibold text-slate-950">
{application.title}
</div>
<div className="mt-0.5 truncate text-[11px] leading-4 text-slate-500">
{application.description}
</div>
</div>
</div>
<div className="flex shrink-0 items-center gap-2">
{current ? (
<span className="rounded-full bg-rose-100 px-2 py-0.5 text-[11px] font-medium text-rose-700">
当前
</span>
) : null}
<span className="rounded-full bg-slate-100 p-1.5 text-slate-500 transition group-hover:bg-rose-50 group-hover:text-rose-600">
<ArrowTopRightOnSquareIcon className="size-3.5" />
</span>
</div>
</div>
{accessLabel || firstRole || firstGroup ? (
<div className="relative mt-2 flex flex-wrap gap-1.5">
{accessLabel ? (
<span className="rounded-full bg-zinc-100 px-2 py-0.5 text-[11px] font-medium text-zinc-700">
{accessLabel}
</span>
) : null}
{firstRole ? (
<span className="rounded-full bg-slate-100 px-2 py-0.5 text-[11px] font-medium text-slate-700">
{firstRole}
</span>
) : null}
{firstGroup ? (
<span className="rounded-full bg-rose-50 px-2 py-0.5 text-[11px] font-medium text-rose-700">
{firstGroup}
</span>
) : null}
</div>
) : null}
<a className="app-switcher-card group flex min-w-0 items-center gap-3 rounded-xl border border-[var(--rc-border-default)] bg-[var(--rc-bg-subtle)] px-3 py-3 text-left transition hover:bg-[var(--accent-soft)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--focus)]" data-current={application.current ? 'true' : undefined} href={application.url} rel={target ? 'noreferrer' : undefined} target={target}>
{application.logo ? <img alt={application.title} className="app-switcher-card__logo" src={application.logo} /> : <span className="app-switcher-card__initial">{application.title.slice(0, 1)}</span>}
<span className="min-w-0 flex-1"><span className="block truncate text-sm font-medium leading-5 text-[var(--rc-text-primary)]">{application.title}</span><span className="mt-0.5 block truncate text-xs leading-4 text-[var(--rc-text-muted)]">{application.description || application.name || '应用'}</span></span>
{application.current ? <span className="shrink-0 rounded-full bg-[var(--rc-brand-primary-soft)] px-2 py-0.5 text-xs font-medium text-[var(--rc-brand-primary)]">当前</span> : null}
</a>
);
}
function LoadingSkeleton() {
return (
<div className="grid gap-2.5 sm:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-6">
{Array.from({ length: 6 }).map((_, index) => (
<div
key={index}
className="min-h-[84px] rounded-2xl border border-zinc-200 bg-white/90 p-2.5 shadow-[0_10px_24px_rgba(15,23,42,0.05)]"
>
<div className="animate-pulse">
<div className="flex items-start gap-3">
<div className="size-9 rounded-xl bg-zinc-200" />
<div className="min-w-0 flex-1 space-y-2">
<div className="h-4 w-2/5 rounded-full bg-zinc-200" />
<div className="h-3 w-full rounded-full bg-zinc-100" />
</div>
</div>
<div className="mt-2 flex gap-1.5">
<div className="h-5 w-12 rounded-full bg-zinc-100" />
<div className="h-5 w-14 rounded-full bg-zinc-100" />
</div>
</div>
</div>
))}
</div>
);
}
export default function ApplicationSwitcherDialog({
open,
onClose,
userInfo,
siteInfo,
}) {
const fallbackApplications = useMemo(() => {
if (!Array.isArray(userInfo?.applications)) {
return [];
}
return userInfo.applications
.map((application) =>
normalizeApplication(application, {
...application,
description: application?.title,
}),
)
.filter((application) => application.name);
}, [userInfo]);
const [applications, setApplications] = useState(fallbackApplications);
const [status, setStatus] = useState(
fallbackApplications.length > 0 ? 'success' : 'idle',
);
const [errorMessage, setErrorMessage] = useState('');
export default function ApplicationSwitcherDialog({ open, onClose, userInfo }) {
const fallback = useMemo(() => (userInfo?.applications || []).map((item) => normalizeApplication(item, item)).filter((item) => item.name), [userInfo]);
const [applications, setApplications] = useState(fallback);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!open) {
return;
}
if (!open) return undefined;
let cancelled = false;
(async () => {
setStatus(fallbackApplications.length > 0 ? 'loading' : 'loading');
setErrorMessage('');
try {
const fetchedApplications = await getApplications();
if (cancelled) {
return;
}
const mergedApplications = mergeApplications(
Array.isArray(fetchedApplications) ? fetchedApplications : [],
fallbackApplications,
);
setApplications(mergedApplications);
setStatus('success');
} catch (error) {
if (cancelled) {
return;
}
setApplications(fallbackApplications);
setStatus(fallbackApplications.length > 0 ? 'success' : 'error');
setErrorMessage(error?.message || '加载应用列表失败');
}
})();
return () => {
cancelled = true;
};
}, [open, fallbackApplications]);
const sections = useMemo(() => {
return buildApplicationSections(applications, siteInfo?.application_groups || []);
}, [applications, siteInfo]);
setLoading(true);
getApplications().then((response) => { if (!cancelled) setApplications(mergeApplications(unwrapApplications(response), fallback)); }).catch(() => { if (!cancelled) setApplications(fallback); }).finally(() => { if (!cancelled) setLoading(false); });
return () => { cancelled = true; };
}, [fallback, open]);
return (
<Headless.Dialog open={open} onClose={onClose} className="relative z-[200]">
<div className="fixed inset-0 bg-slate-950/35 backdrop-blur-sm" aria-hidden="true" />
<div className="fixed inset-0 overflow-y-auto px-3 py-6 sm:px-6 sm:py-10">
<div className="mx-auto flex min-h-full w-full max-w-6xl items-center justify-center">
<Headless.DialogPanel className="w-full overflow-hidden rounded-[30px] border border-white/70 bg-[linear-gradient(180deg,rgba(255,255,255,0.96),rgba(248,250,252,0.94))] shadow-[0_28px_80px_rgba(15,23,42,0.18)] ring-1 ring-slate-950/5">
<div className="border-b border-zinc-200/80 bg-[linear-gradient(135deg,rgba(244,63,94,0.08),rgba(255,255,255,0.6))] px-5 py-4 sm:px-7 sm:py-5">
<div className="flex flex-wrap items-start justify-between gap-4">
<Modal.Root isOpen={open} onOpenChange={(isOpen) => { if (!isOpen) onClose?.(); }}>
<Modal.Trigger aria-hidden="true" aria-label="打开应用切换" className="sr-only" tabIndex={-1} />
<Modal.Backdrop className="rc-dialog-backdrop" variant="blur">
<Modal.Container placement="center" scroll="inside" size="xl">
<Modal.Dialog className="app-switcher-dialog rc-dialog-panel rc-dialog-panel--xl">
<Modal.CloseTrigger aria-label="关闭应用切换" className="rc-dialog-close"><Close size={16} /></Modal.CloseTrigger>
<Modal.Header className="app-switcher-dialog__header">
<div className="min-w-0">
<div className="inline-flex items-center gap-2 rounded-full bg-rose-50 px-3 py-1 text-xs font-medium text-rose-700">
<SparklesIcon className="size-4" />
应用切换
</div>
<Headless.DialogTitle className="mt-3 text-xl font-semibold tracking-tight text-slate-950 sm:text-2xl">
继续前往其他工作区
</Headless.DialogTitle>
<p className="mt-2 max-w-2xl text-sm leading-6 text-slate-500">
当前账号可访问的应用都整理在这里,支持从流程监控快速跳转,不需要返回门户重新进入。
</p>
</div>
<button
type="button"
onClick={onClose}
className="rounded-full border border-zinc-200 bg-white px-4 py-2 text-sm font-medium text-slate-600 transition hover:border-rose-200 hover:text-rose-700"
>
关闭
</button>
</div>
</div>
<div className="max-h-[75vh] overflow-y-auto px-5 py-5 sm:px-7 sm:py-6">
{status === 'loading' ? <LoadingSkeleton /> : null}
{status === 'error' ? (
<div className="rounded-2xl border border-amber-200 bg-amber-50/80 p-4 text-sm text-amber-800">
<div className="flex items-start gap-3">
<ExclamationCircleIcon className="mt-0.5 size-5 shrink-0" />
<div>
<div className="font-medium">应用列表暂时加载失败</div>
<div className="mt-1">{errorMessage || '请稍后再试'}</div>
</div>
</div>
</div>
) : null}
{status !== 'loading' && sections.every((section) => section.applications.length === 0) ? (
<div className="rounded-3xl border border-dashed border-zinc-300 bg-white/90 px-6 py-14 text-center">
<div className="mx-auto flex size-14 items-center justify-center rounded-2xl bg-slate-100 text-slate-500">
<SparklesIcon className="size-7" />
</div>
<div className="mt-4 text-lg font-semibold text-slate-900">
暂无可切换的应用
</div>
<div className="mt-2 text-sm text-slate-500">
当前账号还没有配置其它工作区入口。
</div>
</div>
) : null}
<div className="space-y-6">
{sections.map((section) => (
<section key={section.key}>
<div className="mb-3 flex items-center justify-between gap-3">
<h3 className="text-sm font-semibold tracking-wide text-slate-900">
{section.label}
</h3>
<div className="text-xs text-slate-400">
{section.applications.length} 个应用
</div>
</div>
<div className="grid gap-2.5 sm:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-6">
{section.applications.map((application) => (
<ApplicationCard key={application.name} application={application} />
))}
</div>
</section>
))}
</div>
</div>
</Headless.DialogPanel>
</div>
</div>
</Headless.Dialog>
<Modal.Heading className="rc-dialog-title">应用切换</Modal.Heading>
<p className="app-switcher-dialog__description">选择你可访问的应用</p>
</div>
</Modal.Header>
<Modal.Body className="app-switcher-dialog__body">
{loading && applications.length === 0 ? <div className="app-switcher-state"><Spinner size="sm" /><span>应用加载中...</span></div> : null}
{!loading && applications.length === 0 ? <p className="app-switcher-state">暂无可切换应用</p> : null}
<div className="app-switcher-grid">{applications.map((application) => <ApplicationCard application={application} key={application.name} />)}</div>
</Modal.Body>
</Modal.Dialog>
</Modal.Container>
</Modal.Backdrop>
</Modal.Root>
);
}
import {
Archive,
ChevronDown,
Close,
Database,
FileText,
} from '@/components/AppIcons';
import { ActionBar } from '@heroui-pro/react/action-bar';
import { DataGrid } from '@heroui-pro/react/data-grid';
import { EmptyState } from '@heroui-pro/react/empty-state';
import {
Button,
Chip,
Label,
ListBox,
Pagination,
Select,
Separator,
Spinner,
} from '@heroui/react';
import { Tooltip } from '@heroui/react/tooltip';
import { useEffect, useRef, useState } from 'react';
export const DATA_TABLE_PAGE_SIZE_OPTIONS = Object.freeze([10, 20, 50]);
export function normalizeDataTablePageSize(
value,
fallback = DATA_TABLE_PAGE_SIZE_OPTIONS[0],
) {
const nextValue = Number(value);
return DATA_TABLE_PAGE_SIZE_OPTIONS.includes(nextValue)
? nextValue
: fallback;
}
export function DataGridEmptyIllustration() {
return (
<div className="data-grid-empty-illustration" aria-hidden="true">
<span className="data-grid-empty-illustration-back">
<Database size={28} />
</span>
<span className="data-grid-empty-illustration-front">
<FileText size={32} />
<span className="data-grid-empty-illustration-lines">
<i />
<i />
<i />
</span>
</span>
<span className="data-grid-empty-illustration-badge">
<Archive size={18} />
</span>
</div>
);
}
function normalizeDataTableEmptyTitle(title) {
const text = String(title || '').trim();
if (!text) {
return '暂无数据';
}
if (/^(暂无数据){2,}$/.test(text)) {
return '暂无数据';
}
return title;
}
export function DataTableEmptyState({ title = '暂无数据' }) {
const resolvedTitle = normalizeDataTableEmptyTitle(title);
return (
<EmptyState className="data-grid-empty-state" size="md">
<EmptyState.Header>
<EmptyState.Media>
<DataGridEmptyIllustration />
</EmptyState.Media>
<EmptyState.Title>{resolvedTitle}</EmptyState.Title>
</EmptyState.Header>
</EmptyState>
);
}
function DataTableLoadingState() {
return (
<div className="data-grid-empty-loading" aria-label="数据加载中">
<Spinner size="sm" />
</div>
);
}
function resolveRowKey(row, index, getRowKey) {
const explicitKey = getRowKey?.(row, index);
if (explicitKey) {
return String(explicitKey);
}
return String(
row?.recordKey ??
row?.recordId ??
row?.id ??
row?._id ??
row?.name ??
index,
);
}
function resolveColumnWidth(width) {
if (typeof width === 'number') {
return width;
}
const match = String(width || '').match(/(\d+)px/);
return match ? Number(match[1]) : undefined;
}
export function DataTable({
ariaLabel = '数据表格',
columns,
rows,
isLoading = false,
getRowKey,
selectedRowKeys,
onSelectedRowKeysChange,
emptyTitle = '暂无数据',
emptyDescription = '当前筛选条件下没有可显示的记录。',
className = '',
isWide,
contentMinWidth,
disabledRowKeys,
onRowAction,
sortDescriptor,
onSortChange,
renderSelectionActions,
}) {
const gridViewportRef = useRef(null);
const [gridViewportWidth, setGridViewportWidth] = useState(null);
const selectionEnabled = Boolean(selectedRowKeys && onSelectedRowKeysChange);
const selectedKeys = selectionEnabled ? new Set(selectedRowKeys) : undefined;
const naturalMinWidth = columns.reduce((total, column) => {
const isActions = column.isActions || column.key === 'actions';
const width = resolveColumnWidth(column.width);
return total + (column.minWidth || width || (isActions ? 104 : 128));
}, selectionEnabled ? 40 : 0);
const wide =
isWide ??
(gridViewportWidth !== null && naturalMinWidth > gridViewportWidth + 1);
useEffect(() => {
const element = gridViewportRef.current;
if (!element) return undefined;
const updateWidth = () => {
setGridViewportWidth(element.getBoundingClientRect().width);
};
updateWidth();
const observer = new ResizeObserver(updateWidth);
observer.observe(element);
return () => observer.disconnect();
}, []);
const gridColumns = columns.map((column, index) => {
const isActions = column.isActions || column.key === 'actions';
const isPrimary = column.isPrimary ?? (!isActions && index === 0);
const width = resolveColumnWidth(column.width);
const pinned = wide
? column.pin || (isActions ? 'end' : index === 0 ? 'start' : undefined)
: undefined;
const minWidth =
column.minWidth || width || (isActions ? 104 : pinned ? 220 : 128);
return {
id: String(column.key),
header: column.title,
accessorKey: typeof column.key === 'string' ? column.key : undefined,
align: column.align,
cell: (row) => {
const value = column.render ? column.render(row) : row?.[column.key];
if (value === undefined || value === null || value === '') {
return '-';
}
if (typeof value === 'string' || typeof value === 'number') {
return (
<DataTableText
text={value}
className={
isPrimary ? 'pm-data-grid-primary' : 'pm-data-grid-text'
}
/>
);
}
return value;
},
isRowHeader: index === 0,
allowsSorting: column.sortable === true,
headerClassName: column.headerClassName,
cellClassName: [
'pm-data-grid-cell',
isPrimary ? 'pm-data-grid-cell--primary' : '',
column.cellClassName || '',
]
.filter(Boolean)
.join(' '),
allowsResizing: true,
minWidth,
width: width || (pinned ? minWidth : undefined),
pinned,
};
});
const resolvedContentMinWidth = wide
? Math.max(
Number(contentMinWidth) || 0,
gridColumns.reduce(
(total, column) => total + (column.width || column.minWidth || 128),
selectionEnabled ? 40 : 0,
),
)
: undefined;
const handleSelectionChange = (selection) => {
if (!onSelectedRowKeysChange) {
return;
}
if (selection === 'all') {
onSelectedRowKeysChange(
rows.map((row, index) => resolveRowKey(row, index, getRowKey)),
);
return;
}
onSelectedRowKeysChange(Array.from(selection).map(String));
};
const emptyState = <DataTableEmptyState title={emptyTitle} />;
return (
<div
className={['pm-data-table-selection-scope', className]
.filter(Boolean)
.join(' ')}
>
<div
ref={gridViewportRef}
className={[
'pm-data-grid-wrap',
rows.length > 0 ? 'pm-data-grid-wrap--populated' : '',
!isLoading && rows.length === 0 ? 'pm-data-grid-wrap--empty' : '',
]
.filter(Boolean)
.join(' ')}
style={
resolvedContentMinWidth
? {
'--pm-data-grid-content-min-width': `${resolvedContentMinWidth}px`,
}
: undefined
}
>
{isLoading && rows.length > 0 ? (
<div className="pm-data-grid-loading" aria-label="数据加载中">
<Spinner size="sm" />
</div>
) : null}
<DataGrid
showSelectionCheckboxes={selectionEnabled}
allowsColumnResize
aria-label={ariaLabel}
className="pm-data-grid"
columns={gridColumns}
contentClassName={
wide
? 'pm-data-grid-content pm-data-grid-content--wide'
: 'pm-data-grid-content'
}
data={rows}
disabledKeys={disabledRowKeys}
getRowId={(row) => resolveRowKey(row, rows.indexOf(row), getRowKey)}
renderEmptyState={() =>
isLoading ? (
<DataTableLoadingState />
) : (
<span
className="pm-data-grid-native-empty-fallback"
aria-hidden="true"
/>
)
}
scrollContainerClassName="pm-data-grid-scroll"
selectedKeys={selectedKeys}
selectionMode={selectionEnabled ? 'multiple' : 'none'}
variant="primary"
verticalAlign="middle"
onRowAction={onRowAction}
sortDescriptor={sortDescriptor}
onSortChange={onSortChange}
onSelectionChange={
selectionEnabled ? handleSelectionChange : undefined
}
/>
{!isLoading && rows.length === 0 ? (
<div className="pm-data-grid-empty-overlay">{emptyState}</div>
) : null}
</div>
{selectionEnabled ? (
<DataTableActionBar
selectedCount={selectedRowKeys.length}
onClear={() => onSelectedRowKeysChange([])}
>
{renderSelectionActions ? (
renderSelectionActions(selectedRowKeys)
) : (
<span className="text-sm">已选择当前页记录</span>
)}
</DataTableActionBar>
) : null}
</div>
);
}
export function DataTableActionBar({
selectedCount,
onClear,
children,
ariaLabel = '批量操作',
}) {
return (
<ActionBar
className="pm-data-table-action-bar"
aria-label={ariaLabel}
isOpen={selectedCount > 0}
>
<ActionBar.Prefix>
<Chip className="shrink-0 tabular-nums" size="sm">
{selectedCount}
</Chip>
</ActionBar.Prefix>
<Separator orientation="vertical" />
<ActionBar.Content>{children}</ActionBar.Content>
<Separator orientation="vertical" />
<ActionBar.Suffix>
<Button
isIconOnly
aria-label="清空选择"
size="sm"
variant="ghost"
onPress={onClear}
>
<Close size={16} />
</Button>
</ActionBar.Suffix>
</ActionBar>
);
}
export function DataTableText({
children,
text,
className = '',
showTooltip = false,
}) {
const fullText = String(text ?? children ?? '').trim();
const trigger = (
<span
className={['pm-data-grid-truncate', className]
.filter(Boolean)
.join(' ')}
>
{children ?? (fullText || '-')}
</span>
);
if (!fullText || fullText === '-' || (!showTooltip && fullText.length <= 18)) {
return trigger;
}
return (
<Tooltip delay={0}>
{trigger}
<Tooltip.Content className="pm-data-grid-text-tooltip">{fullText}</Tooltip.Content>
</Tooltip>
);
}
export function DataTablePrimaryText({
children,
text,
className = '',
showTooltip = false,
}) {
return (
<DataTableText
text={text}
showTooltip={showTooltip}
className={['pm-data-grid-primary', className].filter(Boolean).join(' ')}
>
{children}
</DataTableText>
);
}
export function DataTablePrimaryAction({
children,
text,
className = '',
onPress,
isDisabled = false,
ariaLabel,
}) {
const fullText = String(text ?? children ?? '').trim() || '-';
return (
<Tooltip>
<Button
aria-label={ariaLabel || fullText}
className={['pm-data-grid-primary-action', className]
.filter(Boolean)
.join(' ')}
isDisabled={isDisabled}
size="sm"
variant="ghost"
onPress={onPress}
>
<span className="pm-data-grid-primary pm-data-grid-truncate">
{children ?? fullText}
</span>
</Button>
<Tooltip.Content>{fullText}</Tooltip.Content>
</Tooltip>
);
}
export function DataTableSecondaryText({
children,
text,
className = '',
showTooltip = false,
}) {
return (
<DataTableText
text={text}
showTooltip={showTooltip}
className={['pm-data-grid-secondary', className]
.filter(Boolean)
.join(' ')}
>
{children}
</DataTableText>
);
}
export function DataTableTextPair({
primary,
secondary,
className = '',
primaryClassName = '',
secondaryClassName = '',
}) {
const primaryText = String(primary ?? '').trim() || '-';
const hasSecondary =
secondary !== undefined && secondary !== null && secondary !== '';
const secondaryText = hasSecondary ? String(secondary).trim() : '';
const tooltipText = [primaryText, secondaryText].filter(Boolean).join('\n');
const content = (
<span className={['pm-data-grid-text-pair pm-data-grid-tooltip-trigger', className].filter(Boolean).join(' ')} tabIndex={0}>
<span className={['pm-data-grid-truncate pm-data-grid-primary', primaryClassName].filter(Boolean).join(' ')}>{primaryText}</span>
{hasSecondary ? <span className={['pm-data-grid-truncate pm-data-grid-secondary', secondaryClassName].filter(Boolean).join(' ')}>{secondaryText}</span> : null}
</span>
);
return (
<Tooltip delay={0}>
{content}
<Tooltip.Content className="pm-data-grid-text-tooltip whitespace-pre-line">{tooltipText}</Tooltip.Content>
</Tooltip>
);
}
export function DataTablePersonIdentity({ name, account, className = '' }) {
const primaryText = String(name || '').trim() || '-';
const secondaryText = String(account || '').trim();
return (
<DataTableTextPair
primary={primaryText}
secondary={secondaryText}
className={['pm-data-grid-person-identity', className]
.filter(Boolean)
.join(' ')}
/>
);
}
export function DataTableChip({ children, text, className = '' }) {
const fullText = String(text ?? children ?? '').trim();
const trigger = (
<span
className={['pm-data-grid-chip', className].filter(Boolean).join(' ')}
>
<span className="pm-data-grid-chip__label">
{children ?? (fullText || '-')}
</span>
</span>
);
if (!fullText || fullText === '-' || fullText.length <= 18) {
return trigger;
}
return (
<Tooltip delay={0}>
{trigger}
<Tooltip.Content>{fullText}</Tooltip.Content>
</Tooltip>
);
}
export function DataTablePagination({
page,
pageSize,
total,
onPageChange,
pageSizeOptions = DATA_TABLE_PAGE_SIZE_OPTIONS,
onPageSizeChange,
isDisabled = false,
itemLabel = '条',
}) {
const totalPages = Math.max(1, Math.ceil(total / pageSize));
const pageWindowStart = Math.max(0, Math.min(page - 2, totalPages - 5));
const pageWindowEnd = total
? Math.min(totalPages, pageWindowStart + 5)
: pageWindowStart;
const hasPageSizeControl =
pageSizeOptions.length > 0 && Boolean(onPageSizeChange);
return (
<div className="pm-pagination-bar">
<Pagination size="sm" className="w-full">
<Pagination.Summary>
共 {total} {itemLabel},
{hasPageSizeControl ? null : <>每页 {pageSize} 条,</>}第{' '}
{total ? page + 1 : 0} / {total ? totalPages : 0} 页
</Pagination.Summary>
<Pagination.Content>
<Pagination.Item>
<Pagination.Previous
isDisabled={isDisabled || page <= 0}
onPress={() => onPageChange(page - 1)}
>
<Pagination.PreviousIcon />
<span>上一页</span>
</Pagination.Previous>
</Pagination.Item>
{Array.from(
{ length: pageWindowEnd - pageWindowStart },
(_, index) => {
const pageIndex = pageWindowStart + index;
return (
<Pagination.Item key={pageIndex}>
<Pagination.Link
isActive={pageIndex === page}
isDisabled={isDisabled}
onPress={() => onPageChange(pageIndex)}
>
{pageIndex + 1}
</Pagination.Link>
</Pagination.Item>
);
},
)}
<Pagination.Item>
<Pagination.Next
isDisabled={isDisabled || page + 1 >= totalPages || total === 0}
onPress={() => onPageChange(page + 1)}
>
<span>下一页</span>
<Pagination.NextIcon />
</Pagination.Next>
</Pagination.Item>
</Pagination.Content>
</Pagination>
{hasPageSizeControl ? (
<div className="pm-pagination-controls">
<Select
aria-label="每页条数"
className="pm-pagination-page-size-select"
isDisabled={isDisabled}
selectedKey={String(pageSize)}
variant="secondary"
onSelectionChange={(key) => onPageSizeChange(Number(key))}
>
<Select.Trigger>
<span className="pm-pagination-page-size-copy">
<span className="pm-pagination-page-size-label">每页</span>
<Select.Value className="pm-pagination-page-size-value">
{pageSize} 条/页
</Select.Value>
</span>
<ChevronDown className="pm-pagination-page-size-indicator" />
</Select.Trigger>
<Select.Popover
className="rc-form-select__popover rc-filter-select__popover"
placement="bottom start"
>
<ListBox aria-label="每页条数">
{pageSizeOptions.map((option) => (
<ListBox.Item
key={option}
id={String(option)}
className="rc-form-select__option rc-filter-select__option"
textValue={`${option} 条/页`}
>
<Label>{option} 条/页</Label>
<ListBox.ItemIndicator />
</ListBox.Item>
))}
</ListBox>
</Select.Popover>
</Select>
</div>
) : null}
</div>
);
}
import { Upload } from '@/components/AppIcons';
import { DropZone } from '@heroui-pro/react/drop-zone';
import { useCallback } from 'react';
export default function FileUploadField({
accept,
ariaLabel = '选择文件',
className = '',
description = '将文件拖到此处,或从本地选择。',
isDisabled = false,
fileName = '',
onFilesChange,
triggerLabel = '选择文件',
}) {
const emitFiles = useCallback(
(files) => {
if (!isDisabled) {
onFilesChange?.(files);
}
},
[isDisabled, onFilesChange],
);
const handleDrop = useCallback(
async (event) => {
const fileItems = Array.from(event?.items || []).filter(
(item) => item?.kind === 'file' && typeof item.getFile === 'function',
);
if (fileItems.length === 0) return;
const files = await Promise.all(fileItems.map((item) => item.getFile()));
emitFiles(files.slice(0, 1));
},
[emitFiles],
);
return (
<DropZone className={`rc-file-upload ${className}`.trim()}>
<DropZone.Area
aria-label={ariaLabel}
isDisabled={isDisabled}
onDrop={handleDrop}
>
<DropZone.Input
accept={accept}
disabled={isDisabled}
onSelect={emitFiles}
/>
<DropZone.Icon>
<Upload aria-hidden="true" />
</DropZone.Icon>
<DropZone.Label>{fileName || '将文件拖到此处'}</DropZone.Label>
<DropZone.Description>
{fileName ? '已选择文件,可重新选择进行替换。' : description}
</DropZone.Description>
<DropZone.Trigger isDisabled={isDisabled}>{triggerLabel}</DropZone.Trigger>
</DropZone.Area>
</DropZone>
);
}
import { Label, ListBox, Select } from '@heroui/react';
import clsx from 'clsx';
import { useMemo } from 'react';
import { ChevronDown } from './AppIcons';
const EMPTY_OPTION_KEY = '__rc_filter_empty__';
function normalizeOption(option) {
if (option && typeof option === 'object') {
return {
label: String(option.label ?? option.value ?? ''),
value: String(option.value ?? ''),
disabled: Boolean(option.disabled),
};
}
return {
label: String(option ?? ''),
value: String(option ?? ''),
disabled: false,
};
}
export default function FilterSelect({
'aria-label': ariaLabel,
className,
isDisabled = false,
emptyText = '暂无可选项',
onChange,
options = [],
placeholder = '请选择',
value = '',
}) {
const normalizedOptions = useMemo(
() => options.map(normalizeOption),
[options],
);
const selectedValue = String(value ?? '');
const selectedOption = normalizedOptions.find(
(option) => option.value === selectedValue,
);
const hasValue = selectedValue !== '';
const displayLabel = selectedOption?.label || (hasValue ? selectedValue : placeholder);
const selectedKey = selectedValue === '' ? EMPTY_OPTION_KEY : selectedValue;
return (
<Select
aria-label={ariaLabel || placeholder}
className={clsx('rc-filter-select', className)}
fullWidth
isDisabled={isDisabled}
selectedKey={selectedKey}
variant="secondary"
onSelectionChange={(key) => {
const nextValue = key === EMPTY_OPTION_KEY ? '' : String(key ?? '');
onChange?.(nextValue);
}}
>
<Select.Trigger className="rc-filter-select__trigger" data-has-value={hasValue ? 'true' : undefined}>
<Select.Value className="rc-filter-select__value">
<span
className={clsx(
'block min-w-0 truncate',
!hasValue && 'rc-filter-select__placeholder',
)}
title={displayLabel}
>
{displayLabel}
</span>
</Select.Value>
<ChevronDown className="rc-filter-select__indicator" />
</Select.Trigger>
<Select.Popover
className="rc-filter-select__popover"
placement="bottom start"
>
<ListBox
aria-label={ariaLabel || placeholder}
className="rc-filter-select__listbox"
>
{normalizedOptions.length > 0 ? (
normalizedOptions.map((option) => (
<ListBox.Item
key={option.value || EMPTY_OPTION_KEY}
id={option.value || EMPTY_OPTION_KEY}
className="rc-filter-select__option"
isDisabled={option.disabled}
textValue={option.label}
>
<Label
className="rc-filter-select__option-label"
title={option.label}
>
{option.label}
</Label>
<ListBox.ItemIndicator className="rc-filter-select__option-indicator" />
</ListBox.Item>
))
) : (
<div className="rc-filter-select__empty">{emptyText}</div>
)}
</ListBox>
</Select.Popover>
</Select>
);
}
import { Back, Check, Clock, Close, Delete, Edit, Eye, More, Send } from '@/components/AppIcons';
import { Button, Dropdown, Tooltip } from '@heroui/react';
import clsx from 'clsx';
import { Children, Fragment, isValidElement } from 'react';
const ACTION_TONE_CLASSES = {
approve: 'pm-table-action--approve',
delete: 'pm-table-action--danger',
edit: 'pm-table-action--default',
history: 'pm-table-action--default',
reject: 'pm-table-action--danger',
revoke: 'pm-table-action--warning',
submit: 'pm-table-action--primary',
view: 'pm-table-action--default',
};
export const TABLE_ACTION_ICONS = {
approve: Check,
delete: Delete,
edit: Edit,
history: Clock,
reject: Close,
revoke: Back,
submit: Send,
view: Eye,
};
function flattenActions(children) {
const actions = [];
Children.forEach(children, (child) => {
if (!child) return;
if (isValidElement(child) && child.type === Fragment) {
actions.push(...flattenActions(child.props.children));
return;
}
actions.push(child);
});
return actions;
}
export function TableActionGroup({ className, children, ...props }) {
const actions = flattenActions(children);
const hasOverflow = actions.length >= 4;
const visibleActions = hasOverflow ? actions.slice(0, 2) : actions;
const overflowActions = hasOverflow ? actions.slice(2) : [];
return (
<div {...props} className={clsx('icon-actions flex flex-nowrap items-center gap-1.5 whitespace-nowrap', className)}>
{visibleActions}
{overflowActions.length > 0 ? (
<Dropdown>
<Tooltip delay={0}>
<Dropdown.Trigger aria-label="更多操作" className="pm-table-action pm-table-action--default"><More size={16} /></Dropdown.Trigger>
<Tooltip.Content>更多操作</Tooltip.Content>
</Tooltip>
<Dropdown.Popover className="rc-dropdown-popover min-w-36" placement="bottom end">
<Dropdown.Menu aria-label="更多操作" onAction={(key) => overflowActions[Number(key)]?.props?.onPress?.()}>
{overflowActions.map((action, index) => {
const label = action?.props?.label || '操作 ' + (index + 3);
const Icon = action?.props?.icon;
return (
<Dropdown.Item id={String(index)} isDisabled={action?.props?.isDisabled} key={label + '-' + index} className="rc-dropdown-option" textValue={label}>
<span className="flex items-center gap-2">{Icon ? <Icon size={16} aria-hidden="true" /> : null}<span>{label}</span></span>
</Dropdown.Item>
);
})}
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
) : null}
</div>
);
}
export function TableActionButton({ className, icon: Icon, isDisabled = false, label, onPress, tone = 'view' }) {
return (
<Tooltip delay={0}>
<Button
isIconOnly
aria-label={label}
className={clsx('pm-table-action', ACTION_TONE_CLASSES[tone] || ACTION_TONE_CLASSES.view, className)}
isDisabled={isDisabled}
size="sm"
variant="ghost"
onPress={onPress}
>
<Icon size={16} />
</Button>
<Tooltip.Content>{label}</Tooltip.Content>
</Tooltip>
);
}
export const APP_DISPLAY_NAME = '监察稽核管理';
export const APP_ROUTE_NAME = 'audit_manager_new';
export const APP_LOGIN_SUBTITLE = '内部控制与报告归档';
export const APP_NAV_ITEMS = Object.freeze([
{ label: '稽核项目', path: '/audit_process', icon: 'project' },
{ label: '稽核报告', path: '/audit_report', icon: 'report' },
]);
export function getAppRouteTitle(pathname) {
if (pathname.startsWith('/audit_report/')) return '报告详情';
return APP_NAV_ITEMS.find((item) => pathname === item.path)?.label || APP_DISPLAY_NAME;
}
.rc-app-shell {
position: fixed;
inset: 0;
display: flex;
overflow: hidden;
color: var(--rc-text-primary);
background: var(--rc-bg-canvas);
}
.rc-sidebar,
.rc-mobile-sidebar {
display: flex;
flex-direction: column;
color: var(--foreground);
background: var(--rc-bg-canvas);
}
.rc-sidebar {
position: relative;
z-index: 30;
width: 15rem;
height: 100%;
flex: none;
padding: var(--rc-space-4) var(--rc-space-3) var(--rc-space-3);
transition: width var(--rc-motion-normal) var(--rc-ease-standard);
}
.rc-sidebar.is-collapsed {
width: 4rem;
}
.rc-sidebar-brand {
width: 100%;
min-width: 0;
display: flex;
min-height: 3rem;
align-items: center;
gap: var(--rc-space-3);
padding: var(--rc-space-2) var(--rc-space-1);
color: var(--foreground);
font-size: var(--rc-text-sm);
font-weight: var(--rc-font-semibold);
}
.rc-sidebar-brand.is-collapsed {
justify-content: center;
padding-inline: 0;
}
.rc-sidebar-brand-mark {
display: flex;
width: 2.75rem;
height: 1.5rem;
flex: none;
align-items: center;
justify-content: center;
overflow: hidden;
}
.rc-sidebar-logo {
display: block;
width: 100%;
height: 100%;
object-fit: contain;
}
.rc-sidebar-brand-main {
display: flex;
min-width: 0;
flex: 1;
align-items: center;
gap: var(--rc-space-2);
}
.rc-sidebar-app-icon {
display: inline-flex;
width: 1.75rem;
height: 1.75rem;
flex: none;
align-items: center;
justify-content: center;
border-radius: var(--rc-radius-control);
color: var(--rc-brand-light);
background: rgb(255 255 255 / 8%);
}
.rc-sidebar-title {
min-width: 0;
overflow: hidden;
color: var(--foreground);
font-size: 0.95rem;
font-weight: 650;
line-height: 1.2rem;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
text-overflow: ellipsis;
white-space: nowrap;
}
.rc-sidebar-app-switcher {
flex: none;
}
.rc-sidebar-content {
min-height: 0;
flex: 1;
overflow-y: auto;
padding-top: var(--rc-space-4);
scrollbar-width: thin;
}
.rc-sidebar-group + .rc-sidebar-group {
margin-top: var(--rc-space-5);
}
.rc-sidebar-group-label {
margin: 0 0 var(--rc-space-2);
padding: 0 var(--rc-space-3);
color: var(--muted);
font-size: var(--rc-text-xs);
font-weight: var(--rc-font-medium);
line-height: var(--rc-line-normal);
}
.rc-sidebar-menu {
display: grid;
gap: var(--rc-space-1);
}
.rc-sidebar-item {
position: relative;
display: flex;
min-width: 0;
height: 2.5rem;
align-items: center;
gap: var(--rc-space-3);
padding: 0 var(--rc-space-3);
border-radius: var(--rc-radius-control);
color: var(--muted);
font-size: var(--rc-text-sm);
font-weight: var(--rc-font-medium);
text-decoration: none;
transition: color var(--rc-motion-fast) var(--rc-ease-standard),
background var(--rc-motion-fast) var(--rc-ease-standard);
}
.rc-sidebar-item:hover {
color: var(--foreground);
background: var(--default);
}
.rc-sidebar-item.is-active {
color: var(--foreground);
background: var(--default);
}
.rc-sidebar-item.is-active::before {
position: absolute;
inset-block: 0.5rem;
left: 0;
width: 0.1875rem;
border-radius: 0 999px 999px 0;
background: var(--rc-brand-primary);
content: '';
}
.rc-sidebar-item.is-collapsed {
justify-content: center;
padding-inline: 0;
}
.rc-sidebar-item-icon {
flex: none;
font-size: 1rem;
}
.rc-sidebar-item.is-active .rc-sidebar-item-icon {
color: var(--rc-brand-light);
}
.rc-sidebar-item-label {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.rc-sidebar-state {
margin: var(--rc-space-2);
padding: var(--rc-space-3);
border-radius: var(--rc-radius-control);
color: var(--muted);
background: var(--default);
font-size: var(--rc-text-xs);
}
.rc-sidebar-state.is-error {
color: rgb(254 202 202);
background: rgb(201 54 54 / 14%);
}
.rc-sidebar.is-collapsed .rc-sidebar-state {
display: none;
}
.rc-sidebar-user {
display: flex;
width: 100%;
min-height: 3rem;
align-items: center;
gap: var(--rc-space-3);
padding: 0.375rem var(--rc-space-2);
border: 0;
border-radius: var(--rc-radius-container);
color: var(--foreground);
background: transparent;
cursor: pointer;
text-align: left;
}
.rc-sidebar-user:hover,
.rc-sidebar-user:focus-visible {
background: rgb(255 255 255 / 7%);
}
.rc-sidebar-user:focus-visible,
.rc-sidebar-item:focus-visible {
outline: 2px solid var(--rc-brand-light);
outline-offset: 2px;
}
.rc-sidebar-user.is-collapsed {
justify-content: center;
padding-inline: 0;
}
.rc-sidebar-avatar {
flex: none;
background: var(--rc-brand-primary);
}
.rc-sidebar-user-copy {
display: flex;
min-width: 0;
flex: 1;
flex-direction: column;
}
.rc-sidebar-user-copy strong,
.rc-sidebar-user-copy small {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.rc-sidebar-user-copy strong {
font-size: var(--rc-text-sm);
font-weight: var(--rc-font-medium);
}
.rc-sidebar-user-copy small {
margin-top: 0.125rem;
color: var(--muted);
font-size: var(--rc-text-xs);
}
.rc-sidebar-logout {
flex: none;
color: var(--muted);
font-size: 0.875rem;
}
.rc-app-main {
--rc-page-title-inset: calc(
var(--rc-space-6) + var(--rc-navbar-trigger-size) + var(--rc-space-3)
);
display: flex;
min-width: 0;
min-height: 0;
flex: 1;
flex-direction: column;
overflow: hidden;
}
.rc-navbar {
flex: none;
height: 4rem;
min-height: 4rem;
max-height: 4rem;
border: 0;
background: transparent;
box-shadow: none;
backdrop-filter: none;
}
.rc-navbar .navbar__header {
width: 100%;
height: 4rem;
min-height: 4rem;
max-height: 4rem;
gap: var(--rc-space-3);
padding: 0 var(--rc-space-6);
}
.rc-navbar [data-slot='sidebar-trigger'] {
width: var(--rc-navbar-trigger-size);
min-width: var(--rc-navbar-trigger-size);
max-width: var(--rc-navbar-trigger-size);
height: var(--rc-navbar-trigger-size);
flex: 0 0 var(--rc-navbar-trigger-size);
}
.rc-navbar-icon-button.button,
.rc-navbar-icon-button.toggle-button {
width: var(--rc-navbar-icon-button-size);
min-width: var(--rc-navbar-icon-button-size);
max-width: var(--rc-navbar-icon-button-size);
height: var(--rc-navbar-icon-button-size);
flex: 0 0 var(--rc-navbar-icon-button-size);
align-items: center;
justify-content: center;
padding: 0;
border: 0;
border-radius: 50%;
color: var(--foreground);
box-shadow: none;
line-height: 1;
}
.rc-navbar [data-slot='sidebar-trigger'] {
background: transparent !important;
}
.rc-navbar-icon-button.button,
.rc-navbar-icon-button.toggle-button {
background: var(--default) !important;
}
.rc-navbar [data-slot='sidebar-trigger']:hover,
.rc-navbar [data-slot='sidebar-trigger'][data-hovered='true'],
.rc-navbar [data-slot='sidebar-trigger']:focus-visible {
color: var(--foreground);
background: var(--default) !important;
}
.rc-navbar [data-slot='sidebar-trigger']:active,
.rc-navbar [data-slot='sidebar-trigger'][data-pressed='true'] {
background: var(--default-hover) !important;
}
.rc-navbar-icon-button.button:hover,
.rc-navbar-icon-button.button[data-hovered='true'],
.rc-navbar-icon-button.toggle-button:hover,
.rc-navbar-icon-button.toggle-button[data-hovered='true'] {
color: var(--foreground);
background: var(--default-hover) !important;
}
.rc-navbar [data-slot='sidebar-trigger'] > span,
.rc-navbar-icon-button > span,
.rc-navbar-icon-button .anticon {
display: grid;
width: 1rem;
height: 1rem;
place-items: center;
line-height: 1;
}
.rc-navbar [data-slot='sidebar-trigger'] > svg,
.rc-navbar [data-slot='sidebar-trigger'] > span > svg,
.rc-navbar-icon-button > svg,
.rc-navbar-icon-button .anticon > svg {
display: block;
width: 1rem;
height: 1rem;
margin: 0;
}
.rc-navbar-actions {
display: flex;
align-items: center;
gap: var(--rc-space-2);
}
.rc-navbar-title {
min-width: 0;
margin: 0;
overflow: hidden;
color: var(--foreground);
font-size: var(--rc-navbar-title-size);
font-weight: var(--rc-navbar-title-weight);
line-height: var(--rc-navbar-title-line-height);
letter-spacing: 0 !important;
text-overflow: ellipsis;
white-space: nowrap;
}
.rc-navbar-spacer {
flex: 1;
}
.app-shell-content-scroll {
min-width: 0;
background: var(--rc-bg-canvas);
overscroll-behavior: contain;
}
.rc-app-page {
width: 100%;
max-width: none;
min-width: 0;
min-height: 100%;
margin: 0;
padding: var(--rc-space-4) var(--rc-page-title-inset) var(--rc-space-5);
overflow-x: clip;
}
.rc-mobile-sidebar-backdrop {
position: fixed;
inset: 0;
z-index: 200;
background: rgb(15 23 42 / 32%);
transition: opacity var(--rc-motion-normal) var(--rc-ease-standard);
}
.rc-mobile-sidebar-panel {
position: fixed;
inset-block: 0;
left: 0;
z-index: 210;
width: min(19rem, calc(100vw - 3rem));
transition: transform var(--rc-motion-normal) var(--rc-ease-standard);
}
.rc-mobile-sidebar {
position: relative;
width: 100%;
height: 100%;
padding: var(--rc-space-4) var(--rc-space-3) var(--rc-space-3);
box-shadow: var(--rc-shadow-overlay);
}
.rc-mobile-sidebar-close.button {
position: absolute;
top: var(--rc-space-3);
right: var(--rc-space-3);
z-index: 1;
width: 2rem;
min-width: 2rem;
height: 2rem;
border-radius: 999px;
color: rgb(212 212 216);
}
@media (max-width: 1023px) {
.rc-sidebar {
display: none;
}
.rc-navbar {
min-height: 4rem;
}
}
@media (max-width: 640px) {
.rc-app-main {
--rc-page-title-inset: calc(
var(--rc-space-6) + var(--rc-navbar-icon-button-size) + var(--rc-space-3)
);
}
.rc-app-page {
padding: var(--rc-space-4) var(--rc-page-title-inset) var(--rc-space-5);
}
.rc-navbar [data-slot='sidebar-trigger'] {
width: var(--rc-navbar-icon-button-size);
min-width: var(--rc-navbar-icon-button-size);
max-width: var(--rc-navbar-icon-button-size);
height: var(--rc-navbar-icon-button-size);
flex-basis: var(--rc-navbar-icon-button-size);
}
}
/* HeroUI Pro offcanvas shell contract */
.rc-app-shell.sidebar__provider {
position: fixed;
inset: 0;
width: 100%;
height: 100dvh;
min-width: 0;
min-height: 0;
overflow: hidden;
background: var(--rc-bg-canvas);
}
.rc-app-shell .sidebar__offcanvas-wrapper {
--sidebar-width: 15rem;
}
.rc-app-shell .rc-sidebar.sidebar,
.rc-mobile-sidebar {
width: 15rem;
height: 100%;
max-height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
border: 0;
border-right: 1px solid var(--rc-border-default);
border-radius: 0;
background: var(--rc-bg-canvas);
box-shadow: none;
}
.rc-app-shell .sidebar__header,
.rc-mobile-sidebar .sidebar__header {
padding: var(--rc-space-2) var(--rc-space-4) var(--rc-space-2);
}
.rc-sidebar-brand {
min-height: 3rem;
gap: var(--rc-space-3);
padding: var(--rc-space-2) var(--rc-space-1);
}
.rc-sidebar-title {
min-width: 0;
color: var(--foreground);
font-size: 0.95rem;
font-weight: 650;
line-height: 1.2rem;
}
.rc-sidebar-app-trigger.button {
display: grid;
width: var(--rc-sidebar-app-trigger-size);
height: var(--rc-sidebar-app-trigger-size);
min-width: var(--rc-sidebar-app-trigger-size);
max-width: var(--rc-sidebar-app-trigger-size);
flex: 0 0 var(--rc-sidebar-app-trigger-size);
place-items: center;
padding: 0;
border-radius: 50%;
color: var(--foreground);
background: transparent;
line-height: 1;
--button-bg-hover: var(--accent-soft);
--button-bg-pressed: var(--accent-soft);
}
@media (hover: hover) {
.rc-sidebar-app-trigger.button:hover,
.rc-sidebar-app-trigger.button[data-hovered='true'] {
color: var(--foreground);
background: var(--accent-soft);
}
}
.rc-sidebar-app-trigger.button[data-pressed='true'],
.rc-sidebar-app-trigger.button[aria-expanded='true'] {
color: var(--foreground);
background: var(--accent-soft);
}
.rc-sidebar-app-popover {
width: min(45rem, calc(100vw - 2rem));
padding: 0;
}
.rc-sidebar-app-dialog {
max-height: min(32rem, calc(100dvh - 7rem));
padding: 1.5rem;
overflow-y: auto;
outline: none;
}
.rc-sidebar-app-card {
min-width: 0;
cursor: pointer;
}
.rc-sidebar-app-card[aria-disabled='true'] {
cursor: default;
opacity: 0.6;
}
.rc-sidebar-app-logo,
.rc-sidebar-app-initial {
width: 2rem;
height: 2rem;
border-radius: var(--rc-radius-container);
object-fit: contain;
}
.rc-sidebar-app-initial {
display: grid;
place-items: center;
color: var(--foreground);
background: var(--default);
font-size: var(--rc-text-sm);
font-weight: var(--rc-font-semibold);
}
.rc-app-shell .sidebar__content,
.rc-mobile-sidebar .sidebar__content {
min-height: 0;
padding: 0 var(--rc-space-3);
}
.rc-app-shell .sidebar__group,
.rc-mobile-sidebar .sidebar__group {
padding: 0;
}
.rc-app-shell .sidebar__group + .sidebar__group,
.rc-mobile-sidebar .sidebar__group + .sidebar__group {
margin-top: var(--rc-space-2);
}
.rc-app-shell .sidebar__group-label,
.rc-mobile-sidebar .sidebar__group-label {
padding: 0.375rem var(--rc-space-2);
color: var(--rc-text-muted);
}
.rc-app-shell .sidebar__menu-item-content,
.rc-mobile-sidebar .sidebar__menu-item-content {
min-height: 2.25rem;
border-radius: calc(var(--radius) * 2);
color: var(--foreground);
}
.rc-app-shell .sidebar__menu-icon,
.rc-app-shell .sidebar__menu-label,
.rc-mobile-sidebar .sidebar__menu-icon,
.rc-mobile-sidebar .sidebar__menu-label {
color: inherit;
}
.rc-app-shell
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content,
.rc-mobile-sidebar
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content {
color: var(--foreground);
background: var(--default);
}
.rc-app-shell .sidebar__menu-item:hover .sidebar__menu-item-content,
.rc-app-shell
.sidebar__menu-item[data-hovered='true']
.sidebar__menu-item-content,
.rc-mobile-sidebar .sidebar__menu-item:hover .sidebar__menu-item-content,
.rc-mobile-sidebar
.sidebar__menu-item[data-hovered='true']
.sidebar__menu-item-content {
color: var(--foreground);
background: var(--default);
}
.rc-app-shell .sidebar__footer,
.rc-mobile-sidebar .sidebar__footer {
padding: var(--rc-space-2) var(--rc-space-3) var(--rc-space-4);
}
.rc-sidebar-user-trigger.button {
display: flex;
width: 100%;
min-width: 0;
min-height: 3rem;
align-items: center;
justify-content: center;
gap: 0.625rem;
padding: 0.375rem 0.5rem;
border-radius: calc(var(--radius) * 2);
color: var(--foreground);
background: transparent;
text-align: left;
}
.rc-sidebar-user-trigger.button:hover,
.rc-sidebar-user-trigger.button[data-hovered='true'],
.rc-sidebar-user-trigger.button[aria-expanded='true'] {
background: var(--default);
}
.rc-sidebar-avatar {
width: 2rem;
height: 2rem;
min-width: 2rem;
flex: 0 0 2rem;
background: var(--default);
}
.rc-sidebar-user-copy {
gap: 0.125rem;
line-height: 1;
}
.rc-sidebar-user-menu-item {
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
gap: var(--rc-space-2);
}
.rc-app-main.sidebar__main {
height: 100dvh;
min-height: 0;
max-height: 100dvh;
margin: 0;
overflow: hidden;
border: 0;
border-radius: 0;
background: var(--rc-bg-canvas);
}
.rc-navbar {
flex: none;
height: 4rem;
min-height: 4rem;
max-height: 4rem;
border: 0;
background: transparent;
}
.rc-navbar [data-slot='sidebar-trigger'] {
width: var(--rc-navbar-trigger-size);
min-width: var(--rc-navbar-trigger-size);
max-width: var(--rc-navbar-trigger-size);
height: var(--rc-navbar-trigger-size);
flex: 0 0 var(--rc-navbar-trigger-size);
padding: 0;
}
.rc-navbar-icon-button.button,
.rc-navbar-icon-button.toggle-button {
width: var(--rc-navbar-icon-button-size);
min-width: var(--rc-navbar-icon-button-size);
max-width: var(--rc-navbar-icon-button-size);
height: var(--rc-navbar-icon-button-size);
flex: 0 0 var(--rc-navbar-icon-button-size);
padding: 0;
}
@media (max-width: 768px) {
.rc-navbar [data-slot='sidebar-trigger'] {
width: var(--rc-navbar-icon-button-size);
min-width: var(--rc-navbar-icon-button-size);
max-width: var(--rc-navbar-icon-button-size);
height: var(--rc-navbar-icon-button-size);
flex-basis: var(--rc-navbar-icon-button-size);
}
.rc-app-shell > .sidebar__offcanvas-wrapper {
display: none;
}
.rc-mobile-sidebar {
width: 100%;
border-right: 0;
}
}
@media (min-width: 769px) {
.rc-app-shell .rc-sidebar.sidebar {
display: flex;
}
}
.rc-login-page {
display: grid;
min-height: 100dvh;
place-items: center;
padding: var(--rc-space-6);
background: var(--app-background-gradient);
}
.rc-login-shell {
display: grid;
width: min(100%, var(--rc-login-panel-width));
gap: var(--rc-login-brand-gap);
}
.rc-login-brand {
display: flex;
align-items: center;
justify-content: center;
gap: var(--rc-space-3);
color: var(--rc-text-primary);
}
.rc-login-brand img {
width: auto;
max-width: 8rem;
height: 1.5rem;
filter: brightness(0) saturate(100%) invert(13%) sepia(17%) saturate(1318%)
hue-rotate(181deg) brightness(91%) contrast(92%);
object-fit: contain;
}
.rc-login-brand strong {
font-size: var(--rc-login-brand-size);
font-weight: var(--rc-font-semibold);
}
.rc-login-card.card {
border-radius: var(--rc-radius-overlay);
background: var(--rc-bg-surface);
box-shadow: var(--rc-shadow-raised);
}
.rc-login-form {
display: grid;
gap: var(--rc-space-5);
}
@media (max-width: 480px) {
.rc-login-page {
align-items: start;
padding: 12vh var(--rc-space-4) var(--rc-space-4);
}
.rc-login-brand {
align-items: flex-start;
flex-direction: column;
}
}
/* approved Xinyuan baseline Sidebar geometry and typography contract. */
.template-dashboard-shell .app-sidebar-glass,
.rc-mobile-sidebar.app-sidebar-glass {
width: 15rem;
height: 100%;
max-height: 100%;
margin: 0;
overflow: hidden;
border: 0;
border-right: 1px solid var(--rc-border-default);
border-radius: 0;
color: var(--foreground);
background: var(--rc-bg-canvas);
box-shadow: none;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
}
.template-dashboard-shell .sidebar__header,
.rc-mobile-sidebar .sidebar__header {
padding: 6px 1rem 0.5rem;
}
.app-sidebar-brand {
display: flex;
width: 100%;
min-width: 0;
min-height: 3rem;
align-items: center;
gap: 0.75rem;
padding: 0.5rem 0.25rem;
}
.app-sidebar-brand-main {
display: flex;
min-width: 0;
flex: 1;
align-items: center;
gap: 0.5rem;
}
.app-sidebar-brand-mark {
display: flex;
width: 2.75rem;
height: 1.5rem;
flex: none;
align-items: center;
justify-content: center;
overflow: hidden;
}
.app-sidebar-brand-mark img {
display: block;
width: 100%;
height: 100%;
object-fit: contain;
}
.app-sidebar-title {
min-width: 0;
overflow: hidden;
color: var(--foreground);
font-size: 0.95rem;
font-weight: 650;
line-height: 1.2rem;
letter-spacing: 0;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
text-overflow: ellipsis;
white-space: nowrap;
}
.app-sidebar-app-switcher {
flex: none;
}
.app-sidebar-app-trigger.button {
display: grid;
width: var(--rc-sidebar-app-trigger-size);
height: var(--rc-sidebar-app-trigger-size);
min-width: var(--rc-sidebar-app-trigger-size);
max-width: var(--rc-sidebar-app-trigger-size);
flex: none;
place-items: center;
padding: 0;
border-radius: 50%;
color: var(--foreground);
background-color: transparent;
line-height: 1;
--button-bg-hover: var(--accent-soft);
--button-bg-pressed: var(--accent-soft);
--button-fg: var(--foreground);
}
.app-sidebar-app-trigger .anticon {
display: grid;
width: var(--rc-sidebar-app-trigger-icon-size);
height: var(--rc-sidebar-app-trigger-icon-size);
place-items: center;
line-height: 1;
}
.app-sidebar-app-trigger .anticon > svg {
display: block;
width: 100%;
height: 100%;
}
@media (hover: hover) {
.app-sidebar-app-trigger.button:hover,
.app-sidebar-app-trigger.button[data-hovered='true'] {
color: var(--foreground);
background-color: var(--accent-soft);
}
}
.app-sidebar-app-trigger.button:active,
.app-sidebar-app-trigger.button[data-pressed='true'],
.app-sidebar-app-trigger.button[aria-expanded='true'] {
color: var(--foreground);
background-color: var(--accent-soft);
}
.app-sidebar-app-dialog {
overflow: hidden;
outline: none;
}
.app-sidebar-app-scroll {
max-height: min(32rem, calc(100dvh - 7rem));
padding: 1.5rem;
overflow-y: auto;
}
.template-dashboard-shell .sidebar__content,
.rc-mobile-sidebar .sidebar__content {
min-height: 0;
padding-inline: 0.75rem;
}
.template-dashboard-shell .sidebar__group,
.rc-mobile-sidebar .sidebar__group {
padding: 0;
}
.template-dashboard-shell .sidebar__group + .sidebar__group,
.rc-mobile-sidebar .sidebar__group + .sidebar__group {
margin-top: 0.5rem;
}
.template-dashboard-shell .sidebar__group-label,
.rc-mobile-sidebar .sidebar__group-label {
padding: 0.375rem 0.5rem;
color: var(--rc-text-muted);
font-size: 0.75rem;
font-weight: 500;
line-height: 1rem;
letter-spacing: 0;
}
.template-dashboard-shell .sidebar__menu-item-content,
.rc-mobile-sidebar .sidebar__menu-item-content {
min-height: 2.25rem;
gap: 0.75rem;
padding: 0.375rem 0.5rem;
border-radius: calc(var(--radius) * 2);
color: var(--foreground);
font-weight: 400;
}
.template-dashboard-shell .sidebar__menu-icon,
.rc-mobile-sidebar .sidebar__menu-icon {
width: 1.25rem;
height: 1.25rem;
color: var(--muted);
}
.template-dashboard-shell .sidebar__menu-icon svg,
.rc-mobile-sidebar .sidebar__menu-icon svg {
width: 1rem;
height: 1rem;
}
.template-dashboard-shell .sidebar__menu-label,
.rc-mobile-sidebar .sidebar__menu-label {
color: var(--foreground);
font-size: 0.875rem;
font-weight: inherit;
line-height: 1.25rem;
}
.template-dashboard-shell
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content,
.template-dashboard-shell .sidebar__menu-item:hover .sidebar__menu-item-content,
.template-dashboard-shell
.sidebar__menu-item[data-hovered='true']
.sidebar__menu-item-content,
.rc-mobile-sidebar
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content,
.rc-mobile-sidebar .sidebar__menu-item:hover .sidebar__menu-item-content,
.rc-mobile-sidebar
.sidebar__menu-item[data-hovered='true']
.sidebar__menu-item-content {
color: var(--foreground);
background: var(--default);
}
.template-dashboard-shell
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content,
.rc-mobile-sidebar
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content {
font-weight: 500;
}
.template-dashboard-shell
.sidebar__menu-item[data-current='true']
:is(.sidebar__menu-icon, .sidebar__menu-label),
.rc-mobile-sidebar
.sidebar__menu-item[data-current='true']
:is(.sidebar__menu-icon, .sidebar__menu-label) {
color: var(--foreground);
}
.template-dashboard-shell .sidebar__footer,
.rc-mobile-sidebar .sidebar__footer {
padding: 0.5rem 0.75rem 1rem;
}
.app-sidebar-user-trigger.button {
display: flex;
width: 100%;
min-width: 0;
min-height: 3rem;
align-items: center;
justify-content: center;
gap: 0.625rem;
padding: 0.375rem 0.5rem !important;
border-radius: calc(var(--radius) * 2) !important;
color: var(--foreground);
background: transparent;
text-align: left;
}
@media (hover: hover) {
.app-sidebar-user-trigger.button:hover,
.app-sidebar-user-trigger.button[data-hovered='true'] {
background: var(--default);
}
}
.app-sidebar-user-trigger.button[aria-expanded='true'] {
background: var(--default);
}
.app-sidebar-user-avatar {
width: 2rem;
height: 2rem;
min-width: 2rem;
flex: 0 0 2rem;
background: var(--default);
}
.app-sidebar-user-copy {
display: flex;
min-width: 0;
flex: 1;
flex-direction: column;
gap: 0.125rem;
line-height: 1;
}
.app-sidebar-user-name,
.app-sidebar-user-description {
display: block;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.app-sidebar-user-name {
color: var(--foreground);
font-size: 0.875rem;
font-weight: 500;
line-height: 1.125rem;
}
.app-sidebar-user-description {
color: var(--muted);
font-size: 0.75rem;
font-weight: 500;
line-height: 1rem;
}
@media (max-width: 768px) {
.rc-mobile-sidebar.app-sidebar-glass {
width: 100%;
border-right: 0;
}
}
:root,
.light,
.default,
[data-theme='light'],
[data-theme='default'] {
/* Xinyuan brand and semantic colors */
--rc-brand-primary: #9e1b1b;
--rc-brand-light: #b83a3a;
--rc-brand-dark: #7e1515;
--rc-brand-primary-hover: var(--rc-brand-light);
--rc-brand-primary-pressed: var(--rc-brand-dark);
--rc-brand-primary-soft: rgb(158 27 27 / 8%);
--rc-brand-primary-soft-hover: rgb(158 27 27 / 12%);
--rc-brand-glass: rgb(158 27 27 / 4%);
--rc-brand-on-primary: #ffffff;
--rc-color-success: #16845b;
--rc-color-warning: #b46a00;
--rc-color-danger: #c93636;
--rc-color-info: var(--rc-brand-primary);
--rc-color-teal: #0f766e;
--rc-color-favorite: #d68a00;
--rc-color-favorite-soft: rgb(214 138 0 / 10%);
--rc-color-success-soft: rgb(22 132 91 / 9%);
--rc-color-success-border: rgb(22 132 91 / 24%);
--rc-color-warning-soft: rgb(180 106 0 / 10%);
--rc-color-warning-border: rgb(180 106 0 / 25%);
--rc-color-danger-soft: rgb(201 54 54 / 9%);
--rc-color-danger-border: rgb(201 54 54 / 22%);
--rc-color-info-soft: var(--rc-brand-primary-soft);
--rc-color-info-border: color-mix(in srgb, var(--rc-brand-primary) 24%, transparent);
--rc-color-teal-soft: rgb(15 118 110 / 9%);
--rc-color-teal-border: rgb(15 118 110 / 24%);
--rc-color-neutral-soft: #f4f4f5;
--rc-color-neutral-border: #e4e4e7;
/* Surfaces, borders and text */
--rc-bg-canvas: #f5f5f5;
--rc-bg-surface: #ffffff;
--rc-bg-subtle: #f8fafc;
--rc-layout-background: #f4f6f9;
--rc-sidebar-glass: rgb(255 255 255 / 42%);
--rc-main-glass: rgb(255 255 255 / 82%);
--rc-shell-glass: rgb(255 255 255 / 78%);
--rc-glass-border: rgb(255 255 255 / 60%);
--rc-sidebar-border: rgb(226 232 240 / 55%);
--rc-border-default: #e3e8ef;
--rc-border-strong: #cbd3de;
--rc-field-background: #ffffff;
--rc-field-border: #d9e1eb;
--rc-field-border-hover: #b8c4d2;
--rc-field-border-focus: var(--rc-brand-primary);
--rc-button-secondary-border: rgb(9 9 11 / 12%);
--rc-button-secondary-border-hover: rgb(9 9 11 / 20%);
--rc-button-secondary-background-hover: rgb(9 9 11 / 4%);
--rc-text-primary: #172033;
--rc-text-secondary: #4e5d73;
--rc-text-muted: #718096;
/* Typography */
--rc-font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC',
'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;
--rc-font-mono: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;
--rc-text-xs: 0.75rem;
--rc-text-sm: 0.875rem;
--rc-text-md: 1rem;
--rc-text-lg: 1.25rem;
--rc-text-xl: 1.5rem;
--rc-font-regular: 400;
--rc-font-medium: 500;
--rc-font-semibold: 600;
--rc-line-compact: 1.25;
--rc-line-normal: 1.5;
/* Spacing, radius and motion */
--rc-space-1: 0.25rem;
--rc-space-2: 0.5rem;
--rc-space-3: 0.75rem;
--rc-space-4: 1rem;
--rc-space-5: 1.25rem;
--rc-space-6: 1.5rem;
--rc-space-8: 2rem;
--rc-space-10: 2.5rem;
--rc-radius-control: 0.375rem;
--rc-radius-container: 0.5rem;
--rc-radius-overlay: 0.75rem;
--rc-motion-fast: 120ms;
--rc-motion-normal: 180ms;
--rc-ease-standard: cubic-bezier(0.2, 0, 0, 1);
/* Component contracts */
--rc-filter-radius: 0.75rem;
--rc-button-radius: 1.5rem;
--rc-button-sm-height: 2rem;
--rc-button-md-height: 2.25rem;
--rc-button-lg-height: 2.5rem;
--rc-table-action-width: 9rem;
--rc-table-action-gap: 0.5rem;
--rc-table-action-padding-inline: 0.75rem;
--rc-table-action-idle-opacity: 0.55;
--rc-navbar-trigger-size: 2rem;
--rc-navbar-icon-button-size: 2.25rem;
--rc-segment-track-padding: 0.125rem;
--rc-segment-track-radius: 0.75rem;
--rc-segment-item-height: 1.75rem;
--rc-segment-item-radius: 0.625rem;
--rc-segment-item-padding-inline: 0.75rem;
--rc-segment-item-gap: 0.5rem;
--rc-segment-font-size: var(--rc-text-sm);
--rc-segment-font-weight: var(--rc-font-medium);
--rc-segment-line-height: 1.25rem;
--rc-sidebar-app-trigger-size: 2.25rem;
--rc-sidebar-app-trigger-icon-size: 1rem;
--rc-navbar-border-width: 0px;
--rc-navbar-title-size: 1.25rem;
--rc-navbar-page-title-size: var(--rc-navbar-title-size);
--rc-navbar-title-weight: 600;
--rc-navbar-title-line-height: 1.75rem;
--rc-login-panel-width: 24rem;
--rc-login-brand-size: 1.5rem;
--rc-login-brand-weight: var(--rc-font-semibold);
--rc-login-brand-gap: 1.5rem;
--rc-toolbar-filter-background: var(--default);
--rc-toolbar-filter-background-hover: var(--default-hover);
--rc-toolbar-filter-foreground: var(--foreground);
--rc-toolbar-filter-icon: var(--muted);
--rc-toolbar-filter-radius: 999px;
--rc-toolbar-filter-content-gap: 0.5rem;
--rc-data-grid-radius: calc(var(--radius) * 2.5);
--rc-data-grid-header-background: var(--surface-secondary);
--rc-data-grid-header-height: 2.25rem;
--rc-data-grid-header-font-size: var(--rc-text-xs);
--rc-data-grid-header-font-weight: var(--rc-font-medium);
--rc-data-grid-header-line-height: var(--rc-line-compact);
--rc-data-grid-header-color: var(--rc-text-muted);
--rc-data-grid-body-font-size: var(--rc-text-sm);
--rc-data-grid-body-font-weight: var(--rc-font-regular);
--rc-data-grid-body-line-height: var(--rc-line-normal);
--rc-data-grid-body-color: var(--rc-text-secondary);
--rc-data-grid-primary-font-size: var(--rc-text-sm);
--rc-data-grid-primary-font-weight: 550;
--rc-data-grid-primary-line-height: var(--rc-line-normal);
--rc-data-grid-primary-color: var(--rc-text-primary);
--rc-data-grid-secondary-font-size: 0.8125rem;
--rc-data-grid-secondary-font-weight: var(--rc-font-regular);
--rc-data-grid-secondary-line-height: var(--rc-line-compact);
--rc-data-grid-secondary-color: #7a8596;
--rc-data-grid-text-pair-gap: 0.25rem;
--rc-data-grid-chip-font-size: var(--rc-text-xs);
--rc-data-grid-chip-font-weight: var(--rc-font-medium);
--rc-data-grid-chip-line-height: var(--rc-line-compact);
--rc-data-grid-row-hover-background: color-mix(
in oklab,
var(--default) 35%,
var(--surface)
);
--rc-modal-radius: 1.5rem;
--rc-modal-padding: 1.75rem;
--rc-modal-title-size: var(--rc-text-md);
--rc-modal-title-weight: var(--rc-font-medium);
--rc-modal-title-line-height: 1.5rem;
--rc-modal-section-gap: 0.75rem;
--rc-modal-footer-gap: 0.5rem;
--rc-modal-footer-margin: 1.5rem;
--rc-modal-backdrop-blur: 0.75rem;
--rc-modal-close-size: 1.5rem;
--rc-modal-close-inset: var(--rc-modal-padding);
--rc-modal-close-icon-size: 1rem;
--rc-modal-width-xs: 20rem;
--rc-modal-width-sm: 24rem;
--rc-modal-width-md: 30rem;
--rc-modal-width-lg: 40rem;
--rc-modal-width-xl: 46rem;
--rc-modal-width-2xl: 52.5rem;
--rc-modal-width-3xl: 60rem;
--rc-modal-width-4xl: 68rem;
--rc-modal-width-5xl: 76rem;
--rc-data-grid-action-size: 2rem;
--rc-data-grid-action-radius: 999px;
--rc-list-pagination-gap: 0.5rem;
--rc-checkbox-size: 1rem;
--rc-checkbox-radius: 0.25rem;
--rc-checkbox-background: var(--rc-field-background);
--rc-checkbox-border: var(--rc-field-border);
--rc-checkbox-border-hover: var(--rc-field-border-hover);
--rc-checkbox-disabled-background: #f3f5f8;
--rc-checkbox-disabled-border: #d8dee7;
--rc-checkbox-disabled-opacity: 0.56;
--rc-checkbox-checked-background: var(--rc-brand-primary);
--rc-checkbox-checked-border: var(--rc-brand-dark);
--rc-checkbox-check-color: var(--rc-brand-on-primary);
--rc-kbd-background: #f4f6f9;
--rc-content-max-width: 80rem;
--rc-page-gutter: 1.25rem;
/* Elevation */
--rc-shadow-card: 0 1px 2px rgb(15 23 42 / 2%), 0 4px 12px rgb(15 23 42 / 2%);
--rc-shadow-raised: 0 1px 2px rgb(15 23 42 / 6%),
0 8px 24px rgb(15 23 42 / 8%);
--rc-shadow-overlay: 0 16px 40px rgb(15 23 42 / 14%);
--rc-shadow-brand-glow: 0 4px 14px rgb(158 27 27 / 15%);
--rc-shadow-main-glass: 0 8px 28px rgb(148 163 184 / 14%),
inset 0 0 0 1px rgb(255 255 255 / 30%);
/* HeroUI semantic bridge */
--accent: var(--rc-brand-primary);
--accent-foreground: var(--rc-brand-on-primary);
--accent-hover: var(--rc-brand-primary-hover);
--accent-soft: var(--rc-brand-primary-soft);
--accent-soft-hover: var(--rc-brand-primary-soft-hover);
--accent-soft-foreground: color-mix(
in oklab,
var(--rc-brand-primary) 82%,
var(--rc-text-primary) 18%
);
--background: var(--rc-bg-canvas);
--surface: var(--rc-bg-surface);
--surface-shadow: var(--rc-shadow-card);
--foreground: var(--rc-text-primary);
--muted: var(--rc-text-muted);
--muted-foreground: var(--rc-text-secondary);
--separator: var(--rc-border-default);
--focus: var(--rc-brand-primary);
--app-background-gradient: radial-gradient(
circle at 0% 0%,
rgb(158 27 27 / 5%) 0%,
rgb(244 246 249 / 0%) 50%
),
var(--rc-layout-background);
}
.dark,
[data-theme='dark'] {
color-scheme: dark;
--rc-brand-primary-soft: rgb(158 27 27 / 16%);
--rc-brand-primary-soft-hover: rgb(158 27 27 / 22%);
--rc-brand-glass: rgb(158 27 27 / 10%);
--rc-color-success: #4fca92;
--rc-color-warning: #f0ad4e;
--rc-color-danger: #ff7272;
--rc-color-info: #f0a3a3;
--rc-color-teal: #5eead4;
--rc-color-favorite: #f2b84b;
--rc-color-favorite-soft: rgb(242 184 75 / 14%);
--rc-color-success-soft: rgb(79 202 146 / 14%);
--rc-color-success-border: rgb(79 202 146 / 30%);
--rc-color-warning-soft: rgb(240 173 78 / 15%);
--rc-color-warning-border: rgb(240 173 78 / 32%);
--rc-color-danger-soft: rgb(255 114 114 / 14%);
--rc-color-danger-border: rgb(255 114 114 / 30%);
--rc-color-info-soft: rgb(158 27 27 / 18%);
--rc-color-info-border: rgb(184 58 58 / 42%);
--rc-color-teal-soft: rgb(94 234 212 / 14%);
--rc-color-teal-border: rgb(94 234 212 / 30%);
--rc-color-neutral-soft: #27272a;
--rc-color-neutral-border: #3f3f46;
--rc-bg-canvas: #0f0f11;
--rc-bg-surface: #18181b;
--rc-bg-subtle: #202024;
--rc-layout-background: #0f0f11;
--rc-sidebar-glass: #0f0f11;
--rc-main-glass: #0f0f11;
--rc-shell-glass: #0f0f11;
--rc-glass-border: #303036;
--rc-sidebar-border: #303036;
--rc-border-default: #303036;
--rc-border-strong: #45454d;
--rc-field-background: #202024;
--rc-field-border: #3b3b42;
--rc-field-border-hover: #56565f;
--rc-button-secondary-border: rgb(255 255 255 / 18%);
--rc-button-secondary-border-hover: rgb(255 255 255 / 28%);
--rc-button-secondary-background-hover: rgb(255 255 255 / 7%);
--rc-text-primary: #f4f4f5;
--rc-text-secondary: #d4d4d8;
--rc-text-muted: #a1a1aa;
--rc-data-grid-secondary-color: #8b8b95;
--rc-checkbox-background: #202024;
--rc-checkbox-border: #56565f;
--rc-checkbox-border-hover: #6f6f7a;
--rc-checkbox-disabled-background: #1b1b1f;
--rc-checkbox-disabled-border: #36363d;
--rc-checkbox-disabled-opacity: 0.58;
--rc-kbd-background: #202024;
--rc-shadow-card: 0 1px 2px rgb(0 0 0 / 18%), 0 4px 12px rgb(0 0 0 / 12%);
--rc-shadow-raised: 0 1px 2px rgb(0 0 0 / 24%), 0 8px 24px rgb(0 0 0 / 24%);
--rc-shadow-overlay: 0 16px 40px rgb(0 0 0 / 38%);
--rc-shadow-main-glass: none;
--accent: var(--rc-brand-primary);
--accent-foreground: var(--rc-brand-on-primary);
--accent-hover: var(--rc-brand-light);
--accent-soft: var(--rc-brand-primary-soft);
--accent-soft-hover: var(--rc-brand-primary-soft-hover);
--accent-soft-foreground: #f0a3a3;
--background: var(--rc-bg-canvas);
--surface: var(--rc-bg-surface);
--surface-secondary: var(--rc-bg-subtle);
--surface-shadow: var(--rc-shadow-card);
--overlay: var(--rc-bg-surface);
--overlay-foreground: var(--rc-text-primary);
--foreground: var(--rc-text-primary);
--muted: var(--rc-text-muted);
--muted-foreground: var(--rc-text-secondary);
--default: #27272a;
--default-hover: #3f3f46;
--border: var(--rc-border-default);
--separator: var(--rc-border-default);
--field: var(--rc-field-background);
--field-foreground: var(--rc-text-primary);
--focus: var(--rc-brand-primary);
--backdrop: rgb(0 0 0 / 50%);
--app-background-gradient: var(--rc-bg-canvas);
}
:root {
--pm-brand: #009cd9;
--pm-brand-strong: #007bb8;
--pm-surface: #ffffff;
--pm-bg: #eef5f9;
--pm-sidebar: #0f172a;
--pm-sidebar-soft: #1e293b;
--pm-text: #0f172a;
--pm-muted: #64748b;
}
html,
body,
#root {
......@@ -17,21 +6,1009 @@ body,
body {
margin: 0;
background: linear-gradient(180deg, #f8fbfd 0%, #eef5f9 100%);
color: var(--pm-text);
font-family:
"PingFang SC",
"Microsoft YaHei",
"Helvetica Neue",
Arial,
sans-serif;
background: var(--rc-bg-canvas);
}
.login-page {
display: grid;
min-height: 100vh;
place-items: center;
padding: 24px 16px;
background: var(--rc-bg-canvas);
}
.login-card {
width: min(28rem, 100%);
border: 1px solid var(--rc-border-default) !important;
border-radius: 24px !important;
background: var(--rc-bg-surface) !important;
box-shadow: var(--rc-shadow-raised) !important;
}
.login-card [data-slot='card-content'] {
padding: 36px !important;
}
.login-brand {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 32px;
}
.login-brand img {
width: 36px;
height: 36px;
object-fit: contain;
}
.login-brand-logo--dark {
display: none;
}
.dark .login-brand-logo--light {
display: none;
}
.dark .login-brand-logo--dark {
display: block;
}
.login-brand strong {
color: var(--rc-text-primary);
font-size: 32px;
font-weight: var(--rc-login-brand-weight);
line-height: 1.15;
letter-spacing: 0;
}
.login-form {
display: flex;
flex-direction: column;
}
.login-field {
display: flex;
flex-direction: column;
gap: 12px;
}
.login-field + .login-field {
margin-top: 24px;
}
.login-field .label {
color: var(--rc-text-secondary);
font-size: 14px;
font-weight: 500;
}
.login-captcha-row {
display: grid;
grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
gap: 12px;
}
.login-captcha {
display: grid;
height: 40px;
place-items: center;
overflow: hidden;
padding: 0 8px;
border: 1px solid var(--rc-field-border);
border-radius: 8px;
color: var(--rc-text-muted);
background: var(--rc-bg-surface);
cursor: pointer;
}
.login-captcha:hover {
border-color: var(--rc-field-border-hover);
color: var(--rc-brand-primary);
}
.login-captcha span {
display: grid;
width: 100%;
height: 100%;
place-items: center;
line-height: 0;
}
.login-captcha svg {
display: block;
width: auto;
height: auto;
max-width: 100%;
max-height: 30px;
margin: auto;
}
.login-error {
margin-top: 16px;
}
.login-input[data-filled='true'],
.login-input[data-filled='true']:hover,
.login-input[data-filled='true']:focus,
.login-input[data-filled='true']:focus-visible,
.login-input.input--secondary:not(:placeholder-shown),
.login-input.input--secondary:not(:placeholder-shown):focus {
--input-bg: #fbf1f1 !important;
--input-bg-hover: #faeaea !important;
--input-bg-focus: #fbf1f1 !important;
background: #fbf1f1 !important;
background-color: #fbf1f1 !important;
}
.login-input[data-filled='true']:focus,
.login-input[data-filled='true']:focus-visible,
.login-input.input--secondary:not(:placeholder-shown):focus {
box-shadow:
inset 0 0 0 1000px #fbf1f1,
0 0 0 2px rgb(158 27 27 / 16%) !important;
-webkit-box-shadow:
inset 0 0 0 1000px #fbf1f1,
0 0 0 2px rgb(158 27 27 / 16%) !important;
}
.login-input:-webkit-autofill,
.login-input:-webkit-autofill:hover,
.login-input:-webkit-autofill:focus {
-webkit-text-fill-color: var(--rc-text-primary) !important;
box-shadow: inset 0 0 0 1000px #fbf1f1 !important;
-webkit-box-shadow: inset 0 0 0 1000px #fbf1f1 !important;
}
.dark .login-input[data-filled='true'],
.dark .login-input.input--secondary:not(:placeholder-shown) {
--input-bg: rgb(158 27 27 / 14%) !important;
--input-bg-hover: rgb(158 27 27 / 18%) !important;
--input-bg-focus: rgb(158 27 27 / 14%) !important;
background: rgb(158 27 27 / 14%) !important;
box-shadow: none !important;
-webkit-box-shadow: none !important;
}
.rc-sidebar-logo {
width: 28px !important;
height: 28px !important;
object-fit: contain;
}
.rc-sidebar-title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.rc-sidebar-user-trigger {
display: grid !important;
width: 100% !important;
min-width: 0;
min-height: 52px;
grid-template-columns: 32px minmax(0, 1fr);
align-items: center;
justify-content: stretch !important;
gap: 10px;
padding: 8px 10px !important;
border: 0 !important;
border-radius: 12px !important;
background: transparent !important;
color: var(--rc-text-primary) !important;
text-align: left;
box-shadow: none !important;
}
.rc-sidebar-user-trigger:hover,
.rc-sidebar-user-trigger[data-hovered='true'],
.rc-sidebar-user-trigger:focus-visible {
background: var(--default) !important;
}
.rc-sidebar-avatar {
width: 32px !important;
height: 32px !important;
grid-column: 1;
}
.rc-sidebar-user-copy {
display: flex;
min-width: 0;
grid-column: 2;
flex-direction: column;
gap: 2px;
line-height: 1;
}
.rc-sidebar-user-copy strong,
.rc-sidebar-user-copy small {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.rc-sidebar-user-copy strong {
font-size: 14px;
font-weight: 550;
line-height: 18px;
}
.rc-sidebar-user-copy small {
color: var(--rc-text-muted);
font-size: 12px;
font-weight: 400;
line-height: 16px;
}
.rc-sidebar-user-menu-item {
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.rc-sidebar-user-menu-item--danger,
.rc-sidebar-user-menu-item--danger :is(label, .anticon, svg) {
color: var(--danger) !important;
}
.app-switcher-dialog {
width: min(46rem, calc(100vw - 32px)) !important;
max-width: 46rem !important;
max-height: min(42rem, calc(100dvh - 32px));
padding: 0 !important;
overflow: hidden;
}
.app-switcher-dialog__header {
padding: 16px 20px 12px !important;
}
.app-switcher-dialog__description {
margin: 4px 0 0;
color: var(--rc-text-muted);
font-size: 14px;
line-height: 20px;
}
.app-switcher-dialog__body {
min-height: 0;
padding: 4px 20px 20px !important;
overflow-y: auto;
scrollbar-gutter: stable;
}
.app-switcher-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
}
.app-switcher-state {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.app-switcher-card[data-current='true'] {
border-color: color-mix(in srgb, var(--rc-brand-primary) 34%, var(--rc-border-default));
background: var(--rc-brand-primary-soft);
}
.app-switcher-state {
padding: 40px 16px;
color: var(--rc-text-muted);
text-align: center;
font-size: 14px;
}
.audit-page {
display: flex;
min-width: 0;
flex-direction: column;
gap: 20px;
}
.audit-page-intro {
display: flex;
min-width: 0;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.audit-page-intro:has(> :only-child) {
justify-content: flex-end;
}
.audit-page-intro p {
margin: 0;
color: var(--rc-text-muted);
font-size: 14px;
line-height: 20px;
}
* {
box-sizing: border-box;
.audit-filter-card {
display: flex;
min-width: 0;
align-items: end;
justify-content: space-between;
gap: 16px;
padding: 18px;
border: 1px solid var(--rc-border-default);
border-radius: 24px;
background: var(--rc-bg-surface);
box-shadow: var(--rc-shadow-card);
}
a {
color: inherit;
.audit-filter-search {
width: min(360px, 100%);
min-width: 0;
}
.audit-filter-search__control {
display: flex;
width: 100%;
min-width: 0;
height: 40px;
align-items: center;
gap: 8px;
padding-inline: 12px;
border: 1px solid var(--rc-field-border);
border-radius: 8px;
background: var(--rc-bg-surface);
box-shadow: var(--field-shadow);
}
.audit-filter-search__control:focus-within {
border-color: var(--rc-brand-primary);
box-shadow: 0 0 0 2px color-mix(in oklab, var(--rc-brand-primary) 16%, transparent);
}
.audit-filter-search__control .input {
min-width: 0;
padding: 0 !important;
border: 0 !important;
background: transparent !important;
box-shadow: none !important;
}
@media (max-width: 767px) {
.app-switcher-grid {
grid-template-columns: 1fr;
}
.audit-filter-card {
align-items: stretch;
flex-direction: column;
}
.audit-filter-search {
width: 100%;
}
}
.audit-filter-card .rc-input-group,
.audit-filter-card .rc-form-input {
width: min(360px, 100%);
}
.audit-list-header,
.audit-section-header {
display: flex;
min-width: 0;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.audit-section-header h2,
.audit-list-header h2 {
margin: 0;
color: var(--rc-text-primary);
font-size: 16px;
font-weight: 600;
line-height: 24px;
}
.audit-section-meta {
color: var(--rc-text-muted);
font-size: 13px;
font-weight: 400;
}
.audit-project-tabs {
width: max-content;
max-width: 100%;
overflow-x: auto;
}
.audit-project-tabs .tabs__list-container,
.audit-project-tabs .tabs__list {
width: max-content !important;
min-width: max-content !important;
}
.audit-project-tabs .tabs__tab {
width: auto !important;
flex: 0 0 auto !important;
}
.audit-report-section .audit-filter-card > .rc-button:last-child {
margin-left: auto;
}
.audit-stat-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
}
.audit-stat {
display: flex;
min-width: 0;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 16px;
border: 1px solid var(--rc-border-default);
border-radius: 24px;
background: var(--rc-bg-surface);
box-shadow: var(--rc-shadow-card);
}
.audit-stat__copy {
min-width: 0;
}
.audit-stat__label {
display: block;
color: var(--rc-text-muted);
font-size: 13px;
line-height: 18px;
}
.audit-stat__value {
display: block;
margin-top: 6px;
color: var(--rc-text-primary);
font-size: 24px;
font-weight: 600;
line-height: 28px;
}
.audit-stat__icon {
display: grid;
width: 48px;
height: 48px;
flex: none;
place-items: center;
border-radius: 16px;
color: var(--rc-brand-primary);
background: var(--rc-brand-primary-soft);
}
.audit-stat__icon .anticon,
.audit-stat__icon svg {
display: block;
width: 20px;
height: 20px;
margin: 0;
}
.audit-report-sections {
display: grid;
gap: 20px;
}
.audit-report-section {
display: grid;
min-width: 0;
gap: 12px;
}
.rc-form-field {
display: flex;
min-width: 0;
flex-direction: column;
gap: 8px;
}
.rc-form-field > .label,
.rc-form-field [data-slot='label'] {
color: var(--rc-text-secondary);
font-size: 14px;
font-weight: 500;
line-height: 20px;
}
.rc-user-select__chips {
display: flex;
min-width: 0;
flex-wrap: wrap;
align-items: center;
gap: 6px;
margin-top: 8px;
}
.rc-user-select__chip {
display: inline-flex !important;
width: auto !important;
max-width: 100%;
min-height: 24px !important;
height: 24px !important;
flex: none;
align-items: center !important;
gap: 4px !important;
padding: 0 4px 0 8px !important;
border-width: 1px !important;
border-radius: 999px !important;
box-shadow: none !important;
font-size: 12px !important;
font-weight: 500 !important;
line-height: 16px !important;
letter-spacing: 0;
vertical-align: middle;
}
.rc-user-select__chip-label {
display: block;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font: inherit !important;
line-height: 16px !important;
}
.rc-user-select__chip-remove {
display: inline-grid !important;
width: 18px !important;
height: 18px !important;
min-width: 18px !important;
min-height: 18px !important;
flex: none;
place-items: center !important;
padding: 0 !important;
border: 0 !important;
border-radius: 50% !important;
background: transparent !important;
color: currentColor !important;
box-shadow: none !important;
line-height: 0 !important;
}
.rc-user-select__chip-remove:hover,
.rc-user-select__chip-remove[data-hovered='true'] {
background: var(--rc-brand-primary-soft-hover) !important;
}
.rc-user-select__chip-remove :is(.anticon, svg),
.rc-user-select__chip-remove .anticon svg {
display: block !important;
width: 12px !important;
height: 12px !important;
margin: 0 !important;
line-height: 0 !important;
}
.rc-form-input,
.rc-form-textarea {
width: 100%;
min-width: 0;
border-radius: 8px !important;
}
.rc-form-textarea {
min-height: 88px;
}
.rc-toast {
position: fixed;
top: 5rem;
left: 50%;
z-index: 160;
display: flex;
width: min(42rem, calc(100vw - 2rem));
align-items: center;
justify-content: center;
gap: 10px;
padding: 12px 14px;
border: 1px solid var(--rc-color-success-border);
border-radius: 14px;
color: var(--rc-color-success);
background: color-mix(in oklab, var(--rc-color-success-soft) 28%, var(--rc-bg-surface));
box-shadow: var(--rc-shadow-overlay);
font-size: 14px;
font-weight: 500;
transform: translateX(-50%);
}
.rc-toast--error {
border-color: var(--rc-color-danger-border);
color: var(--rc-color-danger);
background: color-mix(in oklab, var(--rc-color-danger-soft) 28%, var(--rc-bg-surface));
}
.rc-toast__icon,
.rc-toast__close {
display: grid;
width: 20px;
height: 20px;
flex: none;
place-items: center;
}
.rc-toast__close {
padding: 0;
border: 0;
border-radius: 999px;
color: currentColor;
background: transparent;
}
.audit-state {
display: flex;
min-height: 16rem;
align-items: center;
justify-content: center;
gap: 10px;
color: var(--rc-text-muted);
font-size: 14px;
}
.audit-state--error {
flex-direction: column;
color: var(--rc-color-danger);
text-align: center;
}
.audit-empty-state {
min-height: 15rem;
border: 1px solid var(--rc-border-default);
border-radius: 20px;
background: var(--rc-bg-surface);
}
.audit-control-section {
display: grid;
min-width: 0;
gap: 12px;
padding-top: 20px;
border-top: 1px solid var(--rc-border-default);
}
.audit-control-section:first-child {
padding-top: 0;
border-top: 0;
}
.audit-control-points-table {
min-width: 0;
}
.audit-control-points-table .pm-data-grid-cell-content {
width: 100%;
}
.audit-control-points-table .rc-form-textarea {
min-width: 220px;
}
.audit-control-points-table .rc-form-select {
min-width: 128px;
}
.audit-attachment-group {
display: grid;
min-width: 260px;
gap: 8px;
}
.audit-attachment-group + .audit-attachment-group {
margin-top: 16px;
}
.audit-attachment-label {
color: var(--rc-text-secondary);
font-size: 12px;
font-weight: 500;
line-height: 18px;
}
.audit-attachment-list {
display: grid;
gap: 4px;
}
.audit-attachment-item {
display: flex;
min-width: 0;
min-height: 32px;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.audit-attachment-link {
display: inline-flex;
min-width: 0;
align-items: center;
gap: 6px;
color: var(--rc-brand-primary);
font-size: 13px;
font-weight: 500;
text-decoration: none;
}
.audit-attachment-link:hover {
color: var(--rc-brand-primary-hover);
text-decoration: underline;
text-underline-offset: 3px;
}
.audit-attachment-empty {
color: var(--rc-text-muted);
font-size: 12px;
}
.audit-attachment-upload .drop-zone__area {
min-height: 116px;
padding: 12px;
}
.audit-attachment-upload .drop-zone__label,
.audit-attachment-upload .drop-zone__description {
font-size: 12px;
}
.audit-attachment-error {
color: var(--rc-color-danger);
font-size: 12px;
line-height: 18px;
}
.audit-detail-header,
.audit-detail-heading,
.audit-detail-title-row {
display: flex;
min-width: 0;
align-items: center;
}
.audit-detail-header {
flex-wrap: wrap;
justify-content: space-between;
gap: 16px;
}
.audit-detail-heading {
gap: 12px;
}
.audit-detail-title-row {
flex-wrap: wrap;
gap: 8px;
}
.audit-detail-title-row h2 {
margin: 0;
color: var(--rc-text-primary);
font-size: 18px;
font-weight: 600;
line-height: 26px;
}
.audit-detail-heading p {
margin: 4px 0 0;
color: var(--rc-text-muted);
font-size: 13px;
line-height: 20px;
}
.audit-back-button {
width: 32px !important;
min-width: 32px !important;
height: 32px !important;
padding: 0 !important;
border-radius: 999px !important;
}
.audit-detail-summary {
display: flex;
flex-wrap: wrap;
gap: 8px 32px;
padding: 14px 0;
border-block: 1px solid var(--rc-border-default);
color: var(--rc-text-muted);
font-size: 14px;
}
.audit-detail-summary strong {
margin-left: 4px;
color: var(--rc-text-primary);
font-weight: 600;
}
.audit-detail-summary strong.is-success {
color: var(--rc-color-success);
}
.audit-fill-page {
min-height: 100vh;
padding: 24px;
color: var(--rc-text-primary);
background: var(--rc-bg-canvas);
}
.audit-fill-shell {
width: min(100%, 1600px);
min-width: 0;
margin: 0 auto;
padding: 28px;
border: 1px solid var(--rc-border-default);
border-radius: 24px;
background: var(--rc-bg-surface);
box-shadow: var(--rc-shadow-card);
}
.audit-fill-header {
display: flex;
min-width: 0;
flex-wrap: wrap;
align-items: flex-start;
justify-content: space-between;
gap: 20px;
padding-bottom: 24px;
border-bottom: 1px solid var(--rc-border-default);
}
.audit-fill-identity {
display: flex;
min-width: 0;
align-items: flex-start;
gap: 12px;
}
.audit-fill-logo {
width: 36px;
height: 36px;
flex: none;
object-fit: contain;
}
.audit-fill-logo--dark {
display: none;
}
.dark .audit-fill-logo--light {
display: none;
}
.dark .audit-fill-logo--dark {
display: block;
}
.audit-fill-identity span {
display: block;
margin-bottom: 4px;
color: var(--rc-brand-primary);
font-size: 12px;
font-weight: 600;
line-height: 18px;
}
.dark .audit-fill-identity span {
color: var(--rc-brand-light);
}
.audit-fill-identity h1 {
margin: 0;
color: var(--rc-text-primary);
font-size: 20px;
font-weight: 600;
line-height: 28px;
}
.audit-fill-identity p {
margin: 4px 0 0;
color: var(--rc-text-muted);
font-size: 14px;
line-height: 20px;
}
.audit-fill-content {
display: grid;
gap: 20px;
margin-top: 24px;
}
.audit-fill-actions {
position: sticky;
bottom: 0;
z-index: 20;
display: flex;
justify-content: flex-end;
gap: 8px;
margin: 24px -8px -8px;
padding: 16px 8px 8px;
border-top: 1px solid var(--rc-border-default);
background: color-mix(in srgb, var(--rc-bg-surface) 94%, transparent);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
}
@media (max-width: 900px) {
.audit-stat-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 767px) {
.login-card [data-slot='card-content'] {
padding: 28px 24px !important;
}
.login-brand strong {
font-size: 28px;
}
.audit-page-intro,
.audit-list-header,
.audit-section-header,
.audit-filter-card {
align-items: stretch;
flex-direction: column;
}
.audit-filter-card .rc-input-group,
.audit-filter-card .rc-form-input {
width: 100%;
}
.audit-report-section .audit-filter-card > .rc-button:last-child {
width: 100%;
margin-left: 0;
}
.audit-stat-grid {
grid-template-columns: 1fr;
}
.audit-detail-header,
.audit-section-header {
align-items: stretch;
}
.audit-detail-header > .rc-button,
.audit-section-header > .rc-button {
width: 100%;
}
.audit-fill-page {
padding: 0;
}
.audit-fill-shell {
padding: 20px 16px;
border: 0;
border-radius: 0;
box-shadow: none;
}
.audit-fill-header > .rc-button {
width: 100%;
}
.audit-fill-actions {
margin-inline: -8px;
}
.audit-fill-actions .rc-button {
flex: 1;
}
}
import xyLogo from '@/assets/xy-logo.png';
import { AuthLayout } from '@/catalyst-ui-kit/javascript/auth-layout';
import { Button } from '@/catalyst-ui-kit/javascript/button';
import { ErrorMessage, Field, Label } from '@/catalyst-ui-kit/javascript/fieldset';
import { Heading } from '@/catalyst-ui-kit/javascript/heading';
import { Input } from '@/catalyst-ui-kit/javascript/input';
import xyLogo from '@/assets/identity/xy-logo-red.png';
import xyLogoWhite from '@/assets/identity/xy-logo-white.png';
import { APP_DISPLAY_NAME } from '@/constants/app';
import authService from '@/services/authService';
import { getCaptcha, userLogin } from '@/services/DataService';
import { SiteContext } from '@/wrapper/Site';
import { Alert, Button, Card, Input, Label, Spinner, TextField } from '@heroui/react';
import CryptoJS from 'crypto-js';
import { useContext, useEffect, useState } from 'react';
import { history, useSearchParams } from 'umi';
......@@ -41,6 +39,7 @@ export default function LoginPage() {
};
useEffect(() => {
document.title = APP_DISPLAY_NAME;
refreshCaptcha();
}, []);
......@@ -69,74 +68,45 @@ export default function LoginPage() {
};
return (
<div className="min-h-dvh bg-zinc-100">
<AuthLayout>
<form onSubmit={handleSubmit} className="w-full max-w-sm space-y-7">
<div>
<img src={xyLogo} alt="鑫元基金" className="h-9 w-auto object-contain" />
<Heading className="mt-7">监察稽核管理</Heading>
<p className="mt-2 text-sm text-zinc-500">{site?.title || site?.name || '鑫元基金'}</p>
<main className="login-page">
<Card className="login-card">
<Card.Content>
<div className="login-brand login-card-brand">
<img alt="鑫元" className="login-brand-logo login-brand-logo--light" src={xyLogo} />
<img alt="" aria-hidden="true" className="login-brand-logo login-brand-logo--dark" src={xyLogoWhite} />
<strong>{APP_DISPLAY_NAME}</strong>
</div>
<div className="space-y-5">
<Field>
<form className="login-form" onSubmit={handleSubmit}>
<TextField className="login-field" isDisabled={loading} isRequired value={identity} onChange={setIdentity}>
<Label>账号</Label>
<Input
value={identity}
onChange={(event) => setIdentity(event.target.value)}
disabled={loading}
autoComplete="username"
required
/>
</Field>
<Field>
<Input autoComplete="username" className="login-input" data-filled={identity ? 'true' : undefined} placeholder="请输入账号" variant="secondary" />
</TextField>
<TextField className="login-field" isDisabled={loading} isRequired value={password} onChange={setPassword}>
<Label>密码</Label>
<Input
type="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
disabled={loading}
autoComplete="current-password"
required
/>
</Field>
<Field>
<Input autoComplete="current-password" className="login-input" data-filled={password ? 'true' : undefined} placeholder="请输入密码" type="password" variant="secondary" />
</TextField>
<TextField className="login-field" isDisabled={loading} isRequired value={captchaCode} onChange={setCaptchaCode}>
<Label>验证码</Label>
<div className="flex items-stretch gap-3">
<Input
value={captchaCode}
onChange={(event) => setCaptchaCode(event.target.value)}
disabled={loading}
required
/>
<Button
outline
type="button"
onClick={() => {
setError('');
refreshCaptcha();
}}
<div className="login-captcha-row">
<Input className="login-input" data-filled={captchaCode ? 'true' : undefined} placeholder="请输入验证码" variant="secondary" />
<button
aria-label="刷新验证码"
className="login-captcha"
disabled={loading || captchaLoading}
className="h-11 w-36 shrink-0 overflow-hidden"
title="刷新验证码"
onClick={() => { setError(''); refreshCaptcha(); }}
type="button"
>
{captchaData?.image ? (
<span
className="captcha-image flex h-full w-full items-center justify-center"
dangerouslySetInnerHTML={{ __html: captchaData.image }}
/>
) : captchaLoading ? '加载中...' : '刷新验证码'}
</Button>
</div>
</Field>
{error ? <ErrorMessage>{error}</ErrorMessage> : null}
{captchaData?.image ? <span dangerouslySetInnerHTML={{ __html: captchaData.image }} /> : captchaLoading ? <Spinner size="sm" /> : '刷新验证码'}
</button>
</div>
<Button color="blue" type="submit" className="w-full" disabled={loading || captchaLoading}>
{loading ? '登录中...' : '登录'}
</TextField>
{error ? <Alert className="login-error" color="danger" role="alert">{error}</Alert> : null}
<Button className="login-submit-button" fullWidth isDisabled={captchaLoading} isPending={loading} type="submit" variant="primary">
{loading ? '正在登录...' : '登录'}
</Button>
</form>
</AuthLayout>
</div>
</Card.Content>
</Card>
</main>
);
}
import {
Alert,
AlertActions,
AlertDescription,
AlertTitle,
} from '@/catalyst-ui-kit/javascript/alert';
import { Badge } from '@/catalyst-ui-kit/javascript/badge';
import { Button } from '@/catalyst-ui-kit/javascript/button';
import {
Dialog,
DialogActions,
DialogBody,
DialogDescription,
DialogTitle,
} from '@/catalyst-ui-kit/javascript/dialog';
import { ErrorMessage, Field, Label } from '@/catalyst-ui-kit/javascript/fieldset';
import { Heading } from '@/catalyst-ui-kit/javascript/heading';
import { Input, InputGroup } from '@/catalyst-ui-kit/javascript/input';
import { Select } from '@/catalyst-ui-kit/javascript/select';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/catalyst-ui-kit/javascript/table';
import { Textarea } from '@/catalyst-ui-kit/javascript/textarea';
import { DataTable, DataTableChip, DataTableTextPair } from '@/components/DataTable';
import FilterSelect from '@/components/FilterSelect';
import { Edit, Search, Delete } from '@/components/AppIcons';
import { TableActionButton, TableActionGroup } from '@/components/TableActionButton';
import { useAuthorization } from '@/hooks/user';
import {
MagnifyingGlassIcon,
PencilSquareIcon,
PlusIcon,
TrashIcon,
} from '@heroicons/react/20/solid';
import { AlertDialog, Button, FieldError, Input, Label, Modal, Tabs, TextArea, TextField, Tooltip } from '@heroui/react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { EmptyState, ErrorState, InlineNotice, LoadingState } from './PageState';
import ReferenceSearchSelect from './ReferenceSearchSelect';
......@@ -112,46 +83,38 @@ function AuditProjectDialog({ open, onClose, onSaved }) {
};
return (
<Dialog open={open} onClose={saving ? () => {} : onClose} size="md">
<DialogTitle>添加稽核项目</DialogTitle>
<DialogDescription>项目序号决定列表与报告中的展示顺序。</DialogDescription>
<DialogBody className="space-y-5">
<Field>
<Label>{requiredLabel('项目名称', true)}</Label>
<Input
value={form.title}
onChange={(event) => setForm((current) => ({ ...current, title: event.target.value }))}
placeholder="请输入项目名称"
autoFocus
/>
</Field>
<Field>
<Label>{requiredLabel('项目序号', true)}</Label>
<Input
type="number"
min="1"
max="500"
value={form.order}
onChange={(event) => setForm((current) => ({ ...current, order: event.target.value }))}
/>
</Field>
<Field>
<Modal.Root isOpen={open} onOpenChange={(isOpen) => { if (!isOpen && !saving) onClose(); }}>
<Modal.Trigger aria-hidden="true" aria-label="打开添加稽核项目弹窗" className="sr-only" tabIndex={-1} />
<Modal.Backdrop className="rc-dialog-backdrop" isDismissable={!saving} variant="blur">
<Modal.Container placement="center" scroll="inside" size="lg">
<Modal.Dialog className="rc-dialog-panel rc-dialog-panel--lg">
<Modal.CloseTrigger aria-label="关闭" className="rc-dialog-close" />
<Modal.Header><Modal.Heading className="rc-dialog-title">添加稽核项目</Modal.Heading></Modal.Header>
<Modal.Body className="rc-dialog-body space-y-5">
<TextField className="rc-form-field" isRequired value={form.title} onChange={(value) => setForm((current) => ({ ...current, title: value }))}>
<Label>项目名称</Label>
<Input placeholder="请输入项目名称" autoFocus />
</TextField>
<TextField className="rc-form-field" isRequired value={String(form.order)} onChange={(value) => setForm((current) => ({ ...current, order: value }))}>
<Label>项目序号</Label>
<Input type="number" min="1" max="500" />
</TextField>
<TextField className="rc-form-field" value={form.description} onChange={(value) => setForm((current) => ({ ...current, description: value }))}>
<Label>项目描述</Label>
<Textarea
rows={4}
value={form.description}
onChange={(event) => setForm((current) => ({ ...current, description: event.target.value }))}
/>
</Field>
{error ? <ErrorMessage>{error}</ErrorMessage> : null}
</DialogBody>
<DialogActions>
<Button plain onClick={onClose} disabled={saving}>取消</Button>
<Button color="blue" onClick={handleSave} disabled={saving}>
<TextArea rows={4} />
</TextField>
{error ? <FieldError>{error}</FieldError> : null}
</Modal.Body>
<Modal.Footer className="rc-dialog-actions">
<Button variant="outline" onPress={onClose} isDisabled={saving}>取消</Button>
<Button variant="primary" onPress={handleSave} isDisabled={saving}>
{saving ? '保存中...' : '保存'}
</Button>
</DialogActions>
</Dialog>
</Modal.Footer>
</Modal.Dialog>
</Modal.Container>
</Modal.Backdrop>
</Modal.Root>
);
}
......@@ -219,19 +182,23 @@ function ControlPointDialog({ open, onClose, onSaved, point, definition }) {
};
return (
<Dialog open={open} onClose={saving ? () => {} : onClose} size="2xl">
<DialogTitle>{point ? '编辑控制点' : '添加控制点'}</DialogTitle>
<DialogDescription>引用字段保存时仅写入记录 ID。</DialogDescription>
<DialogBody className="grid gap-5 sm:grid-cols-2">
<Field className="sm:col-span-2">
<Label>{requiredLabel('名称', true)}</Label>
<Input value={form.name} onChange={(event) => change('name', event.target.value)} />
</Field>
<Field className="sm:col-span-2">
<Modal.Root isOpen={open} onOpenChange={(isOpen) => { if (!isOpen && !saving) onClose(); }}>
<Modal.Trigger aria-hidden="true" aria-label={`打开${point ? '编辑' : '添加'}控制点弹窗`} className="sr-only" tabIndex={-1} />
<Modal.Backdrop className="rc-dialog-backdrop" isDismissable={!saving} variant="blur">
<Modal.Container placement="center" scroll="inside" size="3xl">
<Modal.Dialog className="rc-dialog-panel rc-dialog-panel--3xl">
<Modal.CloseTrigger aria-label="关闭" className="rc-dialog-close" />
<Modal.Header><Modal.Heading className="rc-dialog-title">{point ? '编辑控制点' : '添加控制点'}</Modal.Heading></Modal.Header>
<Modal.Body className="rc-dialog-body grid gap-5 sm:grid-cols-2">
<TextField className="rc-form-field sm:col-span-2" isRequired value={form.name} onChange={(value) => change('name', value)}>
<Label>名称</Label>
<Input />
</TextField>
<TextField className="rc-form-field sm:col-span-2" value={form.description} onChange={(value) => change('description', value)}>
<Label>描述信息</Label>
<Textarea rows={4} value={form.description} onChange={(event) => change('description', event.target.value)} />
</Field>
<Field>
<TextArea rows={4} />
</TextField>
<div className="rc-form-field">
<Label>{requiredLabel('责任人', true)}</Label>
<ReferenceSearchSelect
value={form.owner}
......@@ -239,8 +206,8 @@ function ControlPointDialog({ open, onClose, onSaved, point, definition }) {
loadOptions={loadDepartmentOptions}
placeholder="搜索责任部门"
/>
</Field>
<Field>
</div>
<div className="rc-form-field">
<Label>相关标签</Label>
<ReferenceSearchSelect
multiple
......@@ -249,8 +216,8 @@ function ControlPointDialog({ open, onClose, onSaved, point, definition }) {
loadOptions={searchTags}
placeholder="搜索并添加标签"
/>
</Field>
<Field className="sm:col-span-2">
</div>
<div className="rc-form-field sm:col-span-2">
<Label>相关制度</Label>
<ReferenceSearchSelect
multiple
......@@ -259,59 +226,43 @@ function ControlPointDialog({ open, onClose, onSaved, point, definition }) {
loadOptions={loadRegulationOptions}
placeholder="搜索并添加制度"
/>
</Field>
<Field>
</div>
<div className="rc-form-field">
<Label>{requiredLabel('控制措施', Boolean(typeField?.required))}</Label>
<Select value={form.type} onChange={(event) => change('type', event.target.value)}>
{typeOptions.map((option) => (
<option key={option.value} value={option.value}>{option.label}</option>
))}
</Select>
</Field>
<FilterSelect aria-label="控制措施" options={typeOptions} value={form.type} onChange={(value) => change('type', value)} />
</div>
{form.type === 'form' ? (
<Field>
<TextField className="rc-form-field" value={form.form_label} onChange={(value) => change('form_label', value)}>
<Label>文本描述</Label>
<Input value={form.form_label} onChange={(event) => change('form_label', event.target.value)} />
</Field>
<Input />
</TextField>
) : null}
{form.type === 'attachment' ? (
<Field className="sm:col-span-2">
<div className="rc-form-field sm:col-span-2">
<Label>文件描述</Label>
<div className="space-y-2">
{form.attachment_label.map((label, index) => (
<div key={index} className="flex items-center gap-2">
<Input
value={label}
onChange={(event) => {
<TextField aria-label={`文件描述 ${index + 1}`} className="min-w-0 flex-1" value={label} onChange={(value) => {
const labels = [...form.attachment_label];
labels[index] = event.target.value;
labels[index] = value;
change('attachment_label', labels);
}}
placeholder="请输入文件类别"
/>
<Button
plain
type="button"
aria-label="删除文件描述"
title="删除文件描述"
onClick={() => change('attachment_label', form.attachment_label.filter((_, itemIndex) => itemIndex !== index))}
>
<TrashIcon />
</Button>
}}><Input placeholder="请输入文件类别" /></TextField>
<Tooltip delay={0}><Button isIconOnly type="button" aria-label="删除文件描述" size="sm" variant="ghost" onPress={() => change('attachment_label', form.attachment_label.filter((_, itemIndex) => itemIndex !== index))}><Delete size={16} /></Button><Tooltip.Content>删除文件描述</Tooltip.Content></Tooltip>
</div>
))}
<Button outline type="button" onClick={() => change('attachment_label', [...form.attachment_label, ''])}>
<PlusIcon />添加文件描述
<Button variant="outline" type="button" onPress={() => change('attachment_label', [...form.attachment_label, ''])}>
添加文件描述
</Button>
</div>
</Field>
</div>
) : null}
{form.type === 'computed' ? (
<>
<Field>
<div className="rc-form-field">
<Label>量化指标</Label>
<ReferenceSearchSelect
value={form.indicator}
......@@ -319,28 +270,26 @@ function ControlPointDialog({ open, onClose, onSaved, point, definition }) {
loadOptions={loadIndicatorOptions}
placeholder="搜索量化指标"
/>
</Field>
<Field>
</div>
<TextField className="rc-form-field" value={String(form.computed_interval)} onChange={(value) => change('computed_interval', value)}>
<Label>执行频率(天)</Label>
<Input
type="number"
min="1"
value={form.computed_interval}
onChange={(event) => change('computed_interval', event.target.value)}
/>
</Field>
<Input type="number" min="1" />
</TextField>
</>
) : null}
{error ? <ErrorMessage className="sm:col-span-2">{error}</ErrorMessage> : null}
</DialogBody>
<DialogActions>
<Button plain onClick={onClose} disabled={saving}>取消</Button>
<Button color="blue" onClick={handleSave} disabled={saving}>
{error ? <FieldError className="sm:col-span-2">{error}</FieldError> : null}
</Modal.Body>
<Modal.Footer className="rc-dialog-actions">
<Button variant="outline" onPress={onClose} isDisabled={saving}>取消</Button>
<Button variant="primary" onPress={handleSave} isDisabled={saving}>
{saving ? '保存中...' : '保存'}
</Button>
</DialogActions>
</Dialog>
</Modal.Footer>
</Modal.Dialog>
</Modal.Container>
</Modal.Backdrop>
</Modal.Root>
);
}
......@@ -397,6 +346,50 @@ export default function AuditProcess() {
.filter(Boolean)
.some((value) => String(value).toLowerCase().includes(normalizedKeyword));
});
const pointColumns = [
{
key: 'order',
title: '序号',
width: 84,
render: ({ index }) => `${activeProcess?.order || ''}.${index + 1}`,
},
{
key: 'point',
title: '控制点',
minWidth: 300,
render: ({ point }) => (
<div className="min-w-0">
<DataTableTextPair primary={point.name || '未命名控制点'} secondary={point.description} />
<div className="mt-2"><DataTableChip>{fieldByName(definition, 'type')?.settings?.options?.find((option) => option.value === point.type)?.label || point.type}</DataTableChip></div>
</div>
),
},
{ key: 'owner', title: '责任人', minWidth: 160, render: ({ point }) => point.owner?.title || point.owner?.label || '-' },
{
key: 'tags',
title: '相关标签',
minWidth: 220,
render: ({ point }) => (point.related_tags || []).length ? <div className="flex flex-wrap gap-1">{point.related_tags.map((tag) => <DataTableChip key={normalizeRecordId(tag)}>{tag.label || tag.title || tag.name}</DataTableChip>)}</div> : '-',
},
{
key: 'regulations',
title: '相关制度',
minWidth: 240,
render: ({ point }) => (point.related_regulations || []).length ? point.related_regulations.map((regulation) => <div key={normalizeRecordId(regulation)}>{regulation.title || regulation.name}</div>) : '-',
},
...(canManage ? [{
key: 'actions',
title: '操作',
width: 112,
isActions: true,
render: ({ point, index }) => (
<TableActionGroup className="justify-start">
<TableActionButton icon={Edit} label="编辑控制点" onPress={() => setPointDialog({ point, index })} tone="edit" />
<TableActionButton icon={Delete} label="删除控制点" onPress={() => setDeletePointIndex(index)} tone="delete" />
</TableActionGroup>
),
}] : []),
];
const savePoint = async (nextPoint) => {
const points = controlPoints.map((point) => ({ ...point }));
......@@ -428,16 +421,10 @@ export default function AuditProcess() {
if (error && processes.length === 0) return <ErrorState message={error} onRetry={loadData} />;
return (
<div className="space-y-6">
<div className="flex flex-wrap items-start justify-between gap-4">
<div>
<Heading>稽核项目</Heading>
<p className="mt-1 text-sm text-zinc-500">维护稽核领域与控制点,报告生成后将保留当期快照。</p>
</div>
<div className="audit-page">
<div className="audit-page-intro">
{canManage ? (
<Button color="blue" onClick={() => setProjectDialogOpen(true)}>
<PlusIcon />添加项目
</Button>
<Button size="sm" variant="primary" onPress={() => setProjectDialogOpen(true)}>添加项目</Button>
) : null}
</div>
......@@ -445,108 +432,43 @@ export default function AuditProcess() {
<InlineNotice tone={notice.tone} onDismiss={() => setNotice(null)}>{notice.text}</InlineNotice>
) : null}
{processes.length > 0 ? (
<div className="overflow-x-auto border-y border-zinc-200 py-3">
<div className="flex min-w-max gap-2">
{processes.length > 1 ? (
<Tabs className="filter-tabs-wrap audit-project-tabs" selectedKey={activeId} onSelectionChange={(key) => setActiveId(String(key))}>
<Tabs.ListContainer>
<Tabs.List aria-label="稽核项目">
{processes.map((process) => {
const id = normalizeRecordId(process);
const active = id === activeId;
return (
<button
type="button"
key={id}
onClick={() => setActiveId(id)}
className={`flex min-h-11 items-center gap-3 rounded-lg border px-4 py-2 text-left text-sm transition ${
active
? 'border-[var(--pm-brand)] bg-red-50 text-red-900'
: 'border-zinc-200 bg-white text-zinc-700 hover:border-zinc-300'
}`}
>
<span className="font-medium">{process.order}.{process.title}</span>
<Badge color={active ? 'red' : 'zinc'}>{process.metadata?.control_points?.length || 0}</Badge>
</button>
);
return <Tabs.Tab id={id} key={id}>{process.order}.{process.title}<Tabs.Indicator /></Tabs.Tab>;
})}
</div>
</div>
</Tabs.List>
</Tabs.ListContainer>
</Tabs>
) : null}
{activeProcess ? (
<section className="space-y-4">
<div className="flex flex-wrap items-center justify-between gap-3">
<InputGroup className="w-full max-w-sm">
<MagnifyingGlassIcon />
<Input value={keyword} onChange={(event) => setKeyword(event.target.value)} placeholder="搜索控制点、描述或责任人" />
</InputGroup>
<section className="audit-report-section">
<div className="audit-filter-card">
<TextField aria-label="搜索控制点" className="audit-filter-search" value={keyword} onChange={setKeyword}>
<div className="audit-filter-search__control"><Search size={16} /><Input placeholder="搜索控制点、描述或责任人" /></div>
</TextField>
{canManage ? (
<Button color="blue" onClick={() => setPointDialog({ point: null, index: null })}>
<PlusIcon />添加控制点
</Button>
<Button size="sm" variant="primary" onPress={() => setPointDialog({ point: null, index: null })}>添加控制点</Button>
) : null}
</div>
{filteredPoints.length === 0 ? (
<EmptyState
title={keyword ? '没有匹配的控制点' : '当前项目还没有控制点'}
description={keyword ? '请调整搜索条件。' : '新增控制点后即可在报告中生成对应检查项。'}
<DataTable
ariaLabel="稽核控制点"
columns={pointColumns}
rows={filteredPoints}
getRowKey={({ point, index }) => `${point.name}-${index}`}
emptyTitle={keyword ? '没有匹配的控制点' : '当前项目还没有控制点'}
/>
) : (
<Table dense striped>
<TableHead>
<TableRow>
<TableHeader className="w-20">序号</TableHeader>
<TableHeader>控制点</TableHeader>
<TableHeader>责任人</TableHeader>
<TableHeader>相关标签</TableHeader>
<TableHeader>相关制度</TableHeader>
{canManage ? <TableHeader className="w-24 text-right">操作</TableHeader> : null}
</TableRow>
</TableHead>
<TableBody>
{filteredPoints.map(({ point, index }) => (
<TableRow key={`${point.name}-${index}`}>
<TableCell className="font-semibold text-zinc-900">{activeProcess.order}.{index + 1}</TableCell>
<TableCell className="min-w-80 whitespace-normal">
<div className="font-medium text-zinc-950">{point.name || '未命名控制点'}</div>
{point.description ? <div className="mt-1 whitespace-pre-line text-xs leading-5 text-zinc-500">{point.description}</div> : null}
<div className="mt-2"><Badge color="zinc">{fieldByName(definition, 'type')?.settings?.options?.find((option) => option.value === point.type)?.label || point.type}</Badge></div>
</TableCell>
<TableCell>{point.owner?.title || point.owner?.label || '-'}</TableCell>
<TableCell className="whitespace-normal">
<div className="flex min-w-32 flex-wrap gap-1">
{(point.related_tags || []).length
? point.related_tags.map((tag) => <Badge key={normalizeRecordId(tag)}>{tag.label || tag.title || tag.name}</Badge>)
: '-'}
</div>
</TableCell>
<TableCell className="min-w-48 whitespace-normal">
{(point.related_regulations || []).length
? point.related_regulations.map((regulation) => <div key={normalizeRecordId(regulation)}>{regulation.title || regulation.name}</div>)
: '-'}
</TableCell>
{canManage ? (
<TableCell>
<div className="flex justify-end gap-1">
<Button plain aria-label="编辑控制点" title="编辑控制点" onClick={() => setPointDialog({ point, index })}>
<PencilSquareIcon />
</Button>
<Button plain aria-label="删除控制点" title="删除控制点" onClick={() => setDeletePointIndex(index)}>
<TrashIcon className="text-red-600" />
</Button>
</div>
</TableCell>
) : null}
</TableRow>
))}
</TableBody>
</Table>
)}
</section>
) : (
<EmptyState
title="还没有稽核项目"
description={canManage ? '先创建一个稽核项目,再维护控制点。' : '当前没有可查看的稽核项目。'}
action={canManage ? <Button color="blue" onClick={() => setProjectDialogOpen(true)}><PlusIcon />添加项目</Button> : null}
action={canManage ? <Button size="sm" variant="primary" onPress={() => setProjectDialogOpen(true)}>添加项目</Button> : null}
/>
)}
......@@ -566,14 +488,21 @@ export default function AuditProcess() {
point={pointDialog?.point}
definition={definition}
/>
<Alert open={deletePointIndex !== null} onClose={() => setDeletePointIndex(null)}>
<AlertTitle>删除控制点</AlertTitle>
<AlertDescription>删除后,后续新生成的报告将不再包含该控制点;已生成报告的快照不受影响。</AlertDescription>
<AlertActions>
<Button plain onClick={() => setDeletePointIndex(null)}>取消</Button>
<Button color="red" onClick={removePoint}>删除</Button>
</AlertActions>
</Alert>
<AlertDialog.Root isOpen={deletePointIndex !== null} onOpenChange={(isOpen) => { if (!isOpen) setDeletePointIndex(null); }}>
<AlertDialog.Trigger aria-hidden="true" aria-label="打开删除控制点确认弹窗" className="sr-only" tabIndex={-1} />
<AlertDialog.Backdrop className="rc-dialog-backdrop" variant="blur">
<AlertDialog.Container placement="center" size="md">
<AlertDialog.Dialog className="rc-dialog-panel rc-dialog-panel--md">
<AlertDialog.Header><AlertDialog.Heading className="rc-dialog-title">删除控制点</AlertDialog.Heading></AlertDialog.Header>
<AlertDialog.Body className="rc-dialog-body">删除后,后续新生成的报告将不再包含该控制点;已生成报告的快照不受影响。</AlertDialog.Body>
<AlertDialog.Footer className="rc-dialog-actions">
<Button variant="outline" onPress={() => setDeletePointIndex(null)}>取消</Button>
<Button variant="danger" onPress={removePoint}>确认删除</Button>
</AlertDialog.Footer>
</AlertDialog.Dialog>
</AlertDialog.Container>
</AlertDialog.Backdrop>
</AlertDialog.Root>
</div>
);
}
import {
Alert,
AlertActions,
AlertDescription,
AlertTitle,
} from '@/catalyst-ui-kit/javascript/alert';
import { Badge } from '@/catalyst-ui-kit/javascript/badge';
import { Button } from '@/catalyst-ui-kit/javascript/button';
import {
Dialog,
DialogActions,
DialogBody,
DialogDescription,
DialogTitle,
} from '@/catalyst-ui-kit/javascript/dialog';
import { Heading } from '@/catalyst-ui-kit/javascript/heading';
import { Input, InputGroup } from '@/catalyst-ui-kit/javascript/input';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/catalyst-ui-kit/javascript/table';
import { DataTable, DataTableChip, DataTablePrimaryText } from '@/components/DataTable';
import { CheckCircleIcon, ClipboardDocumentListIcon, ClockIcon, Project } from '@/components/AppIcons';
import { useAuthorization } from '@/hooks/user';
import {
ArrowDownTrayIcon,
CheckCircleIcon,
ClipboardDocumentIcon,
MagnifyingGlassIcon,
PlusIcon,
} from '@heroicons/react/20/solid';
import { Search } from '@/components/AppIcons';
import { AlertDialog, Button, Input, Modal, Tabs, TextField } from '@heroui/react';
import dayjs from 'dayjs';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { history } from 'umi';
import AuditReportContent from './AuditReportContent';
import { EmptyState, ErrorState, InlineNotice, LoadingState } from './PageState';
import {
......@@ -82,23 +55,28 @@ function FillLinksDialog({ open, onClose, report, onNotice }) {
};
return (
<Dialog open={open} onClose={onClose} size="lg">
<DialogTitle>责任部门填报链接</DialogTitle>
<DialogDescription>链接按控制点责任人过滤,只展示该责任人需要填写的内容。</DialogDescription>
<DialogBody>
<div className="divide-y divide-zinc-100 border-y border-zinc-200">
<Modal.Root isOpen={open} onOpenChange={(isOpen) => { if (!isOpen) onClose(); }}>
<Modal.Trigger aria-hidden="true" aria-label="打开责任部门填报链接弹窗" className="sr-only" tabIndex={-1} />
<Modal.Backdrop className="rc-dialog-backdrop" variant="blur">
<Modal.Container placement="center" scroll="inside" size="lg">
<Modal.Dialog className="rc-dialog-panel rc-dialog-panel--lg">
<Modal.CloseTrigger aria-label="关闭" className="rc-dialog-close" />
<Modal.Header><Modal.Heading className="rc-dialog-title">责任部门填报链接</Modal.Heading></Modal.Header>
<Modal.Body className="rc-dialog-body">
<div className="divide-y divide-[var(--rc-border-default)]">
{owners.map((owner) => (
<div key={owner.id} className="flex items-center justify-between gap-3 py-3">
<span className="text-sm font-medium text-zinc-900">{owner.label}</span>
<Button outline onClick={() => copyLink(owner)}>
<ClipboardDocumentIcon />复制链接
</Button>
<span className="text-sm font-medium text-[var(--rc-text-primary)]">{owner.label}</span>
<Button size="sm" variant="outline" onPress={() => copyLink(owner)}>复制链接</Button>
</div>
))}
</div>
</DialogBody>
<DialogActions><Button color="blue" onClick={onClose}>完成</Button></DialogActions>
</Dialog>
</Modal.Body>
<Modal.Footer className="rc-dialog-actions"><Button size="sm" variant="outline" onPress={onClose}>关闭</Button></Modal.Footer>
</Modal.Dialog>
</Modal.Container>
</Modal.Backdrop>
</Modal.Root>
);
}
......@@ -146,6 +124,12 @@ export default function AuditReport() {
}, [loadData]);
const stats = getReportStats(report);
const historyColumns = [
{ key: 'title', title: '报告名称', minWidth: 300, render: (item) => <DataTablePrimaryText text={item.title} showTooltip /> },
{ key: 'created', title: '创建时间', width: 180, render: (item) => dayjs(item.created_at).format('YYYY-MM-DD HH:mm') },
{ key: 'submitted', title: '提交时间', width: 180, render: (item) => item.metadata?.submitted_time ? dayjs(item.metadata.submitted_time).format('YYYY-MM-DD HH:mm') : '-' },
{ key: 'status', title: '状态', width: 120, render: () => <DataTableChip className="rc-status-chip--success">已归档</DataTableChip> },
];
const updatePoint = (processIndex, pointIndex, patch) => {
setReport((current) => updateReportPoint(current, processIndex, pointIndex, patch));
};
......@@ -229,19 +213,15 @@ export default function AuditReport() {
if (error && !report && historyReports.length === 0) return <ErrorState message={error} onRetry={loadData} />;
return (
<div className="space-y-6">
<div className="flex flex-wrap items-start justify-between gap-4">
<div>
<Heading>稽核报告</Heading>
<p className="mt-1 text-sm text-zinc-500">汇总责任部门填报内容,完成复核后提交归档。</p>
</div>
<div className="audit-page">
<div className="audit-page-intro">
{tab === 'current' && report ? (
<div className="flex flex-wrap gap-2">
<Button outline onClick={() => exportAuditReport(report)}><ArrowDownTrayIcon />导出</Button>
{canManage ? <Button outline onClick={() => setLinksOpen(true)}><ClipboardDocumentIcon />填报链接</Button> : null}
<Button size="sm" variant="outline" onPress={() => exportAuditReport(report)}>导出</Button>
{canManage ? <Button size="sm" variant="outline" onPress={() => setLinksOpen(true)}>填报链接</Button> : null}
{canManage ? (
<Button color="blue" disabled={!canSubmitReport(report) || saving} onClick={() => setSubmitOpen(true)}>
<CheckCircleIcon />提交报告
<Button size="sm" variant="primary" isDisabled={!canSubmitReport(report) || saving} onPress={() => setSubmitOpen(true)}>
提交报告
</Button>
) : null}
</div>
......@@ -250,45 +230,35 @@ export default function AuditReport() {
{notice ? <InlineNotice tone={notice.tone} onDismiss={() => setNotice(null)}>{notice.text}</InlineNotice> : null}
<div className="grid border-y border-zinc-200 sm:grid-cols-2 lg:grid-cols-4">
<div className="audit-stat-grid" data-count="4">
{[
['全部控制点', stats.total, 'text-zinc-950'],
['未填写', stats.empty, 'text-zinc-600'],
['待确认', stats.pending, 'text-amber-700'],
['已完成', stats.completed, 'text-emerald-700'],
].map(([label, value, color]) => (
<div key={label} className="border-b border-zinc-200 px-5 py-4 last:border-b-0 sm:border-b-0 sm:border-r sm:last:border-r-0">
<div className="text-xs text-zinc-500">{label}</div>
<div className={`mt-1 text-2xl font-semibold ${color}`}>{value}</div>
['全部控制点', stats.total, Project, 'teal'],
['未填写', stats.empty, ClipboardDocumentListIcon, 'danger'],
['待确认', stats.pending, ClockIcon, 'warning'],
['已完成', stats.completed, CheckCircleIcon, 'success'],
].map(([label, value, Icon, tone]) => (
<div key={label} className="audit-stat">
<div className="audit-stat__copy"><span className="audit-stat__label">{label}</span><strong className="audit-stat__value">{value}</strong></div>
<span className={`audit-stat__icon rc-metric-icon-surface rc-tone-surface--${tone}`}><Icon size={20} /></span>
</div>
))}
</div>
<div className="flex items-center gap-1 border-b border-zinc-200">
{[
['current', '进行中'],
['history', '历史报告'],
].map(([key, label]) => (
<button
type="button"
key={key}
onClick={() => { setTab(key); setKeyword(''); }}
className={`border-b-2 px-4 py-3 text-sm font-medium ${tab === key ? 'border-[var(--pm-brand)] text-red-700' : 'border-transparent text-zinc-500 hover:text-zinc-900'}`}
>
{label}
</button>
))}
</div>
<Tabs className="filter-tabs-wrap audit-project-tabs" selectedKey={tab} onSelectionChange={(key) => { setTab(String(key)); setKeyword(''); }}>
<Tabs.ListContainer><Tabs.List aria-label="报告视图">
<Tabs.Tab id="current">进行中<Tabs.Indicator /></Tabs.Tab>
<Tabs.Tab id="history">历史报告<Tabs.Indicator /></Tabs.Tab>
</Tabs.List></Tabs.ListContainer>
</Tabs>
{tab === 'current' ? (
report ? (
<div className="space-y-4">
<div className="flex flex-wrap items-center justify-between gap-3">
<InputGroup className="w-full max-w-sm">
<MagnifyingGlassIcon />
<Input value={keyword} onChange={(event) => setKeyword(event.target.value)} placeholder="搜索控制点、描述或责任人" />
</InputGroup>
{canManage ? <Button outline onClick={() => persistReport(report)} disabled={saving}>{saving ? '保存中...' : '保存备注修改'}</Button> : null}
<div className="audit-report-section">
<div className="audit-filter-card">
<TextField aria-label="搜索报告控制点" className="audit-filter-search" value={keyword} onChange={setKeyword}>
<div className="audit-filter-search__control"><Search size={16} /><Input placeholder="搜索控制点、描述或责任人" /></div>
</TextField>
{canManage ? <Button size="sm" variant="outline" onPress={() => persistReport(report)} isDisabled={saving}>{saving ? '保存中...' : '保存备注修改'}</Button> : null}
</div>
<AuditReportContent
report={report}
......@@ -304,43 +274,36 @@ export default function AuditReport() {
<EmptyState
title="当前没有进行中的报告"
description="生成报告时会复制现有稽核项目与控制点,形成独立的当期快照。"
action={canManage ? <Button color="blue" onClick={generateReport} disabled={saving || processes.length === 0}><PlusIcon />生成本期报告</Button> : null}
action={canManage ? <Button size="sm" variant="primary" onPress={generateReport} isDisabled={saving || processes.length === 0}>生成本期报告</Button> : null}
/>
)
) : historyReports.length > 0 ? (
<Table dense striped>
<TableHead>
<TableRow>
<TableHeader>报告名称</TableHeader>
<TableHeader>创建时间</TableHeader>
<TableHeader>提交时间</TableHeader>
<TableHeader>状态</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{historyReports.map((item) => (
<TableRow key={normalizeRecordId(item)} href={`/audit_report/${normalizeRecordId(item)}`} title={item.title}>
<TableCell className="font-medium text-zinc-950">{item.title}</TableCell>
<TableCell>{dayjs(item.created_at).format('YYYY-MM-DD HH:mm')}</TableCell>
<TableCell>{item.metadata?.submitted_time ? dayjs(item.metadata.submitted_time).format('YYYY-MM-DD HH:mm') : '-'}</TableCell>
<TableCell><Badge color="green">已归档</Badge></TableCell>
</TableRow>
))}
</TableBody>
</Table>
) : (
<EmptyState title="暂无历史报告" description="提交后的报告将在这里保留只读快照。" />
<DataTable
ariaLabel="历史稽核报告"
columns={historyColumns}
rows={historyReports}
getRowKey={normalizeRecordId}
emptyTitle="暂无历史报告"
onRowAction={(key) => history.push(`/audit_report/${key}`)}
/>
)}
<FillLinksDialog open={linksOpen} onClose={() => setLinksOpen(false)} report={report} onNotice={setNotice} />
<Alert open={submitOpen} onClose={() => setSubmitOpen(false)}>
<AlertTitle>提交并归档报告</AlertTitle>
<AlertDescription>提交后报告将转为只读历史记录,不能继续填报或修改。</AlertDescription>
<AlertActions>
<Button plain onClick={() => setSubmitOpen(false)}>取消</Button>
<Button color="blue" onClick={submit} disabled={saving}>确认提交</Button>
</AlertActions>
</Alert>
<AlertDialog.Root isOpen={submitOpen} onOpenChange={(isOpen) => { if (!isOpen) setSubmitOpen(false); }}>
<AlertDialog.Trigger aria-hidden="true" aria-label="打开提交报告确认弹窗" className="sr-only" tabIndex={-1} />
<AlertDialog.Backdrop className="rc-dialog-backdrop" variant="blur">
<AlertDialog.Container placement="center" size="md">
<AlertDialog.Dialog className="rc-dialog-panel rc-dialog-panel--md">
<AlertDialog.Header><AlertDialog.Heading className="rc-dialog-title">提交并归档报告</AlertDialog.Heading></AlertDialog.Header>
<AlertDialog.Body className="rc-dialog-body">提交后报告将转为只读历史记录,不能继续填报或修改。</AlertDialog.Body>
<AlertDialog.Footer className="rc-dialog-actions">
<Button variant="outline" onPress={() => setSubmitOpen(false)}>取消</Button>
<Button variant="primary" onPress={submit} isDisabled={saving}>确认提交</Button>
</AlertDialog.Footer>
</AlertDialog.Dialog>
</AlertDialog.Container>
</AlertDialog.Backdrop>
</AlertDialog.Root>
</div>
);
}
import { Badge } from '@/catalyst-ui-kit/javascript/badge';
import { Button } from '@/catalyst-ui-kit/javascript/button';
import { Select } from '@/catalyst-ui-kit/javascript/select';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/catalyst-ui-kit/javascript/table';
import { Textarea } from '@/catalyst-ui-kit/javascript/textarea';
import {
ArrowTopRightOnSquareIcon,
ArrowUpTrayIcon,
CheckIcon,
TrashIcon,
} from '@heroicons/react/20/solid';
import { DataTable, DataTableChip, DataTableTextPair } from '@/components/DataTable';
import FileUploadField from '@/components/FileUploadField';
import FilterSelect from '@/components/FilterSelect';
import { Check, Delete, OpenExternal } from '@/components/AppIcons';
import { TableActionButton } from '@/components/TableActionButton';
import { Button, TextArea, TextField } from '@heroui/react';
import dayjs from 'dayjs';
import { useState } from 'react';
import {
......@@ -31,9 +19,9 @@ function normalizeDocuments(value) {
}
export function ReportStatusBadge({ point }) {
if (!hasControlPointValue(point)) return <Badge color="zinc">未填写</Badge>;
if (point.status === CONTROL_POINT_STATUS.DONE) return <Badge color="green">已完成</Badge>;
return <Badge color="amber">待确认</Badge>;
if (!hasControlPointValue(point)) return <DataTableChip className="rc-status-chip--neutral">未填写</DataTableChip>;
if (point.status === CONTROL_POINT_STATUS.DONE) return <DataTableChip className="rc-status-chip--success">已完成</DataTableChip>;
return <DataTableChip className="rc-status-chip--warning">待确认</DataTableChip>;
}
function PointContent({
......@@ -51,12 +39,9 @@ function PointContent({
return <div className="max-w-80 whitespace-pre-wrap text-sm text-zinc-700">{point.value || '-'}</div>;
}
return (
<Textarea
rows={4}
value={point.value || ''}
placeholder={point.form_label || '请输入检查结果'}
onChange={(event) => onUpdate(processIndex, pointIndex, { value: event.target.value })}
/>
<TextField aria-label={point.form_label || '检查结果'} value={point.value || ''} onChange={(value) => onUpdate(processIndex, pointIndex, { value })}>
<TextArea rows={4} placeholder={point.form_label || '请输入检查结果'} />
</TextField>
);
}
......@@ -65,52 +50,50 @@ function PointContent({
<div className="min-w-64 space-y-3">
{(point.attachment_label || []).map((label, labelIndex) => {
const documents = normalizeDocuments(point.value?.[label]);
const inputId = `audit-file-${processIndex}-${pointIndex}-${labelIndex}`;
return (
<div key={`${label}-${labelIndex}`} className="space-y-2">
<div className="text-xs font-medium text-zinc-600">{label}</div>
<div key={`${label}-${labelIndex}`} className="audit-attachment-group">
<div className="audit-attachment-label">{label}</div>
{documents.length > 0 ? (
<div className="space-y-1">
<div className="audit-attachment-list">
{documents.map((document, documentIndex) => (
<div key={normalizeRecordId(document) || documentIndex} className="flex items-center justify-between gap-2 text-sm">
<div key={normalizeRecordId(document) || documentIndex} className="audit-attachment-item">
<a
href={document.url}
target="_blank"
rel="noreferrer"
className="inline-flex min-w-0 items-center gap-1 text-sky-700 hover:text-sky-900"
className="audit-attachment-link"
>
<span className="truncate">{document.title || document.name || '附件'}</span>
<ArrowTopRightOnSquareIcon className="size-3.5 shrink-0" />
<OpenExternal size={13} />
</a>
{editable ? (
<Button
plain
aria-label={`删除${document.title || '附件'}`}
title="删除附件"
onClick={() => {
<TableActionButton
icon={Delete}
label={`删除${document.title || '附件'}`}
tone="delete"
onPress={() => {
const nextDocuments = documents.filter((_, index) => index !== documentIndex);
onUpdate(processIndex, pointIndex, {
value: { ...(point.value || {}), [label]: nextDocuments },
});
}}
>
<TrashIcon className="text-red-600" />
</Button>
/>
) : null}
</div>
))}
</div>
) : (
<div className="text-xs text-zinc-400">未上传</div>
<div className="audit-attachment-empty">未上传</div>
)}
{editable ? (
<>
<input
id={inputId}
type="file"
hidden
onChange={async (event) => {
const file = event.target.files?.[0];
<FileUploadField
ariaLabel={`上传${label}`}
className="audit-attachment-upload"
description="支持拖拽或选择单个文件,上传后可继续追加。"
isDisabled={Boolean(uploadingLabel)}
triggerLabel={uploadingLabel === label ? '上传中...' : '选择文件'}
onFilesChange={async (files) => {
const file = files?.[0];
if (!file) return;
setUploadingLabel(label);
setUploadError('');
......@@ -126,20 +109,14 @@ function PointContent({
setUploadError(requestError?.message || '附件上传失败');
} finally {
setUploadingLabel('');
event.target.value = '';
}
}}
/>
<Button outline type="button" onClick={() => document.getElementById(inputId)?.click()} disabled={Boolean(uploadingLabel)}>
<ArrowUpTrayIcon />
{uploadingLabel === label ? '上传中...' : '上传文件'}
</Button>
</>
) : null}
</div>
);
})}
{uploadError ? <div className="text-xs text-red-600">{uploadError}</div> : null}
{uploadError ? <div className="audit-attachment-error">{uploadError}</div> : null}
</div>
);
}
......@@ -206,45 +183,38 @@ export default function AuditReportContent({
const confirmable = allPoints.some(
(point) => hasControlPointValue(point) && point.status === CONTROL_POINT_STATUS.UNDO,
);
return (
<section key={normalizeRecordId(process) || processIndex} className="border-t border-zinc-200 pt-4 first:border-t-0 first:pt-0">
<div className="mb-3 flex flex-wrap items-center justify-between gap-3">
<div>
<h2 className="text-sm font-semibold text-zinc-950">{process.order}.{process.title || process.metadata?.process_name}</h2>
<div className="mt-0.5 text-xs text-zinc-500">已完成 {completed} / {allPoints.length}</div>
</div>
{reviewer && confirmable ? (
<Button outline onClick={() => onConfirmProcess(processIndex)}>
<CheckIcon />确认本项目已填内容
</Button>
) : null}
</div>
<Table dense striped>
<TableHead>
<TableRow>
<TableHeader className="w-20">序号</TableHeader>
<TableHeader>控制点</TableHeader>
<TableHeader>责任人</TableHeader>
<TableHeader>内容</TableHeader>
<TableHeader>异常</TableHeader>
<TableHeader>备注</TableHeader>
<TableHeader className="w-24">状态</TableHeader>
{reviewer ? <TableHeader className="w-20 text-right">操作</TableHeader> : null}
</TableRow>
</TableHead>
<TableBody>
{visiblePoints.map(({ point, pointIndex }) => {
const rows = visiblePoints.map(({ point, pointIndex }) => ({ point, pointIndex }));
const hasVisibleConfirmAction = rows.some(
({ point }) => hasControlPointValue(point) && point.status === CONTROL_POINT_STATUS.UNDO,
);
const columns = [
{
key: 'order',
title: '序号',
width: 80,
render: ({ pointIndex }) => <span className="pm-data-grid-primary">{process.order}.{pointIndex + 1}</span>,
},
{
key: 'point',
title: '控制点',
minWidth: 288,
render: ({ point }) => (
<DataTableTextPair primary={point.name} secondary={point.description} />
),
},
{
key: 'owner',
title: '责任人',
minWidth: 160,
render: ({ point }) => point.owner?.title || point.owner?.label || '-',
},
{
key: 'content',
title: '内容',
minWidth: 320,
render: ({ point, pointIndex }) => {
const canEditPoint = editableFill && point.status !== CONTROL_POINT_STATUS.DONE;
return (
<TableRow key={`${point.name}-${pointIndex}`}>
<TableCell className="align-top font-semibold">{process.order}.{pointIndex + 1}</TableCell>
<TableCell className="min-w-72 whitespace-normal align-top">
<div className="font-medium text-zinc-950">{point.name}</div>
{point.description ? <div className="mt-1 whitespace-pre-line text-xs leading-5 text-zinc-500">{point.description}</div> : null}
</TableCell>
<TableCell className="align-top">{point.owner?.title || point.owner?.label || '-'}</TableCell>
<TableCell className="whitespace-normal align-top">
<PointContent
point={point}
processIndex={processIndex}
......@@ -252,47 +222,79 @@ export default function AuditReportContent({
editable={canEditPoint}
onUpdate={onUpdate}
/>
</TableCell>
<TableCell className="align-top">
{canEditPoint ? (
<Select
value={point.issue || ''}
onChange={(event) => onUpdate(processIndex, pointIndex, { issue: event.target.value })}
aria-label="是否存在异常"
>
<option value="">请选择</option>
{issueOptions.map((option) => <option key={option.value} value={option.value}>{option.label}</option>)}
</Select>
) : point.issue || '-'}
</TableCell>
<TableCell className="min-w-64 whitespace-normal align-top">
{canEditPoint || reviewer ? (
<Textarea
rows={3}
value={point.remark || ''}
onChange={(event) => onUpdate(processIndex, pointIndex, { remark: event.target.value })}
placeholder="填写备注"
disabled={reviewer && point.status === CONTROL_POINT_STATUS.DONE}
/>
) : point.remark || '-'}
</TableCell>
<TableCell className="align-top"><ReportStatusBadge point={point} /></TableCell>
{reviewer ? (
<TableCell className="align-top">
{hasControlPointValue(point) && point.status === CONTROL_POINT_STATUS.UNDO ? (
<div className="flex justify-end">
<Button plain aria-label="确认控制点" title="确认控制点" onClick={() => onConfirmPoint(processIndex, pointIndex)}>
<CheckIcon className="text-emerald-600" />
</Button>
);
},
},
{
key: 'issue',
title: '异常',
minWidth: 150,
render: ({ point, pointIndex }) => {
const canEditPoint = editableFill && point.status !== CONTROL_POINT_STATUS.DONE;
return canEditPoint ? (
<FilterSelect aria-label="是否存在异常" options={issueOptions} placeholder="请选择" value={point.issue || ''} onChange={(value) => onUpdate(processIndex, pointIndex, { issue: value })} />
) : point.issue || '-';
},
},
{
key: 'remark',
title: '备注',
minWidth: 260,
render: ({ point, pointIndex }) => {
const canEditPoint = editableFill && point.status !== CONTROL_POINT_STATUS.DONE;
return canEditPoint || reviewer ? (
<TextField aria-label="备注" isDisabled={reviewer && point.status === CONTROL_POINT_STATUS.DONE} value={point.remark || ''} onChange={(value) => onUpdate(processIndex, pointIndex, { remark: value })}>
<TextArea rows={3} placeholder="填写备注" />
</TextField>
) : point.remark || '-';
},
},
{
key: 'status',
title: '状态',
minWidth: 112,
render: ({ point }) => <ReportStatusBadge point={point} />,
},
...(reviewer && hasVisibleConfirmAction
? [{
key: 'actions',
title: '操作',
width: 88,
isActions: true,
align: 'end',
render: ({ point, pointIndex }) => (
hasControlPointValue(point) && point.status === CONTROL_POINT_STATUS.UNDO ? (
<div className="icon-actions flex justify-end">
<TableActionButton icon={Check} label="确认控制点" tone="approve" onPress={() => onConfirmPoint(processIndex, pointIndex)} />
</div>
) : null
),
}]
: []),
];
return (
<section key={normalizeRecordId(process) || processIndex} className="audit-control-section">
<div className="audit-section-header">
<div>
<h2>{process.order}.{process.title || process.metadata?.process_name}</h2>
<div className="audit-section-meta">已完成 {completed} / {allPoints.length}</div>
</div>
{reviewer && confirmable ? (
<Button size="sm" variant="outline" onPress={() => onConfirmProcess(processIndex)}>
确认本项目已填内容
</Button>
) : null}
</TableCell>
) : null}
</TableRow>
);
})}
</TableBody>
</Table>
</div>
<DataTable
ariaLabel={`${process.title || process.metadata?.process_name || '稽核项目'}控制点`}
className="audit-control-points-table"
columns={columns}
contentMinWidth={reviewer && hasVisibleConfirmAction ? 1458 : 1370}
getRowKey={({ point, pointIndex }) => `${point.name}-${pointIndex}`}
isWide
rows={rows}
/>
</section>
);
})}
......
import { Badge } from '@/catalyst-ui-kit/javascript/badge';
import { Button } from '@/catalyst-ui-kit/javascript/button';
import { Heading } from '@/catalyst-ui-kit/javascript/heading';
import { ArrowDownTrayIcon, ArrowLeftIcon } from '@heroicons/react/20/solid';
import { Back } from '@/components/AppIcons';
import { Button, Chip, Tooltip } from '@heroui/react';
import dayjs from 'dayjs';
import { useCallback, useEffect, useState } from 'react';
import { history, useParams } from 'umi';
......@@ -50,35 +48,30 @@ export default function AuditReportDetail() {
const stats = getReportStats(report);
return (
<div className="space-y-6">
<div className="flex flex-wrap items-start justify-between gap-4">
<div className="flex items-start gap-3">
<Button plain aria-label="返回历史报告" title="返回" onClick={() => history.back()}>
<ArrowLeftIcon />
</Button>
<div className="audit-page audit-report-detail">
<div className="audit-detail-header">
<div className="audit-detail-heading">
<Tooltip delay={0}><Button className="audit-back-button" isIconOnly aria-label="返回历史报告" size="sm" variant="ghost" onPress={() => history.back()}><Back size={16} /></Button><Tooltip.Content>返回</Tooltip.Content></Tooltip>
<div>
<div className="flex flex-wrap items-center gap-2">
<Heading>{report.title}</Heading>
<Badge color="green">已归档</Badge>
<div className="audit-detail-title-row">
<h2>{report.title}</h2>
<Chip className="rc-status-chip--success" size="sm">已归档</Chip>
</div>
<p className="mt-1 text-sm text-zinc-500">
<p>
提交时间:{report.metadata?.submitted_time ? dayjs(report.metadata.submitted_time).format('YYYY-MM-DD HH:mm') : '-'}
</p>
</div>
</div>
<Button outline onClick={() => exportAuditReport(report)}>
<ArrowDownTrayIcon />导出
</Button>
<Button size="sm" variant="outline" onPress={() => exportAuditReport(report)}>导出</Button>
</div>
<div className="flex flex-wrap gap-x-8 gap-y-2 border-y border-zinc-200 py-4 text-sm">
<span className="text-zinc-500">全部 <strong className="ml-1 text-zinc-950">{stats.total}</strong></span>
<span className="text-zinc-500">未填写 <strong className="ml-1 text-zinc-950">{stats.empty}</strong></span>
<span className="text-zinc-500">已完成 <strong className="ml-1 text-emerald-700">{stats.completed}</strong></span>
<div className="audit-detail-summary">
<span>全部 <strong>{stats.total}</strong></span>
<span>未填写 <strong>{stats.empty}</strong></span>
<span>已完成 <strong className="is-success">{stats.completed}</strong></span>
</div>
<AuditReportContent report={report} mode="readonly" issueOptions={issueOptions} />
</div>
);
}
import {
Alert,
AlertActions,
AlertDescription,
AlertTitle,
} from '@/catalyst-ui-kit/javascript/alert';
import { Button } from '@/catalyst-ui-kit/javascript/button';
import { Heading } from '@/catalyst-ui-kit/javascript/heading';
import xyLogoSmall from '@/assets/xy-logo_small.png';
import { ArrowDownTrayIcon, ArrowPathIcon, CheckIcon } from '@heroicons/react/20/solid';
import { useCallback, useEffect, useMemo, useState } from 'react';
import xyLogo from '@/assets/identity/xy-logo-red.png';
import xyLogoWhite from '@/assets/identity/xy-logo-white.png';
import { APP_DISPLAY_NAME } from '@/constants/app';
import { AlertDialog, Button } from '@heroui/react';
import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react';
import { useLocation } from 'umi';
import AuditReportContent from './AuditReportContent';
import { ErrorState, InlineNotice, LoadingState } from './PageState';
......@@ -66,6 +60,18 @@ export default function AuditReportFill() {
const [notice, setNotice] = useState(null);
const [resetOpen, setResetOpen] = useState(false);
useLayoutEffect(() => {
let theme = 'light';
try {
theme = window.localStorage.getItem('xinyuan_color_theme') === 'dark' ? 'dark' : 'light';
} catch {}
document.documentElement.classList.toggle('dark', theme === 'dark');
document.documentElement.classList.toggle('light', theme === 'light');
document.documentElement.dataset.theme = theme;
document.documentElement.style.colorScheme = theme;
document.title = `填报任务 - ${APP_DISPLAY_NAME}`;
}, []);
const loadData = useCallback(async () => {
if (!reportId || !ownerId) {
setError('填报链接缺少 report 或 owner 参数');
......@@ -145,24 +151,26 @@ export default function AuditReportFill() {
}
};
if (loading) return <div className="min-h-screen bg-zinc-50 p-6"><LoadingState label="正在加载填报任务..." /></div>;
if (error || !report) return <div className="min-h-screen bg-zinc-50 p-6"><ErrorState message={error || '报告不存在'} onRetry={loadData} /></div>;
if (loading) return <div className="audit-fill-page"><LoadingState label="正在加载填报任务..." /></div>;
if (error || !report) return <div className="audit-fill-page"><ErrorState message={error || '报告不存在'} onRetry={loadData} /></div>;
return (
<div className="min-h-screen bg-zinc-100 p-3 sm:p-6">
<main className="mx-auto max-w-[1600px] rounded-lg bg-white p-4 shadow-sm ring-1 ring-zinc-950/5 sm:p-7">
<header className="flex flex-wrap items-start justify-between gap-4 border-b border-zinc-200 pb-5">
<div className="flex items-start gap-3">
<img src={xyLogoSmall} alt="鑫元基金" className="mt-0.5 size-9 object-contain" />
<div className="audit-fill-page">
<main className="audit-fill-shell">
<header className="audit-fill-header">
<div className="audit-fill-identity">
<img className="audit-fill-logo audit-fill-logo--light" src={xyLogo} alt="鑫元" />
<img className="audit-fill-logo audit-fill-logo--dark" src={xyLogoWhite} alt="" aria-hidden="true" />
<div>
<Heading>{report.title}</Heading>
<p className="mt-1 text-sm text-zinc-500">{ownerLabel}填报任务</p>
<span>{APP_DISPLAY_NAME}</span>
<h1>{report.title}</h1>
<p>{ownerLabel}填报任务</p>
</div>
</div>
<Button outline onClick={() => exportAuditReport(report)}><ArrowDownTrayIcon />导出</Button>
<Button size="sm" variant="outline" onPress={() => exportAuditReport(report)}>导出</Button>
</header>
<div className="mt-5 space-y-5">
<div className="audit-fill-content">
{notice ? <InlineNotice tone={notice.tone} onDismiss={() => setNotice(null)}>{notice.text}</InlineNotice> : null}
<AuditReportContent
report={report}
......@@ -173,24 +181,29 @@ export default function AuditReportFill() {
/>
</div>
<footer className="sticky bottom-0 mt-6 flex justify-end gap-2 border-t border-zinc-200 bg-white/95 py-4 backdrop-blur">
<Button outline onClick={() => setResetOpen(true)} disabled={saving || !dirty}>
<ArrowPathIcon />重置
</Button>
<Button color="blue" onClick={save} disabled={saving || !dirty}>
<CheckIcon />{saving ? '保存中...' : '保存填报'}
<footer className="audit-fill-actions">
<Button size="sm" variant="outline" onPress={() => setResetOpen(true)} isDisabled={saving || !dirty}>重置</Button>
<Button size="sm" variant="primary" onPress={save} isDisabled={saving || !dirty}>
{saving ? '保存中...' : '保存填报'}
</Button>
</footer>
</main>
<Alert open={resetOpen} onClose={() => setResetOpen(false)}>
<AlertTitle>重置未保存修改</AlertTitle>
<AlertDescription>将重新加载服务端最新内容,本次尚未保存的编辑会丢失。</AlertDescription>
<AlertActions>
<Button plain onClick={() => setResetOpen(false)}>取消</Button>
<Button color="red" onClick={async () => { setResetOpen(false); await loadData(); }}>确认重置</Button>
</AlertActions>
</Alert>
<AlertDialog.Root isOpen={resetOpen} onOpenChange={(isOpen) => { if (!isOpen) setResetOpen(false); }}>
<AlertDialog.Trigger aria-hidden="true" aria-label="打开重置填报确认弹窗" className="sr-only" tabIndex={-1} />
<AlertDialog.Backdrop className="rc-dialog-backdrop" variant="blur">
<AlertDialog.Container placement="center" size="md">
<AlertDialog.Dialog className="rc-dialog-panel rc-dialog-panel--md">
<AlertDialog.Header><AlertDialog.Heading className="rc-dialog-title">重置未保存修改</AlertDialog.Heading></AlertDialog.Header>
<AlertDialog.Body className="rc-dialog-body">将重新加载服务端最新内容,本次尚未保存的编辑会丢失。</AlertDialog.Body>
<AlertDialog.Footer className="rc-dialog-actions">
<Button variant="outline" onPress={() => setResetOpen(false)}>取消</Button>
<Button variant="danger" onPress={async () => { setResetOpen(false); await loadData(); }}>确认重置</Button>
</AlertDialog.Footer>
</AlertDialog.Dialog>
</AlertDialog.Container>
</AlertDialog.Backdrop>
</AlertDialog.Root>
</div>
);
}
import xyLogoRed from '@/assets/identity/xy-logo-red.png';
import xyLogoWhite from '@/assets/identity/xy-logo-white.png';
import { AppGrid, FileText, LogOut, Moon, Project, Sun } from '@/components/AppIcons';
import ApplicationSwitcherDialog from '@/components/ApplicationSwitcherDialog';
import xyLogoSmall from '@/assets/xy-logo_small.png';
import {
Sidebar,
SidebarBody,
SidebarFooter,
SidebarHeader,
SidebarHeading,
SidebarItem,
SidebarLabel,
SidebarSection,
} from '@/catalyst-ui-kit/javascript/sidebar';
import { SidebarLayout } from '@/catalyst-ui-kit/javascript/sidebar-layout';
import { useUserInfo } from '@/hooks/user';
import { SiteContext } from '@/wrapper/Site';
import {
ArrowRightStartOnRectangleIcon,
DocumentTextIcon,
ShieldCheckIcon,
Squares2X2Icon,
} from '@heroicons/react/24/outline';
import { useContext, useState } from 'react';
import { Outlet, useLocation } from 'umi';
import { APP_DISPLAY_NAME, APP_NAV_ITEMS, getAppRouteTitle } from '@/constants/app';
import { UserContext } from '@/wrapper/Auth';
import { Navbar } from '@heroui-pro/react/navbar';
import { Sidebar } from '@heroui-pro/react/sidebar';
import { Avatar, Button, Dropdown, Label, Tooltip } from '@heroui/react';
import { useContext, useLayoutEffect, useMemo, useState } from 'react';
import { Heading } from 'react-aria-components/Dialog';
import { history, Outlet, useLocation } from 'umi';
const NAV_ITEMS = [
{
label: '稽核项目',
path: '/audit_process',
icon: ShieldCheckIcon,
},
{
label: '稽核报告',
path: '/audit_report',
icon: DocumentTextIcon,
},
];
const THEME_STORAGE_KEY = 'xinyuan_color_theme';
const NAV_ICONS = { project: Project, report: FileText };
export default function AuditManagementLayout() {
const location = useLocation();
const userInfo = useUserInfo();
const siteInfo = useContext(SiteContext);
const [isApplicationDialogOpen, setIsApplicationDialogOpen] = useState(false);
function getInitialTheme() {
if (typeof window === 'undefined') return 'light';
try { return window.localStorage.getItem(THEME_STORAGE_KEY) === 'dark' ? 'dark' : 'light'; } catch { return 'light'; }
}
const sidebar = (
<Sidebar className="border-r border-zinc-200 bg-white">
<SidebarHeader>
<div className="flex items-center gap-3 px-2 py-2">
<img src={xyLogoSmall} alt="鑫元基金" className="size-8 object-contain" />
<div className="min-w-0">
<div className="truncate text-sm font-semibold text-zinc-950">监察稽核管理</div>
<div className="truncate text-xs text-zinc-500">内部控制与报告归档</div>
</div>
function SidebarUserMenu({ userInfo }) {
const displayName = userInfo?.display_name || userInfo?.username || '当前用户';
return (
<Dropdown>
<Dropdown.Trigger className="rc-sidebar-user-trigger" aria-label={`打开${displayName}的用户菜单`}>
<Avatar className="rc-sidebar-avatar">
{userInfo?.avatar ? <Avatar.Image alt={displayName} src={userInfo.avatar} /> : null}
<Avatar.Fallback>{displayName.slice(0, 1).toUpperCase()}</Avatar.Fallback>
</Avatar>
<span className="rc-sidebar-user-copy" data-sidebar="label">
<strong>{displayName}</strong>
<small>{userInfo?.username || '已登录'}</small>
</span>
</Dropdown.Trigger>
<Dropdown.Popover placement="top start">
<Dropdown.Menu aria-label="用户菜单" onAction={(key) => { if (key === 'logout') userInfo?.logout?.(); }}>
<Dropdown.Item id="logout" textValue="退出登录" variant="danger">
<div className="rc-sidebar-user-menu-item rc-sidebar-user-menu-item--danger">
<Label>退出登录</Label>
<LogOut size={14} />
</div>
</SidebarHeader>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Popover>
</Dropdown>
);
}
<SidebarBody>
<SidebarSection>
<SidebarHeading>稽核工作台</SidebarHeading>
{NAV_ITEMS.map((item) => {
const Icon = item.icon;
const current = location.pathname === item.path || location.pathname.startsWith(`${item.path}/`);
function SidebarContents({ pathname, theme, userInfo, onSwitchApplication }) {
return (
<SidebarItem key={item.path} href={item.path} current={current}>
<Icon />
<SidebarLabel>{item.label}</SidebarLabel>
</SidebarItem>
<>
<Sidebar.Header>
<div className="rc-sidebar-brand">
<img alt="鑫元" className="rc-sidebar-logo" src={theme === 'dark' ? xyLogoWhite : xyLogoRed} />
<span className="rc-sidebar-title" data-sidebar="label">{APP_DISPLAY_NAME}</span>
<span data-sidebar="label">
<Tooltip delay={0}>
<Button isIconOnly aria-label="切换应用" className="rc-sidebar-app-trigger" size="sm" variant="ghost" onPress={onSwitchApplication}><AppGrid size={16} /></Button>
<Tooltip.Content>切换应用</Tooltip.Content>
</Tooltip>
</span>
</div>
</Sidebar.Header>
<Sidebar.Content>
<Sidebar.Group>
<Sidebar.GroupLabel>稽核工作台</Sidebar.GroupLabel>
<Sidebar.Menu aria-label="稽核工作台" showGuideLines={false}>
{APP_NAV_ITEMS.map((item) => {
const Icon = NAV_ICONS[item.icon] || FileText;
return (
<Sidebar.MenuItem href={item.path} isCurrent={pathname === item.path || pathname.startsWith(`${item.path}/`)} key={item.path} textValue={item.label} tooltip={item.label}>
<Sidebar.MenuIcon><Icon size={16} /></Sidebar.MenuIcon>
<Sidebar.MenuLabel>{item.label}</Sidebar.MenuLabel>
</Sidebar.MenuItem>
);
})}
</SidebarSection>
</SidebarBody>
<SidebarFooter>
<SidebarSection>
<SidebarItem onClick={() => setIsApplicationDialogOpen(true)}>
<Squares2X2Icon />
<SidebarLabel>切换应用</SidebarLabel>
</SidebarItem>
<SidebarItem onClick={() => userInfo?.logout?.()}>
<ArrowRightStartOnRectangleIcon />
<SidebarLabel>退出登录</SidebarLabel>
</SidebarItem>
</SidebarSection>
<div className="px-2 pb-1 pt-3">
<div className="truncate text-sm font-medium text-zinc-900">
{userInfo?.display_name || userInfo?.username || '当前用户'}
</div>
<div className="truncate text-xs text-zinc-500">{siteInfo?.name || '鑫元基金'}</div>
</div>
</SidebarFooter>
</Sidebar>
</Sidebar.Menu>
</Sidebar.Group>
</Sidebar.Content>
<Sidebar.Footer><SidebarUserMenu userInfo={userInfo} /></Sidebar.Footer>
</>
);
}
export default function AuditManagementLayout() {
const location = useLocation();
const userInfo = useContext(UserContext);
const [theme, setTheme] = useState(getInitialTheme);
const [switcherOpen, setSwitcherOpen] = useState(false);
const pageTitle = useMemo(() => getAppRouteTitle(location.pathname), [location.pathname]);
useLayoutEffect(() => {
document.documentElement.classList.toggle('dark', theme === 'dark');
document.documentElement.classList.toggle('light', theme === 'light');
document.documentElement.dataset.theme = theme;
document.documentElement.style.colorScheme = theme;
document.title = `${pageTitle} - ${APP_DISPLAY_NAME}`;
try { window.localStorage.setItem(THEME_STORAGE_KEY, theme); } catch {}
}, [pageTitle, theme]);
const sidebarProps = { pathname: location.pathname, theme, userInfo, onSwitchApplication: () => setSwitcherOpen(true) };
const themeLabel = theme === 'dark' ? '切换至浅色模式' : '切换至深色模式';
return (
<>
<SidebarLayout
sidebar={sidebar}
navbar={<div className="truncate text-sm font-semibold text-zinc-950">监察稽核管理</div>}
>
<Outlet />
</SidebarLayout>
<ApplicationSwitcherDialog
open={isApplicationDialogOpen}
onClose={() => setIsApplicationDialogOpen(false)}
userInfo={userInfo}
siteInfo={siteInfo}
/>
</>
<Sidebar.Provider className={`rc-app-shell ${theme}`} collapsible="offcanvas" navigate={(href) => history.push(href)}>
<Sidebar className="rc-sidebar"><SidebarContents {...sidebarProps} /></Sidebar>
<Sidebar.Mobile className="rc-mobile-sidebar"><Heading className="sr-only" slot="title">{APP_DISPLAY_NAME}导航</Heading><SidebarContents {...sidebarProps} /></Sidebar.Mobile>
<Sidebar.Main className="rc-app-main">
<Navbar className="rc-navbar" maxWidth="full">
<Navbar.Header>
<Sidebar.Trigger aria-label="打开或收起导航" className="rc-navbar-trigger" />
<h1 className="rc-navbar-title">{pageTitle}</h1>
<Navbar.Spacer />
<Tooltip delay={0}>
<Button isIconOnly aria-label={themeLabel} className="rc-navbar-icon-button" size="sm" variant="ghost" onPress={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>{theme === 'dark' ? <Sun size={16} /> : <Moon size={16} />}</Button>
<Tooltip.Content>{themeLabel}</Tooltip.Content>
</Tooltip>
</Navbar.Header>
</Navbar>
<div className="min-h-0 flex-1 overflow-y-auto"><div className="rc-app-page"><h1 className="sr-only">{pageTitle}</h1><Outlet /></div></div>
</Sidebar.Main>
<ApplicationSwitcherDialog open={switcherOpen} onClose={() => setSwitcherOpen(false)} userInfo={userInfo} />
</Sidebar.Provider>
);
}
import { Button } from '@/catalyst-ui-kit/javascript/button';
import { ArrowPathIcon, ExclamationTriangleIcon } from '@heroicons/react/20/solid';
import { CheckCircleIcon, Close, ExclamationTriangleIcon } from '@/components/AppIcons';
import { EmptyState as HeroEmptyState } from '@heroui-pro/react/empty-state';
import { Button, Spinner } from '@heroui/react';
import { useEffect } from 'react';
export function LoadingState({ label = '正在加载...' }) {
return (
<div className="flex min-h-64 items-center justify-center gap-2 text-sm text-zinc-500">
<ArrowPathIcon className="size-4 animate-spin" />
{label}
</div>
);
return <div className="audit-state"><Spinner size="sm" /><span>{label}</span></div>;
}
export function ErrorState({ message, onRetry }) {
return (
<div className="flex min-h-64 flex-col items-center justify-center gap-3 text-center">
<ExclamationTriangleIcon className="size-7 text-red-600" />
<div className="text-sm text-zinc-600">{message || '数据加载失败'}</div>
{onRetry ? (
<Button outline onClick={onRetry}>
<ArrowPathIcon />
重新加载
</Button>
) : null}
<div className="audit-state audit-state--error">
<ExclamationTriangleIcon size={24} />
<span>{message || '数据加载失败'}</span>
{onRetry ? <Button size="sm" variant="outline" onPress={onRetry}>重新加载</Button> : null}
</div>
);
}
export function EmptyState({ title, description, action }) {
return (
<div className="flex min-h-56 flex-col items-center justify-center border-y border-dashed border-zinc-200 px-6 text-center">
<div className="text-sm font-semibold text-zinc-900">{title}</div>
{description ? <div className="mt-1 max-w-lg text-sm text-zinc-500">{description}</div> : null}
{action ? <div className="mt-4">{action}</div> : null}
</div>
<HeroEmptyState className="audit-empty-state" size="md">
<HeroEmptyState.Header>
<HeroEmptyState.Title>{title}</HeroEmptyState.Title>
{description ? <HeroEmptyState.Description>{description}</HeroEmptyState.Description> : null}
</HeroEmptyState.Header>
{action ? <HeroEmptyState.Content>{action}</HeroEmptyState.Content> : null}
</HeroEmptyState>
);
}
export function InlineNotice({ tone = 'success', children, onDismiss }) {
const tones = {
success: 'border-emerald-200 bg-emerald-50 text-emerald-800',
error: 'border-red-200 bg-red-50 text-red-800',
info: 'border-sky-200 bg-sky-50 text-sky-800',
};
useEffect(() => {
if (!onDismiss) return undefined;
const timeout = window.setTimeout(onDismiss, 3600);
return () => window.clearTimeout(timeout);
}, [children, onDismiss]);
return (
<div className={`flex items-center justify-between gap-3 rounded-lg border px-4 py-3 text-sm ${tones[tone]}`}>
<div className={`rc-toast rc-toast--${tone}`} role={tone === 'error' ? 'alert' : 'status'} aria-live={tone === 'error' ? 'assertive' : 'polite'}>
<span className="rc-toast__icon">{tone === 'error' ? <ExclamationTriangleIcon size={16} /> : <CheckCircleIcon size={16} />}</span>
<span>{children}</span>
{onDismiss ? (
<button type="button" onClick={onDismiss} className="font-medium">关闭</button>
) : null}
{onDismiss ? <button aria-label="关闭提示" className="rc-toast__close" onClick={onDismiss} type="button"><Close size={14} /></button> : null}
</div>
);
}
import {
Combobox,
ComboboxLabel,
ComboboxOption,
} from '@/catalyst-ui-kit/javascript/combobox';
import { BadgeButton } from '@/catalyst-ui-kit/javascript/badge';
import { XMarkIcon } from '@heroicons/react/20/solid';
import { Close } from '@/components/AppIcons';
import { Button, Chip, ComboBox, Input, ListBox, Spinner, Tooltip } from '@heroui/react';
import { useEffect, useMemo, useState } from 'react';
import { normalizeRecordId, normalizeRecordLabel } from './auditManagementService';
......@@ -24,29 +19,32 @@ export default function ReferenceSearchSelect({
multiple = false,
placeholder = '搜索并选择',
}) {
const initialRecords = useMemo(
const valueRecords = useMemo(
() => (Array.isArray(value) ? value : value ? [value] : []).filter((item) => typeof item !== 'string'),
[],
[value],
);
const [query, setQuery] = useState('');
const [options, setOptions] = useState(initialRecords);
const [options, setOptions] = useState(valueRecords);
const [loading, setLoading] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const selectedIds = useMemo(
() => (Array.isArray(value) ? value : value ? [value] : []).map(normalizeRecordId).filter(Boolean),
[value],
);
const selectedRecords = selectedIds
.map((id) => options.find((option) => normalizeRecordId(option) === id))
.map((id) => options.find((option) => normalizeRecordId(option) === id)
|| valueRecords.find((record) => normalizeRecordId(record) === id))
.filter(Boolean);
const selectedLabels = selectedRecords.map(normalizeRecordLabel).filter(Boolean);
const selectionSummary = selectedIds.length === 0
? ''
: selectedLabels.length === selectedIds.length
? selectedLabels.join('、')
: `已选择 ${selectedIds.length} 项`;
useEffect(() => {
const recordsFromValue = (Array.isArray(value) ? value : value ? [value] : []).filter(
(item) => typeof item !== 'string',
);
if (recordsFromValue.length > 0) {
setOptions((current) => uniqueRecords([...current, ...recordsFromValue]));
}
}, [value]);
if (valueRecords.length > 0) setOptions((current) => uniqueRecords([...current, ...valueRecords]));
}, [valueRecords]);
useEffect(() => {
let cancelled = false;
......@@ -59,62 +57,75 @@ export default function ReferenceSearchSelect({
if (!cancelled) setLoading(false);
}
}, 180);
return () => {
cancelled = true;
window.clearTimeout(timer);
};
return () => { cancelled = true; window.clearTimeout(timer); };
}, [loadOptions, query]);
const selectRecord = (record) => {
if (!record) return;
const id = normalizeRecordId(record);
if (multiple) {
onChange(Array.from(new Set([...selectedIds, id])));
} else {
onChange(id);
}
const selectedSet = new Set(selectedIds);
const availableOptions = multiple ? options.filter((record) => !selectedSet.has(normalizeRecordId(record))) : options;
const handleSelection = (key) => {
const id = String(key || '');
if (!id) return;
onChange(multiple ? Array.from(new Set([...selectedIds, id])) : id);
setQuery('');
setIsOpen(false);
};
return (
<div className="space-y-2">
<div className="reference-search-select">
<ComboBox
aria-label={placeholder}
fullWidth
inputValue={isOpen ? query : selectionSummary}
isOpen={isOpen}
menuTrigger="focus"
selectedKey={multiple ? null : selectedIds[0] || null}
onInputChange={setQuery}
onOpenChange={(open) => {
setIsOpen(open);
setQuery('');
}}
onSelectionChange={handleSelection}
>
<ComboBox.InputGroup className="reference-search-select__surface rc-user-combobox__group" data-has-value={selectedIds.length > 0 ? 'true' : undefined}>
<Input
aria-label={selectionSummary && !isOpen ? `${placeholder},当前已选${selectionSummary}` : placeholder}
className="reference-search-select__input"
data-has-value={selectionSummary && !isOpen ? 'true' : undefined}
placeholder={loading && isOpen ? '正在搜索...' : placeholder}
title={selectionSummary && !isOpen ? selectionSummary : undefined}
/>
<ComboBox.Trigger aria-label={`打开${placeholder}列表`} />
</ComboBox.InputGroup>
<ComboBox.Popover className="rc-user-combobox__popover rc-filter-options-popover" placement="bottom start">
<ListBox aria-label={placeholder} items={availableOptions} renderEmptyState={() => <div className="rc-user-select__state">{loading ? '正在搜索...' : '没有匹配结果'}</div>}>
{(record) => (
<ListBox.Item id={normalizeRecordId(record)} textValue={normalizeRecordLabel(record)}>
<span className="rc-user-select__name">{normalizeRecordLabel(record) || normalizeRecordId(record)}</span>
<ListBox.ItemIndicator />
</ListBox.Item>
)}
</ListBox>
{loading ? <div className="rc-user-combobox__loading"><Spinner size="sm" /></div> : null}
</ComboBox.Popover>
</ComboBox>
{multiple && selectedRecords.length > 0 ? (
<div className="flex flex-wrap gap-2">
<div className="rc-user-select__chips">
{selectedRecords.map((record) => {
const id = normalizeRecordId(record);
const label = normalizeRecordLabel(record);
return (
<BadgeButton
key={id}
color="zinc"
type="button"
onClick={() => onChange(selectedIds.filter((selectedId) => selectedId !== id))}
title={`移除${normalizeRecordLabel(record)}`}
>
{normalizeRecordLabel(record)}
<XMarkIcon />
</BadgeButton>
<Chip className="rc-user-select__chip rc-status-chip rc-status-chip--info" key={id} size="sm" variant="soft">
<Chip.Label className="rc-user-select__chip-label">{label}</Chip.Label>
<Tooltip delay={0}>
<Button aria-label={`移除${label}`} className="rc-user-select__chip-remove" isIconOnly variant="ghost" onPress={() => onChange(selectedIds.filter((selectedId) => selectedId !== id))}><Close size={12} /></Button>
<Tooltip.Content>移除</Tooltip.Content>
</Tooltip>
</Chip>
);
})}
</div>
) : null}
<Combobox
value={multiple ? null : selectedRecords[0] || null}
onChange={selectRecord}
options={options}
displayValue={(record) => normalizeRecordLabel(record)}
filter={(record) => normalizeRecordLabel(record).toLowerCase().includes(query.toLowerCase())}
onQueryChange={setQuery}
placeholder={loading ? '加载中...' : placeholder}
aria-label={placeholder}
>
{(record) => (
<ComboboxOption value={record}>
<ComboboxLabel>{normalizeRecordLabel(record) || normalizeRecordId(record)}</ComboboxLabel>
</ComboboxOption>
)}
</Combobox>
</div>
);
}
@import 'tailwindcss';
@import '@heroui/styles';
@import '@heroui-pro/react/css';
@import './design-system/tokens.css';
@import './design-system/shell.css';
@theme {
--font-sans:
"PingFang SC",
"Microsoft YaHei",
"Helvetica Neue",
Arial,
sans-serif;
--font-sans: var(--rc-font-sans);
}
:root {
color-scheme: light;
color-scheme: light !important;
--pm-brand: var(--rc-brand-primary);
--pm-brand-strong: var(--rc-brand-dark);
}
html {
color-scheme: light !important;
}
html.dark,
html[data-theme='dark'] {
color-scheme: dark !important;
}
body {
margin: 0;
font-family: var(--rc-font-sans);
color: var(--rc-text-primary);
background: var(--rc-bg-canvas);
color-scheme: inherit;
}
/* Compatibility bridge for legacy page utilities while modules move to semantic tokens. */
.dark
body
:is(
.bg-white,
[class~='bg-white/95'],
[class~='bg-white/90'],
[class~='bg-white/80'],
[class~='bg-white/70'],
[class~='bg-white/60'],
[class~='bg-white/50'],
[class~='bg-white/10'],
[class~='bg-white/5']
) {
background-color: var(--rc-bg-surface) !important;
}
.dark
body
:is(
.bg-zinc-50,
.bg-zinc-100,
.bg-zinc-200,
.bg-slate-50,
.bg-slate-100,
.bg-slate-200,
[class*='bg-zinc-50/'],
[class*='bg-zinc-100/'],
[class*='bg-slate-50/'],
[class*='bg-slate-100/'],
[class~='bg-zinc-50/95'],
[class~='bg-zinc-50/80'],
[class~='bg-zinc-50/70'],
[class~='bg-zinc-50/60']
) {
background-color: var(--rc-bg-subtle) !important;
}
.dark
body
:is(
.border-zinc-100,
.border-zinc-200,
.border-zinc-300,
[class*='border-zinc-100/'],
[class*='border-zinc-200/'],
[class*='border-slate-100/'],
[class*='border-slate-200/'],
[class~='border-zinc-100/80'],
[class~='border-zinc-200/80'],
[class~='border-zinc-200/70'],
[class~='border-zinc-950/10']
) {
border-color: var(--rc-border-default) !important;
}
.dark
body
:is(
.text-zinc-950,
.text-zinc-900,
.text-zinc-800,
.text-slate-950,
.text-slate-900,
.text-slate-800
) {
color: var(--rc-text-primary) !important;
}
.dark
body
:is(
.text-zinc-700,
.text-zinc-600,
.text-zinc-500,
.text-slate-700,
.text-slate-600,
.text-slate-500
) {
color: var(--rc-text-secondary) !important;
}
.dark body :is(.text-zinc-400, .text-zinc-300) {
color: var(--rc-text-muted) !important;
}
.dark body :is([class~='ring-zinc-950/5'], [class~='ring-zinc-950/10']) {
--tw-ring-color: var(--rc-border-default) !important;
}
.dark body :is(.bg-red-50, .bg-red-100, .bg-rose-50, .bg-rose-100) {
background-color: var(--rc-color-danger-soft) !important;
}
.dark body :is(.bg-amber-50, .bg-amber-100, .bg-yellow-50, .bg-yellow-100) {
background-color: var(--rc-color-warning-soft) !important;
}
.dark body :is(.bg-emerald-50, .bg-emerald-100, .bg-green-50, .bg-green-100) {
background-color: var(--rc-color-success-soft) !important;
}
.dark body :is(.bg-sky-50, .bg-sky-100, .bg-blue-50, .bg-blue-100) {
background-color: var(--rc-color-info-soft) !important;
}
.dark
body
:is(
.bg-cyan-50,
.bg-cyan-100,
.bg-indigo-50,
.bg-indigo-100,
.bg-violet-50,
.bg-violet-100,
.bg-purple-50,
.bg-purple-100
) {
background-color: var(--rc-color-info-soft) !important;
}
.dark body :is(.bg-teal-50, .bg-teal-100) {
background-color: var(--rc-color-teal-soft) !important;
}
.dark body :is(.border-red-200, .border-rose-200) {
border-color: var(--rc-color-danger-border) !important;
}
.dark body :is(.border-amber-200, .border-yellow-200) {
border-color: var(--rc-color-warning-border) !important;
}
.dark body :is(.border-emerald-200, .border-green-200) {
border-color: var(--rc-color-success-border) !important;
}
.dark body :is(.border-sky-200, .border-blue-100, .border-blue-200) {
border-color: var(--rc-color-info-border) !important;
}
.dark
body
:is(
.border-cyan-200,
.border-indigo-200,
.border-violet-200,
.border-purple-200
) {
border-color: var(--rc-color-info-border) !important;
}
.dark body :is(.border-teal-200) {
border-color: var(--rc-color-teal-border) !important;
}
.dark
body
:is(
.text-red-600,
.text-red-700,
.text-red-800,
.text-rose-600,
.text-rose-700
) {
color: var(--rc-color-danger) !important;
}
.dark
body
:is(
.text-amber-600,
.text-amber-700,
.text-amber-800,
.text-amber-900,
.text-yellow-700
) {
color: var(--rc-color-warning) !important;
}
.dark
body
:is(
.text-emerald-600,
.text-emerald-700,
.text-emerald-800,
.text-green-600,
.text-green-700
) {
color: var(--rc-color-success) !important;
}
.dark
body
:is(
.text-sky-600,
.text-sky-700,
.text-sky-800,
.text-blue-600,
.text-blue-700
) {
color: var(--rc-color-info) !important;
}
.dark
body
:is(
.text-cyan-600,
.text-cyan-700,
.text-indigo-600,
.text-indigo-700,
.text-violet-600,
.text-violet-700,
.text-purple-600,
.text-purple-700
) {
color: var(--rc-color-info) !important;
}
.dark body :is(.text-teal-600, .text-teal-700) {
color: var(--rc-color-teal) !important;
}
.dark body [class*='bg-gradient-to-'] {
background-color: var(--rc-bg-subtle) !important;
background-image: none !important;
}
/* Portal surfaces must inherit the root theme exactly like the application Shell. */
.dark
:is(.rc-dialog-viewport, [data-headlessui-portal], [data-overlay-container]) {
color: var(--rc-text-primary);
}
.dark
:is(
.rc-dialog-panel,
.rc-filter-options-popover,
[role='listbox'],
[role='menu']
) {
border-color: var(--rc-border-default);
background-color: var(--overlay);
color: var(--rc-text-primary);
}
.dark .rc-dialog-panel :is(hr, [role='separator']) {
border-color: var(--rc-border-default);
}
.dark
.rc-dialog-panel
:is(input, select, textarea):not([type='checkbox']):not([type='radio']) {
color: var(--rc-text-primary);
caret-color: var(--rc-text-primary);
}
.dark .rc-dialog-panel :is(input, textarea)::placeholder {
color: var(--rc-text-muted);
}
.dark .rc-dialog-panel :is(input, select, textarea):disabled {
color: var(--rc-text-muted);
-webkit-text-fill-color: var(--rc-text-muted);
}
.rc-status-chip {
border-style: solid;
border-width: 1px;
white-space: nowrap;
}
.rc-status-chip--neutral,
.rc-tone-surface--neutral {
border-color: var(--rc-color-neutral-border) !important;
background: var(--rc-color-neutral-soft) !important;
color: var(--rc-text-secondary) !important;
}
.rc-status-chip--info,
.rc-tone-surface--info {
border-color: var(--rc-color-info-border) !important;
background: var(--rc-color-info-soft) !important;
color: var(--rc-color-info) !important;
}
.rc-status-chip--success,
.rc-tone-surface--success {
border-color: var(--rc-color-success-border) !important;
background: var(--rc-color-success-soft) !important;
color: var(--rc-color-success) !important;
}
.rc-status-chip--warning,
.rc-tone-surface--warning {
border-color: var(--rc-color-warning-border) !important;
background: var(--rc-color-warning-soft) !important;
color: var(--rc-color-warning) !important;
}
.rc-status-chip--danger,
.rc-tone-surface--danger {
border-color: var(--rc-color-danger-border) !important;
background: var(--rc-color-danger-soft) !important;
color: var(--rc-color-danger) !important;
}
.rc-status-chip--teal,
.rc-tone-surface--teal {
border-color: var(--rc-color-teal-border) !important;
background: var(--rc-color-teal-soft) !important;
color: var(--rc-color-teal) !important;
}
.rc-metric-icon-surface {
display: grid !important;
width: 3rem !important;
height: 3rem !important;
min-width: 3rem !important;
min-height: 3rem !important;
flex: none;
place-items: center !important;
padding: 0 !important;
border: 0 !important;
border-radius: 1rem !important;
line-height: 1 !important;
}
.rc-metric-icon-surface .anticon,
.rc-metric-icon-surface > svg {
display: grid !important;
width: 1.25rem !important;
height: 1.25rem !important;
place-items: center !important;
margin: 0 !important;
color: inherit !important;
font-size: 1.25rem !important;
line-height: 1 !important;
}
.rc-metric-icon-surface .anticon > svg,
.rc-metric-icon-surface > svg {
display: block !important;
width: 1em !important;
height: 1em !important;
margin: 0 !important;
}
:where(
.button--icon-only,
.rc-filter-reset-button.button,
.rc-form-date-picker__trigger,
.date-picker__trigger,
.app-navbar-icon-button,
.rc-navbar-icon-button,
.app-navbar-trigger,
.rc-navbar-trigger,
.app-sidebar-app-trigger,
.rc-sidebar-app-trigger,
.rc-title-info-trigger,
.pm-table-action,
.rc-tone-action--neutral,
.rc-tone-action--info,
.rc-tone-action--success,
.rc-tone-action--danger
) {
display: inline-grid !important;
place-items: center !important;
align-items: center !important;
justify-content: center !important;
justify-items: center !important;
align-content: center !important;
padding: 0 !important;
line-height: 1 !important;
}
:where(
.button--icon-only,
.rc-filter-reset-button.button,
.rc-form-date-picker__trigger,
.date-picker__trigger,
.app-navbar-icon-button,
.rc-navbar-icon-button,
.app-navbar-trigger,
.rc-navbar-trigger,
.app-sidebar-app-trigger,
.rc-sidebar-app-trigger,
.rc-title-info-trigger,
.pm-table-action,
.rc-tone-action--neutral,
.rc-tone-action--info,
.rc-tone-action--success,
.rc-tone-action--danger
) :where(.anticon, svg) {
display: grid !important;
width: 1rem !important;
height: 1rem !important;
place-items: center !important;
flex: none !important;
margin: 0 !important;
line-height: 0 !important;
vertical-align: middle !important;
transform: none !important;
}
:where(
.button--icon-only,
.rc-filter-reset-button.button,
.rc-form-date-picker__trigger,
.date-picker__trigger,
.app-navbar-icon-button,
.rc-navbar-icon-button,
.app-navbar-trigger,
.rc-navbar-trigger,
.app-sidebar-app-trigger,
.rc-sidebar-app-trigger,
.rc-title-info-trigger,
.pm-table-action,
.rc-tone-action--neutral,
.rc-tone-action--info,
.rc-tone-action--success,
.rc-tone-action--danger
) :where(.anticon > svg, svg > svg) {
display: block !important;
width: 1rem !important;
height: 1rem !important;
margin: 0 !important;
vertical-align: middle !important;
transform: none !important;
}
.rc-tone-action--neutral,
.rc-tone-action--info,
.rc-tone-action--success,
.rc-tone-action--danger {
border: 1px solid transparent !important;
}
.rc-tone-action--neutral {
border-color: var(--rc-color-neutral-border) !important;
background: var(--rc-color-neutral-soft) !important;
color: var(--rc-text-muted) !important;
}
.rc-tone-action--info {
background: var(--rc-color-info-soft) !important;
color: var(--rc-color-info) !important;
}
.rc-tone-action--success {
background: var(--rc-color-success-soft) !important;
color: var(--rc-color-success) !important;
}
.rc-tone-action--danger {
background: var(--rc-color-danger-soft) !important;
color: var(--rc-color-danger) !important;
}
.rc-tone-action--neutral:hover,
.rc-tone-action--info:hover,
.rc-tone-action--success:hover,
.rc-tone-action--danger:hover {
border-color: currentColor !important;
filter: brightness(0.96);
}
.dark
:is(
.rc-tone-action--neutral,
.rc-tone-action--info,
.rc-tone-action--success,
.rc-tone-action--danger
):hover {
filter: brightness(1.16);
}
/* Static business cards use borders for definition and only a light resting lift. */
.rc-card-surface {
border-color: var(--rc-border-default) !important;
background-color: var(--rc-bg-surface) !important;
color: var(--rc-text-primary);
box-shadow: var(--rc-shadow-card) !important;
}
body .login-page {
--rc-login-brand-logo-size: 2.25rem;
--rc-login-card-brand-gap: 0.875rem;
--rc-login-card-brand-margin-bottom: 2rem;
--rc-login-card-brand-title-size: 2rem;
--rc-login-card-brand-title-weight: var(--rc-login-brand-weight);
--rc-login-control-height: 2.5rem;
--rc-login-submit-height: 2.875rem;
--rc-login-submit-margin-top: 0.875rem;
--rc-login-field-filled-background: #fbf1f1;
--rc-login-field-filled-background-hover: #faeaea;
--rc-login-field-autofill-background: #fbf1f1;
--rc-login-field-border-hover: rgb(158 27 27 / 24%);
--rc-login-field-focus-ring: rgb(158 27 27 / 16%);
}
body .login-brand img {
width: var(--rc-login-brand-logo-size) !important;
height: var(--rc-login-brand-logo-size) !important;
}
body .login-card-brand {
justify-content: center;
gap: var(--rc-login-card-brand-gap) !important;
margin-bottom: var(--rc-login-card-brand-margin-bottom);
text-align: center;
}
body .login-card-brand strong {
color: var(--rc-text-primary);
font-size: var(--rc-login-card-brand-title-size);
font-weight: var(--rc-login-card-brand-title-weight);
line-height: 1.15;
letter-spacing: 0;
white-space: nowrap;
}
body .login-card [data-slot='card-header'] {
display: none !important;
}
body .login-card [data-slot='card-content'] {
padding: 2.25rem !important;
}
body
.login-form
.login-field
:is(
.input,
.input--secondary,
.login-input,
[data-slot='input'],
[data-slot='input-wrapper'],
[data-slot='control']
) {
height: var(--rc-login-control-height) !important;
min-height: var(--rc-login-control-height) !important;
}
body
.login-form
.login-field
:is(input, .login-input, input.input--secondary, input[data-slot='input']) {
line-height: var(--rc-login-control-height) !important;
}
body
.login-form
.login-field[data-has-value='true']
:is(.input, .input--secondary, [data-slot='input'], [data-slot='input-wrapper'], [data-slot='control']) {
--input-bg: var(--rc-login-field-filled-background) !important;
--input-bg-hover: var(--rc-login-field-filled-background-hover) !important;
--input-bg-focus: var(--rc-login-field-filled-background) !important;
background: var(--rc-login-field-filled-background) !important;
background-color: var(--rc-login-field-filled-background) !important;
}
body
.login-form
.login-field[data-has-value='true']
:is(.input input, [data-slot='input-wrapper'] input, [data-slot='control'] input) {
background: transparent !important;
background-color: transparent !important;
}
body
.login-form
.login-field
:is(input.input--secondary, input[data-slot='input']):not(:placeholder-shown) {
--input-bg: var(--rc-login-field-filled-background) !important;
--input-bg-hover: var(--rc-login-field-filled-background-hover) !important;
--input-bg-focus: var(--rc-login-field-filled-background) !important;
background: var(--rc-login-field-filled-background) !important;
background-color: var(--rc-login-field-filled-background) !important;
}
body
.login-form
.login-input:is(.input, .input--secondary, [data-slot='input'])[data-filled='true'],
body
.login-form
.login-input:is(.input, .input--secondary, [data-slot='input']):not(:placeholder-shown) {
--input-bg: var(--rc-login-field-filled-background) !important;
--input-bg-hover: var(--rc-login-field-filled-background-hover) !important;
--input-bg-focus: var(--rc-login-field-filled-background) !important;
border-color: transparent !important;
background: var(--rc-login-field-filled-background) !important;
background-color: var(--rc-login-field-filled-background) !important;
box-shadow: inset 0 0 0 1000px var(--rc-login-field-filled-background) !important;
}
body
.login-form
.login-field[data-has-value='true']
:is(.input, .input--secondary, [data-slot='input'], [data-slot='input-wrapper'], [data-slot='control']):hover,
body
.login-form
.login-field[data-has-value='true']
:is(
.input,
.input--secondary,
[data-slot='input'],
[data-slot='input-wrapper'],
[data-slot='control']
)[data-hovered='true'] {
border-color: var(--rc-login-field-border-hover) !important;
background: var(--rc-login-field-filled-background-hover) !important;
background-color: var(--rc-login-field-filled-background-hover) !important;
}
body
.login-form
.login-field[data-has-value='true']
:is(.input, .input--secondary, [data-slot='input'], [data-slot='input-wrapper'], [data-slot='control']):focus,
body
.login-form
.login-field[data-has-value='true']
:is(.input, .input--secondary, [data-slot='input'], [data-slot='input-wrapper'], [data-slot='control']):focus-within,
body
.login-form
.login-field[data-has-value='true']
:is(.input, .input--secondary, [data-slot='input'], [data-slot='input-wrapper'], [data-slot='control'])[data-focused='true'] {
border-color: var(--rc-field-border-focus) !important;
background: var(--rc-login-field-filled-background) !important;
background-color: var(--rc-login-field-filled-background) !important;
box-shadow:
var(--field-shadow, 0 1px 2px rgb(15 23 42 / 4%)),
0 0 0 2px var(--rc-login-field-focus-ring) !important;
}
body
.login-form
.login-field
:is(input.input--secondary, input[data-slot='input']):not(:placeholder-shown):focus,
body
.login-form
.login-field
:is(input.input--secondary, input[data-slot='input']):not(:placeholder-shown)[data-focused='true'] {
border-color: var(--rc-field-border-focus) !important;
background: var(--rc-login-field-filled-background) !important;
background-color: var(--rc-login-field-filled-background) !important;
box-shadow:
var(--field-shadow, 0 1px 2px rgb(15 23 42 / 4%)),
0 0 0 2px var(--rc-login-field-focus-ring) !important;
}
body
.login-form
.login-input:is(.input, .input--secondary, [data-slot='input'])[data-filled='true']:focus,
body
.login-form
.login-input:is(.input, .input--secondary, [data-slot='input'])[data-filled='true'][data-focused='true'],
body
.login-form
.login-input:is(.input, .input--secondary, [data-slot='input']):not(:placeholder-shown):focus,
body
.login-form
.login-input:is(.input, .input--secondary, [data-slot='input']):not(:placeholder-shown)[data-focused='true'] {
--input-bg: var(--rc-login-field-filled-background) !important;
--input-bg-hover: var(--rc-login-field-filled-background-hover) !important;
--input-bg-focus: var(--rc-login-field-filled-background) !important;
border-color: var(--rc-field-border-focus) !important;
background: var(--rc-login-field-filled-background) !important;
background-color: var(--rc-login-field-filled-background) !important;
box-shadow:
inset 0 0 0 1000px var(--rc-login-field-filled-background),
0 0 0 2px var(--rc-login-field-focus-ring) !important;
}
body
.login-form
:is(.input, [data-slot='input-wrapper'], [data-slot='control']):has(
input:-webkit-autofill
),
body
.login-form
:is(.input, [data-slot='input-wrapper'], [data-slot='control']):has(
input:autofill
) {
--input-bg: var(--rc-login-field-autofill-background) !important;
--input-bg-hover: var(--rc-login-field-autofill-background) !important;
--input-bg-focus: var(--rc-login-field-autofill-background) !important;
background: var(--rc-login-field-autofill-background) !important;
background-color: var(--rc-login-field-autofill-background) !important;
}
body .login-form input:-webkit-autofill {
-webkit-box-shadow: 0 0 0 1000px var(--rc-login-field-autofill-background)
inset !important;
-webkit-text-fill-color: var(--rc-text-primary) !important;
caret-color: var(--rc-text-primary);
}
body
.login-form
:is(input.login-input, input.input--secondary, input[data-slot='input'])[data-filled='true'],
body
.login-form
:is(input.login-input, input.input--secondary, input[data-slot='input']):not(
:placeholder-shown
),
body
.login-form
:is(input.login-input, input.input--secondary, input[data-slot='input']):not(
:placeholder-shown
):focus,
body
.login-form
:is(input.login-input, input.input--secondary, input[data-slot='input']):not(
:placeholder-shown
):focus-visible,
body
.login-form
:is(input.login-input, input.input--secondary, input[data-slot='input']):not(
:placeholder-shown
)[data-focused='true'],
body
.login-form
:is(input.login-input, input.input--secondary, input[data-slot='input']):-webkit-autofill,
body
.login-form
:is(input.login-input, input.input--secondary, input[data-slot='input']):-webkit-autofill:hover,
body
.login-form
:is(input.login-input, input.input--secondary, input[data-slot='input']):-webkit-autofill:focus,
body
.login-form
:is(input.login-input, input.input--secondary, input[data-slot='input']):autofill,
body
.login-form
:is(input.login-input, input.input--secondary, input[data-slot='input']):autofill:focus {
--input-bg: var(--rc-login-field-filled-background) !important;
--input-bg-hover: var(--rc-login-field-filled-background-hover) !important;
--input-bg-focus: var(--rc-login-field-filled-background) !important;
appearance: none;
border-color: var(--rc-field-border-focus) !important;
background: var(--rc-login-field-filled-background) !important;
background-color: var(--rc-login-field-filled-background) !important;
background-image: none !important;
background-clip: padding-box !important;
caret-color: var(--rc-text-primary);
box-shadow:
inset 0 0 0 1000px var(--rc-login-field-filled-background),
0 0 0 2px var(--rc-login-field-focus-ring) !important;
-webkit-box-shadow:
inset 0 0 0 1000px var(--rc-login-field-filled-background),
0 0 0 2px var(--rc-login-field-focus-ring) !important;
-webkit-text-fill-color: var(--rc-text-primary) !important;
transition:
background-color 150ms ease,
border-color 150ms ease,
box-shadow 150ms ease;
}
body .login-submit-button {
height: var(--rc-login-submit-height) !important;
min-height: var(--rc-login-submit-height) !important;
margin-top: var(--rc-login-submit-margin-top) !important;
border-radius: var(--rc-button-radius, 999px) !important;
}
.dark .login-page,
body .dark .login-page,
body.dark .login-page,
body [data-theme='dark'] .login-page {
--rc-login-field-filled-background: rgb(158 27 27 / 14%);
--rc-login-field-filled-background-hover: rgb(158 27 27 / 18%);
--rc-login-field-autofill-background: rgb(158 27 27 / 16%);
--rc-login-field-border-hover: rgb(184 58 58 / 42%);
--rc-login-field-focus-ring: rgb(184 58 58 / 22%);
}
.rc-dashboard-card,
.rc-workspace-module {
border-radius: 24px !important;
}
/* HeroUI Pro file selection must use semantic surfaces instead of native file controls. */
.rc-file-upload .drop-zone__area {
min-height: 10rem;
border-color: var(--rc-border-default);
background: var(--rc-field-background);
color: var(--rc-text-primary);
}
.rc-file-upload .drop-zone__area:hover:not([data-disabled]) {
border-color: var(--rc-border-strong);
background: var(--rc-bg-subtle);
}
.rc-file-upload .drop-zone__area[data-drop-target] {
border-color: var(--rc-brand-primary);
background: var(--rc-brand-primary-soft);
}
.rc-file-upload .drop-zone__trigger {
height: var(--rc-button-sm-height);
border-color: var(--rc-button-secondary-border);
border-radius: var(--rc-button-radius);
background: transparent;
color: var(--rc-text-primary);
box-shadow: none;
}
.rc-file-upload .drop-zone__trigger:hover,
.rc-file-upload .drop-zone__trigger[data-hovered='true'] {
border-color: var(--rc-button-secondary-border-hover);
background: var(--rc-button-secondary-background-hover);
}
.rc-file-upload .drop-zone__icon {
color: var(--rc-brand-primary);
}
.rc-file-upload .drop-zone__label {
max-width: 100%;
overflow: hidden;
color: var(--rc-text-primary);
text-overflow: ellipsis;
white-space: nowrap;
}
.rc-file-upload .drop-zone__description {
color: var(--rc-text-muted);
}
@media (max-width: 767px) {
.rc-file-upload .drop-zone__trigger {
height: var(--rc-button-md-height);
}
}
/* Keep form and DataGrid selection controls on Xinyuan checkbox tokens. */
.checkbox .checkbox__control {
border-width: 1px !important;
border-color: var(--rc-checkbox-border) !important;
background-color: var(--rc-checkbox-background) !important;
box-shadow: none !important;
color: var(--rc-text-primary) !important;
}
.checkbox .checkbox__control::before {
background-color: var(--rc-checkbox-checked-background) !important;
}
.checkbox:hover:not([data-disabled='true']):not([data-selected='true']):not(
[aria-checked='true']
):not([data-indeterminate='true'])
.checkbox__control,
.checkbox[data-hovered='true']:not([data-disabled='true']):not(
[data-selected='true']
):not([aria-checked='true']):not([data-indeterminate='true'])
.checkbox__control,
.checkbox:has([data-slot='checkbox-content'][data-hovered='true']):not(
[data-disabled='true']
):not([data-selected='true']):not([aria-checked='true']):not(
[data-indeterminate='true']
)
.checkbox__control {
border-color: var(--rc-checkbox-border-hover) !important;
background-color: color-mix(
in oklab,
var(--rc-checkbox-background) 82%,
var(--rc-text-primary) 18%
) !important;
}
:is(
.checkbox[data-selected='true'],
.checkbox[aria-checked='true'],
.checkbox[data-indeterminate='true']
)
.checkbox__control::before,
.checkbox[data-indeterminate='true'] .checkbox__control {
background-color: var(--rc-checkbox-checked-background) !important;
}
:is(
.checkbox[data-selected='true'],
.checkbox[aria-checked='true'],
.checkbox[data-indeterminate='true']
)
.checkbox__control {
border-color: var(--rc-checkbox-checked-border) !important;
color: var(--rc-checkbox-check-color) !important;
}
.pm-data-table-selection-scope {
position: relative;
min-width: 0;
}
.pm-data-table-selection-scope .action-bar {
position: absolute;
inset-inline: 1rem;
bottom: 1rem;
z-index: 20;
}
.pm-data-table-selection-scope .action-bar__wrapper.pm-data-table-action-bar {
max-width: min(100% - 2rem, 32rem);
}
.rc-button--solid,
.rc-filter-query-button {
border: 0 !important;
background-image: none !important;
box-shadow: none !important;
}
.rc-button--solid::before,
.rc-button--solid::after {
content: none !important;
background: transparent !important;
box-shadow: none !important;
}
.rc-button--primary {
background-color: var(--rc-brand-primary) !important;
}
.rc-button--primary[data-hover],
.rc-button--primary[data-active],
.rc-button--primary[data-hover='true'],
.rc-button--primary[data-active='true'] {
background-color: var(--rc-brand-primary-hover) !important;
}
.rc-button--outline {
border: 1px solid var(--rc-button-secondary-border) !important;
background-color: transparent !important;
color: var(--rc-text-primary) !important;
box-shadow: none !important;
}
.rc-button--outline:hover,
.rc-button--outline:active,
.rc-button--outline[data-hover],
.rc-button--outline[data-active],
.rc-button--outline[data-hover='true'],
.rc-button--outline[data-active='true'] {
border: 1px solid var(--rc-button-secondary-border-hover) !important;
background-color: var(--rc-button-secondary-background-hover) !important;
}
.rc-button--plain {
color: var(--rc-text-primary) !important;
}
.rc-button--danger.rc-button--plain {
border: 1px solid transparent !important;
background-color: transparent !important;
color: var(--rc-color-danger) !important;
box-shadow: none !important;
}
.rc-button--plain:hover,
.rc-button--plain:active,
.rc-button--plain[data-hover],
.rc-button--plain[data-active],
.rc-button--plain[data-hover='true'],
.rc-button--plain[data-active='true'] {
background-color: var(--rc-button-secondary-background-hover) !important;
}
.rc-button--danger.rc-button--plain:hover,
.rc-button--danger.rc-button--plain:active,
.rc-button--danger.rc-button--plain[data-hover],
.rc-button--danger.rc-button--plain[data-active],
.rc-button--danger.rc-button--plain[data-hover='true'],
.rc-button--danger.rc-button--plain[data-active='true'] {
border-color: var(--rc-color-danger-border) !important;
background-color: var(--rc-color-danger-soft) !important;
color: var(--rc-color-danger) !important;
}
.rc-filter-card.card {
gap: 0;
overflow: visible;
border-color: var(--rc-border-default);
border-radius: 1rem;
background: var(--rc-bg-surface);
box-shadow: var(--rc-shadow-card);
}
.rc-filter-card form {
min-width: 0;
}
.rc-filter-label,
.rc-filter-primary-fields > .min-w-0 > :first-child:not(.sr-only),
.rc-filter-expanded-grid > div > :first-child:not(.sr-only) {
color: var(--rc-text-secondary) !important;
font-size: var(--rc-text-sm) !important;
font-weight: var(--rc-font-medium) !important;
line-height: var(--rc-line-normal) !important;
letter-spacing: 0;
}
.rc-filter-card {
--rc-filter-control-border: var(--rc-button-secondary-border);
}
.rc-filter-card
span[data-slot='control']:has(> input, > select, > textarea)::before,
.rc-filter-card .reference-search-select__shell::before {
border-radius: calc(0.5rem - 1px);
background: var(--rc-field-background);
box-shadow: var(--field-shadow);
}
.rc-filter-card :is(input, select, textarea),
.rc-filter-card .reference-search-select__surface {
border: 1px solid var(--rc-filter-control-border) !important;
border-radius: 0.5rem !important;
background: transparent !important;
box-shadow: none !important;
}
.rc-filter-card .rc-form-input {
width: 100%;
min-width: 0;
}
.rc-filter-card
.rc-form-input
:is(.input, [data-slot='input-wrapper'], [data-slot='control']) {
border: 1px solid var(--rc-filter-control-border) !important;
border-radius: 0.5rem !important;
background: var(--rc-field-background) !important;
box-shadow: var(--field-shadow) !important;
}
.rc-filter-card
.rc-form-input
:is(input, [data-slot='input']) {
min-width: 0;
height: 2.25rem !important;
border-color: transparent !important;
background: transparent !important;
box-shadow: none !important;
color: var(--rc-text-secondary) !important;
font-size: var(--rc-text-sm) !important;
font-weight: var(--rc-font-regular) !important;
line-height: 1.25rem !important;
}
.rc-filter-card input.rc-form-input {
width: 100%;
min-width: 0;
height: 2.25rem !important;
border: 1px solid var(--rc-filter-control-border) !important;
border-radius: 0.5rem !important;
background: var(--rc-field-background) !important;
box-shadow: var(--field-shadow) !important;
color: var(--rc-text-secondary) !important;
font-size: var(--rc-text-sm) !important;
font-weight: var(--rc-font-regular) !important;
line-height: 1.25rem !important;
}
.rc-filter-card
.rc-filter-control
:is(label.relative, div.relative):has(> .anticon, > svg)
.rc-form-input
:is(input, [data-slot='input']) {
padding-left: 2.25rem !important;
}
.rc-filter-card
.rc-filter-control
:is(label.relative, div.relative):has(> .anticon, > svg)
input.rc-form-input {
padding-left: 2.25rem !important;
}
.rc-filter-card
.rc-filter-control
:is(label.relative, div.relative):has(> .anticon, > svg)
> :is(.anticon, svg) {
left: 0.75rem !important;
color: var(--rc-text-muted) !important;
}
.rc-filter-card input[type='number'] {
appearance: textfield;
}
.rc-filter-card input[type='number']::-webkit-outer-spin-button,
.rc-filter-card input[type='number']::-webkit-inner-spin-button {
margin: 0;
appearance: none;
}
.rc-filter-card
.reference-search-select__surface
.reference-search-select__input,
.rc-filter-card
.reference-search-select__surface
.reference-search-select__input:hover,
.rc-filter-card
.reference-search-select__surface
.reference-search-select__input:focus {
border: 0 !important;
background: transparent !important;
box-shadow: none !important;
outline: none;
}
.rc-filter-card
:is(
.input,
.select__trigger,
.combo-box__input-group,
.resource-filter-multi-select__trigger
) {
border: 1px solid var(--rc-filter-control-border) !important;
border-radius: 0.5rem !important;
background: var(--rc-field-background) !important;
box-shadow: var(--field-shadow);
}
.rc-filter-card :is(input, select, textarea):hover,
.rc-filter-card .reference-search-select__surface:hover,
.rc-filter-card
:is(
.input,
.select__trigger,
.combo-box__input-group,
.resource-filter-multi-select__trigger
):hover {
border-color: var(--rc-field-border-hover) !important;
}
.rc-filter-card
span[data-slot='control']:has(> input, > select, > textarea):focus-within
> :is(input, select, textarea),
.rc-filter-card
.reference-search-select__shell:focus-within
.reference-search-select__surface {
border-color: var(--rc-field-border-focus) !important;
}
.rc-filter-card
span[data-slot='control']:has(
> input,
> select,
> textarea
):focus-within::after,
.rc-filter-card .reference-search-select__shell:focus-within::after {
--tw-ring-color: var(--rc-brand-primary) !important;
}
.rc-filter-card
:is(.input, .select__trigger, .combo-box__input-group):focus-within,
.rc-filter-card .resource-filter-multi-select__trigger:focus-visible {
border-color: var(--rc-field-border-focus) !important;
box-shadow: var(--field-shadow), 0 0 0 2px var(--rc-brand-primary-soft);
outline: none;
}
.rc-textarea-single-surface::before,
.rc-textarea-single-surface::after {
display: none;
}
.rc-textarea-single-surface textarea {
min-height: inherit;
background: var(--rc-field-background);
box-shadow: var(--field-shadow, 0 1px 2px rgb(15 23 42 / 4%));
}
.rc-textarea-single-surface textarea:focus {
border-color: var(--rc-field-border-focus);
}
.rc-filter-select {
min-width: 0;
width: 100%;
}
.rc-filter-card .rc-filter-select__trigger,
.rc-filter-select__trigger {
display: flex;
width: 100%;
min-width: 0;
height: 2.25rem;
min-height: 2.25rem;
align-items: center;
gap: 0.5rem;
border: 1px solid
var(--rc-filter-control-border, var(--rc-field-border)) !important;
border-radius: 0.5rem !important;
background: var(--rc-field-background) !important;
padding: 0 0.625rem 0 0.75rem;
color: var(--rc-text-secondary);
box-shadow: var(--field-shadow) !important;
font-size: var(--rc-text-sm);
font-weight: var(--rc-font-regular);
line-height: 1.25rem;
text-align: left;
transition: border-color var(--rc-motion-fast) var(--rc-ease-standard),
box-shadow var(--rc-motion-fast) var(--rc-ease-standard);
}
.rc-filter-card .rc-filter-select__trigger:hover,
.rc-filter-select__trigger:hover,
.rc-filter-select__trigger[data-hovered='true'] {
border-color: var(--rc-field-border-hover) !important;
}
.rc-filter-card .rc-filter-select__trigger[data-focus-visible='true'],
.rc-filter-select__trigger[data-focus-visible='true'],
.rc-filter-select__trigger:focus-visible {
border-color: var(--rc-field-border-focus) !important;
box-shadow: var(--field-shadow), 0 0 0 2px var(--rc-brand-primary-soft) !important;
outline: none;
}
.rc-filter-select__trigger[data-disabled='true'] {
cursor: not-allowed;
opacity: 0.5;
}
.rc-filter-select__value {
min-width: 0;
flex: 1;
overflow: hidden;
color: inherit;
}
.rc-filter-select__trigger[data-has-value='true'] {
color: var(--rc-text-primary);
}
.rc-filter-select__trigger[data-has-value='true'] .rc-filter-select__value {
font-weight: var(--rc-font-medium);
}
.rc-filter-select__placeholder {
color: var(--rc-text-muted);
}
.reference-search-select__input[data-has-value='true'] {
color: var(--rc-text-primary) !important;
font-weight: var(--rc-font-medium) !important;
}
.rc-filter-select__indicator {
display: inline-grid !important;
width: 1rem;
height: 1rem;
flex: none;
place-items: center;
color: var(--rc-text-muted);
font-size: 0.875rem;
line-height: 1;
transform-origin: center;
transition:
color var(--rc-motion-fast) var(--rc-ease-standard),
transform var(--rc-motion-fast) var(--rc-ease-standard);
}
.rc-filter-select__indicator :is(svg, .anticon),
.rc-filter-select__indicator svg,
.rc-filter-select__trigger .select__indicator,
.rc-filter-select__trigger .select__indicator svg {
display: block !important;
width: 1em !important;
height: 1em !important;
margin: 0 !important;
color: currentColor !important;
fill: currentColor !important;
line-height: 1 !important;
}
.rc-filter-select__trigger:hover .rc-filter-select__indicator,
.rc-filter-select__trigger[data-hovered='true'] .rc-filter-select__indicator,
.rc-filter-select__trigger[aria-expanded='true'] .rc-filter-select__indicator {
color: var(--rc-text-secondary);
}
.rc-filter-select__indicator[data-open='true'],
.rc-filter-select__trigger[aria-expanded='true'] .rc-filter-select__indicator {
transform: rotate(180deg);
}
.rc-filter-options-popover,
.rc-filter-select__popover {
max-width: min(24rem, calc(100vw - 1.5rem));
border: 1px solid var(--rc-border-default) !important;
border-radius: var(--rc-radius-overlay) !important;
background: var(--rc-bg-surface) !important;
padding: 0.25rem !important;
box-shadow: var(--rc-shadow-raised) !important;
color: var(--rc-text-secondary);
overflow: hidden;
z-index: 1200;
}
.reference-search-select__options {
width: max(var(--trigger-width, 16rem), 16rem);
min-width: min(var(--trigger-width, 16rem), calc(100vw - 1.5rem));
}
.rc-filter-select__popover {
width: max(var(--trigger-width), 11.25rem);
min-width: var(--trigger-width);
}
.rc-form-select__popover {
width: max(var(--trigger-width), 12rem);
}
.rc-form-date-picker {
width: 100%;
min-width: 0;
}
.rc-form-date-picker__group {
position: relative !important;
display: flex !important;
width: 100% !important;
min-height: 2.25rem;
align-items: center !important;
border: 1px solid var(--rc-field-border) !important;
border-radius: 0.5rem !important;
background: var(--rc-field-background) !important;
box-shadow: var(--field-shadow) !important;
color: var(--rc-text-primary) !important;
}
.rc-form-date-picker__group:hover {
border-color: var(--rc-field-border-hover) !important;
}
.rc-form-date-picker[data-focus-within='true'] .rc-form-date-picker__group {
border-color: var(--rc-field-border-focus) !important;
box-shadow: 0 0 0 2px
color-mix(in srgb, var(--rc-brand-primary) 22%, transparent),
var(--field-shadow) !important;
}
.rc-form-date-picker__input {
flex: 1 1 auto !important;
min-width: 0;
padding-inline: 0.75rem 2.75rem !important;
}
.rc-form-date-picker__segment {
color: var(--rc-text-primary) !important;
font-size: var(--rc-text-sm);
font-weight: var(--rc-font-regular);
}
.rc-form-date-picker__segment[data-placeholder='true'] {
color: var(--rc-text-muted) !important;
}
.rc-form-date-picker__suffix {
position: absolute !important;
top: 50% !important;
right: 0.125rem !important;
display: flex !important;
flex: none !important;
align-items: center !important;
justify-content: center !important;
margin-left: auto !important;
padding-right: 0 !important;
transform: translateY(-50%) !important;
}
.rc-form-date-picker__trigger {
display: inline-grid !important;
place-items: center !important;
width: 2rem !important;
height: 2rem !important;
min-width: 2rem !important;
min-height: 2rem !important;
border-radius: 999px !important;
color: var(--rc-text-muted) !important;
padding: 0 !important;
line-height: 1 !important;
}
.rc-form-date-picker__trigger:hover,
.rc-form-date-picker__trigger[data-hovered='true'] {
background: var(--default) !important;
color: var(--rc-text-primary) !important;
}
.rc-form-date-picker__trigger :where(.anticon, svg),
.rc-form-date-picker__trigger [data-slot='date-picker-trigger-indicator'] {
display: grid !important;
width: 1rem !important;
height: 1rem !important;
place-items: center !important;
margin: 0 !important;
color: currentColor !important;
line-height: 1 !important;
}
.rc-form-date-picker__trigger :where(.anticon > svg, svg > svg) {
display: block !important;
width: 100% !important;
height: 100% !important;
margin: 0 !important;
}
.rc-form-date-picker__popover,
.rc-dropdown-popover {
max-width: calc(100vw - 1.5rem);
max-height: min(24rem, calc(100vh - 1.5rem));
border: 1px solid var(--rc-border-default) !important;
border-radius: var(--rc-radius-overlay) !important;
background: var(--rc-bg-surface) !important;
padding: 0.25rem !important;
box-shadow: var(--rc-shadow-raised) !important;
color: var(--rc-text-secondary) !important;
overflow: auto;
z-index: 1400;
}
.rc-form-date-picker__calendar {
background: transparent !important;
color: var(--rc-text-primary) !important;
}
.rc-form-date-picker__popover [data-slot='calendar-nav-button'],
.rc-form-date-picker__popover [data-slot='calendar-cell'] {
color: var(--rc-text-secondary) !important;
}
.rc-form-date-picker__popover
[data-slot='calendar-cell'][data-today='true'] {
color: var(--rc-brand-primary) !important;
font-weight: var(--rc-font-medium) !important;
}
.rc-form-date-picker__popover [data-slot='calendar-nav-button']:hover,
.rc-form-date-picker__popover [data-slot='calendar-cell']:hover,
.rc-form-date-picker__popover [data-slot='calendar-cell'][data-hovered='true'] {
background: var(--default) !important;
color: var(--rc-text-primary) !important;
}
.rc-form-date-picker__popover
[data-slot='calendar-cell'][data-selected='true'],
.rc-form-date-picker__popover
[data-slot='calendar-cell'][aria-selected='true'] {
background: var(--rc-brand-primary-soft) !important;
color: var(--accent-soft-foreground) !important;
}
.rc-form-date-picker__popover [data-slot='calendar-cell'][data-focused='true'],
.rc-form-date-picker__popover [data-slot='calendar-cell'][data-focus-visible='true'] {
outline: 2px solid var(--rc-brand-primary) !important;
outline-offset: -2px;
}
.rc-form-date-picker__popover [data-slot='calendar-header-cell'] {
color: var(--rc-text-muted) !important;
}
.rc-form-date-picker__time-panel {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
margin-top: 0.25rem;
padding: 0.75rem 0.75rem 0.625rem;
border-top: 1px solid var(--rc-border-default);
}
.rc-form-date-picker__time-label {
color: var(--rc-text-secondary);
font-size: var(--rc-text-sm);
font-weight: var(--rc-font-medium);
}
.rc-form-date-picker__time-field {
width: 8.5rem;
min-width: 0;
}
.rc-form-date-picker__time-group {
display: flex !important;
min-height: 2.25rem;
align-items: center !important;
border: 1px solid var(--rc-field-border) !important;
border-radius: 0.5rem !important;
background: var(--rc-field-background) !important;
box-shadow: var(--field-shadow) !important;
color: var(--rc-text-primary) !important;
}
.rc-form-date-picker__time-group:hover {
border-color: var(--rc-field-border-hover) !important;
}
.rc-form-date-picker__time-field[data-focus-within='true']
.rc-form-date-picker__time-group {
border-color: var(--rc-field-border-focus) !important;
box-shadow: 0 0 0 2px
color-mix(in srgb, var(--rc-brand-primary) 22%, transparent),
var(--field-shadow) !important;
}
.rc-form-date-picker__time-input {
flex: 1 1 auto !important;
min-width: 0;
padding-inline: 0.75rem !important;
}
.rc-form-date-picker__segment[data-focused='true'],
.rc-form-date-picker__segment[data-focus-visible='true'] {
border-radius: 0.25rem !important;
background: var(--rc-brand-primary-soft) !important;
color: var(--rc-brand-primary) !important;
outline: none !important;
}
.rc-dropdown-option {
min-height: 2.25rem;
border-radius: 0.5rem !important;
color: var(--rc-text-secondary) !important;
font-size: var(--rc-text-sm);
font-weight: var(--rc-font-regular);
}
.rc-dropdown-option:hover,
.rc-dropdown-option[data-focus='true'],
.rc-dropdown-option[data-focused='true'] {
background: var(--default) !important;
color: var(--rc-text-primary) !important;
}
.rc-filter-select__listbox,
.rc-filter-options-popover {
max-height: 18rem;
overflow-y: auto;
overscroll-behavior: contain;
scrollbar-gutter: stable;
}
.rc-filter-options-popover__status {
display: flex;
min-height: 2.25rem;
align-items: center;
justify-content: center;
padding: 0.5rem 0.75rem;
color: var(--rc-text-muted);
font-size: var(--rc-text-sm);
line-height: 1.25rem;
text-align: center;
}
.rc-filter-options-popover__status--error {
color: var(--rc-color-danger);
}
.rc-filter-option,
.rc-filter-select__option {
display: flex;
width: 100%;
min-width: 0;
min-height: 2.25rem;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
border: 0 !important;
border-radius: 0.5rem !important;
background: transparent !important;
padding: 0.5rem 0.625rem !important;
color: var(--rc-text-secondary) !important;
box-shadow: none !important;
font-size: var(--rc-text-sm);
font-weight: var(--rc-font-regular);
line-height: 1.25rem;
outline: none;
cursor: default;
}
.rc-filter-option:hover,
.rc-filter-option--focused,
.rc-filter-select__option[data-focused='true'],
.rc-filter-select__option[data-hovered='true'] {
background: var(--default) !important;
color: var(--rc-text-primary) !important;
}
.rc-filter-option--selected,
.rc-filter-option[data-selected='true'],
.rc-filter-select__option[data-selected='true'] {
background: var(--rc-brand-primary-soft) !important;
color: var(--accent-soft-foreground) !important;
}
.rc-filter-option--selected.rc-filter-option--focused,
.rc-filter-option[data-selected='true']:hover,
.rc-filter-select__option[data-selected='true'][data-focused='true'] {
background: var(--rc-brand-primary-soft-hover) !important;
color: var(--accent-soft-foreground) !important;
}
.rc-filter-select__option-label {
display: block;
min-width: 0;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: inherit;
font: inherit;
}
.rc-filter-select__option-indicator {
width: 1rem;
height: 1rem;
flex: none;
color: var(--rc-brand-primary);
}
.rc-filter-select__empty {
padding: 1.25rem 0.75rem;
color: var(--rc-text-muted);
font-size: var(--rc-text-sm);
text-align: center;
}
.rc-filter-option__description {
color: var(--rc-text-muted) !important;
}
.rc-filter-option__description--selected,
.rc-filter-option__selected-label {
color: var(--rc-brand-primary) !important;
}
.rc-filter-option__selected-label {
background: var(--rc-brand-primary-soft);
}
.resource-filter-multi-select__popover {
width: max(var(--trigger-width, var(--button-width, 16rem)), 16rem);
min-width: min(
var(--trigger-width, var(--button-width, 16rem)),
calc(100vw - 1.5rem)
);
}
.rc-filter-reset-button.button {
border-color: var(--rc-border-default);
color: var(--rc-text-muted);
}
.rc-filter-reset-button.button:hover,
.rc-filter-reset-button.button[data-hovered='true'] {
border-color: var(--rc-border-strong);
color: var(--rc-text-primary);
}
.rc-filter-query-button.button {
border: 0 !important;
background: var(--rc-brand-primary) !important;
color: white;
box-shadow: none !important;
}
.rc-filter-query-button.button:hover,
.rc-filter-query-button.button:active,
.rc-filter-query-button.button[data-hovered='true'],
.rc-filter-query-button.button[data-pressed='true'] {
background: var(--rc-brand-primary-hover) !important;
}
.rc-filter-toggle-button.button {
color: var(--rc-text-secondary);
}
.rc-filter-toggle-button.button:hover,
.rc-filter-toggle-button.button[data-hovered='true'] {
color: var(--rc-text-primary);
}
@media (max-width: 767px) {
.rc-filter-card.card {
padding: var(--rc-space-4);
}
.rc-filter-actions {
width: 100%;
}
.rc-filter-query-button.button {
flex: 1 1 auto;
}
}
.pm-data-grid-wrap {
position: relative;
min-width: 0;
min-height: 15rem;
overflow: hidden;
border: 0;
border-radius: var(--rc-data-grid-radius);
background: transparent;
clip-path: inset(0 round var(--rc-data-grid-radius));
}
.pm-data-grid-loading {
position: absolute;
inset: 0;
z-index: 12;
display: grid;
place-items: center;
background: color-mix(in oklab, var(--rc-bg-surface) 72%, transparent);
}
/* FullCalendar ships light-only defaults, so bridge them to HeroUI semantics. */
.rc-app-shell .fc {
--fc-page-bg-color: var(--rc-bg-surface);
--fc-neutral-bg-color: var(--rc-bg-subtle);
--fc-neutral-text-color: var(--rc-text-muted);
--fc-border-color: var(--rc-border-default);
--fc-list-event-hover-bg-color: var(--default);
--fc-more-link-bg-color: var(--default);
--fc-more-link-text-color: var(--rc-text-secondary);
--fc-non-business-color: color-mix(in oklab, var(--default) 55%, transparent);
--fc-highlight-color: var(--rc-brand-primary-soft);
--fc-today-bg-color: color-mix(
in oklab,
var(--rc-color-warning) 12%,
transparent
);
color: var(--rc-text-secondary);
}
.rc-app-shell .fc :is(.fc-col-header-cell, .fc-list-day-cushion) {
background: var(--rc-bg-subtle);
color: var(--rc-text-muted);
}
.rc-app-shell .fc :is(.fc-daygrid-day, .fc-list, .fc-list-table td) {
background-color: var(--rc-bg-surface);
}
.rc-app-shell .fc .fc-daygrid-day.fc-day-today {
background-color: var(--fc-today-bg-color);
}
.rc-app-shell .fc .fc-popover {
background: var(--overlay);
border-color: var(--rc-border-default);
color: var(--rc-text-secondary);
box-shadow: var(--rc-shadow-overlay);
}
.pm-data-grid.table-root,
.pm-data-grid .table-root,
.pm-data-grid-scroll,
.pm-data-grid .table__resizable-container {
border: 0 !important;
outline: 0 !important;
border-radius: var(--rc-data-grid-radius);
background: transparent !important;
box-shadow: none !important;
}
.pm-data-grid.table-root,
.pm-data-grid .table-root {
min-height: 15rem;
padding: 0 !important;
overflow: hidden;
}
.pm-data-grid-scroll {
width: 100%;
min-height: 15rem;
overflow: auto;
}
.pm-data-grid .table__resizable-container {
width: 100%;
min-width: 0;
min-height: 15rem;
overflow: visible;
}
.pm-data-grid-wrap--populated,
.pm-data-grid-wrap--populated .pm-data-grid.table-root,
.pm-data-grid-wrap--populated .pm-data-grid .table-root,
.pm-data-grid-wrap--populated .pm-data-grid-scroll,
.pm-data-grid-wrap--populated .pm-data-grid .table__resizable-container {
min-height: 0;
}
.pm-data-grid-content,
.pm-data-grid .table__content {
width: 100%;
background: var(--rc-data-grid-header-background) !important;
}
.pm-data-grid .table__content,
.pm-data-grid .table__header {
background: transparent !important;
}
.pm-data-grid .table__content {
background: var(--rc-data-grid-header-background) !important;
}
.pm-data-grid .table__header {
position: sticky;
top: 0;
z-index: 7;
isolation: isolate;
}
.pm-data-grid .table__header > tr > :first-child {
border-start-start-radius: var(--rc-data-grid-radius);
}
.pm-data-grid .table__header > tr > :last-child {
border-start-end-radius: var(--rc-data-grid-radius);
}
.pm-data-grid .table__body > .table__row:first-child > :first-child,
.pm-data-grid .table__body > .table__row:first-child > :last-child {
border-start-start-radius: 0 !important;
border-start-end-radius: 0 !important;
}
.pm-data-grid .table__body > .table__row:last-child > :first-child {
border-end-start-radius: var(--rc-data-grid-radius) !important;
}
.pm-data-grid .table__body > .table__row:last-child > :last-child {
border-end-end-radius: var(--rc-data-grid-radius) !important;
}
.pm-data-grid .table__body > .table__row:last-child > * {
border-bottom: 0 !important;
}
.pm-data-grid-content--wide {
min-width: max(100%, var(--pm-data-grid-content-min-width, 1080px));
}
.pm-data-grid .table__column,
.pm-data-grid .data-grid__selection-column,
.pm-data-grid .data-grid__drag-handle-column {
position: sticky;
top: 0;
z-index: 6;
height: var(--rc-data-grid-header-height);
background: var(--rc-data-grid-header-background);
color: var(--rc-data-grid-header-color);
font-size: var(--rc-data-grid-header-font-size);
font-weight: var(--rc-data-grid-header-font-weight);
line-height: var(--rc-data-grid-header-line-height);
letter-spacing: 0px;
}
.pm-data-grid .table__cell,
.pm-data-grid .data-grid__selection-cell,
.pm-data-grid .data-grid__drag-handle-cell {
padding-block: 0.625rem;
border-bottom: 1px solid
color-mix(in oklab, var(--separator) 50%, transparent) !important;
background: var(--rc-bg-surface);
color: var(--rc-data-grid-body-color);
font-size: var(--rc-data-grid-body-font-size);
font-weight: var(--rc-data-grid-body-font-weight);
line-height: var(--rc-data-grid-body-line-height);
letter-spacing: 0px;
font-synthesis: none;
white-space: nowrap;
}
.pm-data-grid .table__cell :where(.font-semibold, .font-bold, .font-extrabold),
.pm-data-grid
.data-grid__selection-cell
:where(.font-semibold, .font-bold, .font-extrabold) {
font-weight: var(--rc-font-medium) !important;
}
.pm-data-grid
.table__cell
:where(.text-zinc-950, .text-zinc-900, .text-zinc-800, .text-zinc-700),
.pm-data-grid
.data-grid__selection-cell
:where(.text-zinc-950, .text-zinc-900, .text-zinc-800, .text-zinc-700) {
color: var(--rc-data-grid-body-color) !important;
}
.pm-data-grid
.table__cell
:where(.text-zinc-600, .text-zinc-500, .text-zinc-400),
.pm-data-grid
.data-grid__selection-cell
:where(.text-zinc-600, .text-zinc-500, .text-zinc-400) {
color: var(--rc-data-grid-secondary-color) !important;
}
.pm-data-grid .pm-data-grid-primary {
color: var(--rc-data-grid-primary-color);
font-size: var(--rc-data-grid-primary-font-size);
font-weight: var(--rc-data-grid-primary-font-weight) !important;
line-height: var(--rc-data-grid-primary-line-height);
}
.pm-data-grid .pm-data-grid-primary-action.button {
width: 100%;
min-width: 0;
max-width: 100%;
height: auto;
min-height: 1.35rem;
justify-content: flex-start;
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
color: var(--rc-data-grid-primary-color);
box-shadow: none;
line-height: 1.35rem;
text-align: left;
text-decoration: none;
transition: color var(--rc-motion-fast) var(--rc-ease-standard);
}
.pm-data-grid .pm-data-grid-primary-action.button:hover,
.pm-data-grid .pm-data-grid-primary-action.button[data-hovered='true'],
.pm-data-grid .pm-data-grid-primary-action.button[data-focus-visible='true'] {
background: transparent;
color: var(--rc-brand-primary);
text-decoration: none;
}
.pm-data-grid .pm-data-grid-primary-action .pm-data-grid-primary {
color: currentColor;
}
.pm-data-grid
.pm-data-grid-primary
:where(.text-zinc-950, .text-zinc-900, .text-zinc-800, .text-zinc-700) {
color: var(--rc-data-grid-primary-color) !important;
}
.pm-data-grid .pm-data-grid-text {
color: var(--rc-data-grid-body-color);
font-size: var(--rc-data-grid-body-font-size);
font-weight: var(--rc-data-grid-body-font-weight);
line-height: var(--rc-data-grid-body-line-height);
}
.pm-data-grid .pm-data-grid-secondary {
color: var(--rc-data-grid-secondary-color) !important;
font-size: var(--rc-data-grid-secondary-font-size);
font-weight: var(--rc-data-grid-secondary-font-weight) !important;
line-height: var(--rc-data-grid-secondary-line-height);
}
.pm-data-grid .pm-data-grid-text-pair {
display: flex;
min-width: 0;
max-width: 100%;
flex-direction: column;
gap: var(--rc-data-grid-text-pair-gap);
}
.pm-data-grid .pm-data-grid-number {
font-variant-numeric: tabular-nums;
font-weight: var(--rc-font-regular);
}
.pm-data-grid .pm-data-grid-number--emphasis {
color: var(--rc-data-grid-primary-color);
font-variant-numeric: tabular-nums;
font-weight: var(--rc-font-medium);
}
.pm-data-grid .table__row:hover .table__cell,
.pm-data-grid .table__row[data-hovered='true'] .table__cell,
.pm-data-grid .table__row:hover [data-pinned],
.pm-data-grid .table__row[data-hovered='true'] [data-pinned] {
background: var(--rc-data-grid-row-hover-background) !important;
}
.pm-data-grid [data-pinned] {
background: var(--rc-bg-surface) !important;
}
.pm-data-grid [data-pinned].table__column,
.pm-data-grid [data-pinned].data-grid__selection-column,
.pm-data-grid [data-pinned].data-grid__drag-handle-column {
z-index: 8;
background: var(--rc-data-grid-header-background) !important;
}
.pm-data-grid .table__cell > *,
.pm-data-grid .data-grid__selection-cell > * {
min-width: 0;
max-width: 100%;
}
.pm-data-grid .table__cell > .sticky {
position: static !important;
width: 100% !important;
min-width: 0 !important;
max-width: 100%;
background: transparent !important;
box-shadow: none !important;
}
.pm-data-grid .icon-actions {
justify-content: flex-start;
gap: 0.125rem;
padding-inline: 0;
}
.pm-data-grid .icon-actions > button,
.pm-data-grid .icon-actions > .group > button,
.pm-data-grid .icon-actions > .relative > button {
width: var(--rc-data-grid-action-size);
height: var(--rc-data-grid-action-size);
min-width: var(--rc-data-grid-action-size);
max-width: var(--rc-data-grid-action-size);
flex: 0 0 var(--rc-data-grid-action-size);
border-radius: var(--rc-data-grid-action-radius) !important;
}
.pm-table-action {
width: var(--rc-data-grid-action-size);
height: var(--rc-data-grid-action-size);
min-width: var(--rc-data-grid-action-size);
max-width: var(--rc-data-grid-action-size);
flex: 0 0 var(--rc-data-grid-action-size);
border: 0 !important;
border-radius: var(--rc-data-grid-action-radius) !important;
box-shadow: none !important;
}
.pm-table-action--default {
color: var(--rc-text-primary);
background: var(--default);
}
.pm-table-action--default:hover {
background: var(--default-hover);
}
.pm-table-action--primary {
color: var(--rc-brand-primary);
background: var(--rc-brand-primary-soft);
}
.pm-table-action--approve {
color: var(--rc-color-success);
background: color-mix(in srgb, var(--rc-color-success) 10%, transparent);
}
.pm-table-action--warning {
color: var(--rc-color-warning);
background: color-mix(in srgb, var(--rc-color-warning) 10%, transparent);
}
.pm-table-action--danger {
color: var(--rc-color-danger);
background: color-mix(in srgb, var(--rc-color-danger) 10%, transparent);
}
.pm-data-grid-truncate {
display: block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pm-data-grid-chip {
display: inline-flex;
max-width: 100%;
min-width: 0;
flex: 0 0 auto;
align-items: center;
vertical-align: middle;
white-space: nowrap;
font-size: var(--rc-data-grid-chip-font-size) !important;
font-weight: var(--rc-data-grid-chip-font-weight) !important;
line-height: var(--rc-data-grid-chip-line-height);
letter-spacing: 0px;
}
.pm-data-grid-chip__label {
min-width: 0;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.rc-dialog-backdrop {
width: 100%;
height: 100%;
background: var(--backdrop);
-webkit-backdrop-filter: blur(var(--rc-modal-backdrop-blur));
backdrop-filter: blur(var(--rc-modal-backdrop-blur));
}
.rc-dialog-panel {
--rc-modal-current-width: var(--rc-modal-width-lg);
position: relative;
display: flex;
width: min(calc(100vw - 5rem), var(--rc-modal-current-width)) !important;
max-width: calc(100vw - 5rem) !important;
max-height: calc(100dvh - 2rem);
flex-direction: column;
padding: var(--rc-modal-padding);
overflow: hidden;
border: 0 !important;
border-radius: var(--rc-modal-radius) !important;
background: var(--overlay);
box-shadow: var(--overlay-shadow);
outline: none;
}
.rc-dialog-panel--xs {
--rc-modal-current-width: var(--rc-modal-width-xs);
}
.rc-dialog-panel--sm {
--rc-modal-current-width: var(--rc-modal-width-sm);
}
.rc-dialog-panel--md {
--rc-modal-current-width: var(--rc-modal-width-md);
}
.rc-dialog-panel--lg {
--rc-modal-current-width: var(--rc-modal-width-lg);
}
.rc-dialog-panel--xl {
--rc-modal-current-width: var(--rc-modal-width-xl);
}
.rc-dialog-panel--2xl {
--rc-modal-current-width: var(--rc-modal-width-2xl);
}
.rc-dialog-panel--3xl {
--rc-modal-current-width: var(--rc-modal-width-3xl);
}
.rc-dialog-panel--4xl {
--rc-modal-current-width: var(--rc-modal-width-4xl);
}
.rc-dialog-panel--5xl {
--rc-modal-current-width: var(--rc-modal-width-5xl);
}
.rc-dialog-panel > form {
display: flex;
min-height: 0;
max-height: 100%;
flex-direction: column;
}
.rc-dialog-title {
margin: 0;
padding-right: 2.5rem;
color: var(--rc-text-primary);
font-size: var(--rc-modal-title-size);
font-weight: var(--rc-modal-title-weight);
line-height: var(--rc-modal-title-line-height);
letter-spacing: 0;
text-wrap: balance;
}
.rc-dialog-description {
margin-top: var(--rc-modal-section-gap);
padding-right: 2.5rem;
color: var(--rc-text-muted);
font-size: var(--rc-text-sm);
font-weight: var(--rc-font-regular);
line-height: var(--rc-line-normal);
text-wrap: pretty;
}
.rc-dialog-body {
min-height: 0;
min-width: 0;
flex: 1;
margin-top: var(--rc-modal-section-gap);
margin-right: -0.1875rem;
margin-left: -0.1875rem;
padding: 0.1875rem;
overflow-y: auto;
overscroll-behavior: contain;
color: var(--rc-text-secondary);
font-size: var(--rc-text-sm);
line-height: 1.43;
}
.rc-dialog-actions {
display: flex;
flex: none;
flex-direction: row;
align-items: center;
justify-content: flex-end;
gap: var(--rc-modal-footer-gap);
margin-top: var(--rc-modal-footer-margin);
}
.rc-dialog-actions .rc-button--secondary:not(.rc-button--danger) {
border: 1px solid var(--rc-border-default) !important;
background: var(--rc-bg-surface);
color: var(--rc-text-primary);
box-shadow: none;
}
.rc-dialog-actions .rc-button--secondary:not(.rc-button--danger):hover,
.rc-dialog-actions .rc-button--secondary:not(.rc-button--danger)[data-hover],
.rc-dialog-actions
.rc-button--secondary:not(.rc-button--danger)[data-hover='true'] {
border-color: color-mix(
in oklab,
var(--rc-border-default) 72%,
var(--rc-text-muted)
);
background: var(--default);
}
.rc-dialog-close {
top: var(--rc-modal-close-inset);
right: var(--rc-modal-close-inset);
z-index: 2;
width: var(--rc-modal-close-size);
height: var(--rc-modal-close-size);
min-width: var(--rc-modal-close-size);
padding: 0.25rem;
border: 0;
background: var(--default);
color: var(--rc-text-muted);
box-shadow: none;
transition: color var(--rc-motion-fast) var(--rc-ease-standard),
background-color var(--rc-motion-fast) var(--rc-ease-standard);
}
/* Explicit close controls already live inside the inset title wrapper. */
.rc-dialog-close--explicit {
top: 0;
right: 0;
}
.rc-dialog-panel > .rc-dialog-close--explicit {
top: var(--rc-modal-close-inset);
right: var(--rc-modal-close-inset);
}
.rc-dialog-panel:has(.rc-dialog-close--explicit) > .rc-dialog-close--automatic {
display: none;
}
.rc-dialog-close:hover,
.rc-dialog-close:focus-visible {
background: var(--default-hover);
color: var(--rc-text-primary);
outline: none;
}
.rc-dialog-close .anticon,
.rc-dialog-close .anticon > svg {
display: block;
width: var(--rc-modal-close-icon-size);
height: var(--rc-modal-close-icon-size);
margin: 0;
}
.rc-dialog-close:focus-visible {
box-shadow: 0 0 0 2px var(--rc-brand-primary-soft-hover);
}
.data-grid-empty-state {
min-height: 12rem;
padding: var(--rc-space-8) var(--rc-space-4);
}
.data-grid-empty-loading {
display: flex;
min-height: 18rem;
align-items: center;
justify-content: center;
}
.pm-data-grid-native-empty-fallback {
display: none !important;
}
.pm-data-grid-wrap--empty .data-grid__empty-state,
.pm-data-grid-wrap--empty [data-slot='data-grid-empty-state'] {
display: none !important;
visibility: hidden !important;
}
.pm-data-grid-empty-overlay {
position: absolute;
inset: var(--rc-data-grid-header-height) 0 0;
z-index: 10;
display: grid;
place-items: center;
min-height: 12rem;
padding: var(--rc-space-4);
overflow: hidden;
border-radius: var(--rc-data-grid-radius);
background: var(--surface);
pointer-events: none;
}
.pm-data-grid-empty-overlay .empty-state {
min-height: 0;
padding: 0;
pointer-events: auto;
}
.data-grid-empty-illustration {
position: relative;
width: 7.5rem;
height: 5.75rem;
color: var(--rc-text-muted);
}
.data-grid-empty-illustration-back,
.data-grid-empty-illustration-front,
.data-grid-empty-illustration-badge {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
}
.data-grid-empty-illustration-back {
inset: 0.25rem 1.375rem 1.5rem 0.75rem;
justify-content: flex-start;
padding-left: 1rem;
border: 1px solid
color-mix(in oklab, var(--rc-brand-primary) 14%, var(--border));
border-radius: 0.875rem;
background: color-mix(in oklab, var(--rc-brand-primary) 6%, var(--surface));
color: color-mix(in oklab, var(--rc-brand-primary) 48%, var(--muted));
transform: rotate(-5deg);
}
.data-grid-empty-illustration-front {
inset: 1rem 0.5rem 0.25rem 1.5rem;
justify-content: flex-start;
gap: 0.625rem;
padding-inline: 0.875rem;
border: 1px solid var(--border);
border-radius: 1rem;
background: var(--surface);
color: var(--rc-brand-primary);
box-shadow: var(--surface-shadow);
}
.data-grid-empty-illustration-lines {
display: flex;
width: 2.5rem;
flex-direction: column;
gap: 0.375rem;
}
.data-grid-empty-illustration-lines i {
display: block;
height: 0.25rem;
border-radius: 999px;
background: var(--default);
}
.data-grid-empty-illustration-lines i:nth-child(2) {
width: 75%;
}
.data-grid-empty-illustration-lines i:nth-child(3) {
width: 50%;
}
.data-grid-empty-illustration-badge {
right: 0;
bottom: 0;
width: 2rem;
height: 2rem;
border: 2px solid var(--surface);
border-radius: 50%;
background: var(--default);
color: var(--foreground);
box-shadow: var(--surface-shadow);
}
.pm-pagination-bar {
flex: none;
display: flex;
min-width: 0;
align-items: center;
justify-content: space-between;
gap: 1rem;
margin-top: var(--rc-list-pagination-gap) !important;
padding-top: 0;
border-top: 0;
}
.space-y-6 > :has(+ .pm-pagination-bar),
.space-y-5 > :has(+ .pm-pagination-bar),
.space-y-4 > :has(+ .pm-pagination-bar),
.space-y-3 > :has(+ .pm-pagination-bar),
.space-y-2 > :has(+ .pm-pagination-bar) {
margin-bottom: 0 !important;
}
.rc-page-heading {
display: flex;
min-width: 0;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: var(--rc-space-3);
}
.rc-page-tabs,
.rc-page-controls {
min-width: 0;
}
.rc-page-controls {
display: flex;
margin-left: auto;
align-items: center;
gap: var(--rc-space-3);
}
.rc-page-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
gap: var(--rc-space-2);
}
.rc-page-toolbar {
min-width: 0;
}
.filter-tabs-wrap {
max-width: 100%;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none;
}
.filter-tabs-wrap::-webkit-scrollbar {
display: none;
}
.filter-tabs-wrap .tabs__list-container {
border: 0 !important;
border-radius: calc(var(--radius) * 3) !important;
background: var(--default) !important;
}
.filter-tabs-wrap .tabs__list {
gap: 0;
padding: 0.25rem !important;
}
.filter-tabs-wrap .tabs__tab {
height: 2rem;
padding-inline: 1rem;
border-radius: calc(var(--radius) * 3) !important;
color: var(--muted);
white-space: nowrap;
}
.filter-tabs-wrap .tabs__tab[data-selected='true'] {
color: var(--foreground) !important;
}
.filter-tabs-wrap .tabs__indicator {
inset: 0 !important;
width: 100%;
height: 100% !important;
border-radius: calc(var(--radius) * 3) !important;
background: var(--segment) !important;
box-shadow: var(--surface-shadow) !important;
}
.rc-segmented-control {
display: inline-flex;
min-width: max-content;
align-items: center;
padding: var(--rc-segment-track-padding);
border: 1px solid var(--rc-border-default);
border-radius: var(--rc-segment-track-radius);
background: color-mix(in oklab, var(--default) 78%, var(--rc-bg-surface));
box-shadow: inset 0 0 0 1px
color-mix(in oklab, var(--rc-bg-surface) 82%, transparent),
0 1px 2px rgb(15 23 42 / 4%);
}
.rc-segmented-control > .tabs__list-container,
.rc-segmented-control .tabs__list-container {
border: 0 !important;
border-radius: calc(var(--rc-segment-track-radius) - var(--rc-segment-track-padding)) !important;
background: transparent !important;
box-shadow: none !important;
}
.rc-segmented-control .tabs__list {
min-width: max-content !important;
padding: 0 !important;
gap: 0 !important;
}
.rc-segmented-control .tabs__separator {
display: none !important;
}
.rc-segmented-control__item {
display: inline-flex;
height: var(--rc-segment-item-height);
flex: none;
align-items: center;
justify-content: center;
gap: var(--rc-segment-item-gap);
padding-inline: var(--rc-segment-item-padding-inline);
border: 0 !important;
border-radius: var(--rc-segment-item-radius);
outline: 0 !important;
background: transparent !important;
color: var(--rc-text-secondary) !important;
font-size: var(--rc-segment-font-size);
font-weight: var(--rc-segment-font-weight);
line-height: var(--rc-segment-line-height);
letter-spacing: 0;
box-shadow: none !important;
white-space: nowrap;
transition: color var(--rc-motion-fast) var(--rc-ease-standard),
background-color var(--rc-motion-fast) var(--rc-ease-standard),
box-shadow var(--rc-motion-fast) var(--rc-ease-standard);
}
.rc-segmented-control__item:not(.is-active):not([data-selected='true']) {
border: 0 !important;
outline: 0 !important;
background: transparent !important;
box-shadow: none !important;
}
.rc-segmented-control__item:hover:not(:disabled) {
background: transparent !important;
color: var(--rc-text-primary) !important;
box-shadow: none !important;
}
.rc-segmented-control__item.is-active,
.rc-segmented-control__item[data-selected='true'] {
background: var(--rc-bg-surface) !important;
color: var(--rc-text-primary) !important;
font-weight: var(--rc-font-medium) !important;
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--rc-border-default) 76%, transparent),
0 1px 2px rgb(15 23 42 / 10%) !important;
}
.rc-segmented-control__item[data-selected='true'] .tabs__indicator {
inset: 0 !important;
width: 100% !important;
height: 100% !important;
border-radius: var(--rc-segment-item-radius) !important;
background: var(--rc-bg-surface) !important;
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--rc-border-default) 76%, transparent),
0 1px 2px rgb(15 23 42 / 10%) !important;
}
.rc-segmented-control__item:disabled {
cursor: not-allowed;
opacity: 0.5;
}
.rc-segmented-control__count {
padding: 0.125rem 0.375rem;
border-radius: 999px;
background: color-mix(in oklab, var(--default) 78%, transparent);
color: inherit;
font-size: var(--rc-text-xs);
font-weight: var(--rc-font-medium);
line-height: 1rem;
}
.rc-title-info-trigger,
.rc-title-info-trigger.button {
display: inline-flex !important;
width: 1rem !important;
min-width: 1rem !important;
height: 1rem !important;
min-height: 1rem !important;
flex: none;
align-items: center !important;
justify-content: center !important;
padding: 0 !important;
border: 0 !important;
border-radius: 999px !important;
background: transparent !important;
box-shadow: none !important;
color: var(--rc-text-muted) !important;
line-height: 1 !important;
transform: none !important;
}
.rc-title-info-trigger:hover,
.rc-title-info-trigger:focus-visible,
.rc-title-info-trigger[data-hovered='true'],
.rc-title-info-trigger.button:hover,
.rc-title-info-trigger.button:focus-visible,
.rc-title-info-trigger.button[data-hovered='true'] {
background: transparent !important;
color: var(--rc-text-secondary) !important;
}
.rc-title-info-trigger .anticon,
.rc-title-info-trigger .anticon > svg,
.rc-title-info-trigger > svg {
display: block;
width: 0.875rem !important;
height: 0.875rem !important;
margin: 0 !important;
font-size: 0.875rem !important;
}
.rc-tab-count {
position: relative;
z-index: 1;
min-width: 1.25rem;
padding-inline: 0.375rem;
border-radius: 999px;
background: var(--rc-brand-primary-soft);
color: var(--rc-brand-primary);
font-size: var(--rc-text-xs);
line-height: 1.25rem;
text-align: center;
}
.pm-pagination-bar > .pagination {
min-width: 0;
flex: 1;
width: 100%;
}
.pm-pagination-bar .pagination__summary {
color: var(--rc-text-secondary);
font-size: var(--rc-text-xs);
white-space: nowrap;
}
.pm-pagination-bar .pagination__content {
justify-content: flex-end;
}
.pm-pagination-controls {
display: flex;
flex: none;
align-items: center;
gap: 0.5rem;
}
.pm-pagination-page-size-select {
width: auto;
min-width: 10rem;
max-width: 100%;
}
.pm-pagination-page-size-select .select__trigger {
height: 2.25rem;
min-height: 2.25rem;
padding-inline: 1rem;
border: 0 !important;
border-radius: var(--rc-toolbar-filter-radius) !important;
background: var(--rc-toolbar-filter-background) !important;
box-shadow: none !important;
}
.pm-pagination-page-size-select .select__trigger:hover,
.pm-pagination-page-size-select .select__trigger[data-hovered='true'] {
background: var(--rc-toolbar-filter-background-hover) !important;
}
.pm-pagination-page-size-copy {
display: flex;
min-width: 0;
flex: 1;
align-items: center;
gap: 0.375rem;
}
.pm-pagination-page-size-label,
.pm-pagination-page-size-value {
color: var(--foreground) !important;
font-size: 0.8125rem;
font-weight: 500;
line-height: 1rem;
white-space: nowrap;
}
.pm-pagination-page-size-label::after {
content: ':';
}
.pm-pagination-page-size-select .select__indicator,
.pm-pagination-page-size-indicator {
display: inline-grid !important;
width: 1rem;
height: 1rem;
flex: none;
place-items: center;
color: var(--rc-toolbar-filter-icon);
font-size: 0.875rem;
line-height: 1;
}
.pm-pagination-page-size-indicator :is(svg, .anticon),
.pm-pagination-page-size-indicator svg,
.pm-pagination-page-size-select .select__indicator svg {
display: block !important;
width: 1em !important;
height: 1em !important;
margin: 0 !important;
color: currentColor !important;
fill: currentColor !important;
}
@media (max-width: 768px) {
.rc-page-heading,
.rc-page-controls,
.rc-page-actions {
width: 100%;
}
.rc-page-actions {
justify-content: flex-start;
}
.pm-pagination-bar {
align-items: stretch;
flex-direction: column;
}
.pm-pagination-bar .pagination__content {
max-width: 100%;
justify-content: flex-start;
overflow-x: auto;
scrollbar-width: none;
}
.pm-pagination-bar .pagination__content::-webkit-scrollbar {
display: none;
}
.pm-pagination-bar .pagination__summary {
width: 100%;
white-space: normal;
}
.pm-pagination-controls {
justify-content: flex-start;
}
}
@media (max-width: 767px) {
.external-project-referral-grid .pm-data-grid [data-pinned='start'] {
position: static !important;
left: auto !important;
z-index: auto !important;
}
.rc-dialog-panel {
width: calc(100vw - 2rem) !important;
max-width: calc(100vw - 2rem) !important;
max-height: calc(100dvh - 2rem);
}
.rc-dialog-actions {
flex-direction: column-reverse;
align-items: stretch;
}
.rc-dialog-actions > * {
width: 100%;
}
}
/* Xinyuan legacy brand-color bridge: copied sky/blue/cyan/rose/red/pink utilities render as Xinyuan red. */
body
:is(
.bg-sky-50,
.bg-sky-100,
.bg-blue-50,
.bg-blue-100,
.bg-cyan-50,
.bg-cyan-100,
.bg-rose-50,
.bg-rose-100,
.bg-red-50,
.bg-red-100,
.bg-pink-50,
.bg-pink-100,
[class*='bg-sky-50/'],
[class*='bg-sky-100/'],
[class*='bg-blue-50/'],
[class*='bg-blue-100/'],
[class*='bg-cyan-50/'],
[class*='bg-cyan-100/'],
[class*='bg-rose-50/'],
[class*='bg-rose-100/'],
[class*='bg-red-50/'],
[class*='bg-red-100/'],
[class*='bg-pink-50/'],
[class*='bg-pink-100/']
) {
background-color: var(--rc-brand-primary-soft) !important;
}
body
:is(
.border-sky-100,
.border-sky-200,
.border-sky-300,
.border-sky-400,
.border-blue-100,
.border-blue-200,
.border-blue-300,
.border-blue-400,
.border-cyan-100,
.border-cyan-200,
.border-cyan-300,
.border-cyan-400,
.border-rose-100,
.border-rose-200,
.border-rose-300,
.border-rose-400,
.border-red-100,
.border-red-200,
.border-red-300,
.border-red-400,
.border-pink-100,
.border-pink-200,
.border-pink-300,
.border-pink-400
) {
border-color: color-mix(in srgb, var(--rc-brand-primary) 28%, transparent) !important;
}
body
:is(
.text-sky-500,
.text-sky-600,
.text-sky-700,
.text-sky-800,
.text-blue-500,
.text-blue-600,
.text-blue-700,
.text-blue-800,
.text-cyan-500,
.text-cyan-600,
.text-cyan-700,
.text-cyan-800,
.text-rose-500,
.text-rose-600,
.text-rose-700,
.text-rose-800,
.text-red-500,
.text-red-600,
.text-red-700,
.text-red-800,
.text-pink-500,
.text-pink-600,
.text-pink-700,
.text-pink-800
) {
color: var(--rc-brand-primary) !important;
}
body
:is(
.rc-status-chip--info,
.rc-tone-surface--info,
.pm-data-grid-chip.rc-status-chip--info
) {
border-color: var(--rc-color-info-border) !important;
background: var(--rc-color-info-soft) !important;
color: var(--rc-color-info) !important;
}
body
:is(
.focus\:border-sky-400:focus,
.focus\:border-blue-400:focus,
.focus\:border-cyan-400:focus,
.focus\:border-rose-400:focus,
.focus\:border-red-400:focus,
.focus\:border-pink-400:focus
) {
border-color: var(--rc-brand-primary) !important;
}
body
:is(
.ring-sky-300,
.ring-blue-300,
.ring-cyan-300,
.ring-rose-300,
.ring-red-300,
.ring-pink-300
) {
--tw-ring-color: color-mix(in srgb, var(--rc-brand-primary) 28%, transparent) !important;
}
.app-switcher-card[data-current='true'] {
border-color: color-mix(
in oklab,
var(--rc-brand-primary) 34%,
var(--rc-border-default)
) !important;
background: var(--rc-brand-primary-soft) !important;
}
.app-switcher-card__logo,
.app-switcher-card__initial {
width: 2.25rem;
height: 2.25rem;
flex: 0 0 2.25rem;
border-radius: var(--rc-radius-control);
}
.app-switcher-card__logo {
display: block;
object-fit: contain;
}
.app-switcher-card__initial {
display: inline-grid;
place-items: center;
background: var(--rc-brand-primary);
color: var(--rc-brand-on-primary);
font-size: var(--rc-text-sm);
font-weight: var(--rc-font-semibold);
line-height: 1;
}
/* Keep populated login fields on the opaque Xinyuan tint through HeroUI focus states. */
body
.login-page
.login-card
.login-form
.login-field
input.login-input.input--secondary[data-filled='true']:focus,
body
.login-page
.login-card
.login-form
.login-field
input.login-input.input--secondary[data-filled='true']:focus-visible {
--input-bg: var(--rc-login-field-filled-background) !important;
--input-bg-focus: var(--rc-login-field-filled-background) !important;
background: var(--rc-login-field-filled-background) !important;
background-color: var(--rc-login-field-filled-background) !important;
box-shadow:
inset 0 0 0 1000px var(--rc-login-field-filled-background),
0 0 0 2px var(--rc-login-field-focus-ring) !important;
-webkit-box-shadow:
inset 0 0 0 1000px var(--rc-login-field-filled-background),
0 0 0 2px var(--rc-login-field-focus-ring) !important;
transition:
background-color 150ms ease,
border-color 150ms ease,
box-shadow 150ms ease !important;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment