Commit c4324c3a authored by drigle's avatar drigle

fix: show selected audit references

parent c61a6514
......@@ -6,6 +6,7 @@ import ReferenceSearchSelect from './ReferenceSearchSelect';
import { ControlPointPreviewFields } from './ControlPointPreviewDialog';
import {
AUDIT_SUBJECTS,
getReferenceRecordsByIds,
normalizeRecordId,
searchReferenceRecords,
searchTags,
......@@ -52,6 +53,18 @@ export default function UnifiedControlPointDialog({
const loadDepartmentOptions = useCallback((keyword) => searchReferenceRecords('department', keyword), []);
const loadRegulationOptions = useCallback((keyword) => searchReferenceRecords('internal_regulation', keyword), []);
const loadIndicatorOptions = useCallback((keyword) => searchReferenceRecords(AUDIT_SUBJECTS.INDICATOR, keyword), []);
const resolveDepartmentIds = useCallback(
(ids) => getReferenceRecordsByIds('department', ids),
[],
);
const resolveRegulationIds = useCallback(
(ids) => getReferenceRecordsByIds('internal_regulation', ids),
[],
);
const resolveIndicatorIds = useCallback(
(ids) => getReferenceRecordsByIds(AUDIT_SUBJECTS.INDICATOR, ids),
[],
);
useEffect(() => {
if (!open) return;
......@@ -130,13 +143,13 @@ export default function UnifiedControlPointDialog({
<Modal.Body className="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 placeholder="请输入控制点名称" variant="secondary" /></TextField>
<TextField className="rc-form-field sm:col-span-2" value={form.description} onChange={(value) => change('description', value)}><Label>描述信息</Label><TextArea placeholder="请输入控制点描述" rows={4} variant="secondary" /></TextField>
<div className="rc-form-field"><Label>{requiredLabel('责任人', true)}</Label><ReferenceSearchSelect value={form.owner} onChange={(value) => change('owner', value)} loadOptions={loadDepartmentOptions} placeholder="搜索责任部门" /></div>
<div className="rc-form-field"><Label>{requiredLabel('责任人', true)}</Label><ReferenceSearchSelect value={form.owner} onChange={(value) => change('owner', value)} loadOptions={loadDepartmentOptions} resolveMissingIds={resolveDepartmentIds} placeholder="搜索责任部门" /></div>
<div className="rc-form-field"><Label>相关标签</Label><ReferenceSearchSelect multiple value={form.related_tags} onChange={(value) => change('related_tags', value)} loadOptions={searchTags} placeholder="搜索并添加标签" /></div>
<div className="rc-form-field sm:col-span-2"><Label>相关制度</Label><ReferenceSearchSelect multiple value={form.related_regulations} onChange={(value) => change('related_regulations', value)} loadOptions={loadRegulationOptions} placeholder="搜索并添加制度" /></div>
<div className="rc-form-field sm:col-span-2"><Label>相关制度</Label><ReferenceSearchSelect multiple value={form.related_regulations} onChange={(value) => change('related_regulations', value)} loadOptions={loadRegulationOptions} resolveMissingIds={resolveRegulationIds} placeholder="搜索并添加制度" /></div>
<div className="rc-form-field"><Label>{requiredLabel('控制措施', Boolean(typeField?.required))}</Label><FilterSelect aria-label="控制措施" options={typeOptions} value={form.type} variant="secondary" onChange={(value) => change('type', value)} /></div>
{form.type === 'form' ? <TextField className="rc-form-field" value={form.form_label} onChange={(value) => change('form_label', value)}><Label>文本描述</Label><Input placeholder="请输入文本描述" variant="secondary" /></TextField> : null}
{form.type === 'attachment' ? <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"><TextField aria-label={`文件描述 ${index + 1}`} className="min-w-0 flex-1" value={label} onChange={(value) => { const labels = [...form.attachment_label]; labels[index] = value; change('attachment_label', labels); }}><Input placeholder="请输入文件类别" variant="secondary" /></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 variant="secondary" type="button" onPress={() => change('attachment_label', [...form.attachment_label, ''])}>添加文件描述</Button></div></div> : null}
{form.type === 'computed' ? <><div className="rc-form-field"><Label>量化指标</Label><ReferenceSearchSelect value={form.indicator} onChange={(value) => change('indicator', value)} loadOptions={loadIndicatorOptions} placeholder="搜索量化指标" /></div><TextField className="rc-form-field" value={String(form.computed_interval)} onChange={(value) => change('computed_interval', value)}><Label>执行频率(天)</Label><Input type="number" min="1" variant="secondary" /></TextField></> : null}
{form.type === 'computed' ? <><div className="rc-form-field"><Label>量化指标</Label><ReferenceSearchSelect value={form.indicator} onChange={(value) => change('indicator', value)} loadOptions={loadIndicatorOptions} resolveMissingIds={resolveIndicatorIds} placeholder="搜索量化指标" /></div><TextField className="rc-form-field" value={String(form.computed_interval)} onChange={(value) => change('computed_interval', value)}><Label>执行频率(天)</Label><Input type="number" min="1" variant="secondary" /></TextField></> : null}
{error ? <FieldError className="sm:col-span-2">{error}</FieldError> : null}
</Modal.Body>
) : (
......
......@@ -15,17 +15,20 @@ export default function ReferenceSearchSelect({
value,
onChange,
loadOptions,
resolveMissingIds,
multiple = false,
placeholder = '搜索并选择',
}) {
const [selectedRecordMap, setSelectedRecordMap] = useState(() => new Map());
const valueRecords = useMemo(
() => (Array.isArray(value) ? value : value ? [value] : [])
.filter((item) => typeof item !== 'string'),
[value],
);
const [query, setQuery] = useState('');
const [options, setOptions] = useState(valueRecords);
const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const selectedIds = useMemo(
() => (Array.isArray(value) ? value : value ? [value] : [])
.map(normalizeRecordId)
......@@ -33,20 +36,52 @@ export default function ReferenceSearchSelect({
[value],
);
const mergeRecords = (records) => {
setSelectedRecordMap((current) => {
const nextMap = new Map(current);
records.filter(Boolean).forEach((record) => {
const id = normalizeRecordId(record);
if (id) nextMap.set(id, record);
});
return nextMap;
});
};
useEffect(() => {
if (valueRecords.length > 0) {
setOptions((current) => uniqueRecords([...current, ...valueRecords]));
mergeRecords(valueRecords);
}
}, [valueRecords]);
const missingSelectedIds = selectedIds.filter(
(id) => !selectedRecordMap.get(id),
);
useEffect(() => {
if (!resolveMissingIds || missingSelectedIds.length === 0) return;
let cancelled = false;
resolveMissingIds([...missingSelectedIds]).then((records) => {
if (!cancelled) mergeRecords(records);
}).catch((requestError) => {
if (!cancelled) setError(requestError?.message || '加载已选引用记录失败');
});
return () => {
cancelled = true;
};
}, [missingSelectedIds, resolveMissingIds]);
useEffect(() => {
let cancelled = false;
const timer = window.setTimeout(async () => {
setLoading(true);
setError('');
try {
const records = await loadOptions(query);
if (!cancelled) {
setOptions((current) => uniqueRecords([...current, ...(records || [])]));
mergeRecords(records);
setOptions(records || []);
}
} finally {
if (!cancelled) setLoading(false);
......@@ -59,7 +94,10 @@ export default function ReferenceSearchSelect({
};
}, [loadOptions, query]);
const normalizedOptions = options.map((record) => ({
const normalizedOptions = uniqueRecords([
...selectedIds.map((id) => selectedRecordMap.get(id)).filter(Boolean),
...options,
]).map((record) => ({
id: normalizeRecordId(record),
label: normalizeRecordLabel(record) || normalizeRecordId(record),
record,
......@@ -74,7 +112,9 @@ export default function ReferenceSearchSelect({
: `已选择 ${selectedIds.length} 项`;
return (
<XinyuanAutocomplete
<div className="space-y-1">
{error ? <div className="text-sm text-rose-600">{error}</div> : null}
<XinyuanAutocomplete
aria-label={placeholder}
inputValue={query}
isLoading={loading}
......@@ -100,6 +140,7 @@ export default function ReferenceSearchSelect({
onChange(multiple ? [] : '');
setQuery('');
}}
/>
/>
</div>
);
}
......@@ -196,6 +196,22 @@ export async function searchReferenceRecords(subject, keyword = '') {
return result?.records || [];
}
export async function getReferenceRecordsByIds(subject, ids = []) {
const recordIds = ids.map(normalizeRecordId).filter(Boolean);
if (recordIds.length === 0) return [];
const result = await queryRecords({
subject,
page: 0,
page_size: recordIds.length,
filter: {
_id: { $in: recordIds },
},
mode: 'basic',
});
return result?.records || [];
}
export async function searchTags(keyword = '') {
const result = await queryTags({
page: 0,
......
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