@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
package/web/index.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// ── Manifest ──────────────────────────────────────────────────────────────────
|
|
2
|
+
export { propertyManifest } from './manifest.js'
|
|
3
|
+
|
|
4
|
+
// ── Pages ─────────────────────────────────────────────────────────────────────
|
|
5
|
+
export { default as DashboardPage } from './pages/DashboardPage.js'
|
|
6
|
+
export { default as CommunityPage } from './pages/CommunityPage.js'
|
|
7
|
+
export { default as OwnerPage } from './pages/OwnerPage.js'
|
|
8
|
+
export { default as RepairOrderPage } from './pages/RepairOrderPage.js'
|
|
9
|
+
export { default as FeeBillPage } from './pages/FeeBillPage.js'
|
|
10
|
+
export { default as PatrolPage } from './pages/PatrolPage.js'
|
|
11
|
+
export { default as FacilityPage } from './pages/FacilityPage.js'
|
|
12
|
+
export { default as ContractPage } from './pages/ContractPage.js'
|
|
13
|
+
export { default as MeterPage } from './pages/MeterPage.js'
|
|
14
|
+
|
|
15
|
+
// ── Components ────────────────────────────────────────────────────────────────
|
|
16
|
+
export { RepairStatusBadge } from './components/RepairStatusBadge.js'
|
|
17
|
+
export { BillStatusBadge } from './components/BillStatusBadge.js'
|
|
18
|
+
export { CommunitySelect } from './components/CommunitySelect.js'
|
|
19
|
+
|
|
20
|
+
// ── Services ──────────────────────────────────────────────────────────────────
|
|
21
|
+
export * from './services/property-api.service.js'
|
|
22
|
+
|
|
23
|
+
// ── Stores ────────────────────────────────────────────────────────────────────
|
|
24
|
+
export { usePropertyStore } from './stores/property-store.js'
|
package/web/manifest.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { IFrontendManifest } from '@octo-cyber/core'
|
|
2
|
+
|
|
3
|
+
export const propertyManifest: IFrontendManifest = {
|
|
4
|
+
moduleId: 'property-management',
|
|
5
|
+
icon: 'Building2',
|
|
6
|
+
color: 'cyan',
|
|
7
|
+
position: 'main',
|
|
8
|
+
titleKey: 'property.title',
|
|
9
|
+
descriptionKey: 'property.description',
|
|
10
|
+
pages: [
|
|
11
|
+
{
|
|
12
|
+
path: '/property',
|
|
13
|
+
titleKey: 'property.pages.dashboard',
|
|
14
|
+
icon: 'LayoutDashboard',
|
|
15
|
+
component: '@octo-cyber/property-management/web/pages/DashboardPage',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
path: '/property/communities',
|
|
19
|
+
titleKey: 'property.pages.communities',
|
|
20
|
+
icon: 'Building2',
|
|
21
|
+
component: '@octo-cyber/property-management/web/pages/CommunityPage',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
path: '/property/owners',
|
|
25
|
+
titleKey: 'property.pages.owners',
|
|
26
|
+
icon: 'Users',
|
|
27
|
+
component: '@octo-cyber/property-management/web/pages/OwnerPage',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
path: '/property/repairs',
|
|
31
|
+
titleKey: 'property.pages.repairs',
|
|
32
|
+
icon: 'Wrench',
|
|
33
|
+
component: '@octo-cyber/property-management/web/pages/RepairOrderPage',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
path: '/property/bills',
|
|
37
|
+
titleKey: 'property.pages.bills',
|
|
38
|
+
icon: 'Receipt',
|
|
39
|
+
component: '@octo-cyber/property-management/web/pages/FeeBillPage',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
path: '/property/patrol',
|
|
43
|
+
titleKey: 'property.pages.patrol',
|
|
44
|
+
icon: 'Route',
|
|
45
|
+
component: '@octo-cyber/property-management/web/pages/PatrolPage',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
path: '/property/facilities',
|
|
49
|
+
titleKey: 'property.pages.facilities',
|
|
50
|
+
icon: 'Cog',
|
|
51
|
+
component: '@octo-cyber/property-management/web/pages/FacilityPage',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
path: '/property/contracts',
|
|
55
|
+
titleKey: 'property.pages.contracts',
|
|
56
|
+
icon: 'FileText',
|
|
57
|
+
component: '@octo-cyber/property-management/web/pages/ContractPage',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
path: '/property/meters',
|
|
61
|
+
titleKey: 'property.pages.meters',
|
|
62
|
+
icon: 'Gauge',
|
|
63
|
+
component: '@octo-cyber/property-management/web/pages/MeterPage',
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"property": {
|
|
3
|
+
"title": "Property Management",
|
|
4
|
+
"description": "Community registry, repair orders, billing, and patrol management",
|
|
5
|
+
"pages": {
|
|
6
|
+
"dashboard": "Dashboard",
|
|
7
|
+
"communities": "Communities",
|
|
8
|
+
"owners": "Owners",
|
|
9
|
+
"repairs": "Repair Orders",
|
|
10
|
+
"bills": "Billing",
|
|
11
|
+
"patrol": "Patrol",
|
|
12
|
+
"facilities": "Facilities",
|
|
13
|
+
"contracts": "Contracts",
|
|
14
|
+
"meters": "Meters"
|
|
15
|
+
},
|
|
16
|
+
"common": {
|
|
17
|
+
"create": "Create",
|
|
18
|
+
"edit": "Edit",
|
|
19
|
+
"delete": "Delete",
|
|
20
|
+
"confirm": "Confirm",
|
|
21
|
+
"cancel": "Cancel",
|
|
22
|
+
"save": "Save",
|
|
23
|
+
"search": "Search",
|
|
24
|
+
"loading": "Loading...",
|
|
25
|
+
"noData": "No data",
|
|
26
|
+
"total": "{count} items"
|
|
27
|
+
},
|
|
28
|
+
"community": {
|
|
29
|
+
"name": "Community Name",
|
|
30
|
+
"address": "Address",
|
|
31
|
+
"totalRooms": "Total Units",
|
|
32
|
+
"occupancyRate": "Occupancy Rate",
|
|
33
|
+
"status": {
|
|
34
|
+
"ACTIVE": "Active",
|
|
35
|
+
"INACTIVE": "Inactive"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"repair": {
|
|
39
|
+
"status": {
|
|
40
|
+
"PENDING": "Pending",
|
|
41
|
+
"DISPATCHED": "Dispatched",
|
|
42
|
+
"IN_PROGRESS": "In Progress",
|
|
43
|
+
"COMPLETED": "Completed",
|
|
44
|
+
"VERIFIED": "Verified",
|
|
45
|
+
"CANCELLED": "Cancelled",
|
|
46
|
+
"REOPENED": "Reopened"
|
|
47
|
+
},
|
|
48
|
+
"urgency": {
|
|
49
|
+
"LOW": "Low",
|
|
50
|
+
"MEDIUM": "Normal",
|
|
51
|
+
"HIGH": "Urgent",
|
|
52
|
+
"URGENT": "Critical"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"bill": {
|
|
56
|
+
"status": {
|
|
57
|
+
"UNPAID": "Unpaid",
|
|
58
|
+
"PARTIAL": "Partial",
|
|
59
|
+
"PAID": "Paid",
|
|
60
|
+
"OVERDUE": "Overdue",
|
|
61
|
+
"WAIVED": "Waived"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"patrol": {
|
|
65
|
+
"status": {
|
|
66
|
+
"PENDING": "Pending",
|
|
67
|
+
"IN_PROGRESS": "In Progress",
|
|
68
|
+
"COMPLETED": "Completed",
|
|
69
|
+
"MISSED": "Missed"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"facility": {
|
|
73
|
+
"status": {
|
|
74
|
+
"NORMAL": "Normal",
|
|
75
|
+
"FAULTY": "Faulty",
|
|
76
|
+
"MAINTENANCE": "Maintenance",
|
|
77
|
+
"DECOMMISSIONED": "Decommissioned"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"property": {
|
|
3
|
+
"title": "物业管理",
|
|
4
|
+
"description": "小区台账、报修工单、物业收费、巡检管理一站式平台",
|
|
5
|
+
"pages": {
|
|
6
|
+
"dashboard": "物业驾驶舱",
|
|
7
|
+
"communities": "小区管理",
|
|
8
|
+
"owners": "业主管理",
|
|
9
|
+
"repairs": "报修工单",
|
|
10
|
+
"bills": "收费账单",
|
|
11
|
+
"patrol": "巡检管理",
|
|
12
|
+
"facilities": "设备设施",
|
|
13
|
+
"contracts": "合同管理",
|
|
14
|
+
"meters": "仪表抄表"
|
|
15
|
+
},
|
|
16
|
+
"common": {
|
|
17
|
+
"create": "新建",
|
|
18
|
+
"edit": "编辑",
|
|
19
|
+
"delete": "删除",
|
|
20
|
+
"confirm": "确认",
|
|
21
|
+
"cancel": "取消",
|
|
22
|
+
"save": "保存",
|
|
23
|
+
"search": "搜索",
|
|
24
|
+
"loading": "加载中...",
|
|
25
|
+
"noData": "暂无数据",
|
|
26
|
+
"total": "共 {count} 条"
|
|
27
|
+
},
|
|
28
|
+
"community": {
|
|
29
|
+
"name": "小区名称",
|
|
30
|
+
"address": "地址",
|
|
31
|
+
"totalRooms": "总户数",
|
|
32
|
+
"occupancyRate": "入住率",
|
|
33
|
+
"status": {
|
|
34
|
+
"ACTIVE": "正常",
|
|
35
|
+
"INACTIVE": "停用"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"repair": {
|
|
39
|
+
"status": {
|
|
40
|
+
"PENDING": "待派单",
|
|
41
|
+
"DISPATCHED": "已派单",
|
|
42
|
+
"IN_PROGRESS": "维修中",
|
|
43
|
+
"COMPLETED": "待验收",
|
|
44
|
+
"VERIFIED": "已验收",
|
|
45
|
+
"CANCELLED": "已取消",
|
|
46
|
+
"REOPENED": "已重开"
|
|
47
|
+
},
|
|
48
|
+
"urgency": {
|
|
49
|
+
"LOW": "低",
|
|
50
|
+
"MEDIUM": "普通",
|
|
51
|
+
"HIGH": "紧急",
|
|
52
|
+
"URGENT": "特急"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"bill": {
|
|
56
|
+
"status": {
|
|
57
|
+
"UNPAID": "未缴",
|
|
58
|
+
"PARTIAL": "部分缴",
|
|
59
|
+
"PAID": "已缴清",
|
|
60
|
+
"OVERDUE": "已逾期",
|
|
61
|
+
"WAIVED": "已减免"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"patrol": {
|
|
65
|
+
"status": {
|
|
66
|
+
"PENDING": "待巡检",
|
|
67
|
+
"IN_PROGRESS": "巡检中",
|
|
68
|
+
"COMPLETED": "已完成",
|
|
69
|
+
"MISSED": "漏检"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"facility": {
|
|
73
|
+
"status": {
|
|
74
|
+
"NORMAL": "正常",
|
|
75
|
+
"FAULTY": "故障",
|
|
76
|
+
"MAINTENANCE": "维修中",
|
|
77
|
+
"DECOMMISSIONED": "已报废"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react'
|
|
4
|
+
import { Plus, Pencil, Trash2, Search } from 'lucide-react'
|
|
5
|
+
import { Button } from '@octo-cyber/ui/components/button'
|
|
6
|
+
import { Input } from '@octo-cyber/ui/components/input'
|
|
7
|
+
import { Badge } from '@octo-cyber/ui/components/badge'
|
|
8
|
+
import {
|
|
9
|
+
Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
|
|
10
|
+
} from '@octo-cyber/ui/components/table'
|
|
11
|
+
import {
|
|
12
|
+
Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle,
|
|
13
|
+
} from '@octo-cyber/ui/components/dialog'
|
|
14
|
+
import { Label } from '@octo-cyber/ui/components/label'
|
|
15
|
+
import { toast } from 'sonner'
|
|
16
|
+
import {
|
|
17
|
+
listCommunities, createCommunity, updateCommunity, deleteCommunity,
|
|
18
|
+
type Community,
|
|
19
|
+
} from '../services/property-api.service.js'
|
|
20
|
+
|
|
21
|
+
export default function CommunityPage() {
|
|
22
|
+
const [items, setItems] = useState<Community[]>([])
|
|
23
|
+
const [total, setTotal] = useState(0)
|
|
24
|
+
const [page, setPage] = useState(1)
|
|
25
|
+
const [keyword, setKeyword] = useState('')
|
|
26
|
+
const [loading, setLoading] = useState(false)
|
|
27
|
+
const [dialogOpen, setDialogOpen] = useState(false)
|
|
28
|
+
const [editing, setEditing] = useState<Partial<Community> | null>(null)
|
|
29
|
+
const [saving, setSaving] = useState(false)
|
|
30
|
+
|
|
31
|
+
const load = () => {
|
|
32
|
+
setLoading(true)
|
|
33
|
+
listCommunities({ page, pageSize: 20, keyword })
|
|
34
|
+
.then((r) => { setItems(r.items); setTotal(r.total) })
|
|
35
|
+
.catch(() => toast.error('加载失败'))
|
|
36
|
+
.finally(() => setLoading(false))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
useEffect(() => { load() }, [page, keyword])
|
|
40
|
+
|
|
41
|
+
const openCreate = () => {
|
|
42
|
+
setEditing({ name: '', address: '', propertyType: 'RESIDENTIAL', status: 'ACTIVE' })
|
|
43
|
+
setDialogOpen(true)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const openEdit = (c: Community) => {
|
|
47
|
+
setEditing({ ...c })
|
|
48
|
+
setDialogOpen(true)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const handleSave = async () => {
|
|
52
|
+
if (!editing?.name || !editing?.address) {
|
|
53
|
+
toast.error('小区名称和地址不能为空')
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
setSaving(true)
|
|
57
|
+
try {
|
|
58
|
+
if (editing.id) {
|
|
59
|
+
await updateCommunity(editing.id, editing)
|
|
60
|
+
toast.success('更新成功')
|
|
61
|
+
} else {
|
|
62
|
+
await createCommunity(editing)
|
|
63
|
+
toast.success('创建成功')
|
|
64
|
+
}
|
|
65
|
+
setDialogOpen(false)
|
|
66
|
+
load()
|
|
67
|
+
} catch (e: unknown) {
|
|
68
|
+
toast.error((e as Error).message ?? '操作失败')
|
|
69
|
+
} finally {
|
|
70
|
+
setSaving(false)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const handleDelete = async (id: string, name: string) => {
|
|
75
|
+
if (!confirm(`确认删除小区「${name}」?`)) return
|
|
76
|
+
try {
|
|
77
|
+
await deleteCommunity(id)
|
|
78
|
+
toast.success('删除成功')
|
|
79
|
+
load()
|
|
80
|
+
} catch (e: unknown) {
|
|
81
|
+
toast.error((e as Error).message ?? '删除失败')
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className="space-y-4 p-6">
|
|
87
|
+
<div className="flex items-center justify-between">
|
|
88
|
+
<h1 className="text-2xl font-bold">小区管理</h1>
|
|
89
|
+
<Button onClick={openCreate}><Plus className="mr-2 h-4 w-4" />新建小区</Button>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div className="flex items-center gap-2">
|
|
93
|
+
<Search className="h-4 w-4 text-muted-foreground" />
|
|
94
|
+
<Input
|
|
95
|
+
placeholder="搜索小区名称..."
|
|
96
|
+
value={keyword}
|
|
97
|
+
onChange={(e) => { setKeyword(e.target.value); setPage(1) }}
|
|
98
|
+
className="max-w-xs"
|
|
99
|
+
/>
|
|
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((c) => (
|
|
121
|
+
<TableRow key={c.id}>
|
|
122
|
+
<TableCell className="font-medium">{c.name}</TableCell>
|
|
123
|
+
<TableCell>{c.address}</TableCell>
|
|
124
|
+
<TableCell>{c.totalRooms}</TableCell>
|
|
125
|
+
<TableCell>{c.occupiedRooms}</TableCell>
|
|
126
|
+
<TableCell>
|
|
127
|
+
<Badge variant={c.status === 'ACTIVE' ? 'default' : 'secondary'}>
|
|
128
|
+
{c.status === 'ACTIVE' ? '正常' : '停用'}
|
|
129
|
+
</Badge>
|
|
130
|
+
</TableCell>
|
|
131
|
+
<TableCell>
|
|
132
|
+
<div className="flex gap-1">
|
|
133
|
+
<Button variant="ghost" size="icon" onClick={() => openEdit(c)}>
|
|
134
|
+
<Pencil className="h-4 w-4" />
|
|
135
|
+
</Button>
|
|
136
|
+
<Button variant="ghost" size="icon" onClick={() => handleDelete(c.id, c.name)}>
|
|
137
|
+
<Trash2 className="h-4 w-4 text-destructive" />
|
|
138
|
+
</Button>
|
|
139
|
+
</div>
|
|
140
|
+
</TableCell>
|
|
141
|
+
</TableRow>
|
|
142
|
+
))}
|
|
143
|
+
</TableBody>
|
|
144
|
+
</Table>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
{total > 20 && (
|
|
148
|
+
<div className="flex justify-end gap-2">
|
|
149
|
+
<Button variant="outline" size="sm" disabled={page <= 1} onClick={() => setPage(p => p - 1)}>上一页</Button>
|
|
150
|
+
<span className="flex items-center text-sm text-muted-foreground">第 {page} 页</span>
|
|
151
|
+
<Button variant="outline" size="sm" disabled={page * 20 >= total} onClick={() => setPage(p => p + 1)}>下一页</Button>
|
|
152
|
+
</div>
|
|
153
|
+
)}
|
|
154
|
+
|
|
155
|
+
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
|
156
|
+
<DialogContent>
|
|
157
|
+
<DialogHeader>
|
|
158
|
+
<DialogTitle>{editing?.id ? '编辑小区' : '新建小区'}</DialogTitle>
|
|
159
|
+
</DialogHeader>
|
|
160
|
+
<div className="space-y-4 py-2">
|
|
161
|
+
<div className="space-y-1">
|
|
162
|
+
<Label>小区名称 *</Label>
|
|
163
|
+
<Input value={editing?.name ?? ''} onChange={(e) => setEditing(v => ({ ...v, name: e.target.value }))} />
|
|
164
|
+
</div>
|
|
165
|
+
<div className="space-y-1">
|
|
166
|
+
<Label>地址 *</Label>
|
|
167
|
+
<Input value={editing?.address ?? ''} onChange={(e) => setEditing(v => ({ ...v, address: e.target.value }))} />
|
|
168
|
+
</div>
|
|
169
|
+
<div className="space-y-1">
|
|
170
|
+
<Label>联系电话</Label>
|
|
171
|
+
<Input value={editing?.contactPhone ?? ''} onChange={(e) => setEditing(v => ({ ...v, contactPhone: e.target.value }))} />
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
<DialogFooter>
|
|
175
|
+
<Button variant="outline" onClick={() => setDialogOpen(false)}>取消</Button>
|
|
176
|
+
<Button onClick={handleSave} disabled={saving}>{saving ? '保存中...' : '保存'}</Button>
|
|
177
|
+
</DialogFooter>
|
|
178
|
+
</DialogContent>
|
|
179
|
+
</Dialog>
|
|
180
|
+
</div>
|
|
181
|
+
)
|
|
182
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
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 { Badge } from '@octo-cyber/ui/components/badge'
|
|
8
|
+
import {
|
|
9
|
+
Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
|
|
10
|
+
} from '@octo-cyber/ui/components/table'
|
|
11
|
+
import { toast } from 'sonner'
|
|
12
|
+
import type { Contract } from '../services/property-api.service.js'
|
|
13
|
+
|
|
14
|
+
const STATUS_LABELS: Record<string, string> = {
|
|
15
|
+
DRAFT: '草稿', ACTIVE: '有效', EXPIRED: '已到期', TERMINATED: '已终止',
|
|
16
|
+
}
|
|
17
|
+
const STATUS_VARIANT: Record<string, 'default' | 'secondary' | 'destructive' | 'outline'> = {
|
|
18
|
+
DRAFT: 'outline', ACTIVE: 'default', EXPIRED: 'secondary', TERMINATED: 'destructive',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function fetchContracts(params: { page: number; keyword?: string }): Promise<{ items: Contract[]; total: number }> {
|
|
22
|
+
const qs = new URLSearchParams({ page: String(params.page), pageSize: '20' })
|
|
23
|
+
if (params.keyword) qs.set('keyword', params.keyword)
|
|
24
|
+
const res = await fetch(`/api/v1/property/contracts?${qs}`)
|
|
25
|
+
const json = await res.json()
|
|
26
|
+
if (!res.ok) throw new Error(json.message ?? 'Request failed')
|
|
27
|
+
return json.data
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default function ContractPage() {
|
|
31
|
+
const [items, setItems] = useState<Contract[]>([])
|
|
32
|
+
const [total, setTotal] = useState(0)
|
|
33
|
+
const [page, setPage] = useState(1)
|
|
34
|
+
const [keyword, setKeyword] = useState('')
|
|
35
|
+
const [loading, setLoading] = useState(false)
|
|
36
|
+
|
|
37
|
+
const load = () => {
|
|
38
|
+
setLoading(true)
|
|
39
|
+
fetchContracts({ page, keyword })
|
|
40
|
+
.then((r) => { setItems(r.items); setTotal(r.total) })
|
|
41
|
+
.catch(() => toast.error('加载失败'))
|
|
42
|
+
.finally(() => setLoading(false))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
useEffect(() => { load() }, [page, keyword])
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div className="space-y-4 p-6">
|
|
49
|
+
<div className="flex items-center justify-between">
|
|
50
|
+
<h1 className="text-2xl font-bold">合同管理</h1>
|
|
51
|
+
<Button disabled><Plus className="mr-2 h-4 w-4" />新建合同</Button>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div className="flex items-center gap-2">
|
|
55
|
+
<Search className="h-4 w-4 text-muted-foreground" />
|
|
56
|
+
<Input
|
|
57
|
+
placeholder="搜索合同名称..."
|
|
58
|
+
value={keyword}
|
|
59
|
+
onChange={(e) => { setKeyword(e.target.value); setPage(1) }}
|
|
60
|
+
className="max-w-xs"
|
|
61
|
+
/>
|
|
62
|
+
<span className="text-sm text-muted-foreground">共 {total} 条</span>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div className="rounded-md border">
|
|
66
|
+
<Table>
|
|
67
|
+
<TableHeader>
|
|
68
|
+
<TableRow>
|
|
69
|
+
<TableHead>合同编号</TableHead>
|
|
70
|
+
<TableHead>合同名称</TableHead>
|
|
71
|
+
<TableHead>类型</TableHead>
|
|
72
|
+
<TableHead>乙方</TableHead>
|
|
73
|
+
<TableHead>开始日期</TableHead>
|
|
74
|
+
<TableHead>到期日期</TableHead>
|
|
75
|
+
<TableHead>状态</TableHead>
|
|
76
|
+
</TableRow>
|
|
77
|
+
</TableHeader>
|
|
78
|
+
<TableBody>
|
|
79
|
+
{loading ? (
|
|
80
|
+
<TableRow><TableCell colSpan={7} className="text-center text-muted-foreground">加载中...</TableCell></TableRow>
|
|
81
|
+
) : items.length === 0 ? (
|
|
82
|
+
<TableRow><TableCell colSpan={7} className="text-center text-muted-foreground">暂无数据</TableCell></TableRow>
|
|
83
|
+
) : items.map((c) => (
|
|
84
|
+
<TableRow key={c.id}>
|
|
85
|
+
<TableCell className="font-mono text-xs">{c.contractNo}</TableCell>
|
|
86
|
+
<TableCell className="font-medium">{c.name}</TableCell>
|
|
87
|
+
<TableCell>{c.type}</TableCell>
|
|
88
|
+
<TableCell>{c.partyB}</TableCell>
|
|
89
|
+
<TableCell>{c.startDate?.slice(0, 10)}</TableCell>
|
|
90
|
+
<TableCell>{c.endDate?.slice(0, 10)}</TableCell>
|
|
91
|
+
<TableCell>
|
|
92
|
+
<Badge variant={STATUS_VARIANT[c.status] ?? 'secondary'}>
|
|
93
|
+
{STATUS_LABELS[c.status] ?? c.status}
|
|
94
|
+
</Badge>
|
|
95
|
+
</TableCell>
|
|
96
|
+
</TableRow>
|
|
97
|
+
))}
|
|
98
|
+
</TableBody>
|
|
99
|
+
</Table>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
{total > 20 && (
|
|
103
|
+
<div className="flex justify-end gap-2">
|
|
104
|
+
<Button variant="outline" size="sm" disabled={page <= 1} onClick={() => setPage(p => p - 1)}>上一页</Button>
|
|
105
|
+
<span className="flex items-center text-sm text-muted-foreground">第 {page} 页</span>
|
|
106
|
+
<Button variant="outline" size="sm" disabled={page * 20 >= total} onClick={() => setPage(p => p + 1)}>下一页</Button>
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react'
|
|
4
|
+
import { useTranslations } from 'next-intl'
|
|
5
|
+
import { Building2, Wrench, Receipt, AlertTriangle } from 'lucide-react'
|
|
6
|
+
import { Card, CardContent, CardHeader, CardTitle } from '@octo-cyber/ui/components/card'
|
|
7
|
+
import { toast } from 'sonner'
|
|
8
|
+
import { getStatsDashboard, listCommunities, type DashboardData, type Community } from '../services/property-api.service.js'
|
|
9
|
+
import { CommunitySelect } from '../components/CommunitySelect.js'
|
|
10
|
+
|
|
11
|
+
export default function DashboardPage() {
|
|
12
|
+
const t = useTranslations('property')
|
|
13
|
+
const [communityId, setCommunityId] = useState<string>('')
|
|
14
|
+
const [communities, setCommunities] = useState<Community[]>([])
|
|
15
|
+
const [data, setData] = useState<DashboardData | null>(null)
|
|
16
|
+
const [loading, setLoading] = useState(false)
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
listCommunities({ pageSize: 100 })
|
|
20
|
+
.then((r) => {
|
|
21
|
+
setCommunities(r.items)
|
|
22
|
+
if (r.items.length > 0) setCommunityId(r.items[0].id)
|
|
23
|
+
})
|
|
24
|
+
.catch(() => toast.error('加载小区列表失败'))
|
|
25
|
+
}, [])
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!communityId) return
|
|
29
|
+
setLoading(true)
|
|
30
|
+
getStatsDashboard(communityId)
|
|
31
|
+
.then(setData)
|
|
32
|
+
.catch(() => toast.error('加载数据失败'))
|
|
33
|
+
.finally(() => setLoading(false))
|
|
34
|
+
}, [communityId])
|
|
35
|
+
|
|
36
|
+
const stats = [
|
|
37
|
+
{
|
|
38
|
+
title: '入住率',
|
|
39
|
+
value: data ? `${(data.occupancyRate * 100).toFixed(1)}%` : '--',
|
|
40
|
+
icon: Building2,
|
|
41
|
+
color: 'text-blue-500',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
title: '收费率',
|
|
45
|
+
value: data ? `${(data.collectionRate * 100).toFixed(1)}%` : '--',
|
|
46
|
+
icon: Receipt,
|
|
47
|
+
color: 'text-green-500',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
title: '待处理报修',
|
|
51
|
+
value: data?.pendingRepairs ?? '--',
|
|
52
|
+
icon: Wrench,
|
|
53
|
+
color: 'text-orange-500',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
title: '逾期账单',
|
|
57
|
+
value: data?.overdueCount ?? '--',
|
|
58
|
+
icon: AlertTriangle,
|
|
59
|
+
color: 'text-red-500',
|
|
60
|
+
},
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className="space-y-6 p-6">
|
|
65
|
+
<div className="flex items-center justify-between">
|
|
66
|
+
<h1 className="text-2xl font-bold">{t('pages.dashboard')}</h1>
|
|
67
|
+
<div className="w-48">
|
|
68
|
+
<CommunitySelect value={communityId} onChange={setCommunityId} placeholder="选择小区" />
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
{loading ? (
|
|
73
|
+
<div className="grid grid-cols-2 gap-4 lg:grid-cols-4">
|
|
74
|
+
{[1, 2, 3, 4].map((i) => (
|
|
75
|
+
<Card key={i} className="animate-pulse">
|
|
76
|
+
<CardContent className="p-6">
|
|
77
|
+
<div className="h-8 bg-muted rounded" />
|
|
78
|
+
</CardContent>
|
|
79
|
+
</Card>
|
|
80
|
+
))}
|
|
81
|
+
</div>
|
|
82
|
+
) : (
|
|
83
|
+
<div className="grid grid-cols-2 gap-4 lg:grid-cols-4">
|
|
84
|
+
{stats.map((stat) => (
|
|
85
|
+
<Card key={stat.title}>
|
|
86
|
+
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
87
|
+
<CardTitle className="text-sm font-medium text-muted-foreground">{stat.title}</CardTitle>
|
|
88
|
+
<stat.icon className={`h-4 w-4 ${stat.color}`} />
|
|
89
|
+
</CardHeader>
|
|
90
|
+
<CardContent>
|
|
91
|
+
<div className="text-2xl font-bold">{stat.value}</div>
|
|
92
|
+
</CardContent>
|
|
93
|
+
</Card>
|
|
94
|
+
))}
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
97
|
+
|
|
98
|
+
{data?.community && (
|
|
99
|
+
<Card>
|
|
100
|
+
<CardHeader>
|
|
101
|
+
<CardTitle>{data.community.name}</CardTitle>
|
|
102
|
+
</CardHeader>
|
|
103
|
+
<CardContent>
|
|
104
|
+
<div className="grid grid-cols-2 gap-4 text-sm md:grid-cols-4">
|
|
105
|
+
<div>
|
|
106
|
+
<span className="text-muted-foreground">地址:</span>
|
|
107
|
+
<span>{data.community.address}</span>
|
|
108
|
+
</div>
|
|
109
|
+
<div>
|
|
110
|
+
<span className="text-muted-foreground">总户数:</span>
|
|
111
|
+
<span>{data.community.totalRooms}</span>
|
|
112
|
+
</div>
|
|
113
|
+
<div>
|
|
114
|
+
<span className="text-muted-foreground">已入住:</span>
|
|
115
|
+
<span>{data.community.occupiedRooms}</span>
|
|
116
|
+
</div>
|
|
117
|
+
<div>
|
|
118
|
+
<span className="text-muted-foreground">当前账期:</span>
|
|
119
|
+
<span>{data.currentPeriod}</span>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</CardContent>
|
|
123
|
+
</Card>
|
|
124
|
+
)}
|
|
125
|
+
|
|
126
|
+
{communities.length === 0 && !loading && (
|
|
127
|
+
<Card>
|
|
128
|
+
<CardContent className="flex flex-col items-center justify-center py-12 text-muted-foreground">
|
|
129
|
+
<Building2 className="mb-4 h-12 w-12 opacity-30" />
|
|
130
|
+
<p>暂无小区数据,请先在「小区管理」中添加小区</p>
|
|
131
|
+
</CardContent>
|
|
132
|
+
</Card>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
)
|
|
136
|
+
}
|