@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.
Files changed (263) hide show
  1. package/dist/controllers/community.controller.d.ts +3 -0
  2. package/dist/controllers/community.controller.d.ts.map +1 -0
  3. package/dist/controllers/community.controller.js +104 -0
  4. package/dist/controllers/community.controller.js.map +1 -0
  5. package/dist/controllers/contract.controller.d.ts +3 -0
  6. package/dist/controllers/contract.controller.d.ts.map +1 -0
  7. package/dist/controllers/contract.controller.js +35 -0
  8. package/dist/controllers/contract.controller.js.map +1 -0
  9. package/dist/controllers/facility.controller.d.ts +3 -0
  10. package/dist/controllers/facility.controller.d.ts.map +1 -0
  11. package/dist/controllers/facility.controller.js +60 -0
  12. package/dist/controllers/facility.controller.js.map +1 -0
  13. package/dist/controllers/fee.controller.d.ts +3 -0
  14. package/dist/controllers/fee.controller.d.ts.map +1 -0
  15. package/dist/controllers/fee.controller.js +98 -0
  16. package/dist/controllers/fee.controller.js.map +1 -0
  17. package/dist/controllers/index.d.ts +13 -0
  18. package/dist/controllers/index.d.ts.map +1 -0
  19. package/dist/controllers/index.js +34 -0
  20. package/dist/controllers/index.js.map +1 -0
  21. package/dist/controllers/meter.controller.d.ts +3 -0
  22. package/dist/controllers/meter.controller.d.ts.map +1 -0
  23. package/dist/controllers/meter.controller.js +49 -0
  24. package/dist/controllers/meter.controller.js.map +1 -0
  25. package/dist/controllers/owner.controller.d.ts +3 -0
  26. package/dist/controllers/owner.controller.d.ts.map +1 -0
  27. package/dist/controllers/owner.controller.js +46 -0
  28. package/dist/controllers/owner.controller.js.map +1 -0
  29. package/dist/controllers/patrol.controller.d.ts +3 -0
  30. package/dist/controllers/patrol.controller.d.ts.map +1 -0
  31. package/dist/controllers/patrol.controller.js +79 -0
  32. package/dist/controllers/patrol.controller.js.map +1 -0
  33. package/dist/controllers/repair-order.controller.d.ts +3 -0
  34. package/dist/controllers/repair-order.controller.d.ts.map +1 -0
  35. package/dist/controllers/repair-order.controller.js +85 -0
  36. package/dist/controllers/repair-order.controller.js.map +1 -0
  37. package/dist/controllers/statistics.controller.d.ts +3 -0
  38. package/dist/controllers/statistics.controller.d.ts.map +1 -0
  39. package/dist/controllers/statistics.controller.js +48 -0
  40. package/dist/controllers/statistics.controller.js.map +1 -0
  41. package/dist/entities/bill-detail.entity.d.ts +13 -0
  42. package/dist/entities/bill-detail.entity.d.ts.map +1 -0
  43. package/dist/entities/bill-detail.entity.js +70 -0
  44. package/dist/entities/bill-detail.entity.js.map +1 -0
  45. package/dist/entities/bill.entity.d.ts +19 -0
  46. package/dist/entities/bill.entity.d.ts.map +1 -0
  47. package/dist/entities/bill.entity.js +100 -0
  48. package/dist/entities/bill.entity.js.map +1 -0
  49. package/dist/entities/building.entity.d.ts +13 -0
  50. package/dist/entities/building.entity.d.ts.map +1 -0
  51. package/dist/entities/building.entity.js +70 -0
  52. package/dist/entities/building.entity.js.map +1 -0
  53. package/dist/entities/community.entity.d.ts +17 -0
  54. package/dist/entities/community.entity.d.ts.map +1 -0
  55. package/dist/entities/community.entity.js +90 -0
  56. package/dist/entities/community.entity.js.map +1 -0
  57. package/dist/entities/contract.entity.d.ts +20 -0
  58. package/dist/entities/contract.entity.d.ts.map +1 -0
  59. package/dist/entities/contract.entity.js +105 -0
  60. package/dist/entities/contract.entity.js.map +1 -0
  61. package/dist/entities/facility-category.entity.d.ts +9 -0
  62. package/dist/entities/facility-category.entity.d.ts.map +1 -0
  63. package/dist/entities/facility-category.entity.js +50 -0
  64. package/dist/entities/facility-category.entity.js.map +1 -0
  65. package/dist/entities/facility.entity.d.ts +23 -0
  66. package/dist/entities/facility.entity.d.ts.map +1 -0
  67. package/dist/entities/facility.entity.js +120 -0
  68. package/dist/entities/facility.entity.js.map +1 -0
  69. package/dist/entities/fee-item.entity.d.ts +16 -0
  70. package/dist/entities/fee-item.entity.d.ts.map +1 -0
  71. package/dist/entities/fee-item.entity.js +85 -0
  72. package/dist/entities/fee-item.entity.js.map +1 -0
  73. package/dist/entities/fee-standard.entity.d.ts +13 -0
  74. package/dist/entities/fee-standard.entity.d.ts.map +1 -0
  75. package/dist/entities/fee-standard.entity.js +70 -0
  76. package/dist/entities/fee-standard.entity.js.map +1 -0
  77. package/dist/entities/index.d.ts +55 -0
  78. package/dist/entities/index.d.ts.map +1 -0
  79. package/dist/entities/index.js +114 -0
  80. package/dist/entities/index.js.map +1 -0
  81. package/dist/entities/maintenance-plan.entity.d.ts +15 -0
  82. package/dist/entities/maintenance-plan.entity.d.ts.map +1 -0
  83. package/dist/entities/maintenance-plan.entity.js +80 -0
  84. package/dist/entities/maintenance-plan.entity.js.map +1 -0
  85. package/dist/entities/maintenance-record.entity.d.ts +14 -0
  86. package/dist/entities/maintenance-record.entity.d.ts.map +1 -0
  87. package/dist/entities/maintenance-record.entity.js +75 -0
  88. package/dist/entities/maintenance-record.entity.js.map +1 -0
  89. package/dist/entities/meter-reading.entity.d.ts +13 -0
  90. package/dist/entities/meter-reading.entity.d.ts.map +1 -0
  91. package/dist/entities/meter-reading.entity.js +70 -0
  92. package/dist/entities/meter-reading.entity.js.map +1 -0
  93. package/dist/entities/meter.entity.d.ts +13 -0
  94. package/dist/entities/meter.entity.d.ts.map +1 -0
  95. package/dist/entities/meter.entity.js +70 -0
  96. package/dist/entities/meter.entity.js.map +1 -0
  97. package/dist/entities/owner-room.entity.d.ts +11 -0
  98. package/dist/entities/owner-room.entity.d.ts.map +1 -0
  99. package/dist/entities/owner-room.entity.js +60 -0
  100. package/dist/entities/owner-room.entity.js.map +1 -0
  101. package/dist/entities/owner.entity.d.ts +17 -0
  102. package/dist/entities/owner.entity.d.ts.map +1 -0
  103. package/dist/entities/owner.entity.js +90 -0
  104. package/dist/entities/owner.entity.js.map +1 -0
  105. package/dist/entities/parking-space.entity.d.ts +13 -0
  106. package/dist/entities/parking-space.entity.d.ts.map +1 -0
  107. package/dist/entities/parking-space.entity.js +70 -0
  108. package/dist/entities/parking-space.entity.js.map +1 -0
  109. package/dist/entities/patrol-issue.entity.d.ts +13 -0
  110. package/dist/entities/patrol-issue.entity.d.ts.map +1 -0
  111. package/dist/entities/patrol-issue.entity.js +70 -0
  112. package/dist/entities/patrol-issue.entity.js.map +1 -0
  113. package/dist/entities/patrol-plan.entity.d.ts +15 -0
  114. package/dist/entities/patrol-plan.entity.d.ts.map +1 -0
  115. package/dist/entities/patrol-plan.entity.js +80 -0
  116. package/dist/entities/patrol-plan.entity.js.map +1 -0
  117. package/dist/entities/patrol-point.entity.d.ts +12 -0
  118. package/dist/entities/patrol-point.entity.d.ts.map +1 -0
  119. package/dist/entities/patrol-point.entity.js +65 -0
  120. package/dist/entities/patrol-point.entity.js.map +1 -0
  121. package/dist/entities/patrol-record.entity.d.ts +16 -0
  122. package/dist/entities/patrol-record.entity.d.ts.map +1 -0
  123. package/dist/entities/patrol-record.entity.js +85 -0
  124. package/dist/entities/patrol-record.entity.js.map +1 -0
  125. package/dist/entities/patrol-route.entity.d.ts +11 -0
  126. package/dist/entities/patrol-route.entity.d.ts.map +1 -0
  127. package/dist/entities/patrol-route.entity.js +60 -0
  128. package/dist/entities/patrol-route.entity.js.map +1 -0
  129. package/dist/entities/payment.entity.d.ts +14 -0
  130. package/dist/entities/payment.entity.d.ts.map +1 -0
  131. package/dist/entities/payment.entity.js +75 -0
  132. package/dist/entities/payment.entity.js.map +1 -0
  133. package/dist/entities/repair-category.entity.d.ts +12 -0
  134. package/dist/entities/repair-category.entity.d.ts.map +1 -0
  135. package/dist/entities/repair-category.entity.js +65 -0
  136. package/dist/entities/repair-category.entity.js.map +1 -0
  137. package/dist/entities/repair-evaluation.entity.d.ts +12 -0
  138. package/dist/entities/repair-evaluation.entity.d.ts.map +1 -0
  139. package/dist/entities/repair-evaluation.entity.js +65 -0
  140. package/dist/entities/repair-evaluation.entity.js.map +1 -0
  141. package/dist/entities/repair-order.entity.d.ts +30 -0
  142. package/dist/entities/repair-order.entity.d.ts.map +1 -0
  143. package/dist/entities/repair-order.entity.js +155 -0
  144. package/dist/entities/repair-order.entity.js.map +1 -0
  145. package/dist/entities/room.entity.d.ts +17 -0
  146. package/dist/entities/room.entity.d.ts.map +1 -0
  147. package/dist/entities/room.entity.js +90 -0
  148. package/dist/entities/room.entity.js.map +1 -0
  149. package/dist/entities/unit.entity.d.ts +8 -0
  150. package/dist/entities/unit.entity.d.ts.map +1 -0
  151. package/dist/entities/unit.entity.js +45 -0
  152. package/dist/entities/unit.entity.js.map +1 -0
  153. package/dist/index.d.ts +12 -0
  154. package/dist/index.d.ts.map +1 -0
  155. package/dist/index.js +60 -0
  156. package/dist/index.js.map +1 -0
  157. package/dist/property.module.d.ts +8 -0
  158. package/dist/property.module.d.ts.map +1 -0
  159. package/dist/property.module.js +43 -0
  160. package/dist/property.module.js.map +1 -0
  161. package/dist/schemas/building.schema.d.ts +161 -0
  162. package/dist/schemas/building.schema.d.ts.map +1 -0
  163. package/dist/schemas/building.schema.js +43 -0
  164. package/dist/schemas/building.schema.js.map +1 -0
  165. package/dist/schemas/community.schema.d.ts +77 -0
  166. package/dist/schemas/community.schema.d.ts.map +1 -0
  167. package/dist/schemas/community.schema.js +22 -0
  168. package/dist/schemas/community.schema.js.map +1 -0
  169. package/dist/schemas/contract.schema.d.ts +116 -0
  170. package/dist/schemas/contract.schema.d.ts.map +1 -0
  171. package/dist/schemas/contract.schema.js +30 -0
  172. package/dist/schemas/contract.schema.js.map +1 -0
  173. package/dist/schemas/facility.schema.d.ts +195 -0
  174. package/dist/schemas/facility.schema.d.ts.map +1 -0
  175. package/dist/schemas/facility.schema.js +54 -0
  176. package/dist/schemas/facility.schema.js.map +1 -0
  177. package/dist/schemas/fee.schema.d.ts +175 -0
  178. package/dist/schemas/fee.schema.d.ts.map +1 -0
  179. package/dist/schemas/fee.schema.js +55 -0
  180. package/dist/schemas/fee.schema.js.map +1 -0
  181. package/dist/schemas/meter.schema.d.ts +111 -0
  182. package/dist/schemas/meter.schema.d.ts.map +1 -0
  183. package/dist/schemas/meter.schema.js +31 -0
  184. package/dist/schemas/meter.schema.js.map +1 -0
  185. package/dist/schemas/owner.schema.d.ts +118 -0
  186. package/dist/schemas/owner.schema.d.ts.map +1 -0
  187. package/dist/schemas/owner.schema.js +33 -0
  188. package/dist/schemas/owner.schema.js.map +1 -0
  189. package/dist/schemas/patrol.schema.d.ts +137 -0
  190. package/dist/schemas/patrol.schema.d.ts.map +1 -0
  191. package/dist/schemas/patrol.schema.js +52 -0
  192. package/dist/schemas/patrol.schema.js.map +1 -0
  193. package/dist/schemas/repair-order.schema.d.ts +133 -0
  194. package/dist/schemas/repair-order.schema.d.ts.map +1 -0
  195. package/dist/schemas/repair-order.schema.js +52 -0
  196. package/dist/schemas/repair-order.schema.js.map +1 -0
  197. package/dist/services/bill.service.d.ts +39 -0
  198. package/dist/services/bill.service.d.ts.map +1 -0
  199. package/dist/services/bill.service.js +203 -0
  200. package/dist/services/bill.service.js.map +1 -0
  201. package/dist/services/building.service.d.ts +22 -0
  202. package/dist/services/building.service.d.ts.map +1 -0
  203. package/dist/services/building.service.js +119 -0
  204. package/dist/services/building.service.js.map +1 -0
  205. package/dist/services/community.service.d.ts +22 -0
  206. package/dist/services/community.service.d.ts.map +1 -0
  207. package/dist/services/community.service.js +87 -0
  208. package/dist/services/community.service.js.map +1 -0
  209. package/dist/services/contract.service.d.ts +18 -0
  210. package/dist/services/contract.service.d.ts.map +1 -0
  211. package/dist/services/contract.service.js +76 -0
  212. package/dist/services/contract.service.js.map +1 -0
  213. package/dist/services/facility.service.d.ts +29 -0
  214. package/dist/services/facility.service.d.ts.map +1 -0
  215. package/dist/services/facility.service.js +121 -0
  216. package/dist/services/facility.service.js.map +1 -0
  217. package/dist/services/fee.service.d.ts +16 -0
  218. package/dist/services/fee.service.d.ts.map +1 -0
  219. package/dist/services/fee.service.js +58 -0
  220. package/dist/services/fee.service.js.map +1 -0
  221. package/dist/services/index.d.ts +12 -0
  222. package/dist/services/index.d.ts.map +1 -0
  223. package/dist/services/index.js +26 -0
  224. package/dist/services/index.js.map +1 -0
  225. package/dist/services/meter.service.d.ts +19 -0
  226. package/dist/services/meter.service.d.ts.map +1 -0
  227. package/dist/services/meter.service.js +77 -0
  228. package/dist/services/meter.service.js.map +1 -0
  229. package/dist/services/owner.service.d.ts +23 -0
  230. package/dist/services/owner.service.d.ts.map +1 -0
  231. package/dist/services/owner.service.js +89 -0
  232. package/dist/services/owner.service.js.map +1 -0
  233. package/dist/services/patrol.service.d.ts +32 -0
  234. package/dist/services/patrol.service.d.ts.map +1 -0
  235. package/dist/services/patrol.service.js +126 -0
  236. package/dist/services/patrol.service.js.map +1 -0
  237. package/dist/services/repair-order.service.d.ts +25 -0
  238. package/dist/services/repair-order.service.d.ts.map +1 -0
  239. package/dist/services/repair-order.service.js +143 -0
  240. package/dist/services/repair-order.service.js.map +1 -0
  241. package/dist/services/statistics.service.d.ts +16 -0
  242. package/dist/services/statistics.service.d.ts.map +1 -0
  243. package/dist/services/statistics.service.js +108 -0
  244. package/dist/services/statistics.service.js.map +1 -0
  245. package/package.json +91 -0
  246. package/web/components/BillStatusBadge.tsx +26 -0
  247. package/web/components/CommunitySelect.tsx +40 -0
  248. package/web/components/RepairStatusBadge.tsx +28 -0
  249. package/web/index.ts +24 -0
  250. package/web/manifest.ts +66 -0
  251. package/web/messages/en-US.json +81 -0
  252. package/web/messages/zh-CN.json +81 -0
  253. package/web/pages/CommunityPage.tsx +182 -0
  254. package/web/pages/ContractPage.tsx +111 -0
  255. package/web/pages/DashboardPage.tsx +136 -0
  256. package/web/pages/FacilityPage.tsx +109 -0
  257. package/web/pages/FeeBillPage.tsx +199 -0
  258. package/web/pages/MeterPage.tsx +104 -0
  259. package/web/pages/OwnerPage.tsx +135 -0
  260. package/web/pages/PatrolPage.tsx +91 -0
  261. package/web/pages/RepairOrderPage.tsx +182 -0
  262. package/web/services/property-api.service.ts +202 -0
  263. 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
+ }))