frontend fully integreated with the backend

This commit is contained in:
Kalana B. Thilakarathna
2026-08-25 23:47:33 +05:30
parent 1bee5f91c6
commit 83d3272784
151 changed files with 10724 additions and 573 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
import axios, { type AxiosError } from 'axios'
export const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:5000',
baseURL: process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:5089/api',
headers: { 'Content-Type': 'application/json' },
})
@@ -21,8 +21,8 @@ api.interceptors.response.use(
(error: AxiosError<{ error: string }>) => {
const status = error.response?.status
if (status === 401) {
// Redirect to login when auth is implemented
if (typeof window !== 'undefined') window.location.href = '/login'
return new Promise(() => {}) // redirect in progress — never resolve so no error toast fires
}
return Promise.reject(error)
}
+135
View File
@@ -0,0 +1,135 @@
import { api } from '@/lib/api'
import type {
PaginatedResponse,
ItemSummaryResponse,
ItemDetailResponse,
CategoryResponse,
BrandResponse,
UomResponse,
SyncSource,
} from '@/types/api'
import type { PageParams } from './master'
// ─── Items ────────────────────────────────────────────────────────────────────
export interface ItemListParams extends PageParams {
categoryId?: string
brandId?: string
search?: string
}
export interface ItemBody {
sku: string
name: string
description?: string | null
categoryId: string
subCategoryId?: string | null
brandId?: string | null
baseUomId: string
salePrice: number
syncSource?: SyncSource
isActive?: boolean
}
export const items = {
list: (params?: ItemListParams) =>
api.get<PaginatedResponse<ItemSummaryResponse>>('/items', { params }).then(r => r.data),
get: (id: string) =>
api.get<ItemDetailResponse>(`/items/${id}`).then(r => r.data),
create: (body: ItemBody) =>
api.post<ItemDetailResponse>('/items', body).then(r => r.data),
update: (id: string, body: Partial<ItemBody>) =>
api.put<ItemDetailResponse>(`/items/${id}`, body).then(r => r.data),
remove: (id: string) =>
api.delete(`/items/${id}`),
}
// ─── Categories ───────────────────────────────────────────────────────────────
export interface CategoryBody {
name: string
isActive?: boolean
}
export interface SubCategoryBody {
name: string
isActive?: boolean
}
export const categories = {
list: () =>
api.get<CategoryResponse[]>('/categories').then(r => r.data),
get: (id: string) =>
api.get<CategoryResponse>(`/categories/${id}`).then(r => r.data),
create: (body: CategoryBody) =>
api.post<CategoryResponse>('/categories', body).then(r => r.data),
update: (id: string, body: Partial<CategoryBody>) =>
api.put<CategoryResponse>(`/categories/${id}`, body).then(r => r.data),
createSubCategory: (categoryId: string, body: SubCategoryBody) =>
api.post(`/categories/${categoryId}/sub-categories`, body).then(r => r.data),
updateSubCategory: (categoryId: string, subId: string, body: Partial<SubCategoryBody>) =>
api.put(`/categories/${categoryId}/sub-categories/${subId}`, body).then(r => r.data),
}
// ─── Brands ───────────────────────────────────────────────────────────────────
export interface BrandBody {
name: string
isActive?: boolean
}
export const brands = {
list: (params?: PageParams) =>
api.get<PaginatedResponse<BrandResponse>>('/brands', { params }).then(r => r.data),
get: (id: string) =>
api.get<BrandResponse>(`/brands/${id}`).then(r => r.data),
create: (body: BrandBody) =>
api.post<BrandResponse>('/brands', body).then(r => r.data),
update: (id: string, body: Partial<BrandBody>) =>
api.put<BrandResponse>(`/brands/${id}`, body).then(r => r.data),
}
// ─── UOMs ─────────────────────────────────────────────────────────────────────
export interface UomBody {
name: string
isActive?: boolean
}
export interface UomConversionBody {
toUomId: string
factor: number
}
export const uoms = {
list: () =>
api.get<UomResponse[]>('/uoms').then(r => r.data),
get: (id: string) =>
api.get<UomResponse>(`/uoms/${id}`).then(r => r.data),
create: (body: UomBody) =>
api.post<UomResponse>('/uoms', body).then(r => r.data),
update: (id: string, body: Partial<UomBody>) =>
api.put<UomResponse>(`/uoms/${id}`, body).then(r => r.data),
addConversion: (id: string, body: UomConversionBody) =>
api.post(`/uoms/${id}/conversions`, body).then(r => r.data),
removeConversion: (id: string, conversionId: string) =>
api.delete(`/uoms/${id}/conversions/${conversionId}`),
}
+47
View File
@@ -0,0 +1,47 @@
import { api } from '@/lib/api'
import type {
PaginatedResponse,
DriverProfileResponse,
DriverComplianceConfigResponse,
} from '@/types/api'
import type { PageParams } from './master'
// ─── Driver Profiles ──────────────────────────────────────────────────────────
export interface DriverProfileBody {
userId: string
licenseNumber: string
licenseExpiry: string
medicalClearanceExpiry: string
isActive?: boolean
}
export const drivers = {
list: (params?: PageParams) =>
api.get<PaginatedResponse<DriverProfileResponse>>('/driver-profiles', { params }).then(r => r.data),
get: (id: string) =>
api.get<DriverProfileResponse>(`/driver-profiles/${id}`).then(r => r.data),
create: (body: DriverProfileBody) =>
api.post<DriverProfileResponse>('/driver-profiles', body).then(r => r.data),
update: (id: string, body: Partial<DriverProfileBody>) =>
api.put<DriverProfileResponse>(`/driver-profiles/${id}`, body).then(r => r.data),
}
// ─── Compliance Config ────────────────────────────────────────────────────────
export interface ComplianceConfigBody {
enforceLicenseExpiry: boolean
enforceMedicalClearance: boolean
}
export const compliance = {
get: () =>
api.get<DriverComplianceConfigResponse>('/driver-profiles/compliance').then(r => r.data),
update: (body: ComplianceConfigBody) =>
api.put<DriverComplianceConfigResponse>('/driver-profiles/compliance', body).then(r => r.data),
}
+15
View File
@@ -0,0 +1,15 @@
export { regions, routes, vehicles, warehouses } from './master'
export { items, categories, brands, uoms } from './catalogue'
export { priceLists, schemes } from './pricing'
export { stores } from './stores'
export { suppliers } from './suppliers'
export { users, roles, permissions } from './users'
export { drivers, compliance } from './drivers'
export { purchaseOrders, grn } from './procurement'
export { rides, loadingSheets, deliveryTasks } from './operations'
export { stock } from './stock'
export { sales, orders, payments } from './sales'
export { getPaginatedItems } from './paginated'
export type { PageParams } from './master'
export { getApiError } from '@/lib/api'
+186
View File
@@ -0,0 +1,186 @@
import { api } from '@/lib/api'
import type {
PaginatedResponse,
RegionResponse,
RouteResponse,
VehicleResponse,
WarehouseResponse,
VehicleType,
WarehouseType,
} from '@/types/api'
const VEHICLE_TYPE_CODES: Record<VehicleType, number> = {
Car: 0,
Truck: 1,
Van: 2,
Motorcycle: 3,
Bicycle: 4,
}
const VEHICLE_TYPE_VALUES = ['Car', 'Truck', 'Van', 'Motorcycle', 'Bicycle'] as const
function normalizeVehicleType(type: VehicleType | number): VehicleType {
if (typeof type === 'number') {
return VEHICLE_TYPE_VALUES[type] ?? 'Car'
}
return type
}
function normalizeVehicle(
vehicle: VehicleResponse | (Omit<VehicleResponse, 'type'> & { type: VehicleType | number })
): VehicleResponse {
return {
...vehicle,
type: normalizeVehicleType(vehicle.type),
}
}
function normalizeVehicleListResponse(
response: PaginatedResponse<VehicleResponse> | (Omit<VehicleResponse, 'type'> & { type: VehicleType | number })[],
params?: VehicleListParams
): PaginatedResponse<VehicleResponse> {
if (Array.isArray(response)) {
const page = params?.page ?? 1
const pageSize = params?.pageSize ?? (response.length || 1)
const start = (page - 1) * pageSize
const items = response.slice(start, start + pageSize).map(normalizeVehicle)
return {
page,
pageSize,
total: response.length,
items,
}
}
return {
...response,
items: response.items.map(normalizeVehicle),
}
}
function serializeVehicleBody(body: VehicleBody | Partial<VehicleBody>) {
return {
...body,
...(body.type ? { type: VEHICLE_TYPE_CODES[body.type] } : {}),
}
}
// ─── Pagination ───────────────────────────────────────────────────────────────
export interface PageParams {
page?: number
pageSize?: number
}
// ─── Regions ──────────────────────────────────────────────────────────────────
export interface RegionBody {
name: string
isActive?: boolean
}
export const regions = {
list: (params?: PageParams) =>
api.get<PaginatedResponse<RegionResponse>>('/regions', { params }).then(r => r.data),
get: (id: string) =>
api.get<RegionResponse>(`/regions/${id}`).then(r => r.data),
create: (body: RegionBody) =>
api.post<RegionResponse>('/regions', body).then(r => r.data),
update: (id: string, body: Partial<RegionBody>) =>
api.put<RegionResponse>(`/regions/${id}`, body).then(r => r.data),
remove: (id: string) =>
api.delete(`/regions/${id}`),
}
// ─── Routes ───────────────────────────────────────────────────────────────────
export interface RouteBody {
regionId: string
routeName: string
startPoint: string
endPoint: string
length: number
isActive?: boolean
}
export const routes = {
list: (params?: PageParams & { regionId?: string }) =>
api.get<PaginatedResponse<RouteResponse>>('/routes', { params }).then(r => r.data),
get: (id: string) =>
api.get<RouteResponse>(`/routes/${id}`).then(r => r.data),
create: (body: RouteBody) =>
api.post<RouteResponse>('/routes', body).then(r => r.data),
update: (id: string, body: Partial<RouteBody>) =>
api.put<RouteResponse>(`/routes/${id}`, body).then(r => r.data),
remove: (id: string) =>
api.delete(`/routes/${id}`),
}
// ─── Vehicles ─────────────────────────────────────────────────────────────────
export interface VehicleBody {
regNumber: string
numberPlate: string
type: VehicleType
}
export interface VehicleListParams extends PageParams {
available?: boolean
isActive?: boolean
}
export const vehicles = {
list: (params?: VehicleListParams) =>
api.get<PaginatedResponse<VehicleResponse> | VehicleResponse[]>('/vehicles', { params }).then(r =>
normalizeVehicleListResponse(r.data, params)
),
get: (id: string) =>
api.get<VehicleResponse>(`/vehicles/${id}`).then(r => normalizeVehicle(r.data)),
create: (body: VehicleBody) =>
api.post<VehicleResponse>('/vehicles', serializeVehicleBody(body)).then(r => normalizeVehicle(r.data)),
update: (id: string, body: Partial<VehicleBody>) =>
api.put<VehicleResponse>(`/vehicles/${id}`, serializeVehicleBody(body)).then(r => normalizeVehicle(r.data)),
remove: (id: string) =>
api.delete(`/vehicles/${id}`),
}
// ─── Warehouses ───────────────────────────────────────────────────────────────
export interface WarehouseBody {
name: string
location: string
type: WarehouseType
vehicleId?: string | null
isActive?: boolean
}
export const warehouses = {
list: (params?: PageParams) =>
api.get<PaginatedResponse<WarehouseResponse>>('/warehouses', { params }).then(r => r.data),
get: (id: string) =>
api.get<WarehouseResponse>(`/warehouses/${id}`).then(r => r.data),
create: (body: WarehouseBody) =>
api.post<WarehouseResponse>('/warehouses', body).then(r => r.data),
update: (id: string, body: Partial<WarehouseBody>) =>
api.put<WarehouseResponse>(`/warehouses/${id}`, body).then(r => r.data),
remove: (id: string) =>
api.delete(`/warehouses/${id}`),
}
+133
View File
@@ -0,0 +1,133 @@
import { api } from '@/lib/api'
import type {
PaginatedResponse,
RideResponse,
CancelRequestResponse,
LoadingSheetResponse,
DeliveryTaskResponse,
RideType,
RideStatus,
CancelRequestType,
CancelRequestStatus,
LoadingSheetStatus,
DeliveryTaskStatus,
} from '@/types/api'
import type { PageParams } from './master'
// ─── Rides ────────────────────────────────────────────────────────────────────
export interface RideBody {
rideType: RideType
vehicleId: string
driverProfileId: string
routeId: string
sourceWarehouseId: string
meterstart: number
}
export interface RideListParams extends PageParams {
status?: RideStatus
rideType?: RideType
driverProfileId?: string
vehicleId?: string
}
export interface CancelRequestBody {
typeCode: CancelRequestType
reason: string
}
export interface CancelRequestReviewBody {
status: CancelRequestStatus
}
export const rides = {
list: (params?: RideListParams) =>
api.get<PaginatedResponse<RideResponse>>('/rides', { params }).then(r => r.data),
get: (id: string) =>
api.get<RideResponse>(`/rides/${id}`).then(r => r.data),
create: (body: RideBody) =>
api.post<RideResponse>('/rides', body).then(r => r.data),
activate: (id: string) =>
api.patch<RideResponse>(`/rides/${id}/activate`).then(r => r.data),
close: (id: string, meterend: number) =>
api.patch<RideResponse>(`/rides/${id}/close`, { meterend }).then(r => r.data),
cancelRequests: {
list: (rideId: string) =>
api.get<CancelRequestResponse[]>(`/rides/${rideId}/cancel-requests`).then(r => r.data),
create: (rideId: string, body: CancelRequestBody) =>
api.post<CancelRequestResponse>(`/rides/${rideId}/cancel-requests`, body).then(r => r.data),
review: (rideId: string, requestId: string, body: CancelRequestReviewBody) =>
api.patch<CancelRequestResponse>(`/rides/${rideId}/cancel-requests/${requestId}`, body).then(r => r.data),
},
}
// ─── Loading Sheets ───────────────────────────────────────────────────────────
export interface LoadingSheetItemBody {
itemId: string
quantityDispatched: number
}
export interface LoadingSheetBody {
rideId: string
sourceWarehouseId: string
items: LoadingSheetItemBody[]
}
export interface LoadingSheetReceiptItemBody {
itemId: string
quantityReceived: number
}
export interface LoadingSheetListParams extends PageParams {
rideId?: string
status?: LoadingSheetStatus
}
export const loadingSheets = {
list: (params?: LoadingSheetListParams) =>
api.get<PaginatedResponse<LoadingSheetResponse>>('/loading-sheets', { params }).then(r => r.data),
get: (id: string) =>
api.get<LoadingSheetResponse>(`/loading-sheets/${id}`).then(r => r.data),
create: (body: LoadingSheetBody) =>
api.post<LoadingSheetResponse>('/loading-sheets', body).then(r => r.data),
confirmDispatch: (id: string) =>
api.patch<LoadingSheetResponse>(`/loading-sheets/${id}/dispatch`).then(r => r.data),
confirmReceipt: (id: string, items: LoadingSheetReceiptItemBody[]) =>
api.patch<LoadingSheetResponse>(`/loading-sheets/${id}/receipt`, { items }).then(r => r.data),
resolve: (id: string) =>
api.patch<LoadingSheetResponse>(`/loading-sheets/${id}/resolve`).then(r => r.data),
}
// ─── Delivery Tasks ───────────────────────────────────────────────────────────
export interface DeliveryTaskListParams extends PageParams {
rideId?: string
status?: DeliveryTaskStatus
storeId?: string
}
export const deliveryTasks = {
listByRide: (rideId: string, params?: Omit<DeliveryTaskListParams, 'rideId'>) =>
api.get<PaginatedResponse<DeliveryTaskResponse>>(`/rides/${rideId}/delivery-tasks`, { params }).then(r => r.data),
get: (id: string) =>
api.get<DeliveryTaskResponse>(`/delivery-tasks/${id}`).then(r => r.data),
updateStatus: (id: string, status: DeliveryTaskStatus, note?: string) =>
api.patch<DeliveryTaskResponse>(`/delivery-tasks/${id}/status`, { status, note }).then(r => r.data),
}
+4
View File
@@ -0,0 +1,4 @@
export function getPaginatedItems<T>(value: T[] | { items?: T[] } | null | undefined): T[] {
if (Array.isArray(value)) return value
return Array.isArray(value?.items) ? value.items : []
}
+89
View File
@@ -0,0 +1,89 @@
import { api } from '@/lib/api'
import type {
PaginatedResponse,
PriceListResponse,
SchemeResponse,
SchemeRuleType,
} from '@/types/api'
import type { PageParams } from './master'
// ─── Price Lists ──────────────────────────────────────────────────────────────
export interface PriceListItemBody {
itemId: string
price: number
minPrice?: number | null
}
export interface PriceListBody {
name: string
regionId?: string | null
effectiveFrom: string
effectiveTo: string
isActive?: boolean
items: PriceListItemBody[]
}
export interface PriceListListParams extends PageParams {
regionId?: string
isActive?: boolean
}
export const priceLists = {
list: (params?: PriceListListParams) =>
api.get<PaginatedResponse<PriceListResponse>>('/price-lists', { params }).then(r => r.data),
get: (id: string) =>
api.get<PriceListResponse>(`/price-lists/${id}`).then(r => r.data),
create: (body: PriceListBody) =>
api.post<PriceListResponse>('/price-lists', body).then(r => r.data),
update: (id: string, body: Partial<PriceListBody>) =>
api.put<PriceListResponse>(`/price-lists/${id}`, body).then(r => r.data),
}
// ─── Schemes ──────────────────────────────────────────────────────────────────
export interface BundleComponentBody {
itemId: string
qty: number
}
export interface SchemeBody {
name: string
ruleType: SchemeRuleType
ruleConfig?: string | null
triggerItemId?: string | null
triggerQty?: number
freeItemId?: string | null
freeQty?: number
bundleComponents?: BundleComponentBody[]
regionId?: string | null
eligibleSegment?: string | null
dateRangeFrom: string
dateRangeTo: string
agentLimitExempt?: boolean
baselineExpectedSales?: number
isActive?: boolean
}
export interface SchemeListParams extends PageParams {
regionId?: string
isActive?: boolean
}
export const schemes = {
list: (params?: SchemeListParams) =>
api.get<PaginatedResponse<SchemeResponse>>('/schemes', { params }).then(r => r.data),
get: (id: string) =>
api.get<SchemeResponse>(`/schemes/${id}`).then(r => r.data),
create: (body: SchemeBody) =>
api.post<SchemeResponse>('/schemes', body).then(r => r.data),
update: (id: string, body: Partial<SchemeBody>) =>
api.put<SchemeResponse>(`/schemes/${id}`, body).then(r => r.data),
}
+67
View File
@@ -0,0 +1,67 @@
import { api } from '@/lib/api'
import type {
PaginatedResponse,
PurchaseOrderResponse,
GrnResponse,
PurchaseOrderStatus,
} from '@/types/api'
import type { PageParams } from './master'
// ─── Purchase Orders ──────────────────────────────────────────────────────────
export interface PurchaseOrderItemBody {
itemId: string
quantity: number
unitCost: number
}
export interface PurchaseOrderBody {
supplierId: string
items: PurchaseOrderItemBody[]
}
export interface PurchaseOrderListParams extends PageParams {
supplierId?: string
status?: PurchaseOrderStatus
}
export const purchaseOrders = {
list: (params?: PurchaseOrderListParams) =>
api.get<PaginatedResponse<PurchaseOrderResponse>>('/purchase-orders', { params }).then(r => r.data),
get: (id: string) =>
api.get<PurchaseOrderResponse>(`/purchase-orders/${id}`).then(r => r.data),
create: (body: PurchaseOrderBody) =>
api.post<PurchaseOrderResponse>('/purchase-orders', body).then(r => r.data),
cancel: (id: string, cancelReason: string) =>
api.patch<PurchaseOrderResponse>(`/purchase-orders/${id}/cancel`, { cancelReason }).then(r => r.data),
}
// ─── Goods Receipts (GRN) ─────────────────────────────────────────────────────
export interface GrnItemBody {
purchaseOrderItemId: string
acceptedQuantity: number
rejectedQuantity: number
}
export interface GrnBody {
purchaseOrderId: string
warehouseId: string
receivedAt: string
items: GrnItemBody[]
}
export const grn = {
list: (params?: PageParams & { purchaseOrderId?: string }) =>
api.get<PaginatedResponse<GrnResponse>>('/goods-receipts', { params }).then(r => r.data),
get: (id: string) =>
api.get<GrnResponse>(`/goods-receipts/${id}`).then(r => r.data),
create: (body: GrnBody) =>
api.post<GrnResponse>('/goods-receipts', body).then(r => r.data),
}
+97
View File
@@ -0,0 +1,97 @@
import { api } from '@/lib/api'
import type {
PaginatedResponse,
SaleResponse,
OrderResponse,
PaymentResponse,
SaleStatus,
OrderStatus,
PaymentStatus,
PaymentMethod,
} from '@/types/api'
import type { PageParams } from './master'
// ─── Sales ────────────────────────────────────────────────────────────────────
export interface SaleItemBody {
itemId: string
quantity: number
unitPrice: number
}
export interface SaleBody {
storeId: string
rideId?: string | null
fulfillImmediately?: boolean
items: SaleItemBody[]
}
export interface SaleListParams extends PageParams {
storeId?: string
status?: SaleStatus
paymentStatus?: PaymentStatus
rideId?: string
from?: string
to?: string
}
export const sales = {
list: (params?: SaleListParams) =>
api.get<PaginatedResponse<SaleResponse>>('/sales', { params }).then(r => r.data),
get: (id: string) =>
api.get<SaleResponse>(`/sales/${id}`).then(r => r.data),
create: (body: SaleBody) =>
api.post<SaleResponse>('/sales', body).then(r => r.data),
cancel: (id: string) =>
api.patch<SaleResponse>(`/sales/${id}/cancel`).then(r => r.data),
}
// ─── Orders ───────────────────────────────────────────────────────────────────
export interface OrderListParams extends PageParams {
storeId?: string
status?: OrderStatus
from?: string
to?: string
}
export const orders = {
list: (params?: OrderListParams) =>
api.get<PaginatedResponse<OrderResponse>>('/orders', { params }).then(r => r.data),
get: (id: string) =>
api.get<OrderResponse>(`/orders/${id}`).then(r => r.data),
}
// ─── Payments ─────────────────────────────────────────────────────────────────
export interface PaymentBody {
saleId: string
storeId: string
amount: number
method: PaymentMethod
collectedAt: string
}
export interface PaymentListParams extends PageParams {
storeId?: string
saleId?: string
method?: PaymentMethod
from?: string
to?: string
}
export const payments = {
list: (params?: PaymentListParams) =>
api.get<PaginatedResponse<PaymentResponse>>('/payments', { params }).then(r => r.data),
get: (id: string) =>
api.get<PaymentResponse>(`/payments/${id}`).then(r => r.data),
create: (body: PaymentBody) =>
api.post<PaymentResponse>('/payments', body).then(r => r.data),
}
+32
View File
@@ -0,0 +1,32 @@
import { api } from '@/lib/api'
import type {
PaginatedResponse,
StockBalanceResponse,
StockMovementResponse,
LocationType,
MovementType,
} from '@/types/api'
import type { PageParams } from './master'
export interface StockBalanceParams extends PageParams {
locationId?: string
locationType?: LocationType
itemId?: string
}
export interface StockMovementParams extends PageParams {
itemId?: string
locationId?: string
type?: MovementType
from?: string
to?: string
}
export const stock = {
balances: (params?: StockBalanceParams) =>
api.get<PaginatedResponse<StockBalanceResponse>>('/stock/balances', { params }).then(r => r.data),
movements: (params?: StockMovementParams) =>
api.get<PaginatedResponse<StockMovementResponse>>('/stock/movements', { params }).then(r => r.data),
}
+61
View File
@@ -0,0 +1,61 @@
import { api } from '@/lib/api'
import type {
PaginatedResponse,
StoreResponse,
KycDocumentResponse,
OutstandingBalanceResponse,
StoreDashboardResponse,
KycStatus,
} from '@/types/api'
import type { PageParams } from './master'
// ─── Stores ───────────────────────────────────────────────────────────────────
export interface StoreBody {
routeId: string
ownerName: string
storeName: string
storeAddress: string
latitude: number
longitude: number
oneTimeAmount?: number
creditLimit?: number
segment?: string | null
isActive?: boolean
}
export interface StoreListParams extends PageParams {
routeId?: string
kycStatus?: KycStatus
search?: string
}
export const stores = {
list: (params?: StoreListParams) =>
api.get<PaginatedResponse<StoreResponse>>('/stores', { params }).then(r => r.data),
get: (id: string) =>
api.get<StoreResponse>(`/stores/${id}`).then(r => r.data),
create: (body: StoreBody) =>
api.post<StoreResponse>('/stores', body).then(r => r.data),
update: (id: string, body: Partial<StoreBody>) =>
api.put<StoreResponse>(`/stores/${id}`, body).then(r => r.data),
outstandingBalance: (id: string) =>
api.get<OutstandingBalanceResponse>(`/stores/${id}/outstanding-balance`).then(r => r.data),
dashboard: (id: string, period?: string) =>
api.get<StoreDashboardResponse>(`/stores/${id}/dashboard`, { params: { period } }).then(r => r.data),
kycDocuments: (id: string) =>
api.get<KycDocumentResponse[]>(`/stores/${id}/kyc-documents`).then(r => r.data),
uploadKycDocument: (id: string, documentType: string, fileReference: string) =>
api.post<KycDocumentResponse>(`/stores/${id}/kyc-documents`, { documentType, fileReference }).then(r => r.data),
updateKycStatus: (id: string, kycStatus: KycStatus) =>
api.patch<StoreResponse>(`/stores/${id}/kyc-status`, { kycStatus }).then(r => r.data),
}
+23
View File
@@ -0,0 +1,23 @@
import { api } from '@/lib/api'
import type { PaginatedResponse, SupplierResponse } from '@/types/api'
import type { PageParams } from './master'
export interface SupplierBody {
name: string
contactInfo?: string | null
isActive?: boolean
}
export const suppliers = {
list: (params?: PageParams) =>
api.get<PaginatedResponse<SupplierResponse>>('/suppliers', { params }).then(r => r.data),
get: (id: string) =>
api.get<SupplierResponse>(`/suppliers/${id}`).then(r => r.data),
create: (body: SupplierBody) =>
api.post<SupplierResponse>('/suppliers', body).then(r => r.data),
update: (id: string, body: Partial<SupplierBody>) =>
api.put<SupplierResponse>(`/suppliers/${id}`, body).then(r => r.data),
}
+75
View File
@@ -0,0 +1,75 @@
import { api } from '@/lib/api'
import type {
PaginatedResponse,
UserResponse,
RoleResponse,
PermissionResponse,
} from '@/types/api'
import type { PageParams } from './master'
// ─── Users ────────────────────────────────────────────────────────────────────
export interface UserBody {
username: string
fullName: string
roleId: string
reportsToUserId?: string | null
password?: string
isActive?: boolean
}
export interface UserListParams extends PageParams {
roleId?: string
search?: string
}
export const users = {
list: (params?: UserListParams) =>
api.get<PaginatedResponse<UserResponse>>('/users', { params }).then(r => r.data),
get: (id: string) =>
api.get<UserResponse>(`/users/${id}`).then(r => r.data),
create: (body: UserBody) =>
api.post<UserResponse>('/users', body).then(r => r.data),
update: (id: string, body: Partial<UserBody>) =>
api.put<UserResponse>(`/users/${id}`, body).then(r => r.data),
}
// ─── Roles ────────────────────────────────────────────────────────────────────
export interface RoleBody {
name: string
level: number
isActive?: boolean
}
export interface RoleWithPermissions extends RoleResponse {
permissions: PermissionResponse[]
}
export const roles = {
list: () =>
api.get<RoleResponse[]>('/roles').then(r => r.data),
get: (id: string) =>
api.get<RoleWithPermissions>(`/roles/${id}`).then(r => r.data),
create: (body: RoleBody) =>
api.post<RoleResponse>('/roles', body).then(r => r.data),
update: (id: string, body: Partial<RoleBody>) =>
api.put<RoleResponse>(`/roles/${id}`, body).then(r => r.data),
assignPermissions: (id: string, permissionIds: string[]) =>
api.put(`/roles/${id}/permissions`, { permissionIds }).then(r => r.data),
}
// ─── Permissions ──────────────────────────────────────────────────────────────
export const permissions = {
list: () =>
api.get<PermissionResponse[]>('/permissions').then(r => r.data),
}
+4
View File
@@ -0,0 +1,4 @@
export { zodResolver } from '@hookform/resolvers/zod'
export { useForm, useFormContext, Controller } from 'react-hook-form'
export type { SubmitHandler, FieldValues, DefaultValues, UseFormReturn, ControllerRenderProps, FieldError as RHFFieldError } from 'react-hook-form'
export { z } from 'zod'