Skip to content

Commit 68d89a1

Browse files
authored
MIM-527: Add work order tab on residence detail page (#68)
* tree: scaffold workorder components * base: fix nullish coalesce * base: add log on error * tree: workorder properties are now camelCased * tree: only show work order table for all work orders * tree: render due date * tree: combine xpand and odoo work orders * tree: add sorting by due date if applicable on same status entries * tree: toggle some or all work orders * tree: add link to odoo
1 parent d6dded7 commit 68d89a1

File tree

9 files changed

+341
-90
lines changed

9 files changed

+341
-90
lines changed

packages/property-base/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"test": "jest --config jest.config.js",
1111
"test:watch": "jest --config jest.config.js --watch",
1212
"type-check": "tsc --noEmit",
13-
"type:watch": "tsc --noEmit --watch",
13+
"ts:watch": "tsc --noEmit --watch",
1414
"generate": "npx prisma generate",
1515
"force-generate": "rm -rf node_modules && npm install && npx prisma generate",
1616
"prettier-check": "prettier --check ."

packages/property-base/src/routes/residences-route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ export const routes = (router: KoaRouter) => {
330330
code: residence.propertyObject.propertyStructures[0].buildingCode,
331331
name: residence.propertyObject.propertyStructures[0].buildingName,
332332
},
333-
malarEnergiFacilityId: residence.comments?.[0].text || null,
333+
malarEnergiFacilityId: residence.comments?.[0]?.text || null,
334334
} satisfies ResidenceDetails
335335

336336
ctx.status = 200
@@ -339,6 +339,7 @@ export const routes = (router: KoaRouter) => {
339339
...metadata,
340340
}
341341
} catch (err) {
342+
logger.error(err, 'Error fetching residence by ID')
342343
ctx.status = 500
343344
const errorMessage = err instanceof Error ? err.message : 'unknown error'
344345
ctx.body = { reason: errorMessage, ...metadata }

packages/property-tree/src/components/residence/TenantInformation.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export function TenantInformation(props: Props) {
5252
isSecondaryRental={false}
5353
/>
5454
<div className="space-y-6">
55-
{lease.tenants?.concat(lease.tenants).map((tenant, i) => (
55+
{lease.tenants?.map((tenant, i) => (
5656
<>
5757
{i > 0 && <Separator />}
5858
<TenantCard lease={lease} tenant={tenant} key={i} />
Lines changed: 44 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,89 @@
11
import { useQuery } from '@tanstack/react-query'
2+
import { FilePlus } from 'lucide-react'
3+
24
import { Card } from '@/components/ui/Card'
35
import { Grid } from '@/components/ui/Grid'
46
import {
57
workOrderService,
68
WorkOrder,
79
} from '@/services/api/core/workOrderService'
10+
import { OrdersTable } from '../work-orders/OrderTable'
11+
import { Button } from '../ui/v2/Button'
812

913
interface ResidenceWorkOrdersProps {
1014
rentalId: string
1115
}
1216

13-
const sortByStatus = (a: WorkOrder, b: WorkOrder) => {
17+
const sortWorkOrders = (a: WorkOrder, b: WorkOrder) => {
1418
// TODO we should include the maintenance stage sequence in WorkOrder and sort by that
1519
const statusOrder = [
20+
'Påbörjad',
1621
'Väntar på handläggning',
1722
'Resurs tilldelad',
18-
'Påbörjad',
1923
'Väntar på beställda varor',
2024
'Utförd',
2125
'Avslutad',
2226
]
2327

24-
const statusA = statusOrder.indexOf(a.Status)
25-
const statusB = statusOrder.indexOf(b.Status)
28+
const statusA = statusOrder.indexOf(a.status)
29+
const statusB = statusOrder.indexOf(b.status)
2630

2731
if (statusA < statusB) {
2832
return -1
2933
}
34+
3035
if (statusA > statusB) {
3136
return 1
3237
}
33-
return 0
34-
}
35-
36-
const sortyByRegistered = (a: WorkOrder, b: WorkOrder) => {
37-
const dateA = new Date(a.Registered)
38-
const dateB = new Date(b.Registered)
3938

40-
if (dateA < dateB) {
41-
return -1
42-
}
43-
if (dateA > dateB) {
44-
return 1
39+
if (a.dueDate && b.dueDate) {
40+
return new Date(a.dueDate).getTime() - new Date(b.dueDate).getTime()
4541
}
42+
4643
return 0
4744
}
4845

4946
export function ResidenceWorkOrders({ rentalId }: ResidenceWorkOrdersProps) {
50-
const { data: workOrders, isLoading } = useQuery({
47+
const workOrdersQuery = useQuery({
5148
queryKey: ['workOrders', rentalId],
5249
queryFn: () => workOrderService.getWorkOrdersForResidence(rentalId),
5350
})
5451

55-
if (isLoading) {
52+
if (workOrdersQuery.isLoading) {
5653
return (
57-
<Card title="Ärenden">
58-
<Grid cols={2}>
59-
{[...Array(4)].map((_, i) => (
60-
<div
61-
key={i}
62-
className="h-48 bg-gray-200 dark:bg-gray-700 rounded-xl animate-pulse"
63-
/>
64-
))}
65-
</Grid>
66-
</Card>
54+
<Grid cols={1}>
55+
<div className="h-10 w-36 bg-gray-200 dark:bg-gray-700 rounded-xl animate-pulse" />
56+
<div className="h-24 bg-gray-200 dark:bg-gray-700 rounded-xl animate-pulse" />
57+
</Grid>
6758
)
6859
}
6960

61+
if (workOrdersQuery.error || !workOrdersQuery.data) {
62+
return (
63+
<div className="p-8 text-center">
64+
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100">
65+
Kontrakt hittades inte
66+
</h2>
67+
</div>
68+
)
69+
}
70+
71+
const workOrders = workOrdersQuery.data.sort(sortWorkOrders)
72+
7073
return (
71-
<Card title="Ärenden">
72-
<Grid cols={2}>
73-
{workOrders
74-
?.sort(sortyByRegistered)
75-
.sort(sortByStatus)
76-
.map((workOrder) => (
77-
<a key={workOrder.Id} href={workOrder.Url} target="_blank">
78-
<Card>
79-
<p>{workOrder.Code}</p>
80-
<p className="font-bold">{workOrder.Caption}</p>
81-
<p>{new Date(workOrder.Registered).toLocaleString()}</p>
82-
<p>{workOrder.Status}</p>
83-
</Card>
84-
</a>
85-
))}
86-
</Grid>
87-
</Card>
74+
<div className="space-y-4">
75+
<div className="flex items-center justify-start">
76+
<Button disabled size={'default'} variant={'default'}>
77+
<FilePlus className="mr-2 h-4 w-4" />
78+
Skapa ärende
79+
</Button>
80+
</div>
81+
82+
{workOrders.length > 0 ? (
83+
<OrdersTable orders={workOrdersQuery.data} />
84+
) : (
85+
<p className="text-slate-500 p-2">Ingen ärendehistorik.</p>
86+
)}
87+
</div>
8888
)
8989
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import * as React from 'react'
2+
import { cn } from '@/lib/utils'
3+
4+
const Table = React.forwardRef<
5+
HTMLTableElement,
6+
React.HTMLAttributes<HTMLTableElement>
7+
>(({ className, ...props }, ref) => (
8+
<div className="relative w-full overflow-auto">
9+
<table
10+
ref={ref}
11+
className={cn('w-full caption-bottom text-sm', className)}
12+
{...props}
13+
/>
14+
</div>
15+
))
16+
Table.displayName = 'Table'
17+
18+
const TableHeader = React.forwardRef<
19+
HTMLTableSectionElement,
20+
React.HTMLAttributes<HTMLTableSectionElement>
21+
>(({ className, ...props }, ref) => (
22+
<thead
23+
ref={ref}
24+
className={cn('[&_tr]:border-b bg-secondary/50', className)}
25+
{...props}
26+
/>
27+
))
28+
TableHeader.displayName = 'TableHeader'
29+
30+
const TableBody = React.forwardRef<
31+
HTMLTableSectionElement,
32+
React.HTMLAttributes<HTMLTableSectionElement>
33+
>(({ className, ...props }, ref) => (
34+
<tbody
35+
ref={ref}
36+
className={cn('[&_tr:last-child]:border-0', className)}
37+
{...props}
38+
/>
39+
))
40+
TableBody.displayName = 'TableBody'
41+
42+
const TableFooter = React.forwardRef<
43+
HTMLTableSectionElement,
44+
React.HTMLAttributes<HTMLTableSectionElement>
45+
>(({ className, ...props }, ref) => (
46+
<tfoot
47+
ref={ref}
48+
className={cn(
49+
'border-t bg-muted/50 font-medium [&>tr]:last:border-b-0',
50+
className
51+
)}
52+
{...props}
53+
/>
54+
))
55+
TableFooter.displayName = 'TableFooter'
56+
57+
const TableRow = React.forwardRef<
58+
HTMLTableRowElement,
59+
React.HTMLAttributes<HTMLTableRowElement>
60+
>(({ className, ...props }, ref) => (
61+
<tr
62+
ref={ref}
63+
className={cn('border-b data-[state=selected]:bg-muted', className)}
64+
{...props}
65+
/>
66+
))
67+
TableRow.displayName = 'TableRow'
68+
69+
const TableHead = React.forwardRef<
70+
HTMLTableCellElement,
71+
React.ThHTMLAttributes<HTMLTableCellElement>
72+
>(({ className, ...props }, ref) => (
73+
<th
74+
ref={ref}
75+
className={cn(
76+
'h-11 px-4 text-left align-middle font-semibold text-muted-foreground [&:has([role=checkbox])]:pr-0',
77+
className
78+
)}
79+
{...props}
80+
/>
81+
))
82+
TableHead.displayName = 'TableHead'
83+
84+
const TableCell = React.forwardRef<
85+
HTMLTableCellElement,
86+
React.TdHTMLAttributes<HTMLTableCellElement>
87+
>(({ className, ...props }, ref) => (
88+
<td
89+
ref={ref}
90+
className={cn('p-4 align-middle [&:has([role=checkbox])]:pr-0', className)}
91+
{...props}
92+
/>
93+
))
94+
TableCell.displayName = 'TableCell'
95+
96+
const TableCaption = React.forwardRef<
97+
HTMLTableCaptionElement,
98+
React.HTMLAttributes<HTMLTableCaptionElement>
99+
>(({ className, ...props }, ref) => (
100+
<caption
101+
ref={ref}
102+
className={cn('mt-4 text-sm text-muted-foreground', className)}
103+
{...props}
104+
/>
105+
))
106+
TableCaption.displayName = 'TableCaption'
107+
108+
export {
109+
Table,
110+
TableHeader,
111+
TableBody,
112+
TableFooter,
113+
TableHead,
114+
TableRow,
115+
TableCell,
116+
TableCaption,
117+
}

packages/property-tree/src/components/views/ResidenceView.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,7 @@ export function ResidenceView() {
6464
Hyresgäst
6565
</TabsTrigger>
6666
<TabsTrigger
67-
disabled
68-
value="issues"
67+
value="workorders"
6968
className="flex items-center gap-1.5"
7069
>
7170
<MessageSquare className="h-4 w-4" />
@@ -88,13 +87,19 @@ export function ResidenceView() {
8887
</CardContent>
8988
</Card>
9089
</TabsContent>
90+
<TabsContent value="workorders">
91+
<Card>
92+
<CardContent className="p-4">
93+
{residence.propertyObject.rentalId && (
94+
<ResidenceWorkOrders
95+
rentalId={residence.propertyObject.rentalId}
96+
/>
97+
)}
98+
</CardContent>
99+
</Card>
100+
</TabsContent>
91101
</Tabs>
92102
</div>
93-
<div className="lg:col-span-2 space-y-6">
94-
{residence.propertyObject.rentalId && (
95-
<ResidenceWorkOrders rentalId={residence.propertyObject.rentalId} />
96-
)}
97-
</div>
98103
</div>
99104
)
100105
}

0 commit comments

Comments
 (0)