@octo-cyber/property-management 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllers/community.controller.d.ts +3 -0
- package/dist/controllers/community.controller.d.ts.map +1 -0
- package/dist/controllers/community.controller.js +104 -0
- package/dist/controllers/community.controller.js.map +1 -0
- package/dist/controllers/contract.controller.d.ts +3 -0
- package/dist/controllers/contract.controller.d.ts.map +1 -0
- package/dist/controllers/contract.controller.js +35 -0
- package/dist/controllers/contract.controller.js.map +1 -0
- package/dist/controllers/facility.controller.d.ts +3 -0
- package/dist/controllers/facility.controller.d.ts.map +1 -0
- package/dist/controllers/facility.controller.js +60 -0
- package/dist/controllers/facility.controller.js.map +1 -0
- package/dist/controllers/fee.controller.d.ts +3 -0
- package/dist/controllers/fee.controller.d.ts.map +1 -0
- package/dist/controllers/fee.controller.js +98 -0
- package/dist/controllers/fee.controller.js.map +1 -0
- package/dist/controllers/index.d.ts +13 -0
- package/dist/controllers/index.d.ts.map +1 -0
- package/dist/controllers/index.js +34 -0
- package/dist/controllers/index.js.map +1 -0
- package/dist/controllers/meter.controller.d.ts +3 -0
- package/dist/controllers/meter.controller.d.ts.map +1 -0
- package/dist/controllers/meter.controller.js +49 -0
- package/dist/controllers/meter.controller.js.map +1 -0
- package/dist/controllers/owner.controller.d.ts +3 -0
- package/dist/controllers/owner.controller.d.ts.map +1 -0
- package/dist/controllers/owner.controller.js +46 -0
- package/dist/controllers/owner.controller.js.map +1 -0
- package/dist/controllers/patrol.controller.d.ts +3 -0
- package/dist/controllers/patrol.controller.d.ts.map +1 -0
- package/dist/controllers/patrol.controller.js +79 -0
- package/dist/controllers/patrol.controller.js.map +1 -0
- package/dist/controllers/repair-order.controller.d.ts +3 -0
- package/dist/controllers/repair-order.controller.d.ts.map +1 -0
- package/dist/controllers/repair-order.controller.js +85 -0
- package/dist/controllers/repair-order.controller.js.map +1 -0
- package/dist/controllers/statistics.controller.d.ts +3 -0
- package/dist/controllers/statistics.controller.d.ts.map +1 -0
- package/dist/controllers/statistics.controller.js +48 -0
- package/dist/controllers/statistics.controller.js.map +1 -0
- package/dist/entities/bill-detail.entity.d.ts +13 -0
- package/dist/entities/bill-detail.entity.d.ts.map +1 -0
- package/dist/entities/bill-detail.entity.js +70 -0
- package/dist/entities/bill-detail.entity.js.map +1 -0
- package/dist/entities/bill.entity.d.ts +19 -0
- package/dist/entities/bill.entity.d.ts.map +1 -0
- package/dist/entities/bill.entity.js +100 -0
- package/dist/entities/bill.entity.js.map +1 -0
- package/dist/entities/building.entity.d.ts +13 -0
- package/dist/entities/building.entity.d.ts.map +1 -0
- package/dist/entities/building.entity.js +70 -0
- package/dist/entities/building.entity.js.map +1 -0
- package/dist/entities/community.entity.d.ts +17 -0
- package/dist/entities/community.entity.d.ts.map +1 -0
- package/dist/entities/community.entity.js +90 -0
- package/dist/entities/community.entity.js.map +1 -0
- package/dist/entities/contract.entity.d.ts +20 -0
- package/dist/entities/contract.entity.d.ts.map +1 -0
- package/dist/entities/contract.entity.js +105 -0
- package/dist/entities/contract.entity.js.map +1 -0
- package/dist/entities/facility-category.entity.d.ts +9 -0
- package/dist/entities/facility-category.entity.d.ts.map +1 -0
- package/dist/entities/facility-category.entity.js +50 -0
- package/dist/entities/facility-category.entity.js.map +1 -0
- package/dist/entities/facility.entity.d.ts +23 -0
- package/dist/entities/facility.entity.d.ts.map +1 -0
- package/dist/entities/facility.entity.js +120 -0
- package/dist/entities/facility.entity.js.map +1 -0
- package/dist/entities/fee-item.entity.d.ts +16 -0
- package/dist/entities/fee-item.entity.d.ts.map +1 -0
- package/dist/entities/fee-item.entity.js +85 -0
- package/dist/entities/fee-item.entity.js.map +1 -0
- package/dist/entities/fee-standard.entity.d.ts +13 -0
- package/dist/entities/fee-standard.entity.d.ts.map +1 -0
- package/dist/entities/fee-standard.entity.js +70 -0
- package/dist/entities/fee-standard.entity.js.map +1 -0
- package/dist/entities/index.d.ts +55 -0
- package/dist/entities/index.d.ts.map +1 -0
- package/dist/entities/index.js +114 -0
- package/dist/entities/index.js.map +1 -0
- package/dist/entities/maintenance-plan.entity.d.ts +15 -0
- package/dist/entities/maintenance-plan.entity.d.ts.map +1 -0
- package/dist/entities/maintenance-plan.entity.js +80 -0
- package/dist/entities/maintenance-plan.entity.js.map +1 -0
- package/dist/entities/maintenance-record.entity.d.ts +14 -0
- package/dist/entities/maintenance-record.entity.d.ts.map +1 -0
- package/dist/entities/maintenance-record.entity.js +75 -0
- package/dist/entities/maintenance-record.entity.js.map +1 -0
- package/dist/entities/meter-reading.entity.d.ts +13 -0
- package/dist/entities/meter-reading.entity.d.ts.map +1 -0
- package/dist/entities/meter-reading.entity.js +70 -0
- package/dist/entities/meter-reading.entity.js.map +1 -0
- package/dist/entities/meter.entity.d.ts +13 -0
- package/dist/entities/meter.entity.d.ts.map +1 -0
- package/dist/entities/meter.entity.js +70 -0
- package/dist/entities/meter.entity.js.map +1 -0
- package/dist/entities/owner-room.entity.d.ts +11 -0
- package/dist/entities/owner-room.entity.d.ts.map +1 -0
- package/dist/entities/owner-room.entity.js +60 -0
- package/dist/entities/owner-room.entity.js.map +1 -0
- package/dist/entities/owner.entity.d.ts +17 -0
- package/dist/entities/owner.entity.d.ts.map +1 -0
- package/dist/entities/owner.entity.js +90 -0
- package/dist/entities/owner.entity.js.map +1 -0
- package/dist/entities/parking-space.entity.d.ts +13 -0
- package/dist/entities/parking-space.entity.d.ts.map +1 -0
- package/dist/entities/parking-space.entity.js +70 -0
- package/dist/entities/parking-space.entity.js.map +1 -0
- package/dist/entities/patrol-issue.entity.d.ts +13 -0
- package/dist/entities/patrol-issue.entity.d.ts.map +1 -0
- package/dist/entities/patrol-issue.entity.js +70 -0
- package/dist/entities/patrol-issue.entity.js.map +1 -0
- package/dist/entities/patrol-plan.entity.d.ts +15 -0
- package/dist/entities/patrol-plan.entity.d.ts.map +1 -0
- package/dist/entities/patrol-plan.entity.js +80 -0
- package/dist/entities/patrol-plan.entity.js.map +1 -0
- package/dist/entities/patrol-point.entity.d.ts +12 -0
- package/dist/entities/patrol-point.entity.d.ts.map +1 -0
- package/dist/entities/patrol-point.entity.js +65 -0
- package/dist/entities/patrol-point.entity.js.map +1 -0
- package/dist/entities/patrol-record.entity.d.ts +16 -0
- package/dist/entities/patrol-record.entity.d.ts.map +1 -0
- package/dist/entities/patrol-record.entity.js +85 -0
- package/dist/entities/patrol-record.entity.js.map +1 -0
- package/dist/entities/patrol-route.entity.d.ts +11 -0
- package/dist/entities/patrol-route.entity.d.ts.map +1 -0
- package/dist/entities/patrol-route.entity.js +60 -0
- package/dist/entities/patrol-route.entity.js.map +1 -0
- package/dist/entities/payment.entity.d.ts +14 -0
- package/dist/entities/payment.entity.d.ts.map +1 -0
- package/dist/entities/payment.entity.js +75 -0
- package/dist/entities/payment.entity.js.map +1 -0
- package/dist/entities/repair-category.entity.d.ts +12 -0
- package/dist/entities/repair-category.entity.d.ts.map +1 -0
- package/dist/entities/repair-category.entity.js +65 -0
- package/dist/entities/repair-category.entity.js.map +1 -0
- package/dist/entities/repair-evaluation.entity.d.ts +12 -0
- package/dist/entities/repair-evaluation.entity.d.ts.map +1 -0
- package/dist/entities/repair-evaluation.entity.js +65 -0
- package/dist/entities/repair-evaluation.entity.js.map +1 -0
- package/dist/entities/repair-order.entity.d.ts +30 -0
- package/dist/entities/repair-order.entity.d.ts.map +1 -0
- package/dist/entities/repair-order.entity.js +155 -0
- package/dist/entities/repair-order.entity.js.map +1 -0
- package/dist/entities/room.entity.d.ts +17 -0
- package/dist/entities/room.entity.d.ts.map +1 -0
- package/dist/entities/room.entity.js +90 -0
- package/dist/entities/room.entity.js.map +1 -0
- package/dist/entities/unit.entity.d.ts +8 -0
- package/dist/entities/unit.entity.d.ts.map +1 -0
- package/dist/entities/unit.entity.js +45 -0
- package/dist/entities/unit.entity.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/property.module.d.ts +8 -0
- package/dist/property.module.d.ts.map +1 -0
- package/dist/property.module.js +43 -0
- package/dist/property.module.js.map +1 -0
- package/dist/schemas/building.schema.d.ts +161 -0
- package/dist/schemas/building.schema.d.ts.map +1 -0
- package/dist/schemas/building.schema.js +43 -0
- package/dist/schemas/building.schema.js.map +1 -0
- package/dist/schemas/community.schema.d.ts +77 -0
- package/dist/schemas/community.schema.d.ts.map +1 -0
- package/dist/schemas/community.schema.js +22 -0
- package/dist/schemas/community.schema.js.map +1 -0
- package/dist/schemas/contract.schema.d.ts +116 -0
- package/dist/schemas/contract.schema.d.ts.map +1 -0
- package/dist/schemas/contract.schema.js +30 -0
- package/dist/schemas/contract.schema.js.map +1 -0
- package/dist/schemas/facility.schema.d.ts +195 -0
- package/dist/schemas/facility.schema.d.ts.map +1 -0
- package/dist/schemas/facility.schema.js +54 -0
- package/dist/schemas/facility.schema.js.map +1 -0
- package/dist/schemas/fee.schema.d.ts +175 -0
- package/dist/schemas/fee.schema.d.ts.map +1 -0
- package/dist/schemas/fee.schema.js +55 -0
- package/dist/schemas/fee.schema.js.map +1 -0
- package/dist/schemas/meter.schema.d.ts +111 -0
- package/dist/schemas/meter.schema.d.ts.map +1 -0
- package/dist/schemas/meter.schema.js +31 -0
- package/dist/schemas/meter.schema.js.map +1 -0
- package/dist/schemas/owner.schema.d.ts +118 -0
- package/dist/schemas/owner.schema.d.ts.map +1 -0
- package/dist/schemas/owner.schema.js +33 -0
- package/dist/schemas/owner.schema.js.map +1 -0
- package/dist/schemas/patrol.schema.d.ts +137 -0
- package/dist/schemas/patrol.schema.d.ts.map +1 -0
- package/dist/schemas/patrol.schema.js +52 -0
- package/dist/schemas/patrol.schema.js.map +1 -0
- package/dist/schemas/repair-order.schema.d.ts +133 -0
- package/dist/schemas/repair-order.schema.d.ts.map +1 -0
- package/dist/schemas/repair-order.schema.js +52 -0
- package/dist/schemas/repair-order.schema.js.map +1 -0
- package/dist/services/bill.service.d.ts +39 -0
- package/dist/services/bill.service.d.ts.map +1 -0
- package/dist/services/bill.service.js +203 -0
- package/dist/services/bill.service.js.map +1 -0
- package/dist/services/building.service.d.ts +22 -0
- package/dist/services/building.service.d.ts.map +1 -0
- package/dist/services/building.service.js +119 -0
- package/dist/services/building.service.js.map +1 -0
- package/dist/services/community.service.d.ts +22 -0
- package/dist/services/community.service.d.ts.map +1 -0
- package/dist/services/community.service.js +87 -0
- package/dist/services/community.service.js.map +1 -0
- package/dist/services/contract.service.d.ts +18 -0
- package/dist/services/contract.service.d.ts.map +1 -0
- package/dist/services/contract.service.js +76 -0
- package/dist/services/contract.service.js.map +1 -0
- package/dist/services/facility.service.d.ts +29 -0
- package/dist/services/facility.service.d.ts.map +1 -0
- package/dist/services/facility.service.js +121 -0
- package/dist/services/facility.service.js.map +1 -0
- package/dist/services/fee.service.d.ts +16 -0
- package/dist/services/fee.service.d.ts.map +1 -0
- package/dist/services/fee.service.js +58 -0
- package/dist/services/fee.service.js.map +1 -0
- package/dist/services/index.d.ts +12 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +26 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/meter.service.d.ts +19 -0
- package/dist/services/meter.service.d.ts.map +1 -0
- package/dist/services/meter.service.js +77 -0
- package/dist/services/meter.service.js.map +1 -0
- package/dist/services/owner.service.d.ts +23 -0
- package/dist/services/owner.service.d.ts.map +1 -0
- package/dist/services/owner.service.js +89 -0
- package/dist/services/owner.service.js.map +1 -0
- package/dist/services/patrol.service.d.ts +32 -0
- package/dist/services/patrol.service.d.ts.map +1 -0
- package/dist/services/patrol.service.js +126 -0
- package/dist/services/patrol.service.js.map +1 -0
- package/dist/services/repair-order.service.d.ts +25 -0
- package/dist/services/repair-order.service.d.ts.map +1 -0
- package/dist/services/repair-order.service.js +143 -0
- package/dist/services/repair-order.service.js.map +1 -0
- package/dist/services/statistics.service.d.ts +16 -0
- package/dist/services/statistics.service.d.ts.map +1 -0
- package/dist/services/statistics.service.js +108 -0
- package/dist/services/statistics.service.js.map +1 -0
- package/package.json +91 -0
- package/web/components/BillStatusBadge.tsx +26 -0
- package/web/components/CommunitySelect.tsx +40 -0
- package/web/components/RepairStatusBadge.tsx +28 -0
- package/web/index.ts +24 -0
- package/web/manifest.ts +66 -0
- package/web/messages/en-US.json +81 -0
- package/web/messages/zh-CN.json +81 -0
- package/web/pages/CommunityPage.tsx +182 -0
- package/web/pages/ContractPage.tsx +111 -0
- package/web/pages/DashboardPage.tsx +136 -0
- package/web/pages/FacilityPage.tsx +109 -0
- package/web/pages/FeeBillPage.tsx +199 -0
- package/web/pages/MeterPage.tsx +104 -0
- package/web/pages/OwnerPage.tsx +135 -0
- package/web/pages/PatrolPage.tsx +91 -0
- package/web/pages/RepairOrderPage.tsx +182 -0
- package/web/services/property-api.service.ts +202 -0
- package/web/stores/property-store.ts +18 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react'
|
|
4
|
+
import { Plus, Search } from 'lucide-react'
|
|
5
|
+
import { Button } from '@octo-cyber/ui/components/button'
|
|
6
|
+
import { Input } from '@octo-cyber/ui/components/input'
|
|
7
|
+
import {
|
|
8
|
+
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
|
|
9
|
+
} from '@octo-cyber/ui/components/select'
|
|
10
|
+
import {
|
|
11
|
+
Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
|
|
12
|
+
} from '@octo-cyber/ui/components/table'
|
|
13
|
+
import {
|
|
14
|
+
Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle,
|
|
15
|
+
} from '@octo-cyber/ui/components/dialog'
|
|
16
|
+
import { Label } from '@octo-cyber/ui/components/label'
|
|
17
|
+
import { Textarea } from '@octo-cyber/ui/components/textarea'
|
|
18
|
+
import { toast } from 'sonner'
|
|
19
|
+
import {
|
|
20
|
+
listRepairs, createRepair, dispatchRepair, completeRepair, cancelRepair,
|
|
21
|
+
type RepairOrder,
|
|
22
|
+
} from '../services/property-api.service.js'
|
|
23
|
+
import { RepairStatusBadge } from '../components/RepairStatusBadge.js'
|
|
24
|
+
import { CommunitySelect } from '../components/CommunitySelect.js'
|
|
25
|
+
|
|
26
|
+
const STATUS_OPTIONS = ['PENDING', 'DISPATCHED', 'IN_PROGRESS', 'COMPLETED', 'VERIFIED', 'CANCELLED']
|
|
27
|
+
const URGENCY_LABELS: Record<string, string> = { LOW: '低', MEDIUM: '普通', HIGH: '紧急', URGENT: '特急' }
|
|
28
|
+
|
|
29
|
+
export default function RepairOrderPage() {
|
|
30
|
+
const [items, setItems] = useState<RepairOrder[]>([])
|
|
31
|
+
const [total, setTotal] = useState(0)
|
|
32
|
+
const [page, setPage] = useState(1)
|
|
33
|
+
const [communityId, setCommunityId] = useState('')
|
|
34
|
+
const [statusFilter, setStatusFilter] = useState('')
|
|
35
|
+
const [loading, setLoading] = useState(false)
|
|
36
|
+
const [createOpen, setCreateOpen] = useState(false)
|
|
37
|
+
const [form, setForm] = useState<Partial<RepairOrder>>({ urgency: 'MEDIUM' })
|
|
38
|
+
const [saving, setSaving] = useState(false)
|
|
39
|
+
|
|
40
|
+
const load = () => {
|
|
41
|
+
setLoading(true)
|
|
42
|
+
listRepairs({ page, pageSize: 20, communityId: communityId || undefined, status: statusFilter || undefined })
|
|
43
|
+
.then((r) => { setItems(r.items); setTotal(r.total) })
|
|
44
|
+
.catch(() => toast.error('加载失败'))
|
|
45
|
+
.finally(() => setLoading(false))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
useEffect(() => { load() }, [page, communityId, statusFilter])
|
|
49
|
+
|
|
50
|
+
const handleCreate = async () => {
|
|
51
|
+
if (!form.title || !form.communityId) { toast.error('标题和小区不能为空'); return }
|
|
52
|
+
setSaving(true)
|
|
53
|
+
try {
|
|
54
|
+
await createRepair({ ...form, reporterId: 'current-user' })
|
|
55
|
+
toast.success('报修单创建成功')
|
|
56
|
+
setCreateOpen(false)
|
|
57
|
+
setForm({ urgency: 'MEDIUM' })
|
|
58
|
+
load()
|
|
59
|
+
} catch (e: unknown) {
|
|
60
|
+
toast.error((e as Error).message ?? '操作失败')
|
|
61
|
+
} finally {
|
|
62
|
+
setSaving(false)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const handleCancel = async (id: string) => {
|
|
67
|
+
const reason = prompt('请输入取消原因')
|
|
68
|
+
if (!reason) return
|
|
69
|
+
try {
|
|
70
|
+
await cancelRepair(id, reason)
|
|
71
|
+
toast.success('已取消')
|
|
72
|
+
load()
|
|
73
|
+
} catch (e: unknown) {
|
|
74
|
+
toast.error((e as Error).message ?? '操作失败')
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<div className="space-y-4 p-6">
|
|
80
|
+
<div className="flex items-center justify-between">
|
|
81
|
+
<h1 className="text-2xl font-bold">报修工单</h1>
|
|
82
|
+
<Button onClick={() => setCreateOpen(true)}><Plus className="mr-2 h-4 w-4" />新建报修</Button>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
86
|
+
<div className="w-48">
|
|
87
|
+
<CommunitySelect value={communityId} onChange={(v) => { setCommunityId(v); setPage(1) }} placeholder="全部小区" />
|
|
88
|
+
</div>
|
|
89
|
+
<Select value={statusFilter} onValueChange={(v) => { setStatusFilter(v === 'ALL' ? '' : v); setPage(1) }}>
|
|
90
|
+
<SelectTrigger className="w-36">
|
|
91
|
+
<SelectValue placeholder="全部状态" />
|
|
92
|
+
</SelectTrigger>
|
|
93
|
+
<SelectContent>
|
|
94
|
+
<SelectItem value="ALL">全部状态</SelectItem>
|
|
95
|
+
{STATUS_OPTIONS.map((s) => (
|
|
96
|
+
<SelectItem key={s} value={s}><RepairStatusBadge status={s} /></SelectItem>
|
|
97
|
+
))}
|
|
98
|
+
</SelectContent>
|
|
99
|
+
</Select>
|
|
100
|
+
<span className="text-sm text-muted-foreground">共 {total} 条</span>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div className="rounded-md border">
|
|
104
|
+
<Table>
|
|
105
|
+
<TableHeader>
|
|
106
|
+
<TableRow>
|
|
107
|
+
<TableHead>工单号</TableHead>
|
|
108
|
+
<TableHead>标题</TableHead>
|
|
109
|
+
<TableHead>紧急程度</TableHead>
|
|
110
|
+
<TableHead>状态</TableHead>
|
|
111
|
+
<TableHead>创建时间</TableHead>
|
|
112
|
+
<TableHead className="w-24">操作</TableHead>
|
|
113
|
+
</TableRow>
|
|
114
|
+
</TableHeader>
|
|
115
|
+
<TableBody>
|
|
116
|
+
{loading ? (
|
|
117
|
+
<TableRow><TableCell colSpan={6} className="text-center text-muted-foreground">加载中...</TableCell></TableRow>
|
|
118
|
+
) : items.length === 0 ? (
|
|
119
|
+
<TableRow><TableCell colSpan={6} className="text-center text-muted-foreground">暂无数据</TableCell></TableRow>
|
|
120
|
+
) : items.map((r) => (
|
|
121
|
+
<TableRow key={r.id}>
|
|
122
|
+
<TableCell className="font-mono text-xs">{r.orderNo}</TableCell>
|
|
123
|
+
<TableCell>{r.title}</TableCell>
|
|
124
|
+
<TableCell>{URGENCY_LABELS[r.urgency] ?? r.urgency}</TableCell>
|
|
125
|
+
<TableCell><RepairStatusBadge status={r.status} /></TableCell>
|
|
126
|
+
<TableCell>{r.createdAt?.slice(0, 10)}</TableCell>
|
|
127
|
+
<TableCell>
|
|
128
|
+
{r.status === 'PENDING' && (
|
|
129
|
+
<Button variant="ghost" size="sm" onClick={() => handleCancel(r.id)}>取消</Button>
|
|
130
|
+
)}
|
|
131
|
+
</TableCell>
|
|
132
|
+
</TableRow>
|
|
133
|
+
))}
|
|
134
|
+
</TableBody>
|
|
135
|
+
</Table>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
{total > 20 && (
|
|
139
|
+
<div className="flex justify-end gap-2">
|
|
140
|
+
<Button variant="outline" size="sm" disabled={page <= 1} onClick={() => setPage(p => p - 1)}>上一页</Button>
|
|
141
|
+
<span className="flex items-center text-sm text-muted-foreground">第 {page} 页</span>
|
|
142
|
+
<Button variant="outline" size="sm" disabled={page * 20 >= total} onClick={() => setPage(p => p + 1)}>下一页</Button>
|
|
143
|
+
</div>
|
|
144
|
+
)}
|
|
145
|
+
|
|
146
|
+
<Dialog open={createOpen} onOpenChange={setCreateOpen}>
|
|
147
|
+
<DialogContent>
|
|
148
|
+
<DialogHeader><DialogTitle>新建报修单</DialogTitle></DialogHeader>
|
|
149
|
+
<div className="space-y-4 py-2">
|
|
150
|
+
<div className="space-y-1">
|
|
151
|
+
<Label>小区 *</Label>
|
|
152
|
+
<CommunitySelect value={form.communityId ?? ''} onChange={(v) => setForm(f => ({ ...f, communityId: v }))} />
|
|
153
|
+
</div>
|
|
154
|
+
<div className="space-y-1">
|
|
155
|
+
<Label>标题 *</Label>
|
|
156
|
+
<Input value={form.title ?? ''} onChange={(e) => setForm(f => ({ ...f, title: e.target.value }))} />
|
|
157
|
+
</div>
|
|
158
|
+
<div className="space-y-1">
|
|
159
|
+
<Label>紧急程度</Label>
|
|
160
|
+
<Select value={form.urgency ?? 'MEDIUM'} onValueChange={(v) => setForm(f => ({ ...f, urgency: v }))}>
|
|
161
|
+
<SelectTrigger><SelectValue /></SelectTrigger>
|
|
162
|
+
<SelectContent>
|
|
163
|
+
{Object.entries(URGENCY_LABELS).map(([k, v]) => (
|
|
164
|
+
<SelectItem key={k} value={k}>{v}</SelectItem>
|
|
165
|
+
))}
|
|
166
|
+
</SelectContent>
|
|
167
|
+
</Select>
|
|
168
|
+
</div>
|
|
169
|
+
<div className="space-y-1">
|
|
170
|
+
<Label>详细描述</Label>
|
|
171
|
+
<Textarea value={form.description ?? ''} onChange={(e) => setForm(f => ({ ...f, description: e.target.value }))} rows={3} />
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
<DialogFooter>
|
|
175
|
+
<Button variant="outline" onClick={() => setCreateOpen(false)}>取消</Button>
|
|
176
|
+
<Button onClick={handleCreate} disabled={saving}>{saving ? '提交中...' : '提交'}</Button>
|
|
177
|
+
</DialogFooter>
|
|
178
|
+
</DialogContent>
|
|
179
|
+
</Dialog>
|
|
180
|
+
</div>
|
|
181
|
+
)
|
|
182
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
const BASE = '/api/v1/property'
|
|
2
|
+
|
|
3
|
+
async function request<T>(url: string, options?: RequestInit): Promise<T> {
|
|
4
|
+
const res = await fetch(url, {
|
|
5
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6
|
+
...options,
|
|
7
|
+
})
|
|
8
|
+
const json = await res.json() as { data: T; message?: string }
|
|
9
|
+
if (!res.ok) throw new Error((json as unknown as { message: string }).message ?? 'Request failed')
|
|
10
|
+
return json.data
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export interface Community {
|
|
16
|
+
id: string; name: string; address: string; area: number | null
|
|
17
|
+
totalBuildings: number; totalRooms: number; occupiedRooms: number
|
|
18
|
+
propertyType: string; status: string; contactPhone: string | null
|
|
19
|
+
description: string | null; createdAt: string; updatedAt: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface Building {
|
|
23
|
+
id: string; communityId: string; name: string; code: string
|
|
24
|
+
totalFloors: number; buildingType: string; sortOrder: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface Room {
|
|
28
|
+
id: string; unitId: string; communityId: string; roomNumber: string
|
|
29
|
+
floor: number; area: number | null; roomType: string; status: string
|
|
30
|
+
fullCode: string; createdAt: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface Owner {
|
|
34
|
+
id: string; name: string; phone: string; ownerType: string
|
|
35
|
+
gender: string | null; email: string | null; notes: string | null
|
|
36
|
+
createdAt: string; updatedAt: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface RepairOrder {
|
|
40
|
+
id: string; orderNo: string; communityId: string; roomId: string | null
|
|
41
|
+
reporterId: string; categoryId: string; title: string; description: string
|
|
42
|
+
urgency: string; status: string; assigneeId: string | null
|
|
43
|
+
assignedAt: string | null; completedAt: string | null; verifiedAt: string | null
|
|
44
|
+
createdAt: string; updatedAt: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface Bill {
|
|
48
|
+
id: string; billNo: string; communityId: string; roomId: string
|
|
49
|
+
ownerId: string; billPeriod: string; totalAmount: number; paidAmount: number
|
|
50
|
+
lateFee: number; discountAmount: number; status: string; dueDate: string
|
|
51
|
+
paidAt: string | null; createdAt: string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface PatrolRecord {
|
|
55
|
+
id: string; planId: string; patrolDate: string; patrollerId: string
|
|
56
|
+
routeId: string; status: string; totalPoints: number; checkedPoints: number
|
|
57
|
+
issueCount: number; createdAt: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface Facility {
|
|
61
|
+
id: string; communityId: string; categoryId: string; name: string
|
|
62
|
+
code: string; location: string; status: string; installDate: string | null
|
|
63
|
+
warrantyExpiry: string | null; createdAt: string
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface Contract {
|
|
67
|
+
id: string; communityId: string; contractNo: string; name: string
|
|
68
|
+
type: string; partyA: string; partyB: string; amount: number | null
|
|
69
|
+
startDate: string; endDate: string; status: string; createdAt: string
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface Meter {
|
|
73
|
+
id: string; roomId: string; type: string; meterNo: string
|
|
74
|
+
currentReading: number; status: string; createdAt: string
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface PagedResult<T> {
|
|
78
|
+
items: T[]; total: number; page: number; pageSize: number
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface DashboardData {
|
|
82
|
+
community: Community | null
|
|
83
|
+
occupancyRate: number
|
|
84
|
+
collectionRate: number
|
|
85
|
+
pendingRepairs: number
|
|
86
|
+
overdueCount: number
|
|
87
|
+
currentPeriod: string
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── Community API ─────────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
export async function listCommunities(params?: { page?: number; pageSize?: number; status?: string; keyword?: string }): Promise<PagedResult<Community>> {
|
|
93
|
+
const qs = new URLSearchParams()
|
|
94
|
+
if (params?.page) qs.set('page', String(params.page))
|
|
95
|
+
if (params?.pageSize) qs.set('pageSize', String(params.pageSize))
|
|
96
|
+
if (params?.status) qs.set('status', params.status)
|
|
97
|
+
if (params?.keyword) qs.set('keyword', params.keyword)
|
|
98
|
+
return request<PagedResult<Community>>(`${BASE}/communities?${qs}`)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function getCommunity(id: string): Promise<Community> {
|
|
102
|
+
return request<Community>(`${BASE}/communities/${id}`)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function createCommunity(data: Partial<Community>): Promise<Community> {
|
|
106
|
+
return request<Community>(`${BASE}/communities`, { method: 'POST', body: JSON.stringify(data) })
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function updateCommunity(id: string, data: Partial<Community>): Promise<Community> {
|
|
110
|
+
return request<Community>(`${BASE}/communities/${id}`, { method: 'PUT', body: JSON.stringify(data) })
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function deleteCommunity(id: string): Promise<void> {
|
|
114
|
+
await request<null>(`${BASE}/communities/${id}`, { method: 'DELETE' })
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function getCommunityDashboard(id: string): Promise<DashboardData> {
|
|
118
|
+
return request<DashboardData>(`${BASE}/communities/${id}/dashboard`)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function getCommunityTree(id: string): Promise<unknown[]> {
|
|
122
|
+
return request<unknown[]>(`${BASE}/communities/${id}/tree`)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Owner API ─────────────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
export async function listOwners(params?: { page?: number; pageSize?: number; communityId?: string; keyword?: string }): Promise<PagedResult<Owner>> {
|
|
128
|
+
const qs = new URLSearchParams()
|
|
129
|
+
if (params?.page) qs.set('page', String(params.page))
|
|
130
|
+
if (params?.pageSize) qs.set('pageSize', String(params.pageSize))
|
|
131
|
+
if (params?.communityId) qs.set('communityId', params.communityId)
|
|
132
|
+
if (params?.keyword) qs.set('keyword', params.keyword)
|
|
133
|
+
return request<PagedResult<Owner>>(`${BASE}/owners?${qs}`)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function createOwner(data: Partial<Owner>): Promise<Owner> {
|
|
137
|
+
return request<Owner>(`${BASE}/owners`, { method: 'POST', body: JSON.stringify(data) })
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ── Repair API ────────────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
export async function listRepairs(params?: { page?: number; pageSize?: number; communityId?: string; status?: string }): Promise<PagedResult<RepairOrder>> {
|
|
143
|
+
const qs = new URLSearchParams()
|
|
144
|
+
if (params?.page) qs.set('page', String(params.page))
|
|
145
|
+
if (params?.pageSize) qs.set('pageSize', String(params.pageSize))
|
|
146
|
+
if (params?.communityId) qs.set('communityId', params.communityId)
|
|
147
|
+
if (params?.status) qs.set('status', params.status)
|
|
148
|
+
return request<PagedResult<RepairOrder>>(`${BASE}/repairs?${qs}`)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function createRepair(data: Partial<RepairOrder>): Promise<RepairOrder> {
|
|
152
|
+
return request<RepairOrder>(`${BASE}/repairs`, { method: 'POST', body: JSON.stringify(data) })
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function dispatchRepair(id: string, assigneeId: string): Promise<RepairOrder> {
|
|
156
|
+
return request<RepairOrder>(`${BASE}/repairs/${id}/dispatch`, { method: 'POST', body: JSON.stringify({ assigneeId }) })
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function completeRepair(id: string, data: { repairNote?: string; actualCost?: number }): Promise<RepairOrder> {
|
|
160
|
+
return request<RepairOrder>(`${BASE}/repairs/${id}/complete`, { method: 'POST', body: JSON.stringify(data) })
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export async function verifyRepair(id: string, verifiedBy: string): Promise<RepairOrder> {
|
|
164
|
+
return request<RepairOrder>(`${BASE}/repairs/${id}/verify`, { method: 'POST', body: JSON.stringify({ verifiedBy }) })
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function cancelRepair(id: string, reason: string): Promise<RepairOrder> {
|
|
168
|
+
return request<RepairOrder>(`${BASE}/repairs/${id}/cancel`, { method: 'POST', body: JSON.stringify({ reason }) })
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ── Bill API ──────────────────────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
export async function listBills(params?: { page?: number; pageSize?: number; communityId?: string; status?: string; period?: string }): Promise<PagedResult<Bill>> {
|
|
174
|
+
const qs = new URLSearchParams()
|
|
175
|
+
if (params?.page) qs.set('page', String(params.page))
|
|
176
|
+
if (params?.pageSize) qs.set('pageSize', String(params.pageSize))
|
|
177
|
+
if (params?.communityId) qs.set('communityId', params.communityId)
|
|
178
|
+
if (params?.status) qs.set('status', params.status)
|
|
179
|
+
if (params?.period) qs.set('period', params.period)
|
|
180
|
+
return request<PagedResult<Bill>>(`${BASE}/bills?${qs}`)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export async function generateBills(communityId: string, period: string): Promise<{ created: number; skipped: number }> {
|
|
184
|
+
return request<{ created: number; skipped: number }>(`${BASE}/bills/generate`, {
|
|
185
|
+
method: 'POST',
|
|
186
|
+
body: JSON.stringify({ communityId, period }),
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export async function recordPayment(billId: string, data: { ownerId: string; amount: number; paymentMethod: string }): Promise<unknown> {
|
|
191
|
+
return request<unknown>(`${BASE}/bills/${billId}/payment`, { method: 'POST', body: JSON.stringify(data) })
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ── Statistics API ────────────────────────────────────────────────────────────
|
|
195
|
+
|
|
196
|
+
export async function getStatsDashboard(communityId: string): Promise<DashboardData> {
|
|
197
|
+
return request<DashboardData>(`${BASE}/statistics/dashboard?communityId=${communityId}`)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export async function getCollectionTrend(communityId: string, months = 6): Promise<unknown[]> {
|
|
201
|
+
return request<unknown[]>(`${BASE}/statistics/collection-trend?communityId=${communityId}&months=${months}`)
|
|
202
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { create } from 'zustand'
|
|
2
|
+
import type { Community } from '../services/property-api.service.js'
|
|
3
|
+
|
|
4
|
+
interface PropertyStore {
|
|
5
|
+
selectedCommunityId: string | null
|
|
6
|
+
selectedCommunity: Community | null
|
|
7
|
+
setSelectedCommunity: (community: Community | null) => void
|
|
8
|
+
setSelectedCommunityId: (id: string | null) => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const usePropertyStore = create<PropertyStore>((set) => ({
|
|
12
|
+
selectedCommunityId: null,
|
|
13
|
+
selectedCommunity: null,
|
|
14
|
+
setSelectedCommunity: (community) =>
|
|
15
|
+
set({ selectedCommunity: community, selectedCommunityId: community?.id ?? null }),
|
|
16
|
+
setSelectedCommunityId: (id) =>
|
|
17
|
+
set({ selectedCommunityId: id }),
|
|
18
|
+
}))
|