diff --git a/apps/web/app/(auth)/login/page.tsx b/apps/web/app/(auth)/login/page.tsx index b830186..cdf9bc3 100644 --- a/apps/web/app/(auth)/login/page.tsx +++ b/apps/web/app/(auth)/login/page.tsx @@ -13,7 +13,7 @@ export default function LoginPage() {

Welcome back

-

+

Enter your credentials to access your account

diff --git a/apps/web/app/(dashboard)/error.tsx b/apps/web/app/(dashboard)/error.tsx index 248acee..eaa5fc5 100644 --- a/apps/web/app/(dashboard)/error.tsx +++ b/apps/web/app/(dashboard)/error.tsx @@ -1,6 +1,7 @@ 'use client'; import { useEffect } from 'react'; +import { Button } from '@/components/ui/button'; export default function DashboardError({ error, @@ -16,9 +17,9 @@ export default function DashboardError({ return (
-
+
-

+

Unable to load dashboard

-

+

We encountered a problem while attempting to load your dashboard data. Please try again later.

- +
); } diff --git a/apps/web/app/(dashboard)/layout.tsx b/apps/web/app/(dashboard)/layout.tsx index e00892b..6a5011e 100644 --- a/apps/web/app/(dashboard)/layout.tsx +++ b/apps/web/app/(dashboard)/layout.tsx @@ -1,4 +1,6 @@ import { AuthGuard } from '@/features/auth'; +import { Sidebar } from '@/components/layout/sidebar'; +import { Navbar } from '@/components/layout/navbar'; export default function DashboardLayout({ children, @@ -7,11 +9,16 @@ export default function DashboardLayout({ }) { return ( -
- {/* Skeleton for future Sidebar/Header */} -
- {children} -
+
+
+ +
+
+ +
+ {children} +
+
) diff --git a/apps/web/app/(dashboard)/loading.tsx b/apps/web/app/(dashboard)/loading.tsx index 8d75fb0..118ba9f 100644 --- a/apps/web/app/(dashboard)/loading.tsx +++ b/apps/web/app/(dashboard)/loading.tsx @@ -2,37 +2,37 @@ export default function DashboardLoading() { return (
-
-
+
+
{[1, 2, 3, 4].map((i) => ( -
-
-
-
+
+
+
+
))}
-
-
+
+
-
-
-
+
+
+
-
+
{[1, 2, 3].map((i) => (
-
+
-
-
+
+
diff --git a/apps/web/app/(dashboard)/tickets/[id]/page.tsx b/apps/web/app/(dashboard)/tickets/[id]/page.tsx index 269f97a..e088705 100644 --- a/apps/web/app/(dashboard)/tickets/[id]/page.tsx +++ b/apps/web/app/(dashboard)/tickets/[id]/page.tsx @@ -24,17 +24,17 @@ export default async function TicketDetailPage({ return (
- + ← Back to tickets
-
-
+
+
-

{ticket.title}

-

Ticket ID: {ticket.id}

+

{ticket.title}

+

Ticket ID: {ticket.id}

@@ -44,19 +44,19 @@ export default async function TicketDetailPage({
-

Description

-
+

Description

+
{ticket.description || 'No description provided.'}
-

Category

-

{ticket.category || 'None'}

+

Category

+

{ticket.category || 'None'}

-

Created At

-

{new Date(ticket.created_at).toLocaleString()}

+

Created At

+

{new Date(ticket.created_at).toLocaleString()}

@@ -68,32 +68,32 @@ export default async function TicketDetailPage({
{history.length > 0 && ( -
-

Assignment History

+
+

Assignment History

    {history.map((event, eventIdx) => (
  • {eventIdx !== history.length - 1 ? ( -
  • + ))} +
+ + ); } diff --git a/apps/web/features/dashboard/components/SummaryCards.tsx b/apps/web/features/dashboard/components/SummaryCards.tsx index 2227bd6..a4c845b 100644 --- a/apps/web/features/dashboard/components/SummaryCards.tsx +++ b/apps/web/features/dashboard/components/SummaryCards.tsx @@ -1,4 +1,6 @@ import { DashboardMetric } from '../types'; +import { Card, CardContent } from '@/components/ui/card'; +import { Skeleton } from '@/components/ui/skeleton'; interface SummaryCardsProps { metrics?: DashboardMetric[]; @@ -10,11 +12,11 @@ export function SummaryCards({ metrics, isLoading }: SummaryCardsProps) { return (
{[1, 2, 3, 4].map((i) => ( -
-
-
-
-
+ + + + + ))}
); @@ -22,37 +24,39 @@ export function SummaryCards({ metrics, isLoading }: SummaryCardsProps) { if (!metrics || metrics.length === 0) { return ( -
-

No summary metrics available.

-
+ +

No summary metrics available.

+
); } return (
{metrics.map((metric, index) => ( -
-

{metric.title}

-
-

{metric.value}

- {metric.trend && ( - - {metric.trend} - + + +

{metric.title}

+
+

{metric.value}

+ {metric.trend && ( + + {metric.trend} + + )} +
+ {metric.description && ( +

{metric.description}

)} -
- {metric.description && ( -

{metric.description}

- )} -
+ + ))}
); diff --git a/apps/web/features/tickets/components/TicketAssignment.tsx b/apps/web/features/tickets/components/TicketAssignment.tsx index 399057c..964ee20 100644 --- a/apps/web/features/tickets/components/TicketAssignment.tsx +++ b/apps/web/features/tickets/components/TicketAssignment.tsx @@ -3,6 +3,9 @@ import { useState } from 'react'; import { useRouter } from 'next/navigation'; import { ticketService } from '../api/tickets.api'; +import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; interface Props { ticketId: string; @@ -44,46 +47,48 @@ export function TicketAssignment({ ticketId, currentAssignee }: Props) { }; return ( -
-

Assignment

- - {error && ( -
- {error} -
- )} + + + Assignment + + + {error && ( +
+ {error} +
+ )} - {currentAssignee ? ( -
-

- Assigned to: {currentAssignee} -

- -
- ) : ( -
- setTechnicianId(e.target.value)} - className="block w-full rounded-md border-0 py-1.5 px-3 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-blue-600 sm:text-sm sm:leading-6" - /> - -
- )} -
+ {currentAssignee ? ( +
+

+ Assigned to: {currentAssignee} +

+ +
+ ) : ( +
+ setTechnicianId(e.target.value)} + /> + +
+ )} + + ); } diff --git a/apps/web/features/tickets/components/TicketFilters.tsx b/apps/web/features/tickets/components/TicketFilters.tsx index 8618a77..c5865b4 100644 --- a/apps/web/features/tickets/components/TicketFilters.tsx +++ b/apps/web/features/tickets/components/TicketFilters.tsx @@ -3,6 +3,8 @@ import { useRouter, useSearchParams, usePathname } from 'next/navigation'; import { useCallback, useState } from 'react'; import { TicketStatus, TicketPriority } from '../types'; +import { Card } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; export function TicketFilters() { const router = useRouter(); @@ -35,22 +37,22 @@ export function TicketFilters() { }; return ( -
+
-
- setSearch(e.target.value)} - className="block w-full rounded-md border-0 py-1.5 pl-10 pr-3 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6" + className="pl-10" />
@@ -59,7 +61,7 @@ export function TicketFilters() { handleFilterChange('priority', e.target.value)} - className="block w-full sm:w-auto rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-blue-600 sm:text-sm sm:leading-6" + className="block w-full sm:w-auto rounded-md border-0 py-1.5 pl-3 pr-10 text-foreground ring-1 ring-inset ring-border focus:ring-2 focus:ring-primary sm:text-sm sm:leading-6 bg-background" > {Object.values(TicketPriority).map((priority) => ( @@ -78,6 +80,6 @@ export function TicketFilters() { ))}
-
+ ); } diff --git a/apps/web/features/tickets/components/TicketPagination.tsx b/apps/web/features/tickets/components/TicketPagination.tsx index 165ab60..ec975bf 100644 --- a/apps/web/features/tickets/components/TicketPagination.tsx +++ b/apps/web/features/tickets/components/TicketPagination.tsx @@ -2,6 +2,7 @@ import { useRouter, useSearchParams, usePathname } from 'next/navigation'; import { useCallback } from 'react'; +import { Button } from '@/components/ui/button'; interface Props { page: number; @@ -33,53 +34,56 @@ export function TicketPagination({ page, limit, total, pages, hasMore }: Props) const isNextDisabled = pages ? page >= pages : !hasMore; return ( -
+
- - +
-

- Showing page {page} - {pages && of {pages}} +

+ Showing page {page} + {pages && of {pages}} {total && ({total} total tickets)}

diff --git a/apps/web/features/tickets/components/TicketPriorityBadge.tsx b/apps/web/features/tickets/components/TicketPriorityBadge.tsx index fef4343..7c2c945 100644 --- a/apps/web/features/tickets/components/TicketPriorityBadge.tsx +++ b/apps/web/features/tickets/components/TicketPriorityBadge.tsx @@ -1,4 +1,5 @@ import { TicketPriority } from '../types'; +import { Badge } from '@/components/ui/badge'; interface Props { priority: TicketPriority; @@ -6,26 +7,24 @@ interface Props { } export function TicketPriorityBadge({ priority, className = '' }: Props) { - const getStyles = () => { + const getVariant = () => { switch (priority) { case TicketPriority.LOW: - return 'bg-gray-100 text-gray-800 border-gray-200'; + return 'secondary'; case TicketPriority.MEDIUM: - return 'bg-blue-100 text-blue-800 border-blue-200'; + return 'default'; case TicketPriority.HIGH: - return 'bg-orange-100 text-orange-800 border-orange-200'; + return 'default'; case TicketPriority.URGENT: - return 'bg-red-100 text-red-800 border-red-200'; + return 'destructive'; default: - return 'bg-gray-100 text-gray-800 border-gray-200'; + return 'outline'; } }; return ( - + {priority} - + ); } diff --git a/apps/web/features/tickets/components/TicketStatusBadge.tsx b/apps/web/features/tickets/components/TicketStatusBadge.tsx index 2bb0ce1..b18a70b 100644 --- a/apps/web/features/tickets/components/TicketStatusBadge.tsx +++ b/apps/web/features/tickets/components/TicketStatusBadge.tsx @@ -1,4 +1,5 @@ import { TicketStatus } from '../types'; +import { Badge } from '@/components/ui/badge'; interface Props { status: TicketStatus; @@ -6,20 +7,20 @@ interface Props { } export function TicketStatusBadge({ status, className = '' }: Props) { - const getStyles = () => { + const getVariant = () => { switch (status) { case TicketStatus.OPEN: - return 'bg-blue-100 text-blue-800 border-blue-200'; + return 'default'; case TicketStatus.IN_PROGRESS: - return 'bg-yellow-100 text-yellow-800 border-yellow-200'; + return 'secondary'; case TicketStatus.PENDING: - return 'bg-orange-100 text-orange-800 border-orange-200'; + return 'secondary'; case TicketStatus.RESOLVED: - return 'bg-green-100 text-green-800 border-green-200'; + return 'default'; case TicketStatus.CLOSED: - return 'bg-gray-100 text-gray-800 border-gray-200'; + return 'outline'; default: - return 'bg-gray-100 text-gray-800 border-gray-200'; + return 'outline'; } }; @@ -28,10 +29,8 @@ export function TicketStatusBadge({ status, className = '' }: Props) { }; return ( - + {getLabel()} - + ); } diff --git a/apps/web/features/tickets/components/TicketTable.tsx b/apps/web/features/tickets/components/TicketTable.tsx index dd1469a..fc2c1e6 100644 --- a/apps/web/features/tickets/components/TicketTable.tsx +++ b/apps/web/features/tickets/components/TicketTable.tsx @@ -2,6 +2,8 @@ import { Ticket } from '../types'; import { TicketStatusBadge } from './TicketStatusBadge'; import { TicketPriorityBadge } from './TicketPriorityBadge'; import Link from 'next/link'; +import { Card } from '@/components/ui/card'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; interface Props { tickets: Ticket[]; @@ -9,65 +11,53 @@ interface Props { export function TicketTable({ tickets }: Props) { return ( -
- - - - - - - - - - - - + +
- Title - - Status - - Priority - - Category - - Created - - Actions -
+ + + Title + Status + Priority + Category + Created + Actions + + + {tickets.length === 0 ? ( - - - + + ) : ( tickets.map((ticket) => ( - - - - - - - - + + )) )} - -
+ + No tickets found. -
+ + {ticket.title} - + + - + + - + + {ticket.category || '-'} - + + {new Date(ticket.created_at).toLocaleDateString()} - - + + + View, {ticket.title} -
-
+ + + ); } diff --git a/apps/web/features/tickets/components/TicketWorkflow.tsx b/apps/web/features/tickets/components/TicketWorkflow.tsx index 06e902b..a0b3c8e 100644 --- a/apps/web/features/tickets/components/TicketWorkflow.tsx +++ b/apps/web/features/tickets/components/TicketWorkflow.tsx @@ -4,6 +4,8 @@ import { useState } from 'react'; import { useRouter } from 'next/navigation'; import { ticketService } from '../api/tickets.api'; import { TicketStatus } from '../types'; +import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; interface Props { ticketId: string; @@ -47,27 +49,30 @@ export function TicketWorkflow({ ticketId, currentStatus }: Props) { } return ( -
-

Workflow Actions

- - {error && ( -
- {error} -
- )} + + + Workflow Actions + + + {error && ( +
+ {error} +
+ )} -
- {availableTransitions.map((status) => ( - - ))} -
-
+
+ {availableTransitions.map((status) => ( + + ))} +
+ + ); } diff --git a/apps/web/features/users/components/UserActions.tsx b/apps/web/features/users/components/UserActions.tsx index 51c67de..7b39af0 100644 --- a/apps/web/features/users/components/UserActions.tsx +++ b/apps/web/features/users/components/UserActions.tsx @@ -4,6 +4,8 @@ import React, { useState } from 'react'; import { useRouter } from 'next/navigation'; import { User } from '../types'; import { UserService } from '../api/users.api'; +import { Button } from '@/components/ui/button'; +import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; interface UserActionsProps { user: User; @@ -53,36 +55,36 @@ export function UserActions({ user }: UserActionsProps) { }; return ( -
-

User Actions

+ + + User Actions + - {error && ( -
- {error} -
- )} - -
- + + {error && ( +
+ {error} +
+ )} - -
-
+
+ + + +
+ + ); } diff --git a/apps/web/features/users/components/UserFilters.tsx b/apps/web/features/users/components/UserFilters.tsx index 0f1c91f..812c69c 100644 --- a/apps/web/features/users/components/UserFilters.tsx +++ b/apps/web/features/users/components/UserFilters.tsx @@ -3,6 +3,8 @@ import React, { useState, useEffect } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { UserRole } from '../types'; +import { Card } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; export function UserFilters() { const router = useRouter(); @@ -52,21 +54,20 @@ export function UserFilters() { }; return ( -
+
- setSearch(e.target.value)} - className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500" />
-
+ ); } diff --git a/apps/web/features/users/components/UserForm.tsx b/apps/web/features/users/components/UserForm.tsx index eba5f16..d9ff4f9 100644 --- a/apps/web/features/users/components/UserForm.tsx +++ b/apps/web/features/users/components/UserForm.tsx @@ -4,6 +4,9 @@ import React, { useState } from 'react'; import { useRouter } from 'next/navigation'; import { UserService } from '../api/users.api'; import { User, UserRole, CreateUserPayload, UpdateUserPayload } from '../types'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent } from '@/components/ui/card'; interface UserFormProps { user?: User; // If provided, we are editing. If not, creating. @@ -70,101 +73,102 @@ export function UserForm({ user }: UserFormProps) { }; return ( -
- {error && ( -
- {error} -
- )} - -
-
- - -
- -
- - -
- -
- - -
- -
- - -
- - {!user && ( -
- - + + + + {error && ( +
+ {error} +
+ )} + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + {!user && ( +
+ + +
+ )}
- )} -
-
- - -
- +
+ + +
+ + + ); } diff --git a/apps/web/features/users/components/UserPagination.tsx b/apps/web/features/users/components/UserPagination.tsx index cf1a1f7..e2da3e9 100644 --- a/apps/web/features/users/components/UserPagination.tsx +++ b/apps/web/features/users/components/UserPagination.tsx @@ -3,6 +3,8 @@ import React from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { UserPagination as UserPaginationType } from '../types'; +import { Button } from '@/components/ui/button'; +import { Card } from '@/components/ui/card'; interface UserPaginationProps { pagination: UserPaginationType; @@ -25,57 +27,60 @@ export function UserPagination({ pagination }: UserPaginationProps) { }; return ( -
+
- - +
-

- Showing {Math.min(skip + 1, total)} to{' '} - {Math.min(skip + limit, total)} of{' '} - {total} results +

+ Showing {Math.min(skip + 1, total)} to{' '} + {Math.min(skip + limit, total)} of{' '} + {total} results

-
+ ); } diff --git a/apps/web/features/users/components/UserRoleBadge.tsx b/apps/web/features/users/components/UserRoleBadge.tsx index 5a4c07a..3f54722 100644 --- a/apps/web/features/users/components/UserRoleBadge.tsx +++ b/apps/web/features/users/components/UserRoleBadge.tsx @@ -1,20 +1,24 @@ import React from 'react'; import { UserRole } from '../types'; +import { Badge } from '@/components/ui/badge'; interface UserRoleBadgeProps { role: UserRole; } export function UserRoleBadge({ role }: UserRoleBadgeProps) { - const styles = { - [UserRole.ADMIN]: 'bg-purple-100 text-purple-800 border-purple-200', - [UserRole.TECHNICIAN]: 'bg-blue-100 text-blue-800 border-blue-200', - [UserRole.EMPLOYEE]: 'bg-gray-100 text-gray-800 border-gray-200', + const getVariant = () => { + switch (role) { + case UserRole.ADMIN: return 'default'; + case UserRole.TECHNICIAN: return 'secondary'; + case UserRole.EMPLOYEE: return 'outline'; + default: return 'outline'; + } }; return ( - + {role} - + ); } diff --git a/apps/web/features/users/components/UserStatusBadge.tsx b/apps/web/features/users/components/UserStatusBadge.tsx index 59aafc4..9e6b14f 100644 --- a/apps/web/features/users/components/UserStatusBadge.tsx +++ b/apps/web/features/users/components/UserStatusBadge.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Badge } from '@/components/ui/badge'; interface UserStatusBadgeProps { isActive: boolean; @@ -6,8 +7,8 @@ interface UserStatusBadgeProps { export function UserStatusBadge({ isActive }: UserStatusBadgeProps) { return ( - + {isActive ? 'Active' : 'Inactive'} - + ); } diff --git a/apps/web/features/users/components/UserTable.tsx b/apps/web/features/users/components/UserTable.tsx index 5067d57..55f71fb 100644 --- a/apps/web/features/users/components/UserTable.tsx +++ b/apps/web/features/users/components/UserTable.tsx @@ -3,6 +3,8 @@ import Link from 'next/link'; import { User } from '../types'; import { UserRoleBadge } from './UserRoleBadge'; import { UserStatusBadge } from './UserStatusBadge'; +import { Card } from '@/components/ui/card'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; interface UserTableProps { users: User[]; @@ -11,51 +13,51 @@ interface UserTableProps { export function UserTable({ users }: UserTableProps) { if (!users || users.length === 0) { return ( -
+ No users found. -
+ ); } return ( -
- - - - - - - - - - - + +
NameRoleStatusJoinedActions
+ + + Name + Role + Status + Joined + Actions + + + {users.map((user) => ( - - - - - - - + + ))} - -
+ +
- {user.full_name} - {user.email} + {user.full_name} + {user.email}
-
+ + - + + - + + {new Date(user.created_at).toLocaleDateString()} - - + + + Manage -
-
+ + + ); }