diff --git a/frontend/src/app/(dashboard)/billing/page.tsx b/frontend/src/app/(dashboard)/billing/page.tsx index 0897555..6464010 100644 --- a/frontend/src/app/(dashboard)/billing/page.tsx +++ b/frontend/src/app/(dashboard)/billing/page.tsx @@ -1,10 +1,11 @@ // src/app/(dashboard)/billing/page.tsx 'use client'; import { useState } from 'react'; -import { Search } from 'lucide-react'; +import { Pencil } from 'lucide-react'; import { Button } from '@/components/ui/common/Button'; -import { Input } from '@/components/ui/common/Input'; import { Badge } from '@/components/ui/common/Badge'; +import { Table } from '@/components/ui/common/Table'; +import { SearchBar } from '@/components/ui/common/SearchBar'; import { useAuth } from '@/lib/hooks/useAuth'; import { hasPermission } from '@/shared/permissions'; // Mock data matching your design @@ -78,17 +79,12 @@ export default function BillingPage() { /> {/* Filters */} -
-
-
- setSearch(e.target.value)} - icon={} - /> -
-
+ {['all', 'paid', 'unpaid', 'overdue'].map((status) => (
-
-
+ + )} + /> {/* Invoices Table - Matching your design */} -
- - - +
@@ -134,29 +129,30 @@ export default function BillingPage() { Action - - + } + body={ + <> {invoices.map((invoice) => ( - - + - - - - - - - - + ))} - -
Invoice ID
+
{invoice.id} + {invoice.patient} + {invoice.date} + {invoice.service} + ${invoice.amount} + ${invoice.paid} + +
- {/* Pagination - Matching your design */} -
+ + } + footer={( + <> @@ -188,8 +187,9 @@ export default function BillingPage() { -
-
+ + )} + /> ); } diff --git a/frontend/src/app/(dashboard)/organizations/page.tsx b/frontend/src/app/(dashboard)/organizations/page.tsx index c5575da..0d8390a 100644 --- a/frontend/src/app/(dashboard)/organizations/page.tsx +++ b/frontend/src/app/(dashboard)/organizations/page.tsx @@ -1,7 +1,7 @@ 'use client'; import { useEffect, useState } from 'react'; -import { Search } from 'lucide-react'; +import { Check, Copy, Link2, Trash2, X } from 'lucide-react'; import { useAuth } from '@/lib/hooks/useAuth'; import { organizationApi, @@ -12,6 +12,8 @@ import { import { Button } from '@/components/ui/common/Button'; import { Badge, organizationLinkStatusVariant } from '@/components/ui/common/Badge'; import { Input } from '@/components/ui/common/Input'; +import { SearchBar } from '@/components/ui/common/SearchBar'; +import { Table } from '@/components/ui/common/Table'; import type { ApiError } from '@/types/api'; type StoredInviteLink = { @@ -56,7 +58,7 @@ function formatLinkStatusLabel(status: CounterpartItemDto['status']): string { function formatInvitationStatusLabel(status: OrganizationInvitationHistoryItemDto['status']): string { if (status === 'PENDING') return 'Invitation pending'; - if (status === 'ACTIVE') return 'Invitation accepted'; + if (status === 'ACTIVE') return 'Invitation Accepted'; if (status === 'REJECTED') return 'Invitation rejected'; return formatOrganizationStatusLabel(status); } @@ -69,6 +71,12 @@ function formatApiMessage(err: unknown): string { return 'Something went wrong'; } +function formatTableDate(value: string): string { + const d = new Date(value); + if (Number.isNaN(d.getTime())) return '—'; + return d.toLocaleDateString(); +} + type TableMode = 'existing' | 'search'; export default function OrganizationsPage() { @@ -296,7 +304,7 @@ export default function OrganizationsPage() {

@@ -311,33 +319,36 @@ export default function OrganizationsPage() { )} -
-
-
- setQuery(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') void runSearch(); - }} - icon={} - /> -
- - {mode === 'search' && ( - - )} -
-
+ void runSearch()} + placeholder={`Search ${counterpartLabel.toLowerCase()} by name, email, or phone...`} + actions={ + <> + + {mode === 'search' && ( + + )} + + } + /> -
- - +
- - + } + body={ + <> {loading ? ( - - - - + + - @@ -435,22 +450,23 @@ export default function OrganizationsPage() { ) : searchResults.length > 0 ? ( searchResults.map((r) => ( - - - - + + + - )) @@ -496,9 +512,9 @@ export default function OrganizationsPage() { )} - -
Organization @@ -346,7 +357,7 @@ export default function OrganizationsPage() { Owner email - Type + Date Status @@ -355,8 +366,9 @@ export default function OrganizationsPage() { Action
@@ -379,52 +391,55 @@ export default function OrganizationsPage() { return (
+ {row.organizationName} {row.ownerEmail}Link + {row.ownerEmail} + {formatTableDate(row.createdAt)} + {formatLinkStatusLabel(row.status)} +
{canRespond && ( <> - - + + + )} {row.status === 'ACTIVE' && ( - + + )}
{r.name}{r.owner.email}Directory match + {r.name}{r.owner.email}Today Found - + +
-
+ + } + /> {historyOpen && (
@@ -508,7 +524,7 @@ export default function OrganizationsPage() { aria-modal="true" >
-

Invitation history

+

Invitation History

@@ -519,44 +535,64 @@ export default function OrganizationsPage() { ) : historyItems.length === 0 ? (

No invitations yet.

) : ( -
- - - - - - - - - - +
OrganizationOwner emailStatusAction
+ + + + + + + } + body={ + <> {historyItems.map((inv) => ( - - - - + + + + - ))} - -
+ Organization + + Owner email + + Date + + Status + + Action +
{inv.organizationName}{inv.ownerEmail} +
{inv.organizationName}{inv.ownerEmail} + {formatTableDate(inv.createdAt)} + {formatInvitationStatusLabel(inv.status)} + {inv.status === 'PENDING' ? ( - + {copiedId === inv.id ? ( + + ) : ( + + )} + ) : ( )}
-
+ + } + /> )}
diff --git a/frontend/src/app/(dashboard)/staff/page.tsx b/frontend/src/app/(dashboard)/staff/page.tsx index 4359e9b..ea9e9e9 100644 --- a/frontend/src/app/(dashboard)/staff/page.tsx +++ b/frontend/src/app/(dashboard)/staff/page.tsx @@ -23,6 +23,7 @@ import { Button } from '@/components/ui/common/Button'; import { Badge } from '@/components/ui/common/Badge'; import { Input } from '@/components/ui/common/Input'; import { Checkbox } from '@/components/ui/common/Checkbox'; +import { Table } from '@/components/ui/common/Table'; import type { ApiError } from '@/types/api'; type StoredInviteLink = { @@ -424,31 +425,31 @@ export default function StaffPage() { {loading ? (

Loading team…

) : ( -
- - - - - - - - - - - - +
NameEmailRoleStatusAccessActions
+ + + + + + + + } + body={ + <> {members.map((m) => ( - - - - + + + - - - ))} - -
NameEmailRoleStatusAccessActions
{m.name}{m.email} +
{m.name}{m.email} {m.isOwner ? ( Owner ) : ( Staff )} + {m.isOwner || m.invitationStatus === 'ACTIVE' ? ( Active ) : m.invitationStatus === 'PENDING' ? ( @@ -457,7 +458,7 @@ export default function StaffPage() { Expired )} + {m.isOwner ? ( All features ) : ( @@ -466,7 +467,7 @@ export default function StaffPage() { )} + {!m.isOwner && (
{m.invitationStatus === 'PENDING' && pendingInviteLinks[m.id]?.invitationUrl && ( @@ -529,9 +530,9 @@ export default function StaffPage() {
-
+ + } + /> )} {inviteOpen && ( diff --git a/frontend/src/app/(public)/accept-invite/page.tsx b/frontend/src/app/(public)/accept-invite/page.tsx index 7f2553f..bfb3b3c 100644 --- a/frontend/src/app/(public)/accept-invite/page.tsx +++ b/frontend/src/app/(public)/accept-invite/page.tsx @@ -78,7 +78,7 @@ function AcceptInviteContent() { name: name.trim(), password, }); - setSuccess('Invitation accepted. Redirecting to login...'); + setSuccess('Invitation Accepted. Redirecting to login...'); setTimeout(() => { router.replace('/login'); }, 1000); diff --git a/frontend/src/app/(public)/accept-organization-invite/page.tsx b/frontend/src/app/(public)/accept-organization-invite/page.tsx index 672478e..5c4af67 100644 --- a/frontend/src/app/(public)/accept-organization-invite/page.tsx +++ b/frontend/src/app/(public)/accept-organization-invite/page.tsx @@ -72,7 +72,7 @@ function AcceptOrganizationInviteContent() { organizationName: organizationName.trim(), password, }); - setSuccess('Invitation accepted. Redirecting to login...'); + setSuccess('Invitation Accepted. Redirecting to login...'); setTimeout(() => router.replace('/login'), 1000); } catch (e: any) { setError(e?.message || 'Could not accept invitation'); diff --git a/frontend/src/components/ui/common/SearchBar.tsx b/frontend/src/components/ui/common/SearchBar.tsx new file mode 100644 index 0000000..c93b8eb --- /dev/null +++ b/frontend/src/components/ui/common/SearchBar.tsx @@ -0,0 +1,38 @@ +import { Search } from 'lucide-react'; +import type { ReactNode } from 'react'; +import { Input } from './Input'; + +interface SearchBarProps { + value: string; + onChange: (value: string) => void; + placeholder: string; + onSubmit?: () => void; + actions?: ReactNode; +} + +export function SearchBar({ + value, + onChange, + placeholder, + onSubmit, + actions, +}: SearchBarProps) { + return ( +
+
+
+ onChange(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') onSubmit?.(); + }} + icon={} + /> +
+ {actions &&
{actions}
} +
+
+ ); +} diff --git a/frontend/src/components/ui/common/Table.tsx b/frontend/src/components/ui/common/Table.tsx new file mode 100644 index 0000000..656b1fe --- /dev/null +++ b/frontend/src/components/ui/common/Table.tsx @@ -0,0 +1,27 @@ +import type { ReactNode } from 'react'; + +interface TableProps { + headers: ReactNode; + body: ReactNode; + footer?: ReactNode; +} + +export function Table({ headers, body, footer }: TableProps) { + return ( +
+ + + {headers} + + + {body} + +
+ {footer && ( +
+ {footer} +
+ )} +
+ ); +}