Areas.tsx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. import React from 'react';
  2. import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
  3. import { Button } from '@/client/components/ui/button';
  4. import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
  5. import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/client/components/ui/table';
  6. import { DataTablePagination } from '../components/DataTablePagination';
  7. import { Plus, Edit, Trash2, Search, Power, ListTree, Table as TableIcon, RotateCcw } from 'lucide-react';
  8. import { useState, useCallback } from 'react';
  9. import { areaClient } from '@/client/api';
  10. import type { InferResponseType, InferRequestType } from 'hono/client';
  11. import { Input } from '@/client/components/ui/input';
  12. import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/client/components/ui/select';
  13. import { Badge } from '@/client/components/ui/badge';
  14. import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/client/components/ui/dialog';
  15. import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/client/components/ui/alert-dialog';
  16. import { AreaForm } from '../components/AreaForm';
  17. import { AreaTree } from '../components/AreaTree';
  18. import type { CreateAreaInput, UpdateAreaInput } from '@d8d/server/modules/areas/area.schema';
  19. import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/client/components/ui/tabs';
  20. // 类型提取规范
  21. type AreaResponse = InferResponseType<typeof areaClient.$get, 200>['data'][0];
  22. type SearchAreaRequest = InferRequestType<typeof areaClient.$get>['query'];
  23. type CreateAreaRequest = InferRequestType<typeof areaClient.$post>['json'];
  24. type UpdateAreaRequest = InferRequestType<typeof areaClient[':id']['$put']>['json'];
  25. // 树形节点类型
  26. interface AreaNode {
  27. id: number;
  28. name: string;
  29. code: string;
  30. level: number;
  31. parentId: number | null;
  32. isDisabled: number;
  33. children?: AreaNode[];
  34. }
  35. // 统一操作处理函数
  36. const handleOperation = async (operation: () => Promise<any>) => {
  37. try {
  38. await operation();
  39. // toast.success('操作成功');
  40. console.log('操作成功');
  41. } catch (error) {
  42. console.error('操作失败:', error);
  43. // toast.error('操作失败,请重试');
  44. throw error;
  45. }
  46. };
  47. // 防抖搜索函数
  48. const debounce = (func: Function, delay: number) => {
  49. let timeoutId: NodeJS.Timeout;
  50. return (...args: any[]) => {
  51. clearTimeout(timeoutId);
  52. timeoutId = setTimeout(() => func(...args), delay);
  53. };
  54. };
  55. export const AreasPage: React.FC = () => {
  56. const queryClient = useQueryClient();
  57. const [page, setPage] = useState(1);
  58. const [pageSize, setPageSize] = useState(20);
  59. const [keyword, setKeyword] = useState('');
  60. const [level, setLevel] = useState<string>('all');
  61. const [parentId, setParentId] = useState<string>('');
  62. const [isDisabled, setIsDisabled] = useState<string>('all');
  63. const [viewMode, setViewMode] = useState<'table' | 'tree'>('table');
  64. const [expandedNodes, setExpandedNodes] = useState<Set<number>>(new Set());
  65. const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
  66. const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
  67. const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
  68. const [isStatusDialogOpen, setIsStatusDialogOpen] = useState(false);
  69. const [selectedArea, setSelectedArea] = useState<AreaResponse | null>(null);
  70. // 构建搜索参数
  71. const filters: Record<string, any> = {};
  72. if (level && level !== 'all') filters.level = Number(level);
  73. if (parentId) filters.parentId = Number(parentId);
  74. if (isDisabled && isDisabled !== 'all') filters.isDisabled = Number(isDisabled);
  75. const searchParams:SearchAreaRequest = {
  76. page,
  77. pageSize,
  78. keyword: keyword || undefined,
  79. filters: Object.keys(filters).length > 0 ? JSON.stringify(filters) : undefined,
  80. sortBy: 'id',
  81. sortOrder: 'ASC'
  82. };
  83. // 查询省市区列表
  84. const { data, isLoading } = useQuery({
  85. queryKey: ['areas', searchParams],
  86. queryFn: async () => {
  87. const res = await areaClient.$get({
  88. query: searchParams
  89. });
  90. if (res.status !== 200) throw new Error('获取省市区列表失败');
  91. return await res.json();
  92. },
  93. staleTime: 5 * 60 * 1000,
  94. gcTime: 10 * 60 * 1000,
  95. });
  96. // 查询树形结构数据
  97. const { data: treeData, isLoading: isTreeLoading } = useQuery({
  98. queryKey: ['areas-tree'],
  99. queryFn: async () => {
  100. const res = await areaClient.tree.$get();
  101. if (res.status !== 200) throw new Error('获取省市区树形数据失败');
  102. const response = await res.json();
  103. return response.data;
  104. },
  105. staleTime: 5 * 60 * 1000,
  106. gcTime: 10 * 60 * 1000,
  107. enabled: viewMode === 'tree'
  108. });
  109. // 创建省市区
  110. const createMutation = useMutation({
  111. mutationFn: async (data: CreateAreaRequest) => {
  112. await handleOperation(async () => {
  113. const res = await areaClient.$post({ json: data });
  114. if (res.status !== 201) throw new Error('创建省市区失败');
  115. });
  116. },
  117. onSuccess: () => {
  118. queryClient.invalidateQueries({ queryKey: ['areas'] });
  119. setIsCreateDialogOpen(false);
  120. },
  121. });
  122. // 更新省市区
  123. const updateMutation = useMutation({
  124. mutationFn: async ({ id, data }: { id: number; data: UpdateAreaRequest }) => {
  125. await handleOperation(async () => {
  126. const res = await areaClient[':id'].$put({
  127. param: { id },
  128. json: data
  129. });
  130. if (res.status !== 200) throw new Error('更新省市区失败');
  131. });
  132. },
  133. onSuccess: () => {
  134. queryClient.invalidateQueries({ queryKey: ['areas'] });
  135. setIsEditDialogOpen(false);
  136. setSelectedArea(null);
  137. },
  138. });
  139. // 删除省市区
  140. const deleteMutation = useMutation({
  141. mutationFn: async (id: number) => {
  142. await handleOperation(async () => {
  143. const res = await areaClient[':id'].$delete({
  144. param: { id }
  145. });
  146. if (res.status !== 204) throw new Error('删除省市区失败');
  147. });
  148. },
  149. onSuccess: () => {
  150. queryClient.invalidateQueries({ queryKey: ['areas'] });
  151. setIsDeleteDialogOpen(false);
  152. setSelectedArea(null);
  153. },
  154. });
  155. // 启用/禁用省市区
  156. const toggleStatusMutation = useMutation({
  157. mutationFn: async ({ id, isDisabled }: { id: number; isDisabled: number }) => {
  158. await handleOperation(async () => {
  159. const res = await areaClient[':id'].$put({
  160. param: { id },
  161. json: { isDisabled }
  162. });
  163. if (res.status !== 200) throw new Error('更新省市区状态失败');
  164. });
  165. },
  166. onSuccess: () => {
  167. queryClient.invalidateQueries({ queryKey: ['areas'] });
  168. setIsStatusDialogOpen(false);
  169. setSelectedArea(null);
  170. },
  171. });
  172. // 防抖搜索
  173. const debouncedSearch = useCallback(
  174. debounce((keyword: string) => {
  175. setKeyword(keyword);
  176. setPage(1);
  177. }, 300),
  178. []
  179. );
  180. // 处理筛选变化
  181. const handleFilterChange = (filterType: string, value: string) => {
  182. switch (filterType) {
  183. case 'level':
  184. setLevel(value);
  185. break;
  186. case 'parentId':
  187. setParentId(value);
  188. break;
  189. case 'isDisabled':
  190. setIsDisabled(value);
  191. break;
  192. }
  193. setPage(1);
  194. };
  195. // 处理创建省市区
  196. const handleCreateArea = async (data: CreateAreaInput | UpdateAreaInput) => {
  197. await createMutation.mutateAsync(data as CreateAreaInput);
  198. };
  199. // 处理更新省市区
  200. const handleUpdateArea = async (data: UpdateAreaInput) => {
  201. if (!selectedArea) return;
  202. await updateMutation.mutateAsync({ id: selectedArea.id, data });
  203. };
  204. // 处理删除省市区
  205. const handleDeleteArea = async () => {
  206. if (!selectedArea) return;
  207. await deleteMutation.mutateAsync(selectedArea.id);
  208. };
  209. // 处理启用/禁用省市区
  210. const handleToggleStatus = async (isDisabled: number) => {
  211. if (!selectedArea) return;
  212. await toggleStatusMutation.mutateAsync({ id: selectedArea.id, isDisabled });
  213. };
  214. // 打开编辑对话框
  215. const handleEdit = (area: AreaNode) => {
  216. // 将 AreaNode 转换为 AreaResponse
  217. const areaResponse: AreaResponse = {
  218. ...area,
  219. isDeleted: 0,
  220. createdAt: new Date().toISOString(),
  221. updatedAt: new Date().toISOString(),
  222. createdBy: null,
  223. updatedBy: null
  224. };
  225. setSelectedArea(areaResponse);
  226. setIsEditDialogOpen(true);
  227. };
  228. // 打开删除对话框
  229. const handleDelete = (area: AreaNode) => {
  230. // 将 AreaNode 转换为 AreaResponse
  231. const areaResponse: AreaResponse = {
  232. ...area,
  233. isDeleted: 0,
  234. createdAt: new Date().toISOString(),
  235. updatedAt: new Date().toISOString(),
  236. createdBy: null,
  237. updatedBy: null
  238. };
  239. setSelectedArea(areaResponse);
  240. setIsDeleteDialogOpen(true);
  241. };
  242. // 打开状态切换对话框
  243. const handleToggleStatusDialog = (area: AreaNode) => {
  244. // 将 AreaNode 转换为 AreaResponse
  245. const areaResponse: AreaResponse = {
  246. ...area,
  247. isDeleted: 0,
  248. createdAt: new Date().toISOString(),
  249. updatedAt: new Date().toISOString(),
  250. createdBy: null,
  251. updatedBy: null
  252. };
  253. setSelectedArea(areaResponse);
  254. setIsStatusDialogOpen(true);
  255. };
  256. // 切换节点展开状态
  257. const handleToggleNode = (nodeId: number) => {
  258. setExpandedNodes(prev => {
  259. const newSet = new Set(prev);
  260. if (newSet.has(nodeId)) {
  261. newSet.delete(nodeId);
  262. } else {
  263. newSet.add(nodeId);
  264. }
  265. return newSet;
  266. });
  267. };
  268. // 重置筛选
  269. const handleResetFilters = () => {
  270. setKeyword('');
  271. setLevel('all');
  272. setParentId('');
  273. setIsDisabled('all');
  274. setPage(1);
  275. };
  276. // 获取层级显示名称
  277. const getLevelName = (level: number) => {
  278. switch (level) {
  279. case 1: return '省/直辖市';
  280. case 2: return '市';
  281. case 3: return '区/县';
  282. default: return '未知';
  283. }
  284. };
  285. return (
  286. <div className="space-y-6">
  287. <div className="flex items-center justify-between">
  288. <div>
  289. <h1 className="text-3xl font-bold tracking-tight">省市区管理</h1>
  290. <p className="text-muted-foreground">
  291. 管理省市区三级联动数据
  292. </p>
  293. </div>
  294. <div className="flex gap-2">
  295. <Button
  296. variant={viewMode === 'table' ? 'default' : 'outline'}
  297. size="sm"
  298. onClick={() => setViewMode('table')}
  299. >
  300. <TableIcon className="mr-2 h-4 w-4" />
  301. 表格视图
  302. </Button>
  303. <Button
  304. variant={viewMode === 'tree' ? 'default' : 'outline'}
  305. size="sm"
  306. onClick={() => setViewMode('tree')}
  307. >
  308. <ListTree className="mr-2 h-4 w-4" />
  309. 树形视图
  310. </Button>
  311. <Button onClick={() => setIsCreateDialogOpen(true)}>
  312. <Plus className="mr-2 h-4 w-4" />
  313. 新增省市区
  314. </Button>
  315. </div>
  316. </div>
  317. <Tabs value={viewMode} onValueChange={(value) => setViewMode(value as 'table' | 'tree')}>
  318. <TabsList className="mb-4">
  319. <TabsTrigger value="table">
  320. <TableIcon className="mr-2 h-4 w-4" />
  321. 表格视图
  322. </TabsTrigger>
  323. <TabsTrigger value="tree">
  324. <ListTree className="mr-2 h-4 w-4" />
  325. 树形视图
  326. </TabsTrigger>
  327. </TabsList>
  328. {/* 表格视图 */}
  329. <TabsContent value="table">
  330. <Card>
  331. <CardHeader>
  332. <CardTitle>省市区列表</CardTitle>
  333. <CardDescription>
  334. 查看和管理所有省市区数据
  335. </CardDescription>
  336. </CardHeader>
  337. <CardContent>
  338. {/* 搜索和筛选区域 */}
  339. <div className="flex flex-col gap-4 mb-6">
  340. <div className="flex gap-4">
  341. <div className="flex-1">
  342. <div className="relative">
  343. <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
  344. <Input
  345. placeholder="搜索省市区名称或代码..."
  346. className="pl-8"
  347. value={keyword}
  348. onChange={(e) => {
  349. setKeyword(e.target.value);
  350. debouncedSearch(e.target.value);
  351. }}
  352. />
  353. </div>
  354. </div>
  355. <Button
  356. variant="outline"
  357. onClick={handleResetFilters}
  358. disabled={!keyword && level === 'all' && isDisabled === 'all'}
  359. >
  360. <RotateCcw className="mr-2 h-4 w-4" />
  361. 重置
  362. </Button>
  363. </div>
  364. <div className="flex gap-4">
  365. <Select value={level} onValueChange={(value) => handleFilterChange('level', value)}>
  366. <SelectTrigger className="w-[180px]">
  367. <SelectValue placeholder="选择层级" />
  368. </SelectTrigger>
  369. <SelectContent>
  370. <SelectItem value="all">全部层级</SelectItem>
  371. <SelectItem value="1">省/直辖市</SelectItem>
  372. <SelectItem value="2">市</SelectItem>
  373. <SelectItem value="3">区/县</SelectItem>
  374. </SelectContent>
  375. </Select>
  376. <Select value={isDisabled} onValueChange={(value) => handleFilterChange('isDisabled', value)}>
  377. <SelectTrigger className="w-[180px]">
  378. <SelectValue placeholder="选择状态" />
  379. </SelectTrigger>
  380. <SelectContent>
  381. <SelectItem value="all">全部状态</SelectItem>
  382. <SelectItem value="0">启用</SelectItem>
  383. <SelectItem value="1">禁用</SelectItem>
  384. </SelectContent>
  385. </Select>
  386. </div>
  387. </div>
  388. {/* 数据表格 */}
  389. <div className="rounded-md border">
  390. <Table>
  391. <TableHeader>
  392. <TableRow>
  393. <TableHead>ID</TableHead>
  394. <TableHead>名称</TableHead>
  395. <TableHead>代码</TableHead>
  396. <TableHead>层级</TableHead>
  397. <TableHead>父级ID</TableHead>
  398. <TableHead>状态</TableHead>
  399. <TableHead>创建时间</TableHead>
  400. <TableHead className="text-right">操作</TableHead>
  401. </TableRow>
  402. </TableHeader>
  403. <TableBody>
  404. {isLoading ? (
  405. <TableRow>
  406. <TableCell colSpan={8} className="text-center py-8">
  407. 加载中...
  408. </TableCell>
  409. </TableRow>
  410. ) : !data?.data || data.data.length === 0 ? (
  411. <TableRow>
  412. <TableCell colSpan={8} className="text-center py-8">
  413. 暂无数据
  414. </TableCell>
  415. </TableRow>
  416. ) : (
  417. data.data.map((area) => (
  418. <TableRow key={area.id}>
  419. <TableCell className="font-medium">{area.id}</TableCell>
  420. <TableCell>{area.name}</TableCell>
  421. <TableCell>{area.code}</TableCell>
  422. <TableCell>
  423. <Badge variant="outline">
  424. {getLevelName(area.level)}
  425. </Badge>
  426. </TableCell>
  427. <TableCell>{area.parentId || '-'}</TableCell>
  428. <TableCell>
  429. <Badge variant={area.isDisabled === 0 ? 'default' : 'secondary'}>
  430. {area.isDisabled === 0 ? '启用' : '禁用'}
  431. </Badge>
  432. </TableCell>
  433. <TableCell>
  434. {new Date(area.createdAt).toLocaleDateString('zh-CN')}
  435. </TableCell>
  436. <TableCell className="text-right">
  437. <div className="flex justify-end gap-2">
  438. <Button
  439. variant="outline"
  440. size="sm"
  441. onClick={() => handleEdit(area)}
  442. >
  443. <Edit className="h-4 w-4" />
  444. </Button>
  445. <Button
  446. variant="outline"
  447. size="sm"
  448. onClick={() => handleToggleStatusDialog(area)}
  449. >
  450. <Power className="h-4 w-4" />
  451. </Button>
  452. <Button
  453. variant="outline"
  454. size="sm"
  455. onClick={() => handleDelete(area)}
  456. >
  457. <Trash2 className="h-4 w-4" />
  458. </Button>
  459. </div>
  460. </TableCell>
  461. </TableRow>
  462. ))
  463. )}
  464. </TableBody>
  465. </Table>
  466. </div>
  467. {/* 分页 */}
  468. {data && (
  469. <div className="mt-4">
  470. <DataTablePagination
  471. currentPage={page}
  472. pageSize={pageSize}
  473. totalCount={data.pagination.total}
  474. onPageChange={(newPage, newPageSize) => {
  475. setPage(newPage);
  476. setPageSize(newPageSize);
  477. }}
  478. />
  479. </div>
  480. )}
  481. </CardContent>
  482. </Card>
  483. </TabsContent>
  484. {/* 树形视图 */}
  485. <TabsContent value="tree">
  486. <Card>
  487. <CardHeader>
  488. <CardTitle>省市区树形结构</CardTitle>
  489. <CardDescription>
  490. 以树形结构查看和管理省市区层级关系
  491. </CardDescription>
  492. </CardHeader>
  493. <CardContent>
  494. {isTreeLoading ? (
  495. <div className="text-center py-8">
  496. 加载中...
  497. </div>
  498. ) : !treeData || treeData.length === 0 ? (
  499. <div className="text-center py-8">
  500. 暂无数据
  501. </div>
  502. ) : (
  503. <AreaTree
  504. areas={treeData}
  505. expandedNodes={expandedNodes}
  506. onToggleNode={handleToggleNode}
  507. onEdit={handleEdit}
  508. onDelete={handleDelete}
  509. onToggleStatus={handleToggleStatusDialog}
  510. />
  511. )}
  512. </CardContent>
  513. </Card>
  514. </TabsContent>
  515. </Tabs>
  516. {/* 创建省市区对话框 */}
  517. <Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
  518. <DialogContent className="max-w-2xl">
  519. <DialogHeader>
  520. <DialogTitle>新增省市区</DialogTitle>
  521. <DialogDescription>
  522. 填写省市区信息
  523. </DialogDescription>
  524. </DialogHeader>
  525. <AreaForm
  526. onSubmit={handleCreateArea}
  527. isLoading={createMutation.isPending}
  528. onCancel={() => setIsCreateDialogOpen(false)}
  529. />
  530. </DialogContent>
  531. </Dialog>
  532. {/* 编辑省市区对话框 */}
  533. <Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
  534. <DialogContent className="max-w-2xl">
  535. <DialogHeader>
  536. <DialogTitle>编辑省市区</DialogTitle>
  537. <DialogDescription>
  538. 修改省市区信息
  539. </DialogDescription>
  540. </DialogHeader>
  541. {selectedArea && (
  542. <AreaForm
  543. area={{
  544. id: selectedArea.id,
  545. parentId: selectedArea.parentId || undefined,
  546. name: selectedArea.name,
  547. level: selectedArea.level,
  548. code: selectedArea.code,
  549. isDisabled: selectedArea.isDisabled
  550. }}
  551. onSubmit={handleUpdateArea}
  552. isLoading={updateMutation.isPending}
  553. onCancel={() => {
  554. setIsEditDialogOpen(false);
  555. setSelectedArea(null);
  556. }}
  557. />
  558. )}
  559. </DialogContent>
  560. </Dialog>
  561. {/* 删除确认对话框 */}
  562. <AlertDialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
  563. <AlertDialogContent>
  564. <AlertDialogHeader>
  565. <AlertDialogTitle>确认删除</AlertDialogTitle>
  566. <AlertDialogDescription>
  567. 确定要删除省市区 "{selectedArea?.name}" 吗?此操作不可恢复。
  568. </AlertDialogDescription>
  569. </AlertDialogHeader>
  570. <AlertDialogFooter>
  571. <AlertDialogCancel>取消</AlertDialogCancel>
  572. <AlertDialogAction
  573. onClick={handleDeleteArea}
  574. disabled={deleteMutation.isPending}
  575. >
  576. {deleteMutation.isPending ? '删除中...' : '确认删除'}
  577. </AlertDialogAction>
  578. </AlertDialogFooter>
  579. </AlertDialogContent>
  580. </AlertDialog>
  581. {/* 状态切换确认对话框 */}
  582. <AlertDialog open={isStatusDialogOpen} onOpenChange={setIsStatusDialogOpen}>
  583. <AlertDialogContent>
  584. <AlertDialogHeader>
  585. <AlertDialogTitle>
  586. {selectedArea?.isDisabled === 0 ? '禁用' : '启用'}确认
  587. </AlertDialogTitle>
  588. <AlertDialogDescription>
  589. 确定要{selectedArea?.isDisabled === 0 ? '禁用' : '启用'}省市区 "{selectedArea?.name}" 吗?
  590. </AlertDialogDescription>
  591. </AlertDialogHeader>
  592. <AlertDialogFooter>
  593. <AlertDialogCancel>取消</AlertDialogCancel>
  594. <AlertDialogAction
  595. onClick={() => handleToggleStatus(selectedArea?.isDisabled === 0 ? 1 : 0)}
  596. disabled={toggleStatusMutation.isPending}
  597. >
  598. {toggleStatusMutation.isPending ? '处理中...' : '确认'}
  599. </AlertDialogAction>
  600. </AlertDialogFooter>
  601. </AlertDialogContent>
  602. </AlertDialog>
  603. </div>
  604. );
  605. };