diff --git a/frontend/messages/en.json b/frontend/messages/en.json
index f930b0a..01bcb4e 100644
--- a/frontend/messages/en.json
+++ b/frontend/messages/en.json
@@ -30,7 +30,9 @@
"copied": "Copied",
"copyLink": "Copy link",
"none": "None",
- "preview": "Preview"
+ "preview": "Preview",
+ "openMenu": "Open menu",
+ "closeMenu": "Close menu"
},
"language": {
"label": "Language",
@@ -64,6 +66,7 @@
"signOut": "Log out",
"register": "Register",
"startTrial": "Start Trial",
+ "startTrialShort": "Try free",
"startFreeTrial": "Start Free Trial",
"dashboard": "Dashboard",
"signInTitle": "Sign in to your account",
diff --git a/frontend/messages/fa.json b/frontend/messages/fa.json
index f55025d..7dd8243 100644
--- a/frontend/messages/fa.json
+++ b/frontend/messages/fa.json
@@ -30,7 +30,9 @@
"copied": "کپی شد",
"copyLink": "کپی لینک",
"none": "هیچکدام",
- "preview": "پیشنمایش"
+ "preview": "پیشنمایش",
+ "openMenu": "باز کردن منو",
+ "closeMenu": "بستن منو"
},
"language": {
"label": "زبان",
@@ -64,6 +66,7 @@
"signOut": "خروج",
"register": "ثبتنام",
"startTrial": "شروع دوره آزمایشی",
+ "startTrialShort": "آزمایشی",
"startFreeTrial": "شروع دوره آزمایشی رایگان",
"dashboard": "داشبورد",
"signInTitle": "به حساب کاربری خود وارد شوید",
diff --git a/frontend/messages/nl.json b/frontend/messages/nl.json
index b98bf33..98ed3c8 100644
--- a/frontend/messages/nl.json
+++ b/frontend/messages/nl.json
@@ -30,7 +30,9 @@
"copied": "Gekopieerd",
"copyLink": "Link kopiëren",
"none": "Geen",
- "preview": "Voorbeeld"
+ "preview": "Voorbeeld",
+ "openMenu": "Menu openen",
+ "closeMenu": "Menu sluiten"
},
"language": {
"label": "Taal",
@@ -64,6 +66,7 @@
"signOut": "Uitloggen",
"register": "Registreren",
"startTrial": "Proefperiode starten",
+ "startTrialShort": "Gratis proberen",
"startFreeTrial": "Gratis proefperiode starten",
"dashboard": "Dashboard",
"signInTitle": "Meld u aan bij uw account",
diff --git a/frontend/src/app/[locale]/(dashboard)/appointments/page.tsx b/frontend/src/app/[locale]/(dashboard)/appointments/page.tsx
index bcbbf40..df110b4 100644
--- a/frontend/src/app/[locale]/(dashboard)/appointments/page.tsx
+++ b/frontend/src/app/[locale]/(dashboard)/appointments/page.tsx
@@ -289,7 +289,7 @@ export default function AppointmentsPage() {
return (
-
{t('title')}
+
{t('title')}
{t('subtitle')}
diff --git a/frontend/src/app/[locale]/(dashboard)/billing/page.tsx b/frontend/src/app/[locale]/(dashboard)/billing/page.tsx
index 3d90a1a..e2420c4 100644
--- a/frontend/src/app/[locale]/(dashboard)/billing/page.tsx
+++ b/frontend/src/app/[locale]/(dashboard)/billing/page.tsx
@@ -1,6 +1,7 @@
// src/app/(dashboard)/billing/page.tsx
'use client';
-import { useState } from 'react';
+
+import { useMemo, useState } from 'react';
import { Pencil } from 'lucide-react';
import { Button } from '@/components/ui/shared/Button';
import { Badge } from '@/components/ui/shared/Badge';
@@ -9,206 +10,287 @@ import { Table } from '@/components/ui/shared/Table';
import { SearchBar } from '@/components/ui/shared/SearchBar';
import { useAuth } from '@/lib/hooks/useAuth';
import { hasPermission } from '@/components/shared/permissions';
-// Mock data matching your design
-const invoices = [
- { id: '#123456', patient: 'Ali Rahmani', date: '24/9/2026', service: 'Hygiene', amount: 300, paid: 0, status: 'unpaid' },
- { id: '#123457', patient: 'Neda Akbari', date: '01/10/2026', service: 'Filling', amount: 700, paid: 400, status: 'overdue' },
- { id: '#123458', patient: 'Nima Haghi', date: '09/12/2026', service: 'Extraction', amount: 450, paid: 450, status: 'paid' },
+
+type InvoiceStatus = 'paid' | 'unpaid' | 'overdue';
+
+type Invoice = {
+ id: string;
+ patient: string;
+ date: string;
+ service: string;
+ amount: number;
+ paid: number;
+ status: InvoiceStatus;
+};
+
+const invoices: Invoice[] = [
+ { id: '#123456', patient: 'Ali Rahmani', date: '24/9/2026', service: 'Hygiene', amount: 300, paid: 0, status: 'unpaid' },
+ { id: '#123457', patient: 'Neda Akbari', date: '01/10/2026', service: 'Filling', amount: 700, paid: 400, status: 'overdue' },
+ { id: '#123458', patient: 'Nima Haghi', date: '09/12/2026', service: 'Extraction', amount: 450, paid: 450, status: 'paid' },
];
+
const statusColors = {
- paid: 'success',
- unpaid: 'warning',
- overdue: 'danger',
+ paid: 'success',
+ unpaid: 'warning',
+ overdue: 'danger',
} as const;
+
+const statusFilters = ['all', 'paid', 'unpaid', 'overdue'] as const;
+
type StatCardColor = 'blue' | 'yellow' | 'green' | 'red';
interface StatCardProps {
- title: string;
- count: number;
- amount: number;
- color: StatCardColor;
+ title: string;
+ count: number;
+ amount: number;
+ color: StatCardColor;
}
-export default function BillingPage() {
- const { currentOrganization } = useAuth();
- const [search, setSearch] = useState('');
- const [statusFilter, setStatusFilter] = useState('all');
- const canEditBilling = hasPermission(currentOrganization, 'TAB_BILLING_EDIT');
- const stats = {
- total: { count: 235, amount: 80900 },
- unpaid: { count: 30, amount: 2800 },
- paid: { count: 190, amount: 80900 },
- overdue: { count: 235, amount: 80900 },
- };
- return (
-
- {/* Header */}
-
-
Billing
-
-
- {/* Stats Cards - Matching your design */}
-
-
-
-
-
-
- {/* Filters */}
-
- {['all', 'paid', 'unpaid', 'overdue'].map((status) => (
-
- ))}
- >
- )}
- />
- {/* Invoices Table - Matching your design */}
-
- |
- Invoice ID
- |
-
- Patient name
- |
-
- Date
- |
-
- Service
- |
-
- Total amount
- |
-
- Paid
- |
-
- Status
- |
-
- Action
- |
-
- }
- body={
- <>
- {invoices.map((invoice) => (
-
- |
- {invoice.id}
- |
-
- {invoice.patient}
- |
-
- {invoice.date}
- |
-
- {invoice.service}
- |
-
- ${invoice.amount}
- |
-
- ${invoice.paid}
- |
-
-
- {invoice.status}
-
- |
-
-
- |
-
- ))}
- >
- }
- footer={(
- <>
-
-
- Page 1 of 10
-
-
- >
- )}
- />
-
- );
-}
-function StatCard({ title, count, amount, color }: StatCardProps) {
- const colors: Record = {
- blue: '!bg-purpose-visit-bg !text-purpose-visit-fg !border-purpose-visit-border',
- yellow: '!bg-badge-warning-bg !text-badge-warning-fg !border-badge-warning-border',
- green: '!bg-badge-success-bg !text-badge-success-fg !border-badge-success-border',
- red: '!bg-badge-danger-bg !text-badge-danger-fg !border-badge-danger-border',
- };
- return (
-
- {title}
- {count}
-
- ${amount.toLocaleString()}
-
-
- );
+export default function BillingPage() {
+ const { currentOrganization } = useAuth();
+ const [search, setSearch] = useState('');
+ const [statusFilter, setStatusFilter] = useState<(typeof statusFilters)[number]>('all');
+ const canEditBilling = hasPermission(currentOrganization, 'TAB_BILLING_EDIT');
+
+ const stats = {
+ total: { count: 235, amount: 80900 },
+ unpaid: { count: 30, amount: 2800 },
+ paid: { count: 190, amount: 80900 },
+ overdue: { count: 235, amount: 80900 },
+ };
+
+ const filteredInvoices = useMemo(() => {
+ const query = search.trim().toLowerCase();
+
+ return invoices.filter((invoice) => {
+ const matchesStatus = statusFilter === 'all' || invoice.status === statusFilter;
+ const matchesSearch =
+ !query ||
+ invoice.patient.toLowerCase().includes(query) ||
+ invoice.id.toLowerCase().includes(query) ||
+ invoice.service.toLowerCase().includes(query);
+
+ return matchesStatus && matchesSearch;
+ });
+ }, [search, statusFilter]);
+
+ return (
+
+
+
Billing
+
+
+
+
+
+
+
+
+
+
+
+ {statusFilters.map((status) => (
+
+ ))}
+ >
+ )}
+ />
+
+
+ {filteredInvoices.length === 0 ? (
+
No invoices match your filters.
+ ) : (
+ filteredInvoices.map((invoice) => (
+
+ ))
+ )}
+
+
+
+
+
+ |
+ Invoice ID
+ |
+
+ Patient name
+ |
+
+ Date
+ |
+
+ Service
+ |
+
+ Total amount
+ |
+
+ Paid
+ |
+
+ Status
+ |
+
+ Action
+ |
+
+ }
+ body={
+ <>
+ {filteredInvoices.map((invoice) => (
+
+ | {invoice.id} |
+ {invoice.patient} |
+ {invoice.date} |
+ {invoice.service} |
+ ${invoice.amount} |
+ ${invoice.paid} |
+
+
+ {invoice.status}
+
+ |
+
+
+ |
+
+ ))}
+ >
+ }
+ footer={}
+ />
+
+
+ );
+}
+
+function InvoiceMobileCard({
+ invoice,
+ canEditBilling,
+}: {
+ invoice: Invoice;
+ canEditBilling: boolean;
+}) {
+ const remaining = invoice.amount - invoice.paid;
+
+ return (
+
+
+
+
{invoice.patient}
+
{invoice.id}
+
+
+ {invoice.status}
+
+
+
+
+ {invoice.service}
+ ·
+ {invoice.date}
+
+
+
+
+
Total
+
${invoice.amount}
+
+
+
Paid
+
${invoice.paid}
+
+
+
+
+
+
+
+
+ );
+}
+
+function InvoiceEditButton({ canEditBilling }: { canEditBilling: boolean }) {
+ return (
+
+ );
+}
+
+function InvoicePagination({ className = '' }: { className?: string }) {
+ return (
+
+
+
Page 1 of 10
+
+
+ );
+}
+
+function StatCard({ title, count, amount, color }: StatCardProps) {
+ const colors: Record = {
+ blue: '!bg-purpose-visit-bg !text-purpose-visit-fg !border-purpose-visit-border',
+ yellow: '!bg-badge-warning-bg !text-badge-warning-fg !border-badge-warning-border',
+ green: '!bg-badge-success-bg !text-badge-success-fg !border-badge-success-border',
+ red: '!bg-badge-danger-bg !text-badge-danger-fg !border-badge-danger-border',
+ };
+
+ return (
+
+ {title}
+ {count}
+
+ ${amount.toLocaleString()}
+
+
+ );
}
diff --git a/frontend/src/app/[locale]/(dashboard)/cases/page.tsx b/frontend/src/app/[locale]/(dashboard)/cases/page.tsx
index 7fd12b1..c6df3f4 100644
--- a/frontend/src/app/[locale]/(dashboard)/cases/page.tsx
+++ b/frontend/src/app/[locale]/(dashboard)/cases/page.tsx
@@ -19,6 +19,7 @@ import { tasksApi } from '@/lib/api/tasks';
import { treatmentCatalogApi } from '@/lib/api/treatment-catalog';
import { treatmentTypeLabelFromCatalog } from '@/components/ui/treatment/treatmentTypeDisplay';
import { Button } from '@/components/ui/shared/Button';
+import { MobileDetailBackButton } from '@/components/ui/shared/MobileDetailBackButton';
import { FORM_SELECT_CLASS } from '@/components/ui/shared/formSelectStyles';
import { SearchBar } from '@/components/ui/shared/SearchBar';
import type { TreatmentCatalogEntry } from '@/types/treatment-catalog';
@@ -60,6 +61,7 @@ export default function CasesPage() {
const [treatmentCatalog, setTreatmentCatalog] = useState([]);
const [selectedCaseId, setSelectedCaseId] = useState(null);
+ const [mobileDetailOpen, setMobileDetailOpen] = useState(false);
const [selectedCase, setSelectedCase] = useState(null);
const [loadingList, setLoadingList] = useState(false);
const [loadingDetail, setLoadingDetail] = useState(false);
@@ -146,9 +148,16 @@ export default function CasesPage() {
const caseIdFromUrl = searchParams.get('caseId');
if (caseIdFromUrl) {
setSelectedCaseId(caseIdFromUrl);
+ setMobileDetailOpen(true);
}
}, [searchParams]);
+ useEffect(() => {
+ if (!selectedCaseId) {
+ setMobileDetailOpen(false);
+ }
+ }, [selectedCaseId]);
+
useEffect(() => {
const timeout = setTimeout(() => {
void loadCases({
@@ -220,12 +229,16 @@ export default function CasesPage() {
return (
-
{t('title')}
+
{t('title')}
{t('subtitle')}
-
+
-
+
+ {mobileDetailOpen && selectedCaseId ? (
+ setMobileDetailOpen(false)} />
+ ) : null}
{!selectedCaseId ? (
{t('selectCaseHint')}
) : loadingDetail || !selectedCase ? (
diff --git a/frontend/src/app/[locale]/(dashboard)/layout.tsx b/frontend/src/app/[locale]/(dashboard)/layout.tsx
index 037e1c7..4d7ffbe 100644
--- a/frontend/src/app/[locale]/(dashboard)/layout.tsx
+++ b/frontend/src/app/[locale]/(dashboard)/layout.tsx
@@ -1,7 +1,8 @@
'use client';
-import { memo, useEffect } from 'react';
+import { memo, useEffect, useState } from 'react';
import { useTranslations } from 'next-intl';
+import { Menu } from 'lucide-react';
import { usePathname, useRouter } from '@/i18n/navigation';
import { useAuth } from '@/lib/hooks/useAuth';
import Sidebar from '@/components/ui/shared/Sidebar';
@@ -17,6 +18,22 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
const { user, currentOrganization, isAuthReady } = useAuth();
const router = useRouter();
const pathname = usePathname();
+ const [sidebarOpen, setSidebarOpen] = useState(false);
+
+ useEffect(() => {
+ setSidebarOpen(false);
+ }, [pathname]);
+
+ useEffect(() => {
+ if (!sidebarOpen) {
+ return;
+ }
+ const previousOverflow = document.body.style.overflow;
+ document.body.style.overflow = 'hidden';
+ return () => {
+ document.body.style.overflow = previousOverflow;
+ };
+ }, [sidebarOpen]);
useEffect(() => {
if (!isAuthReady) return;
@@ -53,14 +70,26 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
}
return (
-
-
+
+ {sidebarOpen ? (
+