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

refine material center controls and surfaces

parent 811086b9
...@@ -53,6 +53,11 @@ paths are not valid delivery mechanisms. ...@@ -53,6 +53,11 @@ paths are not valid delivery mechanisms.
Filters use a direct HeroUI `Surface`; lists remain unframed around the Filters use a direct HeroUI `Surface`; lists remain unframed around the
DataGrid. Cards are reserved for discrete business objects and analytical tools. DataGrid. Cards are reserved for discrete business objects and analytical tools.
Material archive filters use an explicit-submit rhythm: draft fields stay local
until `查询`, with a compact secondary reset icon button immediately before the
primary red query button and the native Disclosure `展开` control to its right.
Reset and category Tab changes submit their corresponding complete filter state;
typing alone never reloads the list.
## Component Rules ## Component Rules
...@@ -60,7 +65,14 @@ DataGrid. Cards are reserved for discrete business objects and analytical tools. ...@@ -60,7 +65,14 @@ DataGrid. Cards are reserved for discrete business objects and analytical tools.
their documented `@heroui-pro/react/*` subpaths. their documented `@heroui-pro/react/*` subpaths.
- Routine text buttons are text-only; icon actions use Ant Design icons, - Routine text buttons are text-only; icon actions use Ant Design icons,
tooltips and accessible names. tooltips and accessible names.
- Business page, module and field titles stay visually clean without adjacent
information-tip icons; reserve Tooltips for actual icon actions and controls.
- Dropdown-like selection uses HeroUI Autocomplete/ListBox contracts. - Dropdown-like selection uses HeroUI Autocomplete/ListBox contracts.
- Form checkboxes on light surfaces use HeroUI `Checkbox` with
`variant="secondary"` so the unchecked control has a visible neutral fill
against white surfaces. Keep the official `Checkbox.Content` ->
`Checkbox.Control` -> `Checkbox.Indicator` compound structure; selected
controls retain the brand accent, and field values/handlers remain unchanged.
- Date and date-time fields use HeroUI DatePicker and preserve the existing - Date and date-time fields use HeroUI DatePicker and preserve the existing
string protocol at the page boundary. string protocol at the page boundary.
- Files use HeroUI Pro DropZone while preserving the page-owned `File` object. - Files use HeroUI Pro DropZone while preserving the page-owned `File` object.
...@@ -69,6 +81,9 @@ DataGrid. Cards are reserved for discrete business objects and analytical tools. ...@@ -69,6 +81,9 @@ DataGrid. Cards are reserved for discrete business objects and analytical tools.
- Routine success feedback is a fixed, auto-dismissing Toast. - Routine success feedback is a fixed, auto-dismissing Toast.
- Modal view mode uses one secondary Surface for the complete read-only field - Modal view mode uses one secondary Surface for the complete read-only field
group; edit mode removes that Surface and renders secondary fields directly. group; edit mode removes that Surface and renders secondary fields directly.
- Detail view modal footers expose only the contextual edit action; destructive
material actions stay in the DataGrid row menu or batch ActionBar to reduce
accidental deletion from a read-only view.
- Empty results stay empty and request failures stay errors. UI code must never - Empty results stay empty and request failures stay errors. UI code must never
populate dashboard or list surfaces with fabricated records or numbers. populate dashboard or list surfaces with fabricated records or numbers.
......
import { Avatar, Button, Chip, Pagination, Spinner, Tooltip } from "@heroui/react"; import { Button, Chip, Pagination, Spinner, Tooltip } from "@heroui/react";
import { ActionBar } from "@heroui-pro/react/action-bar"; import { ActionBar } from "@heroui-pro/react/action-bar";
import { DataGrid } from "@heroui-pro/react/data-grid"; import { DataGrid } from "@heroui-pro/react/data-grid";
import { EmptyState } from "@heroui-pro/react/empty-state"; import { EmptyState } from "@heroui-pro/react/empty-state";
...@@ -240,18 +240,6 @@ export function DataTableTextPair({ primary, secondary, className = "" }) { ...@@ -240,18 +240,6 @@ export function DataTableTextPair({ primary, secondary, className = "" }) {
); );
} }
function getPersonFallback(name) {
const normalized = String(name ?? "").trim();
if (!normalized || normalized === "-") {
return "?";
}
const compact = normalized.replace(/\s+/g, "");
if (/^[\u3400-\u9fff]/.test(compact)) {
return Array.from(compact)[0];
}
return Array.from(compact).slice(0, 2).join("").toUpperCase();
}
export function DataTablePersonIdentity({ export function DataTablePersonIdentity({
name, name,
account, account,
...@@ -265,15 +253,11 @@ export function DataTablePersonIdentity({ ...@@ -265,15 +253,11 @@ export function DataTablePersonIdentity({
return ( return (
<Tooltip delay={0}> <Tooltip delay={0}>
<div <div
className={["flex min-w-0 items-center gap-2", className] className={["flex min-w-0 items-center", className]
.filter(Boolean) .filter(Boolean)
.join(" ")} .join(" ")}
tabIndex={0} tabIndex={0}
> >
<Avatar size="sm" variant="soft" className="shrink-0">
{avatar ? <Avatar.Image alt={primaryText} src={avatar} /> : null}
<Avatar.Fallback>{getPersonFallback(primaryText)}</Avatar.Fallback>
</Avatar>
<span className="grid min-w-0 gap-1"> <span className="grid min-w-0 gap-1">
<span className="min-w-0 truncate text-sm font-medium"> <span className="min-w-0 truncate text-sm font-medium">
{primaryText} {primaryText}
...@@ -347,7 +331,7 @@ export function DataTablePagination({ ...@@ -347,7 +331,7 @@ export function DataTablePagination({
{hasPageSizePicker ? ( {hasPageSizePicker ? (
<XinyuanAutocomplete <XinyuanAutocomplete
aria-label="每页条数" aria-label="每页条数"
className="w-24 shrink-0" className="w-32 shrink-0"
isDisabled={isDisabled} isDisabled={isDisabled}
options={pageSizeOptionsForPicker} options={pageSizeOptionsForPicker}
placeholder="每页条数" placeholder="每页条数"
......
...@@ -41,11 +41,13 @@ export default function XinyuanAutocomplete({ ...@@ -41,11 +41,13 @@ export default function XinyuanAutocomplete({
fullWidth = true, fullWidth = true,
inputValue, inputValue,
isDisabled = false, isDisabled = false,
isClearable = false,
isLoading = false, isLoading = false,
isOpen, isOpen,
isRequired = false, isRequired = false,
name, name,
onInputChange, onInputChange,
onClear,
onOpenChange, onOpenChange,
onSelectionChange, onSelectionChange,
options = [], options = [],
...@@ -80,6 +82,7 @@ export default function XinyuanAutocomplete({ ...@@ -80,6 +82,7 @@ export default function XinyuanAutocomplete({
(selectedOptions.length > 0 (selectedOptions.length > 0
? selectedOptions.map((option) => option.label).join("、") ? selectedOptions.map((option) => option.label).join("、")
: placeholder); : placeholder);
const hasSelection = Array.from(selectedKeyStringSet).some(Boolean);
const rootSelectionProps = const rootSelectionProps =
selectionMode === "multiple" ? { selectedKeys } : { selectedKey }; selectionMode === "multiple" ? { selectedKeys } : { selectedKey };
const listBoxSelectionProps = const listBoxSelectionProps =
...@@ -118,6 +121,7 @@ export default function XinyuanAutocomplete({ ...@@ -118,6 +121,7 @@ export default function XinyuanAutocomplete({
isOpen={isOpen} isOpen={isOpen}
isRequired={isRequired} isRequired={isRequired}
name={name} name={name}
onClear={onClear}
selectionMode={selectionMode} selectionMode={selectionMode}
variant={variant} variant={variant}
onOpenChange={onOpenChange} onOpenChange={onOpenChange}
...@@ -129,6 +133,11 @@ export default function XinyuanAutocomplete({ ...@@ -129,6 +133,11 @@ export default function XinyuanAutocomplete({
{resolvedValueText} {resolvedValueText}
</span> </span>
</Autocomplete.Value> </Autocomplete.Value>
{isClearable && hasSelection ? (
<Autocomplete.ClearButton
aria-label={`清除${ariaLabel || placeholder}`}
/>
) : null}
<Autocomplete.Indicator> <Autocomplete.Indicator>
<ChevronDown size={16} style={FIELD_SUFFIX_ICON_STYLE} /> <ChevronDown size={16} style={FIELD_SUFFIX_ICON_STYLE} />
</Autocomplete.Indicator> </Autocomplete.Indicator>
......
...@@ -19,11 +19,11 @@ import { ...@@ -19,11 +19,11 @@ import {
} from "@/components/TableActionButton"; } from "@/components/TableActionButton";
import { UserContext } from "@/wrapper/Auth"; import { UserContext } from "@/wrapper/Auth";
import { import {
ArrowPathIcon,
ArrowTopRightOnSquareIcon, ArrowTopRightOnSquareIcon,
ChevronDownIcon, ChevronDownIcon,
ClipboardDocumentIcon, ClipboardDocumentIcon,
FolderIcon, FolderIcon,
InformationCircleIcon,
MagnifyingGlassIcon, MagnifyingGlassIcon,
XMarkIcon, XMarkIcon,
} from "@/components/AppIcons"; } from "@/components/AppIcons";
...@@ -51,7 +51,6 @@ import { DropZone } from "@heroui-pro/react/drop-zone"; ...@@ -51,7 +51,6 @@ import { DropZone } from "@heroui-pro/react/drop-zone";
import { import {
startTransition, startTransition,
useContext, useContext,
useDeferredValue,
useEffect, useEffect,
useMemo, useMemo,
useState, useState,
...@@ -235,35 +234,12 @@ function buildDetailForm(material) { ...@@ -235,35 +234,12 @@ function buildDetailForm(material) {
}; };
} }
function TitleInfoTooltip({ label, text, tone = "neutral" }) {
return (
<Tooltip delay={0}>
<Button
isIconOnly
aria-label={`${label}说明`}
className={clsx(
"rc-title-info-trigger",
tone === "warning" && "text-warning",
)}
type="button"
variant="ghost"
>
<InformationCircleIcon size={14} />
</Button>
<Tooltip.Content>{text}</Tooltip.Content>
</Tooltip>
);
}
function FormSection({ title, description, children, className }) { function FormSection({ title, description, children, className }) {
return ( return (
<section className={clsx("min-w-0 space-y-4", className)}> <section className={clsx("min-w-0 space-y-4", className)}>
<div className="min-w-0"> <div className="min-w-0">
<div className="inline-flex items-center gap-1.5 text-base font-medium"> <div className="inline-flex items-center gap-1.5 text-base font-medium">
<span>{title}</span> <span>{title}</span>
{description ? (
<TitleInfoTooltip label={title} text={description} />
) : null}
</div> </div>
</div> </div>
<div>{children}</div> <div>{children}</div>
...@@ -288,9 +264,6 @@ function FieldLabel({ children, required = false, infoText, infoTone }) { ...@@ -288,9 +264,6 @@ function FieldLabel({ children, required = false, infoText, infoTone }) {
{required ? ( {required ? (
<span className="text-danger">*</span> <span className="text-danger">*</span>
) : null} ) : null}
{infoText ? (
<TitleInfoTooltip label={label} text={infoText} tone={infoTone} />
) : null}
</div> </div>
); );
} }
...@@ -350,6 +323,7 @@ function ConfiguredSelect({ ...@@ -350,6 +323,7 @@ function ConfiguredSelect({
emptyText={`暂无${fieldLabel}配置`} emptyText={`暂无${fieldLabel}配置`}
errorText={error} errorText={error}
isDisabled={disabled || loading || Boolean(error)} isDisabled={disabled || loading || Boolean(error)}
isClearable={String(value ?? "") !== ""}
isLoading={loading} isLoading={loading}
options={[ options={[
{ id: "", label: emptyLabel }, { id: "", label: emptyLabel },
...@@ -368,6 +342,9 @@ function ConfiguredSelect({ ...@@ -368,6 +342,9 @@ function ConfiguredSelect({
onSelectionChange={(key) => { onSelectionChange={(key) => {
props.onChange?.({ target: { value: String(key ?? "") } }); props.onChange?.({ target: { value: String(key ?? "") } });
}} }}
onClear={() => {
props.onChange?.({ target: { value: "" } });
}}
/> />
{showError && error ? ( {showError && error ? (
<div className="mt-2 text-xs text-danger" role="alert"> <div className="mt-2 text-xs text-danger" role="alert">
...@@ -415,9 +392,10 @@ export default function ResourceBookingPage() { ...@@ -415,9 +392,10 @@ export default function ResourceBookingPage() {
const [materialSearchForm, setMaterialSearchForm] = useState( const [materialSearchForm, setMaterialSearchForm] = useState(
buildEmptyMaterialSearchForm, buildEmptyMaterialSearchForm,
); );
const [submittedMaterialSearchForm, setSubmittedMaterialSearchForm] =
useState(buildEmptyMaterialSearchForm);
const [isMaterialFilterExpanded, setIsMaterialFilterExpanded] = const [isMaterialFilterExpanded, setIsMaterialFilterExpanded] =
useState(false); useState(false);
const deferredMaterialSearchForm = useDeferredValue(materialSearchForm);
const [activeCategory, setActiveCategory] = useState(ALL_CATEGORY_KEY); const [activeCategory, setActiveCategory] = useState(ALL_CATEGORY_KEY);
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE); const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
const [page, setPage] = useState(0); const [page, setPage] = useState(0);
...@@ -509,7 +487,7 @@ export default function ResourceBookingPage() { ...@@ -509,7 +487,7 @@ export default function ResourceBookingPage() {
async function runFirstPage() { async function runFirstPage() {
setLoading(true); setLoading(true);
const result = await loadMaterialPage({ const result = await loadMaterialPage({
...deferredMaterialSearchForm, ...submittedMaterialSearchForm,
page, page,
pageSize, pageSize,
}); });
...@@ -546,7 +524,7 @@ export default function ResourceBookingPage() { ...@@ -546,7 +524,7 @@ export default function ResourceBookingPage() {
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [activeMaterialId, deferredMaterialSearchForm, page, pageSize]); }, [activeMaterialId, submittedMaterialSearchForm, page, pageSize]);
const sourceOptions = useMemo(() => { const sourceOptions = useMemo(() => {
const dynamicOptions = (dashboard.sourceSuggestions || []).filter(Boolean); const dynamicOptions = (dashboard.sourceSuggestions || []).filter(Boolean);
...@@ -763,12 +741,18 @@ export default function ResourceBookingPage() { ...@@ -763,12 +741,18 @@ export default function ResourceBookingPage() {
...prev, ...prev,
[fieldName]: value, [fieldName]: value,
})); }));
}
function submitMaterialSearch() {
setSubmittedMaterialSearchForm({ ...materialSearchForm });
setActiveCategory(ALL_CATEGORY_KEY); setActiveCategory(ALL_CATEGORY_KEY);
setPage(0); setPage(0);
} }
function resetMaterialSearch() { function resetMaterialSearch() {
setMaterialSearchForm(buildEmptyMaterialSearchForm()); const emptySearchForm = buildEmptyMaterialSearchForm();
setMaterialSearchForm(emptySearchForm);
setSubmittedMaterialSearchForm(emptySearchForm);
setActiveCategory(ALL_CATEGORY_KEY); setActiveCategory(ALL_CATEGORY_KEY);
setPage(0); setPage(0);
setIsMaterialFilterExpanded(false); setIsMaterialFilterExpanded(false);
...@@ -776,16 +760,18 @@ export default function ResourceBookingPage() { ...@@ -776,16 +760,18 @@ export default function ResourceBookingPage() {
function handleCategoryTabClick(categoryKey) { function handleCategoryTabClick(categoryKey) {
setActiveCategory(categoryKey); setActiveCategory(categoryKey);
setMaterialSearchForm((prev) => ({ const nextSearchForm = {
...prev, ...submittedMaterialSearchForm,
archiveCategory: categoryKey === ALL_CATEGORY_KEY ? "" : categoryKey, archiveCategory: categoryKey === ALL_CATEGORY_KEY ? "" : categoryKey,
})); };
setMaterialSearchForm(nextSearchForm);
setSubmittedMaterialSearchForm(nextSearchForm);
setPage(0); setPage(0);
} }
async function reloadMaterials(options = {}) { async function reloadMaterials(options = {}) {
const result = await loadMaterialPage({ const result = await loadMaterialPage({
...deferredMaterialSearchForm, ...submittedMaterialSearchForm,
page: options.page ?? page, page: options.page ?? page,
pageSize, pageSize,
}); });
...@@ -1293,6 +1279,12 @@ export default function ResourceBookingPage() { ...@@ -1293,6 +1279,12 @@ export default function ResourceBookingPage() {
isExpanded={shouldShowAdvancedMaterialFilters} isExpanded={shouldShowAdvancedMaterialFilters}
onExpandedChange={setIsMaterialFilterExpanded} onExpandedChange={setIsMaterialFilterExpanded}
> >
<form
onSubmit={(event) => {
event.preventDefault();
submitMaterialSearch();
}}
>
<div className="grid min-w-0 gap-3 xl:grid-cols-[minmax(0,1fr)_auto] xl:items-end"> <div className="grid min-w-0 gap-3 xl:grid-cols-[minmax(0,1fr)_auto] xl:items-end">
<div className="grid min-w-0 gap-3 md:grid-cols-2 xl:grid-cols-3"> <div className="grid min-w-0 gap-3 md:grid-cols-2 xl:grid-cols-3">
<div className="min-w-0"> <div className="min-w-0">
...@@ -1344,7 +1336,32 @@ export default function ResourceBookingPage() { ...@@ -1344,7 +1336,32 @@ export default function ResourceBookingPage() {
</div> </div>
<div className="flex min-w-0 flex-wrap items-end gap-2 md:justify-end xl:flex-nowrap"> <div className="flex min-w-0 flex-wrap items-end gap-2 md:justify-end xl:flex-nowrap">
<Disclosure.Trigger className="button button--sm button--ghost"> <Tooltip delay={0}>
<Button
aria-label="重置筛选"
isDisabled={loading}
isIconOnly
size="sm"
type="button"
variant="secondary"
onPress={resetMaterialSearch}
>
<ArrowPathIcon size={16} />
</Button>
<Tooltip.Content>重置筛选</Tooltip.Content>
</Tooltip>
<Button
isDisabled={loading}
size="sm"
type="submit"
variant="primary"
>
查询
</Button>
<Disclosure.Trigger
className="button button--sm button--ghost"
type="button"
>
<span>{shouldShowAdvancedMaterialFilters ? "收起" : "展开"}</span> <span>{shouldShowAdvancedMaterialFilters ? "收起" : "展开"}</span>
<Disclosure.Indicator> <Disclosure.Indicator>
<ChevronDownIcon aria-hidden="true" size={14} /> <ChevronDownIcon aria-hidden="true" size={14} />
...@@ -1463,6 +1480,7 @@ export default function ResourceBookingPage() { ...@@ -1463,6 +1480,7 @@ export default function ResourceBookingPage() {
</div> </div>
</Disclosure.Content> </Disclosure.Content>
</div> </div>
</form>
</Disclosure> </Disclosure>
</Surface> </Surface>
...@@ -1660,13 +1678,7 @@ export default function ResourceBookingPage() { ...@@ -1660,13 +1678,7 @@ export default function ResourceBookingPage() {
</Modal.CloseTrigger> </Modal.CloseTrigger>
<Modal.Header> <Modal.Header>
<div className="min-w-0"> <div className="min-w-0">
<div className="inline-flex items-center gap-1.5"> <Modal.Heading>材料详情</Modal.Heading>
<Modal.Heading>材料详情</Modal.Heading>
<TitleInfoTooltip
label="材料详情"
text="默认以只读方式查看材料元数据;点击编辑后再修改,保存成功后生效。"
/>
</div>
</div> </div>
</Modal.Header> </Modal.Header>
<Modal.Body> <Modal.Body>
...@@ -1888,6 +1900,7 @@ export default function ResourceBookingPage() { ...@@ -1888,6 +1900,7 @@ export default function ResourceBookingPage() {
<div className="grid gap-3 sm:grid-cols-2"> <div className="grid gap-3 sm:grid-cols-2">
<HeroCheckbox <HeroCheckbox
className="min-h-18" className="min-h-18"
variant="secondary"
isSelected={detailForm.isConfidential} isSelected={detailForm.isConfidential}
isDisabled={saving} isDisabled={saving}
onChange={(checked) => onChange={(checked) =>
...@@ -1913,6 +1926,7 @@ export default function ResourceBookingPage() { ...@@ -1913,6 +1926,7 @@ export default function ResourceBookingPage() {
</HeroCheckbox> </HeroCheckbox>
<HeroCheckbox <HeroCheckbox
className="min-h-18" className="min-h-18"
variant="secondary"
isSelected={detailForm.isAuditDeptMaterial} isSelected={detailForm.isAuditDeptMaterial}
isDisabled={saving} isDisabled={saving}
onChange={(checked) => onChange={(checked) =>
...@@ -1966,14 +1980,6 @@ export default function ResourceBookingPage() { ...@@ -1966,14 +1980,6 @@ export default function ResourceBookingPage() {
)} )}
</Modal.Body> </Modal.Body>
<Modal.Footer> <Modal.Footer>
<Button
variant="danger-soft"
className="cursor-pointer"
onPress={handleRemoveActiveMaterial}
isDisabled={saving || !activeMaterial}
>
{saving ? "处理中..." : "删除材料"}
</Button>
{isDetailEditing ? ( {isDetailEditing ? (
<> <>
<Button <Button
...@@ -2051,6 +2057,7 @@ export default function ResourceBookingPage() { ...@@ -2051,6 +2057,7 @@ export default function ResourceBookingPage() {
<div className="grid min-w-0 gap-4 sm:grid-cols-2"> <div className="grid min-w-0 gap-4 sm:grid-cols-2">
<HeroCheckbox <HeroCheckbox
className="min-h-20" className="min-h-20"
variant="secondary"
isSelected={batchEditFields.archiveYear} isSelected={batchEditFields.archiveYear}
onChange={(checked) => onChange={(checked) =>
setBatchEditFields((prev) => ({ setBatchEditFields((prev) => ({
...@@ -2097,6 +2104,7 @@ export default function ResourceBookingPage() { ...@@ -2097,6 +2104,7 @@ export default function ResourceBookingPage() {
<HeroCheckbox <HeroCheckbox
className="min-h-20" className="min-h-20"
variant="secondary"
isSelected={batchEditFields.sourceApp} isSelected={batchEditFields.sourceApp}
onChange={(checked) => onChange={(checked) =>
setBatchEditFields((prev) => ({ setBatchEditFields((prev) => ({
...@@ -2144,6 +2152,7 @@ export default function ResourceBookingPage() { ...@@ -2144,6 +2152,7 @@ export default function ResourceBookingPage() {
<HeroCheckbox <HeroCheckbox
className="min-h-20" className="min-h-20"
variant="secondary"
isSelected={batchEditFields.sourceSystem} isSelected={batchEditFields.sourceSystem}
onChange={(checked) => onChange={(checked) =>
setBatchEditFields((prev) => ({ setBatchEditFields((prev) => ({
...@@ -2193,6 +2202,7 @@ export default function ResourceBookingPage() { ...@@ -2193,6 +2202,7 @@ export default function ResourceBookingPage() {
<HeroCheckbox <HeroCheckbox
className="min-h-20" className="min-h-20"
variant="secondary"
isSelected={batchEditFields.archiveCategory} isSelected={batchEditFields.archiveCategory}
onChange={(checked) => onChange={(checked) =>
setBatchEditFields((prev) => ({ setBatchEditFields((prev) => ({
...@@ -2242,6 +2252,7 @@ export default function ResourceBookingPage() { ...@@ -2242,6 +2252,7 @@ export default function ResourceBookingPage() {
<HeroCheckbox <HeroCheckbox
className="min-h-20" className="min-h-20"
variant="secondary"
isSelected={batchEditFields.provideDepartment} isSelected={batchEditFields.provideDepartment}
onChange={(checked) => onChange={(checked) =>
setBatchEditFields((prev) => ({ setBatchEditFields((prev) => ({
...@@ -2288,6 +2299,7 @@ export default function ResourceBookingPage() { ...@@ -2288,6 +2299,7 @@ export default function ResourceBookingPage() {
<HeroCheckbox <HeroCheckbox
className="min-h-20" className="min-h-20"
variant="secondary"
isSelected={batchEditFields.provideTime} isSelected={batchEditFields.provideTime}
onChange={(checked) => onChange={(checked) =>
setBatchEditFields((prev) => ({ setBatchEditFields((prev) => ({
...@@ -2339,6 +2351,7 @@ export default function ResourceBookingPage() { ...@@ -2339,6 +2351,7 @@ export default function ResourceBookingPage() {
<div className="grid gap-4 sm:grid-cols-2"> <div className="grid gap-4 sm:grid-cols-2">
<div className="space-y-3"> <div className="space-y-3">
<HeroCheckbox <HeroCheckbox
variant="secondary"
isSelected={batchEditFields.isConfidential} isSelected={batchEditFields.isConfidential}
onChange={(checked) => onChange={(checked) =>
setBatchEditFields((prev) => ({ setBatchEditFields((prev) => ({
...@@ -2368,6 +2381,7 @@ export default function ResourceBookingPage() { ...@@ -2368,6 +2381,7 @@ export default function ResourceBookingPage() {
</HeroCheckbox.Content> </HeroCheckbox.Content>
</HeroCheckbox> </HeroCheckbox>
<HeroCheckbox <HeroCheckbox
variant="secondary"
isSelected={batchEditForm.isConfidential} isSelected={batchEditForm.isConfidential}
onChange={(checked) => onChange={(checked) =>
setBatchEditForm((prev) => ({ setBatchEditForm((prev) => ({
...@@ -2394,6 +2408,7 @@ export default function ResourceBookingPage() { ...@@ -2394,6 +2408,7 @@ export default function ResourceBookingPage() {
<div className="space-y-3"> <div className="space-y-3">
<HeroCheckbox <HeroCheckbox
variant="secondary"
isSelected={batchEditFields.isAuditDeptMaterial} isSelected={batchEditFields.isAuditDeptMaterial}
onChange={(checked) => onChange={(checked) =>
setBatchEditFields((prev) => ({ setBatchEditFields((prev) => ({
...@@ -2423,6 +2438,7 @@ export default function ResourceBookingPage() { ...@@ -2423,6 +2438,7 @@ export default function ResourceBookingPage() {
</HeroCheckbox.Content> </HeroCheckbox.Content>
</HeroCheckbox> </HeroCheckbox>
<HeroCheckbox <HeroCheckbox
variant="secondary"
isSelected={batchEditForm.isAuditDeptMaterial} isSelected={batchEditForm.isAuditDeptMaterial}
onChange={(checked) => onChange={(checked) =>
setBatchEditForm((prev) => ({ setBatchEditForm((prev) => ({
...@@ -2566,16 +2582,10 @@ export default function ResourceBookingPage() { ...@@ -2566,16 +2582,10 @@ export default function ResourceBookingPage() {
> >
<Modal.Header> <Modal.Header>
<Modal.Heading>上传材料</Modal.Heading> <Modal.Heading>上传材料</Modal.Heading>
<Description>
创建页已按最新对象文档补齐字段,上传完成后会统一保存到材料档案。
</Description>
</Modal.Header> </Modal.Header>
<Modal.Body> <Modal.Body>
<div className="space-y-6"> <div className="space-y-6">
<FormSection <div>
title="上传文件"
description="先完成文件选择,系统会自动尝试用文件名回填材料标题。"
>
<DropZone className="min-h-48"> <DropZone className="min-h-48">
<DropZone.Area <DropZone.Area
onDrop={async (event) => { onDrop={async (event) => {
...@@ -2619,7 +2629,7 @@ export default function ResourceBookingPage() { ...@@ -2619,7 +2629,7 @@ export default function ResourceBookingPage() {
</div> </div>
) : null} ) : null}
</DropZone> </DropZone>
</FormSection> </div>
{selectedFileIsZip ? ( {selectedFileIsZip ? (
<FormSection <FormSection
...@@ -2844,6 +2854,7 @@ export default function ResourceBookingPage() { ...@@ -2844,6 +2854,7 @@ export default function ResourceBookingPage() {
<div className="grid gap-4 sm:grid-cols-2"> <div className="grid gap-4 sm:grid-cols-2">
<HeroCheckbox <HeroCheckbox
className="min-h-20" className="min-h-20"
variant="secondary"
isSelected={uploadForm.isConfidential} isSelected={uploadForm.isConfidential}
onChange={(checked) => onChange={(checked) =>
setUploadForm((prev) => ({ setUploadForm((prev) => ({
...@@ -2874,6 +2885,7 @@ export default function ResourceBookingPage() { ...@@ -2874,6 +2885,7 @@ export default function ResourceBookingPage() {
</HeroCheckbox> </HeroCheckbox>
<HeroCheckbox <HeroCheckbox
className="min-h-20" className="min-h-20"
variant="secondary"
isSelected={uploadForm.isAuditDeptMaterial} isSelected={uploadForm.isAuditDeptMaterial}
onChange={(checked) => onChange={(checked) =>
setUploadForm((prev) => ({ setUploadForm((prev) => ({
......
...@@ -13,7 +13,6 @@ import { ...@@ -13,7 +13,6 @@ import {
CircleStackIcon, CircleStackIcon,
DocumentTextIcon, DocumentTextIcon,
FolderOpenIcon, FolderOpenIcon,
InformationCircleIcon,
ShieldCheckIcon, ShieldCheckIcon,
ShieldExclamationIcon, ShieldExclamationIcon,
} from "@/components/AppIcons"; } from "@/components/AppIcons";
...@@ -29,7 +28,6 @@ import { ...@@ -29,7 +28,6 @@ import {
Chip, Chip,
ProgressBar as HeroProgressBar, ProgressBar as HeroProgressBar,
Spinner, Spinner,
Tooltip,
} from "@heroui/react"; } from "@heroui/react";
import { EmptyState } from "@heroui-pro/react/empty-state"; import { EmptyState } from "@heroui-pro/react/empty-state";
import { KPI } from "@heroui-pro/react/kpi"; import { KPI } from "@heroui-pro/react/kpi";
...@@ -48,9 +46,6 @@ function Panel({ title, titleInfo, className, children }) { ...@@ -48,9 +46,6 @@ function Panel({ title, titleInfo, className, children }) {
<Card.Title className="rc-module-title min-w-0 flex-1"> <Card.Title className="rc-module-title min-w-0 flex-1">
{title} {title}
</Card.Title> </Card.Title>
{titleInfo ? (
<TitleInfoTooltip label={title} text={titleInfo} />
) : null}
</Card.Header> </Card.Header>
) : null} ) : null}
<Card.Content className={className}>{children}</Card.Content> <Card.Content className={className}>{children}</Card.Content>
...@@ -65,9 +60,6 @@ function GovernanceCard({ title, titleInfo, icon: Icon, tone, children }) { ...@@ -65,9 +60,6 @@ function GovernanceCard({ title, titleInfo, icon: Icon, tone, children }) {
<div className="flex w-full min-w-0 items-center justify-between gap-4"> <div className="flex w-full min-w-0 items-center justify-between gap-4">
<Card.Title className="rc-module-title inline-flex min-w-0 items-center gap-1.5"> <Card.Title className="rc-module-title inline-flex min-w-0 items-center gap-1.5">
<span>{title}</span> <span>{title}</span>
{titleInfo ? (
<TitleInfoTooltip label={title} text={titleInfo} />
) : null}
</Card.Title> </Card.Title>
<span <span
aria-hidden="true" aria-hidden="true"
...@@ -85,23 +77,6 @@ function GovernanceCard({ title, titleInfo, icon: Icon, tone, children }) { ...@@ -85,23 +77,6 @@ function GovernanceCard({ title, titleInfo, icon: Icon, tone, children }) {
); );
} }
function TitleInfoTooltip({ label, text }) {
return (
<Tooltip delay={0}>
<Button
isIconOnly
aria-label={`${label}说明`}
className="rc-title-info-trigger"
type="button"
variant="ghost"
>
<InformationCircleIcon size={14} />
</Button>
<Tooltip.Content>{text}</Tooltip.Content>
</Tooltip>
);
}
function MetricCard({ icon: Icon, label, value, hint, tone }) { function MetricCard({ icon: Icon, label, value, hint, tone }) {
const numericValue = Number(value || 0); const numericValue = Number(value || 0);
...@@ -111,7 +86,6 @@ function MetricCard({ icon: Icon, label, value, hint, tone }) { ...@@ -111,7 +86,6 @@ function MetricCard({ icon: Icon, label, value, hint, tone }) {
<KPI.Title> <KPI.Title>
<span className="inline-flex items-center gap-1.5"> <span className="inline-flex items-center gap-1.5">
{label} {label}
{hint ? <TitleInfoTooltip label={label} text={hint} /> : null}
</span> </span>
</KPI.Title> </KPI.Title>
</KPI.Header> </KPI.Header>
...@@ -135,9 +109,6 @@ function SectionHeader({ title, description }) { ...@@ -135,9 +109,6 @@ function SectionHeader({ title, description }) {
return ( return (
<h2 className="rc-section-title inline-flex items-center gap-1.5"> <h2 className="rc-section-title inline-flex items-center gap-1.5">
<span>{title}</span> <span>{title}</span>
{description ? (
<TitleInfoTooltip label={title} text={description} />
) : null}
</h2> </h2>
); );
} }
......
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