feat(customers): implement customer management page with mock data

- Created CustomersPage component to display a list of companies with search and pagination features.
- Added mock data for companies, including various statuses and profiles.
- Implemented a service layer to simulate API calls for fetching company data, bookings, documents, and payments.
- Defined TypeScript types for company and related entities to ensure type safety.
- Integrated Mantine components for UI consistency and improved user experience.
This commit is contained in:
Nathnael
2026-06-22 12:32:52 +00:00
parent d66ccc5363
commit c6bc636495
12 changed files with 2124 additions and 25 deletions

View File

@@ -0,0 +1,72 @@
/**
* Customers service.
*
* Currently backed by in-memory mock fixtures (`customers.mock.ts`); the public
* surface mirrors the other services (e.g. `bookings.service.ts`) — async
* methods returning `{ items, total }` / detail objects — so it can be pointed
* at the live `/companies` API later without touching the hooks or pages.
*/
import {
getBookingsFor,
getCompanyById,
getDocumentsFor,
getPaymentsFor,
MOCK_COMPANIES,
} from "@/pages/customers/customers.mock";
import type {
Company,
CompanyListFilter,
CustomerBooking,
CustomerDocument,
CustomerPayment,
PaginatedCompanies,
} from "@/types/customer";
/** Simulate network latency so loading states are visible during UI work. */
const delay = <T>(value: T, ms = 350): Promise<T> =>
new Promise((resolve) => setTimeout(() => resolve(value), ms));
function matchesSearch(company: Company, search: string): boolean {
const q = search.trim().toLowerCase();
if (!q) return true;
return (
company.name.toLowerCase().includes(q) ||
company.tin.toLowerCase().includes(q) ||
company.email?.toLowerCase().includes(q) === true ||
company.companyProfiles.some((p) => p.reference.toLowerCase().includes(q))
);
}
export const customersService = {
list(filter: CompanyListFilter): Promise<PaginatedCompanies> {
const { page, pageSize, search = "", type, status } = filter;
const filtered = MOCK_COMPANIES.filter(
(c) =>
matchesSearch(c, search) &&
(!type || c.type === type) &&
(!status || c.status === status),
);
const start = (page - 1) * pageSize;
const items = filtered.slice(start, start + pageSize);
return delay({ items, total: filtered.length });
},
getById(id: string): Promise<Company | undefined> {
return delay(getCompanyById(id));
},
bookingsFor(companyId: string): Promise<CustomerBooking[]> {
return delay(getBookingsFor(companyId));
},
documentsFor(companyId: string): Promise<CustomerDocument[]> {
return delay(getDocumentsFor(companyId));
},
paymentsFor(companyId: string): Promise<CustomerPayment[]> {
return delay(getPaymentsFor(companyId));
},
};