|
|
@@ -1,478 +0,0 @@
|
|
|
-import React, { useState, useEffect } from 'react';
|
|
|
-import { useQuery } from '@tanstack/react-query';
|
|
|
-import {
|
|
|
- Dialog,
|
|
|
- DialogContent,
|
|
|
- DialogHeader,
|
|
|
- DialogTitle,
|
|
|
- DialogFooter,
|
|
|
-} from '@d8d/shared-ui-components/components/ui/dialog';
|
|
|
-import { Button } from '@d8d/shared-ui-components/components/ui/button';
|
|
|
-import { Input } from '@d8d/shared-ui-components/components/ui/input';
|
|
|
-import {
|
|
|
- Select,
|
|
|
- SelectContent,
|
|
|
- SelectItem,
|
|
|
- SelectTrigger,
|
|
|
- SelectValue,
|
|
|
-} from '@d8d/shared-ui-components/components/ui/select';
|
|
|
-import { Label } from '@d8d/shared-ui-components/components/ui/label';
|
|
|
-import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@d8d/shared-ui-components/components/ui/table';
|
|
|
-import { DataTablePagination } from '@d8d/shared-ui-components/components/admin/DataTablePagination';
|
|
|
-import { Checkbox } from '@d8d/shared-ui-components/components/ui/checkbox';
|
|
|
-import { Alert, AlertDescription } from '@d8d/shared-ui-components/components/ui/alert';
|
|
|
-import { AlertCircle } from 'lucide-react';
|
|
|
-import { AreaSelect } from '@d8d/area-management-ui';
|
|
|
-import { disabilityClient } from '../api/disabilityClient';
|
|
|
-import type {
|
|
|
- DisabledPersonData,
|
|
|
- AreaSelection,
|
|
|
-} from '../api/types';
|
|
|
-
|
|
|
-interface DisabledPersonSelectorProps {
|
|
|
- open: boolean;
|
|
|
- onOpenChange: (open: boolean) => void;
|
|
|
- onSelect: (person: DisabledPersonData | DisabledPersonData[]) => void;
|
|
|
- mode?: 'single' | 'multiple';
|
|
|
- selectedIds?: number[];
|
|
|
- disabledIds?: number[];
|
|
|
-}
|
|
|
-
|
|
|
-const DisabledPersonSelector: React.FC<DisabledPersonSelectorProps> = ({
|
|
|
- open,
|
|
|
- onOpenChange,
|
|
|
- onSelect,
|
|
|
- mode = 'single',
|
|
|
- disabledIds = [],
|
|
|
-}) => {
|
|
|
- const [searchParams, setSearchParams] = useState<{
|
|
|
- keyword: string;
|
|
|
- page: number;
|
|
|
- pageSize: number;
|
|
|
- }>({
|
|
|
- keyword: '',
|
|
|
- page: 1,
|
|
|
- pageSize: 10,
|
|
|
- });
|
|
|
- const [selectedPersons, setSelectedPersons] = useState<DisabledPersonData[]>([]);
|
|
|
- const [areaSelection, setAreaSelection] = useState<AreaSelection>({});
|
|
|
- const [showBlacklistConfirm, setShowBlacklistConfirm] = useState(false);
|
|
|
- const [pendingSelection, setPendingSelection] = useState<DisabledPersonData | DisabledPersonData[] | null>(null);
|
|
|
-
|
|
|
- // 搜索残疾人列表
|
|
|
- const { data, isLoading, refetch } = useQuery({
|
|
|
- queryKey: ['disabled-persons-search', searchParams],
|
|
|
- queryFn: async () => {
|
|
|
- const response = await disabilityClient.searchDisabledPersons.$get({
|
|
|
- query: {
|
|
|
- keyword: searchParams.keyword || '',
|
|
|
- skip: ((searchParams.page || 1) - 1) * (searchParams.pageSize || 10),
|
|
|
- take: searchParams.pageSize || 10,
|
|
|
- },
|
|
|
- });
|
|
|
- if (response.status !== 200) throw new Error('搜索残疾人失败');
|
|
|
- return await response.json();
|
|
|
- },
|
|
|
- enabled: open && !!searchParams.keyword,
|
|
|
- });
|
|
|
-
|
|
|
- // 获取所有残疾人列表(当没有搜索关键词时)
|
|
|
- const { data: allData, isLoading: isLoadingAll } = useQuery({
|
|
|
- queryKey: ['disabled-persons-all', searchParams.page, searchParams.pageSize],
|
|
|
- queryFn: async () => {
|
|
|
- const response = await disabilityClient.getAllDisabledPersons.$get({
|
|
|
- query: {
|
|
|
- skip: ((searchParams.page || 1) - 1) * (searchParams.pageSize || 10),
|
|
|
- take: searchParams.pageSize || 10,
|
|
|
- },
|
|
|
- });
|
|
|
- if (response.status !== 200) throw new Error('获取残疾人列表失败');
|
|
|
- return await response.json();
|
|
|
- },
|
|
|
- enabled: open && !searchParams.keyword,
|
|
|
- });
|
|
|
-
|
|
|
- const personsData = searchParams.keyword ? data : allData;
|
|
|
- const isLoadingData = searchParams.keyword ? isLoading : isLoadingAll;
|
|
|
-
|
|
|
- // 重置选择器状态
|
|
|
- useEffect(() => {
|
|
|
- if (!open) {
|
|
|
- setSearchParams({ keyword: '', page: 1, pageSize: 10 });
|
|
|
- setSelectedPersons([]);
|
|
|
- setAreaSelection({});
|
|
|
- setShowBlacklistConfirm(false);
|
|
|
- setPendingSelection(null);
|
|
|
- }
|
|
|
- }, [open]);
|
|
|
-
|
|
|
- // 处理搜索
|
|
|
- const handleSearch = () => {
|
|
|
- refetch();
|
|
|
- };
|
|
|
-
|
|
|
- // 处理重置搜索
|
|
|
- const handleResetSearch = () => {
|
|
|
- setSearchParams({
|
|
|
- keyword: '',
|
|
|
- page: 1,
|
|
|
- pageSize: 10,
|
|
|
- });
|
|
|
- setAreaSelection({});
|
|
|
- };
|
|
|
-
|
|
|
- // 处理选择人员
|
|
|
- const handleSelectPerson = (person: DisabledPersonData) => {
|
|
|
- if (disabledIds.includes(person.id)) {
|
|
|
- return; // 跳过禁用的人员
|
|
|
- }
|
|
|
-
|
|
|
- if (person.isInBlackList === 1) {
|
|
|
- // 黑名单人员需要二次确认
|
|
|
- setPendingSelection(person);
|
|
|
- setShowBlacklistConfirm(true);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (mode === 'single') {
|
|
|
- onSelect(person);
|
|
|
- onOpenChange(false);
|
|
|
- } else {
|
|
|
- const isSelected = selectedPersons.some(p => p.id === person.id);
|
|
|
- let newSelectedPersons: DisabledPersonData[];
|
|
|
-
|
|
|
- if (isSelected) {
|
|
|
- newSelectedPersons = selectedPersons.filter(p => p.id !== person.id);
|
|
|
- } else {
|
|
|
- newSelectedPersons = [...selectedPersons, person];
|
|
|
- }
|
|
|
-
|
|
|
- setSelectedPersons(newSelectedPersons);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- // 处理批量选择
|
|
|
- const handleBatchSelect = () => {
|
|
|
- if (selectedPersons.length === 0) return;
|
|
|
-
|
|
|
- // 检查是否有黑名单人员
|
|
|
- const blacklistPersons = selectedPersons.filter(p => p.isInBlackList === 1);
|
|
|
- if (blacklistPersons.length > 0) {
|
|
|
- setPendingSelection(selectedPersons);
|
|
|
- setShowBlacklistConfirm(true);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- onSelect(selectedPersons);
|
|
|
- onOpenChange(false);
|
|
|
- };
|
|
|
-
|
|
|
- // 确认选择黑名单人员
|
|
|
- const handleConfirmBlacklistSelection = () => {
|
|
|
- if (pendingSelection) {
|
|
|
- onSelect(pendingSelection);
|
|
|
- onOpenChange(false);
|
|
|
- }
|
|
|
- setShowBlacklistConfirm(false);
|
|
|
- setPendingSelection(null);
|
|
|
- };
|
|
|
-
|
|
|
- // 取消选择黑名单人员
|
|
|
- const handleCancelBlacklistSelection = () => {
|
|
|
- setShowBlacklistConfirm(false);
|
|
|
- setPendingSelection(null);
|
|
|
- };
|
|
|
-
|
|
|
- // 处理分页变化
|
|
|
- const handlePageChange = (page: number, pageSize: number) => {
|
|
|
- setSearchParams(prev => ({ ...prev, page, pageSize }));
|
|
|
- };
|
|
|
-
|
|
|
- // 表格列定义
|
|
|
- const columns = [
|
|
|
- { key: 'name', label: '姓名' },
|
|
|
- { key: 'gender', label: '性别' },
|
|
|
- { key: 'idCard', label: '身份证号' },
|
|
|
- { key: 'disabilityId', label: '残疾证号' },
|
|
|
- { key: 'phone', label: '联系电话' },
|
|
|
- { key: 'province', label: '省份' },
|
|
|
- { key: 'city', label: '城市' },
|
|
|
- { key: 'disabilityType', label: '残疾类型' },
|
|
|
- { key: 'disabilityLevel', label: '残疾等级' },
|
|
|
- { key: 'blacklist', label: '黑名单' },
|
|
|
- ];
|
|
|
-
|
|
|
- return (
|
|
|
- <>
|
|
|
- <Dialog open={open} onOpenChange={onOpenChange}>
|
|
|
- <DialogContent className="max-w-6xl max-h-[80vh] overflow-hidden flex flex-col">
|
|
|
- <DialogHeader>
|
|
|
- <DialogTitle>选择残疾人</DialogTitle>
|
|
|
- </DialogHeader>
|
|
|
-
|
|
|
- {/* 搜索区域 */}
|
|
|
- <div className="space-y-4 p-4 border rounded-lg">
|
|
|
- <div className="grid grid-cols-4 gap-4">
|
|
|
- <div className="space-y-2">
|
|
|
- <Label htmlFor="name">姓名</Label>
|
|
|
- <Input
|
|
|
- id="name"
|
|
|
- placeholder="输入姓名"
|
|
|
- value={searchParams.keyword || ''}
|
|
|
- onChange={(e) => setSearchParams(prev => ({ ...prev, keyword: e.target.value }))}
|
|
|
- data-testid="search-name-input"
|
|
|
- />
|
|
|
- </div>
|
|
|
-
|
|
|
- <div className="space-y-2">
|
|
|
- <Label htmlFor="gender">性别</Label>
|
|
|
- <Select
|
|
|
- value={searchParams.keyword?.includes('男') ? '男' : searchParams.keyword?.includes('女') ? '女' : ''}
|
|
|
- onValueChange={(value) => {
|
|
|
- if (value) {
|
|
|
- setSearchParams(prev => ({ ...prev, keyword: value }));
|
|
|
- }
|
|
|
- }}
|
|
|
- >
|
|
|
- <SelectTrigger>
|
|
|
- <SelectValue placeholder="选择性别" />
|
|
|
- </SelectTrigger>
|
|
|
- <SelectContent>
|
|
|
- <SelectItem value="男">男</SelectItem>
|
|
|
- <SelectItem value="女">女</SelectItem>
|
|
|
- </SelectContent>
|
|
|
- </Select>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div className="space-y-2">
|
|
|
- <Label htmlFor="disabilityId">残疾证号</Label>
|
|
|
- <Input
|
|
|
- id="disabilityId"
|
|
|
- placeholder="输入残疾证号"
|
|
|
- value={searchParams.keyword || ''}
|
|
|
- onChange={(e) => setSearchParams(prev => ({ ...prev, keyword: e.target.value }))}
|
|
|
- data-testid="search-disability-id-input"
|
|
|
- />
|
|
|
- </div>
|
|
|
-
|
|
|
- <div className="space-y-2">
|
|
|
- <Label htmlFor="phone">联系电话</Label>
|
|
|
- <Input
|
|
|
- id="phone"
|
|
|
- placeholder="输入联系电话"
|
|
|
- value={searchParams.keyword || ''}
|
|
|
- onChange={(e) => setSearchParams(prev => ({ ...prev, keyword: e.target.value }))}
|
|
|
- data-testid="search-phone-input"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div className="grid grid-cols-4 gap-4">
|
|
|
- <div className="space-y-2">
|
|
|
- <Label>省份/城市</Label>
|
|
|
- <AreaSelect
|
|
|
- value={areaSelection}
|
|
|
- onChange={setAreaSelection}
|
|
|
- data-testid="area-select"
|
|
|
- />
|
|
|
- </div>
|
|
|
-
|
|
|
- <div className="space-y-2">
|
|
|
- <Label htmlFor="disabilityType">残疾类型</Label>
|
|
|
- <Select
|
|
|
- value={searchParams.keyword || ''}
|
|
|
- onValueChange={(value) => {
|
|
|
- if (value) {
|
|
|
- setSearchParams(prev => ({ ...prev, keyword: value }));
|
|
|
- }
|
|
|
- }}
|
|
|
- >
|
|
|
- <SelectTrigger>
|
|
|
- <SelectValue placeholder="选择残疾类型" />
|
|
|
- </SelectTrigger>
|
|
|
- <SelectContent>
|
|
|
- <SelectItem value="视力残疾">视力残疾</SelectItem>
|
|
|
- <SelectItem value="听力残疾">听力残疾</SelectItem>
|
|
|
- <SelectItem value="言语残疾">言语残疾</SelectItem>
|
|
|
- <SelectItem value="肢体残疾">肢体残疾</SelectItem>
|
|
|
- <SelectItem value="智力残疾">智力残疾</SelectItem>
|
|
|
- <SelectItem value="精神残疾">精神残疾</SelectItem>
|
|
|
- <SelectItem value="多重残疾">多重残疾</SelectItem>
|
|
|
- </SelectContent>
|
|
|
- </Select>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div className="space-y-2">
|
|
|
- <Label htmlFor="disabilityLevel">残疾等级</Label>
|
|
|
- <Select
|
|
|
- value={searchParams.keyword || ''}
|
|
|
- onValueChange={(value) => {
|
|
|
- if (value) {
|
|
|
- setSearchParams(prev => ({ ...prev, keyword: value }));
|
|
|
- }
|
|
|
- }}
|
|
|
- >
|
|
|
- <SelectTrigger>
|
|
|
- <SelectValue placeholder="选择残疾等级" />
|
|
|
- </SelectTrigger>
|
|
|
- <SelectContent>
|
|
|
- <SelectItem value="一级">一级</SelectItem>
|
|
|
- <SelectItem value="二级">二级</SelectItem>
|
|
|
- <SelectItem value="三级">三级</SelectItem>
|
|
|
- <SelectItem value="四级">四级</SelectItem>
|
|
|
- </SelectContent>
|
|
|
- </Select>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div className="flex items-end space-x-2">
|
|
|
- <Button onClick={handleSearch} data-testid="search-button">
|
|
|
- 搜索
|
|
|
- </Button>
|
|
|
- <Button variant="outline" onClick={handleResetSearch} data-testid="reset-button">
|
|
|
- 重置
|
|
|
- </Button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- {/* 表格区域 */}
|
|
|
- <div className="flex-1 overflow-auto">
|
|
|
- {isLoadingData ? (
|
|
|
- <div className="flex items-center justify-center h-64">
|
|
|
- <div className="text-center">加载中...</div>
|
|
|
- </div>
|
|
|
- ) : personsData?.data && personsData.data.length > 0 ? (
|
|
|
- <>
|
|
|
- <div className="border rounded-md">
|
|
|
- <Table data-testid="disabled-persons-table">
|
|
|
- <TableHeader>
|
|
|
- <TableRow>
|
|
|
- {mode === 'multiple' && (
|
|
|
- <TableHead className="w-12">
|
|
|
- <Checkbox
|
|
|
- checked={selectedPersons.length === personsData.data.length}
|
|
|
- onCheckedChange={(checked) => {
|
|
|
- if (checked) {
|
|
|
- const selectablePersons = personsData.data.filter(
|
|
|
- (person: DisabledPersonData) => !disabledIds.includes(person.id)
|
|
|
- );
|
|
|
- setSelectedPersons(selectablePersons);
|
|
|
- } else {
|
|
|
- setSelectedPersons([]);
|
|
|
- }
|
|
|
- }}
|
|
|
- aria-label="全选"
|
|
|
- />
|
|
|
- </TableHead>
|
|
|
- )}
|
|
|
- {columns.map((column) => (
|
|
|
- <TableHead key={column.key}>{column.label}</TableHead>
|
|
|
- ))}
|
|
|
- </TableRow>
|
|
|
- </TableHeader>
|
|
|
- <TableBody>
|
|
|
- {personsData.data.map((person: DisabledPersonData) => (
|
|
|
- <TableRow
|
|
|
- key={person.id}
|
|
|
- onClick={() => mode === 'single' && handleSelectPerson(person)}
|
|
|
- className={mode === 'single' ? 'cursor-pointer hover:bg-muted' : ''}
|
|
|
- data-testid={`table-row-${person.id}`}
|
|
|
- >
|
|
|
- {mode === 'multiple' && (
|
|
|
- <TableCell>
|
|
|
- <Checkbox
|
|
|
- checked={selectedPersons.some(p => p.id === person.id)}
|
|
|
- onCheckedChange={(checked) => {
|
|
|
- if (checked) {
|
|
|
- setSelectedPersons([...selectedPersons, person]);
|
|
|
- } else {
|
|
|
- setSelectedPersons(selectedPersons.filter(p => p.id !== person.id));
|
|
|
- }
|
|
|
- }}
|
|
|
- disabled={disabledIds.includes(person.id)}
|
|
|
- aria-label="选择"
|
|
|
- />
|
|
|
- </TableCell>
|
|
|
- )}
|
|
|
- <TableCell>{person.name}</TableCell>
|
|
|
- <TableCell>{person.gender}</TableCell>
|
|
|
- <TableCell>{person.idCard}</TableCell>
|
|
|
- <TableCell>{person.disabilityId}</TableCell>
|
|
|
- <TableCell>{person.phone}</TableCell>
|
|
|
- <TableCell>{person.province}</TableCell>
|
|
|
- <TableCell>{person.city}</TableCell>
|
|
|
- <TableCell>{person.disabilityType}</TableCell>
|
|
|
- <TableCell>{person.disabilityLevel}</TableCell>
|
|
|
- <TableCell>{person.isInBlackList === 1 ? '是' : '否'}</TableCell>
|
|
|
- </TableRow>
|
|
|
- ))}
|
|
|
- </TableBody>
|
|
|
- </Table>
|
|
|
- </div>
|
|
|
- <DataTablePagination
|
|
|
- currentPage={searchParams.page || 1}
|
|
|
- pageSize={searchParams.pageSize || 10}
|
|
|
- totalCount={personsData.total || 0}
|
|
|
- onPageChange={handlePageChange}
|
|
|
- data-testid="pagination"
|
|
|
- />
|
|
|
- </>
|
|
|
- ) : (
|
|
|
- <div className="flex items-center justify-center h-64">
|
|
|
- <div className="text-center">暂无数据</div>
|
|
|
- </div>
|
|
|
- )}
|
|
|
- </div>
|
|
|
-
|
|
|
- {/* 底部操作区域 */}
|
|
|
- <DialogFooter className="flex justify-between">
|
|
|
- <div className="text-sm text-muted-foreground">
|
|
|
- {mode === 'multiple' && (
|
|
|
- <span>已选择 {selectedPersons.length} 人</span>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- <div className="space-x-2">
|
|
|
- <Button variant="outline" onClick={() => onOpenChange(false)} data-testid="cancel-button">
|
|
|
- 取消
|
|
|
- </Button>
|
|
|
- {mode === 'multiple' && (
|
|
|
- <Button
|
|
|
- onClick={handleBatchSelect}
|
|
|
- disabled={selectedPersons.length === 0}
|
|
|
- data-testid="confirm-batch-button"
|
|
|
- >
|
|
|
- 确认选择 ({selectedPersons.length})
|
|
|
- </Button>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- </DialogFooter>
|
|
|
- </DialogContent>
|
|
|
- </Dialog>
|
|
|
-
|
|
|
- {/* 黑名单确认对话框 */}
|
|
|
- <Dialog open={showBlacklistConfirm} onOpenChange={setShowBlacklistConfirm}>
|
|
|
- <DialogContent>
|
|
|
- <DialogHeader>
|
|
|
- <DialogTitle>确认选择黑名单人员</DialogTitle>
|
|
|
- </DialogHeader>
|
|
|
- <Alert variant="destructive">
|
|
|
- <AlertCircle className="h-4 w-4" />
|
|
|
- <AlertDescription>
|
|
|
- 您选择的人员在黑名单中,是否确认选择?
|
|
|
- </AlertDescription>
|
|
|
- </Alert>
|
|
|
- <DialogFooter>
|
|
|
- <Button variant="outline" onClick={handleCancelBlacklistSelection} data-testid="cancel-blacklist-button">
|
|
|
- 取消
|
|
|
- </Button>
|
|
|
- <Button onClick={handleConfirmBlacklistSelection} data-testid="confirm-blacklist-button">
|
|
|
- 确认选择
|
|
|
- </Button>
|
|
|
- </DialogFooter>
|
|
|
- </DialogContent>
|
|
|
- </Dialog>
|
|
|
- </>
|
|
|
- );
|
|
|
-};
|
|
|
-
|
|
|
-export default DisabledPersonSelector;
|