d7fb7b7a7b
Build & Deploy / Build & Push Docker Image (push) Failing after 47s
Build & Deploy / Deploy to VPS (push) Has been skipped
Test / API Unit Tests (push) Failing after 5m4s
Test / Marketplace Unit Tests (push) Failing after 4m55s
Test / Admin Unit Tests (push) Successful in 9m37s
Test / Dashboard Unit Tests (push) Successful in 9m37s
Test / API Integration Tests (push) Successful in 9m54s
125 lines
4.7 KiB
TypeScript
125 lines
4.7 KiB
TypeScript
'use client'
|
||
|
||
import { useEffect, useState } from 'react'
|
||
import { apiFetch } from '@/lib/api'
|
||
import { useDashboardI18n } from '@/components/I18nProvider'
|
||
|
||
interface CustomerRow {
|
||
id: string
|
||
firstName: string
|
||
lastName: string
|
||
email: string
|
||
phone: string | null
|
||
flagged: boolean
|
||
licenseValidationStatus: string
|
||
licenseImageUrl: string | null
|
||
}
|
||
|
||
export default function CustomersPage() {
|
||
const { language } = useDashboardI18n()
|
||
const [rows, setRows] = useState<CustomerRow[]>([])
|
||
const [error, setError] = useState<string | null>(null)
|
||
const copy = {
|
||
en: {
|
||
title: 'Customers',
|
||
subtitle: 'A company CRM with license validation and risk flags.',
|
||
customer: 'Customer',
|
||
contact: 'Contact',
|
||
license: 'License',
|
||
flags: 'Flags',
|
||
imageUploaded: 'Image uploaded',
|
||
imageMissing: 'No image',
|
||
noPhone: 'No phone',
|
||
flagged: 'Flagged',
|
||
clear: 'Clear',
|
||
empty: 'No customers yet.',
|
||
},
|
||
fr: {
|
||
title: 'Clients',
|
||
subtitle: 'CRM de l’entreprise avec validation des permis et indicateurs de risque.',
|
||
customer: 'Client',
|
||
contact: 'Contact',
|
||
license: 'Permis',
|
||
flags: 'Signalements',
|
||
imageUploaded: 'Image téléversée',
|
||
imageMissing: 'Aucune image',
|
||
noPhone: 'Pas de téléphone',
|
||
flagged: 'Signalé',
|
||
clear: 'Aucun risque',
|
||
empty: 'Aucun client pour le moment.',
|
||
},
|
||
ar: {
|
||
title: 'العملاء',
|
||
subtitle: 'نظام إدارة عملاء خاص بالشركة مع التحقق من الرخص ومؤشرات المخاطر.',
|
||
customer: 'العميل',
|
||
contact: 'التواصل',
|
||
license: 'الرخصة',
|
||
flags: 'العلامات',
|
||
imageUploaded: 'تم رفع الصورة',
|
||
imageMissing: 'لا توجد صورة',
|
||
noPhone: 'لا يوجد هاتف',
|
||
flagged: 'تم تمييزه',
|
||
clear: 'لا توجد مخاطر',
|
||
empty: 'لا يوجد عملاء حتى الآن.',
|
||
},
|
||
}[language]
|
||
|
||
useEffect(() => {
|
||
apiFetch<CustomerRow[]>('/customers?pageSize=100')
|
||
.then((result) => setRows(result ?? []))
|
||
.catch((err) => setError(err.message))
|
||
}, [])
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<div className="rdg-page-heading">
|
||
<h2 className="text-xl font-semibold text-slate-900">{copy.title}</h2>
|
||
<p className="text-sm text-slate-500 mt-1">{copy.subtitle}</p>
|
||
</div>
|
||
<div className="card overflow-hidden">
|
||
{error ? (
|
||
<div className="p-8 text-sm text-red-600">{error}</div>
|
||
) : (
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full">
|
||
<thead>
|
||
<tr className="bg-slate-50 border-b border-slate-200">
|
||
<th className="text-left px-6 py-3 text-xs font-medium text-slate-500 uppercase tracking-wide">{copy.customer}</th>
|
||
<th className="text-left px-6 py-3 text-xs font-medium text-slate-500 uppercase tracking-wide">{copy.contact}</th>
|
||
<th className="text-left px-6 py-3 text-xs font-medium text-slate-500 uppercase tracking-wide">{copy.license}</th>
|
||
<th className="text-left px-6 py-3 text-xs font-medium text-slate-500 uppercase tracking-wide">{copy.flags}</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="divide-y divide-slate-100">
|
||
{rows.map((row) => (
|
||
<tr key={row.id}>
|
||
<td className="px-6 py-4 text-sm font-semibold text-slate-900">{row.firstName} {row.lastName}</td>
|
||
<td className="px-6 py-4">
|
||
<p className="text-sm text-slate-700">{row.email}</p>
|
||
<p className="text-xs text-slate-500">{row.phone ?? copy.noPhone}</p>
|
||
</td>
|
||
<td className="px-6 py-4">
|
||
<div className="flex flex-wrap items-center gap-2">
|
||
<span className="badge-gray">{row.licenseValidationStatus}</span>
|
||
<span className={row.licenseImageUrl ? 'badge-green' : 'badge-gray'}>
|
||
{row.licenseImageUrl ? copy.imageUploaded : copy.imageMissing}
|
||
</span>
|
||
</div>
|
||
</td>
|
||
<td className="px-6 py-4">{row.flagged ? <span className="badge-red">{copy.flagged}</span> : <span className="badge-green">{copy.clear}</span>}</td>
|
||
</tr>
|
||
))}
|
||
{rows.length === 0 && (
|
||
<tr>
|
||
<td colSpan={4} className="px-6 py-10 text-center text-sm text-slate-400">{copy.empty}</td>
|
||
</tr>
|
||
)}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|