update: use components

This commit is contained in:
tao.chen
2026-08-26 10:37:06 +08:00
parent fe2fdb5ece
commit 83190ea57e
3 changed files with 172 additions and 51 deletions
@@ -1,14 +1,16 @@
import { useEffect, useState } from "react";
// TODO: split file (>500 lines per CLAUDE.md 3.2.1 budget)
import { X } from "lucide-react";
import { ApiRequestError, type Employee, type Workspace, type WorkspaceMember } from "../../services/api";
import { useApi, useAuth } from "../../context/AuthContext";
import Icon from "../../components/common/Icon";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
import { Textarea } from "~/components/ui/textarea";
import { UserMultiSelect } from "./UserMultiSelect";
import {
formFieldClass,
formInputClass,
formTextareaClass,
iconButtonClass,
modalBackdropClass,
modalCompactClass,
modalEyebrowClass,
@@ -16,8 +18,6 @@ import {
modalFormClass,
modalHeaderClass,
modalTitleClass,
primaryButtonClass,
secondaryButtonClass,
} from "../platform/modalUi";
import "../../styles/admin.css";
@@ -29,6 +29,17 @@ const EMPTY_PROJECT_FORM = {
description: "",
};
const CLOSE_BUTTON_CLASS =
"size-[34px] rounded-[7px] border border-[#dfe6ed] bg-white hover:bg-[#f7faff] hover:border-[#b9c9da]";
const SECONDARY_BUTTON_CLASS =
"h-[37px] gap-[7px] rounded-md border-[#d8e1e9] bg-white px-[15px] text-xs font-[650] text-[#56687c]";
const PRIMARY_BUTTON_CLASS =
"h-[37px] gap-[7px] rounded-md border border-[#126ac3] bg-gradient-to-br from-[#1881e7] to-[#1268c9] px-[15px] text-xs font-[650] text-white shadow-[0_6px_15px_rgb(20_111_202/18%)] enabled:hover:from-[#1175d8] enabled:hover:to-[#0e5bad]";
const FORM_INPUT_CLASS =
"h-[39px] w-full rounded-md border border-[#d6e0e9] bg-white px-[11px] text-xs text-[#283a4e] outline-none focus:border-[#5b9ddb] focus:shadow-[0_0_0_3px_rgb(38_125_207/8%)] disabled:cursor-not-allowed disabled:opacity-50";
const FORM_TEXTAREA_CLASS =
"min-h-[92px] w-full resize-y rounded-md border border-[#d6e0e9] bg-white px-[11px] py-2.5 text-xs leading-[1.55] text-[#283a4e] outline-none focus:border-[#5b9ddb] focus:shadow-[0_0_0_3px_rgb(38_125_207/8%)]";
export function ProjectManagementPage({
onNotify,
onConnectionChange,
@@ -253,15 +264,17 @@ export function ProjectManagementPage({
onChange={(event) => setProjectSearchTerm(event.target.value)}
/>
</div>
<button
className="primary-button admin-toolbar__button"
<Button
variant="default"
size="sm"
type="button"
disabled={!canManage}
onClick={openCreateProject}
className={PRIMARY_BUTTON_CLASS}
>
<Icon name="plus" size={15} />
</button>
</Button>
</div>
{!canManage && <div className="admin-readonly"></div>}
@@ -331,16 +344,23 @@ export function ProjectManagementPage({
<span className={modalEyebrowClass}>PROJECT</span>
<h2 className={modalTitleClass}>{editingProject ? "编辑项目" : "新建项目"}</h2>
</div>
<button className={iconButtonClass} type="button" aria-label="关闭" onClick={() => setProjectDialogOpen(false)}>
<Icon name="close" />
</button>
<Button
variant="ghost"
size="icon-sm"
type="button"
aria-label="关闭"
onClick={() => setProjectDialogOpen(false)}
className={CLOSE_BUTTON_CLASS}
>
<X size={16} />
</Button>
</div>
<form className={modalFormClass} onSubmit={(event) => void submitProject(event)}>
{!editingProject && (
<label className={formFieldClass}>
<span><span className="text-[#e74c3c]">*</span></span>
<input
className={formInputClass}
<Input
className={FORM_INPUT_CLASS}
autoFocus
value={projectForm.workspace_code}
onChange={(event) => setProjectForm({ ...projectForm, workspace_code: event.target.value })}
@@ -350,8 +370,8 @@ export function ProjectManagementPage({
)}
<label className={formFieldClass}>
<span><span className="text-[#e74c3c]">*</span></span>
<input
className={formInputClass}
<Input
className={FORM_INPUT_CLASS}
value={projectForm.workspace_name}
onChange={(event) => setProjectForm({ ...projectForm, workspace_name: event.target.value })}
placeholder="请输入项目名称"
@@ -359,8 +379,8 @@ export function ProjectManagementPage({
</label>
<label className={formFieldClass}>
<span></span>
<input
className={formInputClass}
<Input
className={FORM_INPUT_CLASS}
type="number"
value={projectForm.quota_bytes}
onChange={(event) => setProjectForm({ ...projectForm, quota_bytes: parseInt(event.target.value) || 0 })}
@@ -369,8 +389,8 @@ export function ProjectManagementPage({
</label>
<label className={formFieldClass}>
<span></span>
<textarea
className={formTextareaClass}
<Textarea
className={FORM_TEXTAREA_CLASS}
value={projectForm.description}
onChange={(event) => setProjectForm({ ...projectForm, description: event.target.value })}
placeholder="请输入项目描述(可选)"
@@ -378,8 +398,24 @@ export function ProjectManagementPage({
/>
</label>
<div className={modalFooterClass}>
<button className={secondaryButtonClass} type="button" onClick={() => setProjectDialogOpen(false)}></button>
<button className={primaryButtonClass} type="submit" disabled={saving}>{saving ? "保存中…" : "保存"}</button>
<Button
variant="outline"
size="sm"
type="button"
onClick={() => setProjectDialogOpen(false)}
className={SECONDARY_BUTTON_CLASS}
>
</Button>
<Button
variant="default"
size="sm"
type="submit"
disabled={saving}
className={PRIMARY_BUTTON_CLASS}
>
{saving ? "保存中…" : "保存"}
</Button>
</div>
</form>
</section>
@@ -394,9 +430,16 @@ export function ProjectManagementPage({
<span className={modalEyebrowClass}>IMPORT MEMBER</span>
<h2 className={modalTitleClass}> {selectedProject?.workspace_name ?? "项目"}</h2>
</div>
<button className={iconButtonClass} type="button" aria-label="关闭" onClick={() => setImportMemberDialogOpen(false)}>
<Icon name="close" />
</button>
<Button
variant="ghost"
size="icon-sm"
type="button"
aria-label="关闭"
onClick={() => setImportMemberDialogOpen(false)}
className={CLOSE_BUTTON_CLASS}
>
<X size={16} />
</Button>
</div>
<form className={modalFormClass}>
<label className={`${formFieldClass} pb-[120px]`}>
@@ -409,10 +452,25 @@ export function ProjectManagementPage({
/>
</label>
<div className={modalFooterClass}>
<button className={secondaryButtonClass} type="button" onClick={() => setImportMemberDialogOpen(false)}></button>
<button className={primaryButtonClass} type="button" disabled={saving || selectedUserIds.length === 0} onClick={() => void importMember()}>
<Button
variant="outline"
size="sm"
type="button"
onClick={() => setImportMemberDialogOpen(false)}
className={SECONDARY_BUTTON_CLASS}
>
</Button>
<Button
variant="default"
size="sm"
type="button"
disabled={saving || selectedUserIds.length === 0}
onClick={() => void importMember()}
className={PRIMARY_BUTTON_CLASS}
>
{saving ? "添加中…" : `添加 (${selectedUserIds.length})`}
</button>
</Button>
</div>
</form>
</section>