添加体检客户列表懒加载&搜索
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState, useRef } from 'react';
|
||||||
import type { ExamClient, ExamModalTab } from '../../data/mockData';
|
import type { ExamClient, ExamModalTab } from '../../data/mockData';
|
||||||
import { EXAM_TAGS } from '../../data/mockData';
|
import { EXAM_TAGS } from '../../data/mockData';
|
||||||
import { getTodayExamProgress } from '../../api';
|
import { getTodayExamProgress } from '../../api';
|
||||||
import { isExamActionDone } from '../../utils/examActions';
|
import { isExamActionDone } from '../../utils/examActions';
|
||||||
import { Badge, Card, CardContent, CardHeader, InfoCard } from '../ui';
|
import { Badge, Card, CardContent, CardHeader, InfoCard, Input } from '../ui';
|
||||||
import { cls } from '../../utils/cls';
|
import { cls } from '../../utils/cls';
|
||||||
|
|
||||||
interface ExamSectionProps {
|
interface ExamSectionProps {
|
||||||
@@ -12,14 +12,23 @@ interface ExamSectionProps {
|
|||||||
examFilterTag: (typeof EXAM_TAGS)[number];
|
examFilterTag: (typeof EXAM_TAGS)[number];
|
||||||
onFilterChange: (tag: (typeof EXAM_TAGS)[number]) => void;
|
onFilterChange: (tag: (typeof EXAM_TAGS)[number]) => void;
|
||||||
onOpenModal: (id: string, tab: ExamModalTab) => void;
|
onOpenModal: (id: string, tab: ExamModalTab) => void;
|
||||||
|
searchValue: string;
|
||||||
|
onSearchChange: (value: string) => void;
|
||||||
|
loading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const INITIAL_LOAD_COUNT = 9; // 初始加载数量(3列 x 3行)
|
||||||
|
const LOAD_MORE_COUNT = 9; // 每次加载更多时的数量
|
||||||
|
|
||||||
export const ExamSection = ({
|
export const ExamSection = ({
|
||||||
filteredClients,
|
filteredClients,
|
||||||
selectedExamClient,
|
selectedExamClient,
|
||||||
examFilterTag,
|
examFilterTag,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
onOpenModal,
|
onOpenModal,
|
||||||
|
searchValue,
|
||||||
|
onSearchChange,
|
||||||
|
loading = false,
|
||||||
}: ExamSectionProps) => {
|
}: ExamSectionProps) => {
|
||||||
const [progressStats, setProgressStats] = useState([
|
const [progressStats, setProgressStats] = useState([
|
||||||
{ label: '预约人数', value: 0 },
|
{ label: '预约人数', value: 0 },
|
||||||
@@ -27,6 +36,9 @@ export const ExamSection = ({
|
|||||||
{ label: '体检中', value: 0 },
|
{ label: '体检中', value: 0 },
|
||||||
{ label: '用餐', value: 0 },
|
{ label: '用餐', value: 0 },
|
||||||
]);
|
]);
|
||||||
|
const [displayCount, setDisplayCount] = useState(INITIAL_LOAD_COUNT);
|
||||||
|
const observerTarget = useRef<HTMLDivElement>(null);
|
||||||
|
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getTodayExamProgress({})
|
getTodayExamProgress({})
|
||||||
@@ -44,6 +56,49 @@ export const ExamSection = ({
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 当 filteredClients 变化时,重置显示数量
|
||||||
|
useEffect(() => {
|
||||||
|
setDisplayCount(INITIAL_LOAD_COUNT);
|
||||||
|
}, [filteredClients.length, searchValue, examFilterTag]);
|
||||||
|
|
||||||
|
// 懒加载:使用 Intersection Observer 监听底部元素
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
if (entries[0].isIntersecting && !loading && !isLoadingMore) {
|
||||||
|
const hasMore = displayCount < filteredClients.length;
|
||||||
|
if (hasMore) {
|
||||||
|
setIsLoadingMore(true);
|
||||||
|
// 模拟加载延迟,让用户看到 loading 效果
|
||||||
|
setTimeout(() => {
|
||||||
|
setDisplayCount((prev) => Math.min(prev + LOAD_MORE_COUNT, filteredClients.length));
|
||||||
|
setIsLoadingMore(false);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: null,
|
||||||
|
rootMargin: '100px',
|
||||||
|
threshold: 0.1,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentTarget = observerTarget.current;
|
||||||
|
if (currentTarget) {
|
||||||
|
observer.observe(currentTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (currentTarget) {
|
||||||
|
observer.unobserve(currentTarget);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [displayCount, filteredClients.length, loading, isLoadingMore]);
|
||||||
|
|
||||||
|
const displayedClients = filteredClients.slice(0, displayCount);
|
||||||
|
const hasMore = displayCount < filteredClients.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='space-y-4'>
|
<div className='space-y-4'>
|
||||||
<Card>
|
<Card>
|
||||||
@@ -56,123 +111,192 @@ export const ExamSection = ({
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
<div className='w-[320px]'>
|
||||||
|
<Input
|
||||||
|
placeholder='搜索 姓名 / 手机号 / 身份证号 / 卡号'
|
||||||
|
value={searchValue}
|
||||||
|
onChange={(e) => onSearchChange(e.target.value)}
|
||||||
|
className='text-sm'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<span>体检客户列表</span>
|
<div className='flex items-center justify-between w-full'>
|
||||||
<div className='flex items-center gap-2 text-xs'>
|
<span>体检客户列表</span>
|
||||||
{EXAM_TAGS.map((tag) => (
|
<div className='flex items-center gap-3'>
|
||||||
<button
|
<div className='flex items-center gap-2 text-xs'>
|
||||||
key={tag}
|
{EXAM_TAGS.map((tag) => (
|
||||||
onClick={() => onFilterChange(tag)}
|
<button
|
||||||
className={cls(
|
key={tag}
|
||||||
'px-3 py-1 rounded-2xl border',
|
onClick={() => onFilterChange(tag)}
|
||||||
examFilterTag === tag ? 'bg-gray-900 text-white border-gray-900' : 'bg-white text-gray-700',
|
className={cls(
|
||||||
)}
|
'px-3 py-1 rounded-2xl border',
|
||||||
>
|
examFilterTag === tag ? 'bg-gray-900 text-white border-gray-900' : 'bg-white text-gray-700',
|
||||||
{tag}
|
)}
|
||||||
</button>
|
>
|
||||||
))}
|
{tag}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{filteredClients.length === 0 ? (
|
{loading && filteredClients.length === 0 ? (
|
||||||
|
<div className='text-center text-gray-500 py-8'>
|
||||||
|
<div className='flex items-center justify-center gap-2'>
|
||||||
|
<svg
|
||||||
|
className='animate-spin h-5 w-5 text-gray-600'
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
fill='none'
|
||||||
|
viewBox='0 0 24 24'
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
className='opacity-25'
|
||||||
|
cx='12'
|
||||||
|
cy='12'
|
||||||
|
r='10'
|
||||||
|
stroke='currentColor'
|
||||||
|
strokeWidth='4'
|
||||||
|
></circle>
|
||||||
|
<path
|
||||||
|
className='opacity-75'
|
||||||
|
fill='currentColor'
|
||||||
|
d='M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z'
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<span className='text-sm'>加载中...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : filteredClients.length === 0 ? (
|
||||||
<div className='text-center text-gray-500 py-8'>
|
<div className='text-center text-gray-500 py-8'>
|
||||||
<div className='text-sm'>暂无体检客户数据</div>
|
<div className='text-sm'>暂无体检客户数据</div>
|
||||||
<div className='text-xs mt-1'>请尝试调整筛选条件或搜索关键词</div>
|
<div className='text-xs mt-1'>请尝试调整筛选条件或搜索关键词</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className='grid grid-cols-3 gap-3 text-sm'>
|
<>
|
||||||
{filteredClients.map((client) => {
|
<div className='grid grid-cols-3 gap-3 text-sm'>
|
||||||
// 检查操作记录:优先使用 localStorage 记录,如果没有则使用原有逻辑
|
{displayedClients.map((client) => {
|
||||||
const idCardSignInDone = isExamActionDone(client.id, 'idCardSignIn');
|
// 检查操作记录:优先使用 localStorage 记录,如果没有则使用原有逻辑
|
||||||
const printSignDone = isExamActionDone(client.id, 'printSign');
|
const idCardSignInDone = isExamActionDone(client.id, 'idCardSignIn');
|
||||||
|
const printSignDone = isExamActionDone(client.id, 'printSign');
|
||||||
|
|
||||||
const signDone = idCardSignInDone || client.signStatus === '已登记' || client.checkedItems.includes('签到');
|
const signDone = idCardSignInDone || client.signStatus === '已登记' || client.checkedItems.includes('签到');
|
||||||
const addonCount = client.addonCount || 0;
|
const addonCount = client.addonCount || 0;
|
||||||
const printDone = printSignDone || !!client.guidePrinted;
|
const printDone = printSignDone || !!client.guidePrinted;
|
||||||
const openModal = (tab: ExamModalTab) => onOpenModal(client.id, tab);
|
const openModal = (tab: ExamModalTab) => onOpenModal(client.id, tab);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={client.id}
|
key={client.id}
|
||||||
role='button'
|
role='button'
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={() => openModal('detail')}
|
onClick={() => openModal('detail')}
|
||||||
className={cls(
|
className={cls(
|
||||||
'text-left p-3 rounded-2xl border bg-white hover:bg-gray-50 flex flex-col gap-1 cursor-pointer',
|
'text-left p-3 rounded-2xl border bg-white hover:bg-gray-50 flex flex-col gap-1 cursor-pointer',
|
||||||
selectedExamClient?.id === client.id && 'border-gray-900 bg-gray-50',
|
selectedExamClient?.id === client.id && 'border-gray-900 bg-gray-50',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className='flex items-center justify-between mb-1'>
|
<div className='flex items-center justify-between mb-1'>
|
||||||
<span className='font-medium'>{client.name}</span>
|
<span className='font-medium'>{client.name}</span>
|
||||||
<Badge>{client.level}</Badge>
|
<Badge>{client.level}</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div className='text-xs text-gray-500 truncate'>套餐:{client.packageName}</div>
|
<div className='text-xs text-gray-500 truncate'>套餐:{client.packageName}</div>
|
||||||
<div className='flex items-center justify-between text-xs text-gray-500 mt-1'>
|
<div className='flex items-center justify-between text-xs text-gray-500 mt-1'>
|
||||||
<span>状态:{client.status}</span>
|
<span>状态:{client.status}</span>
|
||||||
<span>已耗时:{client.elapsed}</span>
|
<span>已耗时:{client.elapsed}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='mt-2 flex flex-wrap gap-1 text-[11px]'>
|
<div className='mt-2 flex flex-wrap gap-1 text-[11px]'>
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
className='px-2 py-0.5 rounded-2xl border bg-white hover:bg-gray-100 flex items-center gap-1'
|
className='px-2 py-0.5 rounded-2xl border bg-white hover:bg-gray-100 flex items-center gap-1'
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
openModal('detail');
|
openModal('detail');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>详情</span>
|
<span>详情</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
className='px-2 py-0.5 rounded-2xl border bg-white hover:bg-gray-100 flex items-center gap-1'
|
className='px-2 py-0.5 rounded-2xl border bg-white hover:bg-gray-100 flex items-center gap-1'
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
openModal('sign');
|
openModal('sign');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>签到</span>
|
<span>签到</span>
|
||||||
{signDone && <span>✅</span>}
|
{signDone && <span>✅</span>}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
className='px-2 py-0.5 rounded-2xl border bg-white hover:bg-gray-100 flex items-center gap-1'
|
className='px-2 py-0.5 rounded-2xl border bg-white hover:bg-gray-100 flex items-center gap-1'
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
openModal('addon');
|
openModal('addon');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>加项</span>
|
<span>加项</span>
|
||||||
{addonCount > 0 && <span className='opacity-80'>({addonCount})</span>}
|
{addonCount > 0 && <span className='opacity-80'>({addonCount})</span>}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
className='px-2 py-0.5 rounded-2xl border bg-white hover:bg-gray-100 flex items-center gap-1'
|
className='px-2 py-0.5 rounded-2xl border bg-white hover:bg-gray-100 flex items-center gap-1'
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
openModal('print');
|
openModal('print');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>打印导检单</span>
|
<span>打印导检单</span>
|
||||||
{printDone && <span>✅</span>}
|
{printDone && <span>✅</span>}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
className='px-2 py-0.5 rounded-2xl border bg-white hover:bg-gray-100 flex items-center gap-1'
|
className='px-2 py-0.5 rounded-2xl border bg-white hover:bg-gray-100 flex items-center gap-1'
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
openModal('delivery');
|
openModal('delivery');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>报告寄送</span>
|
<span>报告寄送</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
})}
|
||||||
})}
|
</div>
|
||||||
</div>
|
{/* 懒加载触发区域和 loading */}
|
||||||
|
{(hasMore || isLoadingMore) && (
|
||||||
|
<div ref={observerTarget} className='flex items-center justify-center py-4'>
|
||||||
|
{isLoadingMore && (
|
||||||
|
<div className='flex items-center gap-2 text-gray-500'>
|
||||||
|
<svg
|
||||||
|
className='animate-spin h-4 w-4 text-gray-600'
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
fill='none'
|
||||||
|
viewBox='0 0 24 24'
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
className='opacity-25'
|
||||||
|
cx='12'
|
||||||
|
cy='12'
|
||||||
|
r='10'
|
||||||
|
stroke='currentColor'
|
||||||
|
strokeWidth='4'
|
||||||
|
></circle>
|
||||||
|
<path
|
||||||
|
className='opacity-75'
|
||||||
|
fill='currentColor'
|
||||||
|
d='M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z'
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<span className='text-xs'>加载更多...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { useOutletContext } from 'react-router-dom';
|
|
||||||
|
|
||||||
import type { ExamClient, ExamModalTab } from '../data/mockData';
|
import type { ExamClient, ExamModalTab } from '../data/mockData';
|
||||||
import { EXAM_TAGS } from '../data/mockData';
|
import { EXAM_TAGS } from '../data/mockData';
|
||||||
import { ExamSection } from '../components/exam/ExamSection';
|
import { ExamSection } from '../components/exam/ExamSection';
|
||||||
import { ExamModal } from '../components/exam/ExamModal';
|
import { ExamModal } from '../components/exam/ExamModal';
|
||||||
import type { MainLayoutContext } from '../layouts/MainLayout';
|
|
||||||
import { getPhysicalExamCustomerList } from '../api';
|
import { getPhysicalExamCustomerList } from '../api';
|
||||||
|
|
||||||
export const ExamPage = () => {
|
export const ExamPage = () => {
|
||||||
const { search } = useOutletContext<MainLayoutContext>();
|
const [searchValue, setSearchValue] = useState<string>('');
|
||||||
const [clients, setClients] = useState<ExamClient[]>([]);
|
const [clients, setClients] = useState<ExamClient[]>([]);
|
||||||
const [examSelectedId, setExamSelectedId] = useState<string>('');
|
const [examSelectedId, setExamSelectedId] = useState<string>('');
|
||||||
const [examPanelTab, setExamPanelTab] = useState<ExamModalTab>('detail');
|
const [examPanelTab, setExamPanelTab] = useState<ExamModalTab>('detail');
|
||||||
const [examModalOpen, setExamModalOpen] = useState(false);
|
const [examModalOpen, setExamModalOpen] = useState(false);
|
||||||
const [examFilterTag, setExamFilterTag] = useState<(typeof EXAM_TAGS)[number]>('全部');
|
const [examFilterTag, setExamFilterTag] = useState<(typeof EXAM_TAGS)[number]>('全部');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
// 将筛选标签映射为接口 filter_type
|
// 将筛选标签映射为接口 filter_type
|
||||||
const filterType = useMemo(() => {
|
const filterType = useMemo(() => {
|
||||||
@@ -40,14 +39,61 @@ export const ExamPage = () => {
|
|||||||
}
|
}
|
||||||
}, [examFilterTag]);
|
}, [examFilterTag]);
|
||||||
|
|
||||||
|
// 智能识别搜索内容类型
|
||||||
|
const getSearchParams = useMemo(() => {
|
||||||
|
const trimmed = searchValue.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return {
|
||||||
|
customer_name: undefined,
|
||||||
|
phone: undefined,
|
||||||
|
id_no: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否为手机号(11位数字,以1开头)
|
||||||
|
if (/^1[3-9]\d{9}$/.test(trimmed)) {
|
||||||
|
return {
|
||||||
|
customer_name: undefined,
|
||||||
|
phone: trimmed,
|
||||||
|
id_no: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否为身份证号(15位或18位,最后一位可能是X)
|
||||||
|
if (/^(\d{15}|\d{17}[\dXx])$/.test(trimmed)) {
|
||||||
|
return {
|
||||||
|
customer_name: undefined,
|
||||||
|
phone: undefined,
|
||||||
|
id_no: trimmed,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否为卡号(纯数字,长度在6-20位之间)
|
||||||
|
if (/^\d{6,20}$/.test(trimmed)) {
|
||||||
|
// 接口中没有卡号字段,暂时使用 customer_name 搜索
|
||||||
|
// 如果后续接口支持卡号字段,可以添加 card_no 参数
|
||||||
|
return {
|
||||||
|
customer_name: trimmed,
|
||||||
|
phone: undefined,
|
||||||
|
id_no: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认为姓名
|
||||||
|
return {
|
||||||
|
customer_name: trimmed,
|
||||||
|
phone: undefined,
|
||||||
|
id_no: undefined,
|
||||||
|
};
|
||||||
|
}, [searchValue]);
|
||||||
|
|
||||||
// 从接口拉取体检客户列表
|
// 从接口拉取体检客户列表
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const payload = {
|
const payload = {
|
||||||
customer_name: search.trim() || undefined,
|
...getSearchParams,
|
||||||
phone: undefined,
|
|
||||||
id_no: undefined,
|
|
||||||
filter_type: filterType,
|
filter_type: filterType,
|
||||||
};
|
};
|
||||||
|
setLoading(true);
|
||||||
getPhysicalExamCustomerList(payload)
|
getPhysicalExamCustomerList(payload)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const list = res.Data || [];
|
const list = res.Data || [];
|
||||||
@@ -96,8 +142,11 @@ export const ExamPage = () => {
|
|||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error('获取体检客户列表失败', err);
|
console.error('获取体检客户列表失败', err);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false);
|
||||||
});
|
});
|
||||||
}, [search, filterType, examSelectedId]);
|
}, [getSearchParams, filterType, examSelectedId]);
|
||||||
|
|
||||||
const selectedExamClient: ExamClient | undefined = useMemo(
|
const selectedExamClient: ExamClient | undefined = useMemo(
|
||||||
() => clients.find((c) => c.id === examSelectedId) || clients[0],
|
() => clients.find((c) => c.id === examSelectedId) || clients[0],
|
||||||
@@ -118,6 +167,9 @@ export const ExamPage = () => {
|
|||||||
examFilterTag={examFilterTag}
|
examFilterTag={examFilterTag}
|
||||||
onFilterChange={setExamFilterTag}
|
onFilterChange={setExamFilterTag}
|
||||||
onOpenModal={handleOpenModal}
|
onOpenModal={handleOpenModal}
|
||||||
|
searchValue={searchValue}
|
||||||
|
onSearchChange={setSearchValue}
|
||||||
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{examModalOpen && selectedExamClient && (
|
{examModalOpen && selectedExamClient && (
|
||||||
|
|||||||
Reference in New Issue
Block a user