From a206a56a73c5495ed02b9be8a9f0fdfa0ebe2f35 Mon Sep 17 00:00:00 2001 From: itsjoxdev Date: Sat, 25 Jul 2026 16:07:24 +0300 Subject: [PATCH 1/3] feat: initialize dashboard and ticket management features with base UI components and layouts --- apps/web/app/(auth)/login/page.tsx | 2 +- apps/web/app/(dashboard)/error.tsx | 16 +- apps/web/app/(dashboard)/layout.tsx | 17 +- apps/web/app/(dashboard)/loading.tsx | 30 +-- .../web/app/(dashboard)/tickets/[id]/page.tsx | 38 ++-- apps/web/app/(dashboard)/tickets/error.tsx | 12 +- apps/web/app/(dashboard)/tickets/page.tsx | 4 +- apps/web/app/(dashboard)/users/[id]/page.tsx | 6 +- apps/web/app/(dashboard)/users/error.tsx | 33 +-- apps/web/app/(dashboard)/users/loading.tsx | 2 +- apps/web/app/(dashboard)/users/new/page.tsx | 4 +- apps/web/app/(dashboard)/users/page.tsx | 16 +- apps/web/app/globals.css | 67 ++++++ apps/web/components/layout/breadcrumb.tsx | 33 +++ apps/web/components/layout/mobile-menu.tsx | 29 +++ apps/web/components/layout/navbar.tsx | 22 ++ apps/web/components/layout/sidebar.tsx | 48 +++++ apps/web/components/layout/user-menu.tsx | 40 ++++ apps/web/components/ui/avatar.tsx | 40 ++++ apps/web/components/ui/badge.tsx | 28 +++ apps/web/components/ui/button.tsx | 51 +++++ apps/web/components/ui/card.tsx | 46 +++++ apps/web/components/ui/dropdown.tsx | 66 ++++++ apps/web/components/ui/form-field.tsx | 33 +++ apps/web/components/ui/input.tsx | 23 +++ apps/web/components/ui/modal.tsx | 59 ++++++ apps/web/components/ui/select.tsx | 22 ++ apps/web/components/ui/skeleton.tsx | 13 ++ apps/web/components/ui/table.tsx | 48 +++++ apps/web/components/ui/toast.tsx | 56 +++++ apps/web/components/ui/tooltip.tsx | 22 ++ .../features/auth/components/AuthGuard.tsx | 6 +- .../features/auth/components/LoginForm.tsx | 28 +-- .../features/auth/components/LogoutButton.tsx | 8 +- .../dashboard/components/DashboardCharts.tsx | 85 ++++---- .../dashboard/components/DashboardHeader.tsx | 4 +- .../dashboard/components/RecentActivity.tsx | 117 ++++++----- .../dashboard/components/SummaryCards.tsx | 62 +++--- .../tickets/components/TicketAssignment.tsx | 85 ++++---- .../tickets/components/TicketFilters.tsx | 16 +- .../tickets/components/TicketPagination.tsx | 36 ++-- .../components/TicketPriorityBadge.tsx | 19 +- .../tickets/components/TicketStatusBadge.tsx | 21 +- .../tickets/components/TicketTable.tsx | 39 ++-- .../tickets/components/TicketWorkflow.tsx | 47 +++-- .../features/users/components/UserActions.tsx | 60 +++--- .../features/users/components/UserFilters.tsx | 13 +- .../features/users/components/UserForm.tsx | 192 +++++++++--------- .../users/components/UserPagination.tsx | 43 ++-- .../users/components/UserRoleBadge.tsx | 16 +- .../users/components/UserStatusBadge.tsx | 5 +- .../features/users/components/UserTable.tsx | 33 +-- 52 files changed, 1338 insertions(+), 523 deletions(-) create mode 100644 apps/web/components/layout/breadcrumb.tsx create mode 100644 apps/web/components/layout/mobile-menu.tsx create mode 100644 apps/web/components/layout/navbar.tsx create mode 100644 apps/web/components/layout/sidebar.tsx create mode 100644 apps/web/components/layout/user-menu.tsx create mode 100644 apps/web/components/ui/avatar.tsx create mode 100644 apps/web/components/ui/badge.tsx create mode 100644 apps/web/components/ui/button.tsx create mode 100644 apps/web/components/ui/card.tsx create mode 100644 apps/web/components/ui/dropdown.tsx create mode 100644 apps/web/components/ui/form-field.tsx create mode 100644 apps/web/components/ui/input.tsx create mode 100644 apps/web/components/ui/modal.tsx create mode 100644 apps/web/components/ui/select.tsx create mode 100644 apps/web/components/ui/skeleton.tsx create mode 100644 apps/web/components/ui/table.tsx create mode 100644 apps/web/components/ui/toast.tsx create mode 100644 apps/web/components/ui/tooltip.tsx 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..8211489 100644 --- a/apps/web/features/tickets/components/TicketTable.tsx +++ b/apps/web/features/tickets/components/TicketTable.tsx @@ -2,6 +2,7 @@ import { Ticket } from '../types'; import { TicketStatusBadge } from './TicketStatusBadge'; import { TicketPriorityBadge } from './TicketPriorityBadge'; import Link from 'next/link'; +import { Card } from '@/components/ui/card'; interface Props { tickets: Ticket[]; @@ -9,23 +10,23 @@ interface Props { export function TicketTable({ tickets }: Props) { return ( -
- - - -
+ + + + + - - - - - + {tickets.length === 0 ? ( - ) : ( tickets.map((ticket) => ( - - + - - - - @@ -68,6 +69,6 @@ export function TicketTable({ tickets }: Props) { )}
Title + Status + Priority + Category + Created @@ -33,33 +34,33 @@ export function TicketTable({ tickets }: Props) {
+ 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..a2598ee 100644 --- a/apps/web/features/users/components/UserTable.tsx +++ b/apps/web/features/users/components/UserTable.tsx @@ -3,6 +3,7 @@ import Link from 'next/link'; import { User } from '../types'; import { UserRoleBadge } from './UserRoleBadge'; import { UserStatusBadge } from './UserStatusBadge'; +import { Card } from '@/components/ui/card'; interface UserTableProps { users: User[]; @@ -11,31 +12,31 @@ interface UserTableProps { export function UserTable({ users }: UserTableProps) { if (!users || users.length === 0) { return ( -
+ No users found. -
+ ); } return ( -
- - + +
+ - - - - + + + + - + {users.map((user) => ( - + - @@ -56,6 +57,6 @@ export function UserTable({ users }: UserTableProps) { ))}
NameRoleStatusJoinedNameRoleStatusJoined Actions
- {user.full_name} - {user.email} + {user.full_name} + {user.email}
@@ -44,11 +45,11 @@ export function UserTable({ users }: UserTableProps) { + {new Date(user.created_at).toLocaleDateString()} - + Manage
-
+ ); } From d01601c794b03ff323a4dedcd1c97994654124f8 Mon Sep 17 00:00:00 2001 From: itsjoxdev Date: Sat, 25 Jul 2026 16:10:22 +0300 Subject: [PATCH 2/3] feat: add reusable TicketTable and UserTable components for data visualization --- .../tickets/components/TicketTable.tsx | 77 ++++++++----------- .../features/users/components/UserTable.tsx | 55 ++++++------- 2 files changed, 61 insertions(+), 71 deletions(-) diff --git a/apps/web/features/tickets/components/TicketTable.tsx b/apps/web/features/tickets/components/TicketTable.tsx index 8211489..fc2c1e6 100644 --- a/apps/web/features/tickets/components/TicketTable.tsx +++ b/apps/web/features/tickets/components/TicketTable.tsx @@ -3,6 +3,7 @@ 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[]; @@ -11,64 +12,52 @@ 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/users/components/UserTable.tsx b/apps/web/features/users/components/UserTable.tsx index a2598ee..55f71fb 100644 --- a/apps/web/features/users/components/UserTable.tsx +++ b/apps/web/features/users/components/UserTable.tsx @@ -4,6 +4,7 @@ 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[]; @@ -20,43 +21,43 @@ export function UserTable({ users }: UserTableProps) { 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 -
+ +
); } From 19bd59616c51fafa35925166c23ebb55f229b125 Mon Sep 17 00:00:00 2001 From: itsjoxdev Date: Sat, 25 Jul 2026 16:12:36 +0300 Subject: [PATCH 3/3] feat: add Table and Input UI components and implement ticket listing page --- apps/web/app/(dashboard)/tickets/page.tsx | 12 ++++++------ apps/web/components/ui/input.tsx | 2 +- apps/web/components/ui/table.tsx | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/web/app/(dashboard)/tickets/page.tsx b/apps/web/app/(dashboard)/tickets/page.tsx index 6bd9d81..f58ef11 100644 --- a/apps/web/app/(dashboard)/tickets/page.tsx +++ b/apps/web/app/(dashboard)/tickets/page.tsx @@ -20,7 +20,7 @@ export default async function TicketsPage({ const cookieStore = await cookies(); const allCookies = cookieStore.getAll().map((c: { name: string; value: string }) => `${c.name}=${c.value}`).join('; '); - + const response = await ticketService.getTickets( { page, limit, status, priority, search }, { Cookie: allCookies } @@ -40,12 +40,12 @@ export default async function TicketsPage({
- + - - {} +export interface InputProps extends React.InputHTMLAttributes { } const Input = React.forwardRef( ({ className, type, ...props }, ref) => { diff --git a/apps/web/components/ui/table.tsx b/apps/web/components/ui/table.tsx index fd28467..6893881 100644 --- a/apps/web/components/ui/table.tsx +++ b/apps/web/components/ui/table.tsx @@ -33,14 +33,14 @@ TableRow.displayName = 'TableRow'; const TableHead = React.forwardRef>( ({ className, ...props }, ref) => ( - + ) ); TableHead.displayName = 'TableHead'; const TableCell = React.forwardRef>( ({ className, ...props }, ref) => ( - + ) ); TableCell.displayName = 'TableCell';