create driver page and client page also create oll driver , client layer
This commit is contained in:
@@ -1,46 +1,48 @@
|
||||
// services/client.service.ts
|
||||
// خدمة العملاء — كل نداءات الـ API الخاصة بالعملاء تكون هنا فقط
|
||||
// Mirrors user.service.ts exactly: token parameter, buildPayload, buildQuery.
|
||||
import { get, post, put, del } from "./api";
|
||||
import type { ApiResponse, ApiListResponse, Client, ClientFormData } from "../types/client";
|
||||
|
||||
import { get, post } from "./api";
|
||||
import type { ClientAddress, Order } from "../types/client";
|
||||
|
||||
export interface CreateClientPayload {
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
companyName?: string;
|
||||
/** نوع الاستجابة لعملية واحدة على عميل */
|
||||
export interface ClientResponse {
|
||||
data: Client;
|
||||
}
|
||||
|
||||
export interface CreateAddressPayload {
|
||||
label: string;
|
||||
branchName?: string | null;
|
||||
contactPerson: { name: string; phone: string };
|
||||
details: {
|
||||
city: string;
|
||||
district?: string;
|
||||
street: string;
|
||||
buildingNo?: string;
|
||||
unitNo?: string;
|
||||
zipCode?: string;
|
||||
};
|
||||
/** بناء query string لجلب العملاء مع البحث والصفحات */
|
||||
function buildClientsQuery(page: number, search: string): string {
|
||||
return `?page=${page}&limit=10${search ? `&search=${encodeURIComponent(search)}` : ""}`;
|
||||
}
|
||||
|
||||
export const clientService = {
|
||||
/** Register a new client account */
|
||||
create: (payload: CreateClientPayload) =>
|
||||
post<{ id: string; name: string; email: string; phone: string }>(
|
||||
"client",
|
||||
payload,
|
||||
),
|
||||
/** جلب قائمة العملاء مع إمكانية البحث والتصفح بين الصفحات */
|
||||
getAll: (page: number, search: string, token: string | null) =>
|
||||
get<ApiListResponse<Client>>(`client${buildClientsQuery(page, search)}`, token),
|
||||
|
||||
/** Fetch all saved delivery addresses for a client */
|
||||
getAddresses: (clientId: string) =>
|
||||
get<ClientAddress[]>(`client/${clientId}/addresses`),
|
||||
/** جلب عميل واحد بالـ ID (مع العناوين المرفقة) */
|
||||
getById: (id: string, token: string | null) =>
|
||||
get<ApiResponse<Client>>(`client/${id}`, token),
|
||||
|
||||
/** Save a new delivery address */
|
||||
addAddress: (clientId: string, payload: CreateAddressPayload) =>
|
||||
post<ClientAddress>(`client/${clientId}/addresses`, payload),
|
||||
/** إنشاء عميل جديد */
|
||||
create: (data: ClientFormData, token: string | null) =>
|
||||
post<ClientResponse>("client", buildPayload(data), token),
|
||||
|
||||
/** Fetch all orders placed by a client */
|
||||
getOrders: (clientId: string) =>
|
||||
get<Order[]>(`client/${clientId}/orders`),
|
||||
};
|
||||
/** تعديل بيانات عميل موجود */
|
||||
update: (id: string, data: ClientFormData, token: string | null) =>
|
||||
put<ClientResponse>(`client/${id}`, buildPayload(data), token),
|
||||
|
||||
/** حذف عميل */
|
||||
delete: (id: string, token: string | null) =>
|
||||
del<void>(`client/${id}`, token),
|
||||
};
|
||||
|
||||
/** تحويل بيانات النموذج إلى payload مناسب للـ API */
|
||||
function buildPayload(data: ClientFormData): Record<string, string> {
|
||||
const payload: Record<string, string> = {
|
||||
name: data.name.trim(),
|
||||
email: data.email.trim(),
|
||||
phone: data.phone.trim(),
|
||||
};
|
||||
if (data.taxId?.trim()) payload.taxId = data.taxId.trim();
|
||||
if (data.notes?.trim()) payload.notes = data.notes.trim();
|
||||
return payload;
|
||||
}
|
||||
50
services/clientAddress.service.ts
Normal file
50
services/clientAddress.service.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
// خدمة عناوين العملاء — كل نداءات الـ API الخاصة بالعناوين تكون هنا فقط
|
||||
// Mirrors user.service.ts pattern: explicit token param, buildPayload helper.
|
||||
import { get, post, put, patch, del } from "./api";
|
||||
import type {
|
||||
ApiResponse,
|
||||
ClientAddress,
|
||||
ClientAddressFormData,
|
||||
} from "../types/client";
|
||||
|
||||
const base = (clientId: string) => `client/${clientId}/addresses`;
|
||||
|
||||
export const clientAddressService = {
|
||||
/** جلب جميع عناوين عميل معين */
|
||||
getAll: (clientId: string, token: string | null) =>
|
||||
get<ApiResponse<ClientAddress[]>>(base(clientId), token),
|
||||
|
||||
/** جلب عنوان واحد بالـ ID */
|
||||
getById: (clientId: string, addressId: string, token: string | null) =>
|
||||
get<ApiResponse<ClientAddress>>(`${base(clientId)}/${addressId}`, token),
|
||||
|
||||
/** إضافة عنوان جديد لعميل */
|
||||
create: (clientId: string, data: ClientAddressFormData, token: string | null) =>
|
||||
post<ApiResponse<ClientAddress>>(base(clientId), buildPayload(clientId, data), token),
|
||||
|
||||
/** تعديل عنوان موجود */
|
||||
update: (clientId: string, addressId: string, data: ClientAddressFormData, token: string | null) =>
|
||||
put<ApiResponse<ClientAddress>>(`${base(clientId)}/${addressId}`, buildPayload(clientId, data), token),
|
||||
|
||||
/** حذف عنوان */
|
||||
delete: (clientId: string, addressId: string, token: string | null) =>
|
||||
del<void>(`${base(clientId)}/${addressId}`, token),
|
||||
|
||||
/** تعيين عنوان كعنوان رئيسي — الباكند يُلغي الاختيار عن الباقين */
|
||||
setPrimary: (clientId: string, addressId: string, token: string | null) =>
|
||||
patch<ApiResponse<ClientAddress>>(`${base(clientId)}/${addressId}/primary`, {}, token),
|
||||
};
|
||||
|
||||
/** تحويل بيانات النموذج إلى payload */
|
||||
function buildPayload(clientId: string, data: ClientAddressFormData): Record<string, unknown> {
|
||||
return {
|
||||
clientId,
|
||||
label: data.label.trim(),
|
||||
street: data.street.trim(),
|
||||
city: data.city.trim(),
|
||||
state: data.state.trim(),
|
||||
postalCode: data.postalCode.trim(),
|
||||
country: data.country.trim(),
|
||||
isPrimary: data.isPrimary,
|
||||
};
|
||||
}
|
||||
@@ -1 +1,154 @@
|
||||
export const driverService = {};
|
||||
// services/driver.service.ts
|
||||
// All API calls for the Driver module.
|
||||
// Endpoints extracted from:
|
||||
// - src/routes/driver.route.js → /api/v1/driver/...
|
||||
// - src/routes/driver_report.route.js → /api/v1/drivers/:id/reports/daily
|
||||
|
||||
import { get, post, put, del } from "./api";
|
||||
import type {
|
||||
Driver,
|
||||
DriverListResponse,
|
||||
DriverDetailResponse,
|
||||
DriverReportResponse,
|
||||
CreateDriverPayload,
|
||||
UpdateDriverPayload,
|
||||
} from "../types/driver";
|
||||
|
||||
/** Build a query string for list endpoints */
|
||||
function buildQuery(
|
||||
params: Record<string, string | number | undefined>,
|
||||
): string {
|
||||
const entries = Object.entries(params).filter(
|
||||
([, v]) => v !== undefined && v !== "",
|
||||
);
|
||||
if (!entries.length) return "";
|
||||
return (
|
||||
"?" +
|
||||
entries
|
||||
.map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`)
|
||||
.join("&")
|
||||
);
|
||||
}
|
||||
|
||||
export const driverService = {
|
||||
/**
|
||||
* GET /driver
|
||||
* Fetch paginated + searchable driver list.
|
||||
* Requires: "read-driver" permission
|
||||
*/
|
||||
getAll: (page = 1, search = "", token: string | null) =>
|
||||
get<DriverListResponse>(
|
||||
`driver${buildQuery({ page, limit: 12, search: search || undefined })}`,
|
||||
token,
|
||||
),
|
||||
|
||||
/**
|
||||
* GET /driver/archived
|
||||
* Fetch soft-deleted (archived) drivers.
|
||||
* Requires: "read-deleted-driver" permission
|
||||
*/
|
||||
getArchived: (token: string | null) =>
|
||||
get<DriverListResponse>("driver/archived", token),
|
||||
|
||||
/**
|
||||
* GET /driver/me
|
||||
* Fetch the currently authenticated driver's info.
|
||||
* Requires: "read-driver" permission
|
||||
*/
|
||||
getMe: (token: string | null) =>
|
||||
get<DriverDetailResponse>("driver/me", token),
|
||||
|
||||
/**
|
||||
* GET /driver/:id
|
||||
* Fetch a single driver with status history and branch info.
|
||||
* Requires: "read-driver" permission
|
||||
*/
|
||||
getById: (id: string, token: string | null) =>
|
||||
get<DriverDetailResponse>(`driver/${id}`, token),
|
||||
|
||||
/**
|
||||
* POST /driver
|
||||
* Create a new driver (also generates a random userName & password).
|
||||
* Requires: "create-driver" permission
|
||||
* Note: for file uploads (photo / nationalPhoto / driverCardPhoto) use uploadWithImages.
|
||||
*/
|
||||
create: (payload: CreateDriverPayload, token: string | null) =>
|
||||
post<{ data: Driver }>("driver", payload, token),
|
||||
|
||||
/**
|
||||
* PATCH /driver/:id
|
||||
* Update driver data or status.
|
||||
* Requires: "update-driver" permission
|
||||
*/
|
||||
update: (id: string, payload: UpdateDriverPayload, token: string | null) =>
|
||||
put<{ data: Driver }>(`driver/${id}`, payload, token),
|
||||
|
||||
/**
|
||||
* DELETE /driver/:id
|
||||
* Soft-delete a driver.
|
||||
* Requires: "delete-driver" permission
|
||||
*/
|
||||
delete: (id: string, token: string | null) =>
|
||||
del<void>(`driver/${id}`, token),
|
||||
|
||||
// ── Reports ───────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* GET /driver/:id/reports/daily?date=YYYY-MM-DD
|
||||
* Generate an HTML daily report for a driver on a specific date.
|
||||
* Returns { reportUrl, filename }
|
||||
* Requires: "generate-driver-report" permission
|
||||
*/
|
||||
getDailyReport: (id: string, date: string, token: string | null) =>
|
||||
get<DriverReportResponse>(
|
||||
`driver/${id}/reports/daily?date=${encodeURIComponent(date)}`,
|
||||
token,
|
||||
),
|
||||
|
||||
// ── Image upload (multipart) ──────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* POST /driver (multipart/form-data)
|
||||
* Create a driver with photos attached.
|
||||
* photo / nationalPhoto / driverCardPhoto are optional file fields.
|
||||
*/
|
||||
createWithImages: async (
|
||||
payload: CreateDriverPayload & {
|
||||
photo?: File;
|
||||
nationalPhoto?: File;
|
||||
driverCardPhoto?: File;
|
||||
},
|
||||
token: string | null,
|
||||
): Promise<{ data: Driver }> => {
|
||||
const form = new FormData();
|
||||
|
||||
// Text fields
|
||||
Object.entries(payload).forEach(([key, val]) => {
|
||||
if (
|
||||
val !== undefined &&
|
||||
val !== null &&
|
||||
!(val instanceof File)
|
||||
) {
|
||||
form.append(key, String(val));
|
||||
}
|
||||
});
|
||||
|
||||
// File fields
|
||||
if (payload.photo) form.append("photo", payload.photo);
|
||||
if (payload.nationalPhoto) form.append("nationalPhoto", payload.nationalPhoto);
|
||||
if (payload.driverCardPhoto) form.append("driverCardPhoto", payload.driverCardPhoto);
|
||||
|
||||
const res = await fetch("/api/proxy/driver", {
|
||||
method: "POST",
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
body: form,
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const json = await res.json().catch(() => null);
|
||||
throw new Error(json?.message ?? `HTTP ${res.status}`);
|
||||
}
|
||||
return res.json() as Promise<{ data: Driver }>;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user