fix(dashboard): resolve Asma tester-reported UI issues

- 3.1 currency: replace hardcoded $ with AED <Money/> in item & job-card price cells
- 5.1 calendar: /calendars now renders the month calendar (was redirect to list)
- 7.2 nav: remove dead Time Clocks / Time Sheets links (no routes, 404)
- 5.4 check-in: enforce required department at schema level
- 5.6 payments received: add invoice picker so the invoice is marked Paid
- 5.2 share link: forward ws workspace param from the public inspection page

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Najjar\NajjarV02
2026-06-05 14:58:18 +04:00
co-authored by Claude Opus 4.8
parent 0171daea38
commit 11d920ba6a
15 changed files with 104 additions and 26 deletions
@@ -1,5 +1,12 @@
import { redirect } from "next/navigation"
"use client"
import { AppointmentsCalendarView } from "@/modules/appointments/appointments-calendar-view"
export default function CalendarsPage() {
return redirect("/calendar/appointment/list")
return (
<div className="space-y-4 p-4">
<h1 className="text-xl font-semibold">Calendar</h1>
<AppointmentsCalendarView />
</div>
)
}
@@ -4,6 +4,7 @@ import { ResourcePage } from "@/shared/data-view/resource-page"
import { ColumnHeader } from "@/shared/data-view/table-view"
import FormDialog from "@/shared/components/form-dialog"
import { ExpenseItemForm } from "@/modules/expense-items/expense-item-form"
import { Money } from "@/shared/components/money"
import { EXPENSE_ITEM_ROUTES } from "@garage/api"
import type { ExpenseItemsClient } from "@garage/api"
@@ -41,7 +42,7 @@ export default function ExpenseItemPage() {
header: ({ column }) => <ColumnHeader column={column} title="Purchase Price" />,
cell: ({ row }) => {
const val = (row.original as any).purchase_price
return val != null ? `$${Number(val).toFixed(2)}` : "—"
return val != null ? <Money value={val} /> : "—"
},
},
{
@@ -4,6 +4,7 @@ import { ResourcePage } from "@/shared/data-view/resource-page"
import { ColumnHeader } from "@/shared/data-view/table-view"
import FormDialog from "@/shared/components/form-dialog"
import { ServiceGroupForm } from "@/modules/service-groups/service-group-form"
import { Money } from "@/shared/components/money"
import { Badge } from "@/shared/components/ui/badge"
import { SERVICE_GROUP_ROUTES } from "@garage/api"
import type { ServiceGroupsClient } from "@garage/api"
@@ -50,7 +51,7 @@ export default function ServiceGroupPage() {
header: ({ column }) => <ColumnHeader column={column} title="Price" />,
cell: ({ row }) => {
const val = (row.original as any).selling_price
return val != null ? `$${Number(val).toFixed(2)}` : "—"
return val != null ? <Money value={val} /> : "—"
},
},
{
@@ -5,6 +5,7 @@ import { ColumnHeader } from "@/shared/data-view/table-view"
import FormDialog from "@/shared/components/form-dialog"
import { ImportDataButton } from "@/shared/components/import-data-button"
import { ExportDataButton } from "@/shared/components/export-data-button"
import { Money } from "@/shared/components/money"
import { useAuthApi } from "@/shared/useApi"
import { ServiceForm } from "@/modules/services/service-form"
import { SERVICE_ROUTES } from "@garage/api"
@@ -75,7 +76,7 @@ export default function ServicesPage() {
header: ({ column }) => <ColumnHeader column={column} title="Price" />,
cell: ({ row }) => {
const val = (row.original as any).selling_price
return val != null ? `$${Number(val).toFixed(2)}` : "—"
return val != null ? <Money value={val} /> : "—"
},
},
{
@@ -24,6 +24,7 @@ import { toast } from "sonner"
import { Ellipsis, Plus } from "lucide-react"
import type { ColumnDef } from "@tanstack/react-table"
import { JobCardExpenseItemForm } from "@/modules/job-cards/job-card-expense-item-form"
import { Money } from "@/shared/components/money"
import { formatDate } from "@/shared/utils/formatters"
// TODO: expense items invalidation is not working properly when create new expense item line. Need to investigate why and fix it.
@@ -93,7 +94,7 @@ export default function JobCardExpenseItemsPage({
header: ({ column }) => <ColumnHeader column={column} title="Rate" />,
cell: ({ row }) => {
const val = row.original.rate
return val != null ? `$${Number(val).toFixed(2)}` : "—"
return val != null ? <Money value={val} /> : "—"
},
},
{
@@ -101,7 +102,7 @@ export default function JobCardExpenseItemsPage({
header: ({ column }) => <ColumnHeader column={column} title="Discount" />,
cell: ({ row }) => {
const val = row.original.discount_amount
return val != null && val > 0 ? `$${Number(val).toFixed(2)}` : "—"
return val != null && val > 0 ? <Money value={val} /> : "—"
},
},
{
@@ -24,6 +24,7 @@ import { toast } from "sonner"
import { Ellipsis, Plus } from "lucide-react"
import type { ColumnDef } from "@tanstack/react-table"
import { JobCardPartForm } from "@/modules/job-cards/job-card-part-form"
import { Money } from "@/shared/components/money"
import { formatDate } from "@/shared/utils/formatters"
import { JOB_CARD_ROUTES } from "@garage/api"
@@ -93,7 +94,7 @@ export default function JobCardPartsPage({
header: ({ column }) => <ColumnHeader column={column} title="Rate" />,
cell: ({ row }) => {
const val = row.original.rate
return val != null ? `$${Number(val).toFixed(2)}` : "—"
return val != null ? <Money value={val} /> : "—"
},
},
{
@@ -101,7 +102,7 @@ export default function JobCardPartsPage({
header: ({ column }) => <ColumnHeader column={column} title="Discount" />,
cell: ({ row }) => {
const val = row.original.discount_amount
return val != null && val > 0 ? `$${Number(val).toFixed(2)}` : "—"
return val != null && val > 0 ? <Money value={val} /> : "—"
},
},
{
@@ -24,6 +24,7 @@ import { toast } from "sonner"
import { Ellipsis, Plus } from "lucide-react"
import type { ColumnDef } from "@tanstack/react-table"
import { JobCardServiceForm } from "@/modules/job-cards/job-card-service-form"
import { Money } from "@/shared/components/money"
import { formatDate } from "@/shared/utils/formatters"
import { Badge } from "@/shared/components/ui/badge"
@@ -108,7 +109,7 @@ export default function JobCardServicesPage({
header: ({ column }) => <ColumnHeader column={column} title="Rate" />,
cell: ({ row }) => {
const val = row.original.rate
return val != null ? `$${Number(val).toFixed(2)}` : "—"
return val != null ? <Money value={val} /> : "—"
},
},
{
@@ -116,7 +117,7 @@ export default function JobCardServicesPage({
header: ({ column }) => <ColumnHeader column={column} title="Labor Rate" />,
cell: ({ row }) => {
const lr = row.original.labor_rate
return lr ? `${lr.title} ($${Number(lr.rate).toFixed(2)})` : "—"
return lr ? <span>{lr.title} (<Money value={lr.rate} />)</span> : "—"
},
},
{
@@ -134,7 +135,7 @@ export default function JobCardServicesPage({
header: ({ column }) => <ColumnHeader column={column} title="Discount" />,
cell: ({ row }) => {
const val = row.original.discount_amount
return val != null && val > 0 ? `$${Number(val).toFixed(2)}` : "—"
return val != null && val > 0 ? <Money value={val} /> : "—"
},
},
{
+27 -6
View File
@@ -1,7 +1,7 @@
"use client"
import { useEffect, useState } from "react"
import { useParams } from "next/navigation"
import { Suspense, useEffect, useState } from "react"
import { useParams, useSearchParams } from "next/navigation"
import {
AlertCircle,
Calendar,
@@ -125,9 +125,13 @@ function ScoreRing({ score }: { score: number | null }) {
)
}
export default function PublicInspectionPage() {
function PublicInspectionContent() {
const params = useParams<{ token: string }>()
const token = params.token
// Workspace UUID baked into the share URL — the public API endpoint needs it
// to resolve which tenant DB holds this inspection.
const searchParams = useSearchParams()
const ws = searchParams.get("ws") ?? ""
const [data, setData] = useState<PublicInspection | null>(null)
const [error, setError] = useState<string | null>(null)
const [loading, setLoading] = useState(true)
@@ -135,7 +139,8 @@ export default function PublicInspectionPage() {
useEffect(() => {
const base = process.env.NEXT_PUBLIC_API_URL ?? ""
fetch(`${base.replace(/\/$/, "")}/api/public/inspections/${token}`, {
const wsQuery = ws ? `?ws=${encodeURIComponent(ws)}` : ""
fetch(`${base.replace(/\/$/, "")}/api/public/inspections/${token}${wsQuery}`, {
headers: { Accept: "application/json", "X-Lang": "en" },
})
.then(async (res) => {
@@ -148,7 +153,7 @@ export default function PublicInspectionPage() {
.then((json: { data: PublicInspection }) => setData(json.data))
.catch((e: Error) => setError(e.message))
.finally(() => setLoading(false))
}, [token])
}, [token, ws])
if (loading) {
return (
@@ -200,7 +205,7 @@ export default function PublicInspectionPage() {
<span className="font-medium text-foreground">Inspection report</span>
</div>
<a
href={`${(process.env.NEXT_PUBLIC_API_URL ?? "").replace(/\/$/, "")}/api/public/inspections/${token}/print`}
href={`${(process.env.NEXT_PUBLIC_API_URL ?? "").replace(/\/$/, "")}/api/public/inspections/${token}/print${ws ? `?ws=${encodeURIComponent(ws)}` : ""}`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm border bg-white hover:bg-muted/40 transition"
@@ -418,6 +423,22 @@ export default function PublicInspectionPage() {
)
}
export default function PublicInspectionPage() {
return (
<Suspense
fallback={
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-gray-50 to-gray-100">
<div className="flex items-center gap-2 text-muted-foreground">
<Loader2 className="size-4 animate-spin" /> Loading inspection…
</div>
</div>
}
>
<PublicInspectionContent />
</Suspense>
)
}
function InfoCell({
icon: Icon,
label,