Initialize the admin app with banner management for app sections.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,628 @@
|
||||
import { apiRequest } from '@/services/http'
|
||||
import type {
|
||||
AdminNotification,
|
||||
AdminUser,
|
||||
BankAccount,
|
||||
CashPayment,
|
||||
Catalog,
|
||||
Check,
|
||||
CheckDeadline,
|
||||
Company,
|
||||
Customer,
|
||||
InternalMessage,
|
||||
Order,
|
||||
OrderItem,
|
||||
OrderStatus,
|
||||
OrderStatusHistoryItem,
|
||||
Product,
|
||||
RawOrderStatus,
|
||||
SettlementMethod,
|
||||
Shipment,
|
||||
ShipmentStatus,
|
||||
UserRole,
|
||||
} from '@/types'
|
||||
|
||||
/** Normalize admin list order status badges */
|
||||
export function mapAdminOrder<
|
||||
T extends {
|
||||
status: string
|
||||
adminStatus?: string
|
||||
allowedTransitions?: string[]
|
||||
settlementMethod?: string | null
|
||||
settlementType?: string | null
|
||||
meterage?: number
|
||||
totalMeterage?: number
|
||||
},
|
||||
>(
|
||||
order: T,
|
||||
): T & {
|
||||
status: OrderStatus
|
||||
rawStatus: RawOrderStatus
|
||||
allowedTransitions: RawOrderStatus[]
|
||||
settlementMethod: SettlementMethod | null
|
||||
meterage: number
|
||||
} {
|
||||
return {
|
||||
...order,
|
||||
status: (order.adminStatus ?? order.status) as OrderStatus,
|
||||
rawStatus: order.status as RawOrderStatus,
|
||||
allowedTransitions: (order.allowedTransitions ?? []) as RawOrderStatus[],
|
||||
settlementMethod: (order.settlementMethod ??
|
||||
order.settlementType ??
|
||||
null) as SettlementMethod | null,
|
||||
meterage: order.meterage ?? order.totalMeterage ?? 0,
|
||||
}
|
||||
}
|
||||
|
||||
export function slugifyCompanyName(name: string): string {
|
||||
const slug = name
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/[^a-z0-9-]/g, '')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-|-$/g, '')
|
||||
return slug || `c-${Date.now()}`
|
||||
}
|
||||
|
||||
/* ── Companies ─────────────────────────────────────────────── */
|
||||
|
||||
export type CreateCompanyPayload = {
|
||||
name: string
|
||||
slug: string
|
||||
description?: string
|
||||
logoUrl?: string | null
|
||||
displayOrder?: number
|
||||
isActive?: boolean
|
||||
}
|
||||
|
||||
export type UpdateCompanyPayload = Partial<Omit<CreateCompanyPayload, 'slug'>> & {
|
||||
slug?: string
|
||||
}
|
||||
|
||||
export const companiesApi = {
|
||||
list: (activeOnly?: boolean) =>
|
||||
apiRequest<Company[]>('/companies', { query: { activeOnly: activeOnly ? 'true' : undefined } }),
|
||||
get: (id: string) => apiRequest<Company>(`/companies/${id}`),
|
||||
create: (body: CreateCompanyPayload) => apiRequest<Company>('/companies', { body }),
|
||||
update: (id: string, body: UpdateCompanyPayload) =>
|
||||
apiRequest<Company>(`/companies/${id}`, { method: 'PATCH', body }),
|
||||
setActive: (id: string, isActive: boolean) =>
|
||||
apiRequest<Company>(`/companies/${id}/active`, { method: 'PATCH', body: { isActive } }),
|
||||
}
|
||||
|
||||
/* ── Bank accounts ─────────────────────────────────────────── */
|
||||
|
||||
export type CreateBankAccountPayload = {
|
||||
bankName: string
|
||||
accountHolder: string
|
||||
accountNumber: string
|
||||
cardNumber: string
|
||||
iban: string
|
||||
displayOrder?: number
|
||||
isActive?: boolean
|
||||
notes?: string
|
||||
}
|
||||
|
||||
export type UpdateBankAccountPayload = Partial<CreateBankAccountPayload>
|
||||
|
||||
export const bankAccountsApi = {
|
||||
listByCompany: (companyId: string) =>
|
||||
apiRequest<BankAccount[]>(`/companies/${companyId}/bank-accounts`),
|
||||
create: (companyId: string, body: CreateBankAccountPayload) =>
|
||||
apiRequest<BankAccount>(`/companies/${companyId}/bank-accounts`, { body }),
|
||||
update: (id: string, body: UpdateBankAccountPayload) =>
|
||||
apiRequest<BankAccount>(`/bank-accounts/${id}`, { method: 'PATCH', body }),
|
||||
setActive: (id: string, isActive: boolean) =>
|
||||
apiRequest<BankAccount>(`/bank-accounts/${id}/active`, { method: 'PATCH', body: { isActive } }),
|
||||
}
|
||||
|
||||
/* ── Catalogs ──────────────────────────────────────────────── */
|
||||
|
||||
export type CreateCatalogPayload = {
|
||||
companyId: string
|
||||
name: string
|
||||
code: string
|
||||
description?: string
|
||||
displayOrder?: number
|
||||
isActive?: boolean
|
||||
}
|
||||
|
||||
export type UpdateCatalogPayload = Partial<Omit<CreateCatalogPayload, 'companyId'>>
|
||||
|
||||
export const catalogsApi = {
|
||||
list: (params?: { companyId?: string; activeOnly?: boolean }) =>
|
||||
apiRequest<Catalog[]>('/catalogs', {
|
||||
query: {
|
||||
companyId: params?.companyId,
|
||||
activeOnly: params?.activeOnly ? 'true' : undefined,
|
||||
},
|
||||
}),
|
||||
create: (body: CreateCatalogPayload) => apiRequest<Catalog>('/catalogs', { body }),
|
||||
update: (id: string, body: UpdateCatalogPayload) =>
|
||||
apiRequest<Catalog>(`/catalogs/${id}`, { method: 'PATCH', body }),
|
||||
setActive: (id: string, isActive: boolean) =>
|
||||
apiRequest<Catalog>(`/catalogs/${id}/active`, { method: 'PATCH', body: { isActive } }),
|
||||
}
|
||||
|
||||
/* ── Products ──────────────────────────────────────────────── */
|
||||
|
||||
export type CreateProductPayload = {
|
||||
companyId: string
|
||||
catalogId: string
|
||||
name: string
|
||||
code?: string
|
||||
productCode?: string
|
||||
color: string
|
||||
minMeterage?: number
|
||||
orderStep?: number
|
||||
orderMultiplier?: number
|
||||
price?: number | null
|
||||
basePrice?: number | null
|
||||
internalStock?: number | null
|
||||
stock?: number | null
|
||||
isActive?: boolean
|
||||
description?: string
|
||||
usageCategory?: string
|
||||
season?: string
|
||||
composition?: string | null
|
||||
weaveType?: string | null
|
||||
subtitle?: string | null
|
||||
rollMeters?: number
|
||||
widthCm?: number | null
|
||||
weightGrams?: number | null
|
||||
isNew?: boolean
|
||||
colorsJson?: string[]
|
||||
imageUrlsJson?: string[]
|
||||
tagsJson?: string[]
|
||||
}
|
||||
|
||||
export type UpdateProductPayload = Partial<Omit<CreateProductPayload, 'companyId'>>
|
||||
|
||||
export const productsApi = {
|
||||
list: (params?: { catalogId?: string; companyId?: string; activeOnly?: boolean }) =>
|
||||
apiRequest<Product[]>('/products', {
|
||||
query: {
|
||||
catalogId: params?.catalogId,
|
||||
companyId: params?.companyId,
|
||||
activeOnly: params?.activeOnly ? 'true' : undefined,
|
||||
},
|
||||
}),
|
||||
create: (body: CreateProductPayload) => apiRequest<Product>('/products', { body }),
|
||||
update: (id: string, body: UpdateProductPayload) =>
|
||||
apiRequest<Product>(`/products/${id}`, { method: 'PATCH', body }),
|
||||
setActive: (id: string, isActive: boolean) =>
|
||||
apiRequest<Product>(`/products/${id}/active`, { method: 'PATCH', body: { isActive } }),
|
||||
}
|
||||
|
||||
/* ── Customers ─────────────────────────────────────────────── */
|
||||
|
||||
export type CreateCustomerPayload = {
|
||||
name?: string
|
||||
firstName?: string
|
||||
lastName?: string
|
||||
phone: string
|
||||
nationalId?: string
|
||||
customerType?: Customer['customerType']
|
||||
companyId?: string | null
|
||||
isActive?: boolean
|
||||
levelDefId?: string | null
|
||||
assignedAgentId?: string | null
|
||||
appRole?: 'customer' | 'agent'
|
||||
agentCode?: string | null
|
||||
brandIds?: string[]
|
||||
}
|
||||
|
||||
export type UpdateCustomerPayload = Partial<CreateCustomerPayload>
|
||||
|
||||
export const customersApi = {
|
||||
list: (q?: string, appRole?: 'customer' | 'agent') =>
|
||||
apiRequest<Customer[]>('/admin/customers', { query: { q, appRole } }),
|
||||
create: (body: CreateCustomerPayload) => apiRequest<Customer>('/admin/customers', { body }),
|
||||
update: (id: string, body: UpdateCustomerPayload) =>
|
||||
apiRequest<Customer>(`/admin/customers/${id}`, { method: 'PATCH', body }),
|
||||
setActive: (id: string, isActive: boolean) =>
|
||||
apiRequest<Customer>(`/admin/customers/${id}/active`, { method: 'PATCH', body: { isActive } }),
|
||||
}
|
||||
|
||||
/* ── Orders ────────────────────────────────────────────────── */
|
||||
|
||||
export type AdminCreateOrderPayload = {
|
||||
companyId: string
|
||||
customerId?: string
|
||||
customerName?: string
|
||||
customerPhone?: string
|
||||
userId?: string
|
||||
settlementMethod?: SettlementMethod
|
||||
settlementType?: SettlementMethod
|
||||
meterage?: number
|
||||
notes?: string
|
||||
}
|
||||
|
||||
export type OrderItemPricingPayload = {
|
||||
id: string
|
||||
confirmedMeterage?: number
|
||||
unitPrice?: number
|
||||
supplyPrice?: number
|
||||
}
|
||||
|
||||
export type UpdateOrderPricingPayload = {
|
||||
items: OrderItemPricingPayload[]
|
||||
shippingCost?: number
|
||||
note?: string
|
||||
}
|
||||
|
||||
export type ApiOrder = Order & {
|
||||
adminStatus?: string
|
||||
settlementType?: SettlementMethod | null
|
||||
totalMeterage?: number
|
||||
shippingCost?: number | null
|
||||
finalAmount?: number | null
|
||||
checkRassDays?: number | null
|
||||
checkRassDate?: string
|
||||
items?: OrderItem[]
|
||||
checks?: Check[]
|
||||
statusHistory?: OrderStatusHistoryItem[]
|
||||
/** وضعیتهای مجاز بعدی از دید ماشین وضعیت سرور */
|
||||
allowedTransitions?: string[]
|
||||
cashReceipt?: { id: string } | null
|
||||
}
|
||||
|
||||
export type UpdateOrderStatusPayload = {
|
||||
status: string
|
||||
note?: string
|
||||
reason?: string
|
||||
/** گذر خارج از ماشین وضعیت؛ سرور reason را اجباری میکند */
|
||||
force?: boolean
|
||||
}
|
||||
|
||||
export const ordersApi = {
|
||||
list: (params?: { status?: string; q?: string }) =>
|
||||
apiRequest<ApiOrder[]>('/admin/orders', { query: { status: params?.status, q: params?.q } }),
|
||||
get: (id: string) => apiRequest<ApiOrder>(`/admin/orders/${id}`),
|
||||
create: (body: AdminCreateOrderPayload) => apiRequest<ApiOrder>('/admin/orders', { body }),
|
||||
updatePricing: (id: string, body: UpdateOrderPricingPayload) =>
|
||||
apiRequest<ApiOrder>(`/admin/orders/${id}/pricing`, { method: 'PATCH', body }),
|
||||
updateStatus: (id: string, body: UpdateOrderStatusPayload) =>
|
||||
apiRequest<ApiOrder>(`/admin/orders/${id}/status`, { method: 'PATCH', body }),
|
||||
}
|
||||
|
||||
/* ── Checks ────────────────────────────────────────────────── */
|
||||
|
||||
export const checksApi = {
|
||||
list: (status?: Check['status']) =>
|
||||
apiRequest<Check[]>('/admin/checks', { query: { status } }),
|
||||
listByOrder: (orderId: string) =>
|
||||
apiRequest<Check[]>(`/admin/orders/${orderId}/checks`),
|
||||
updateStatus: (id: string, body: { status: Check['status']; note?: string }) =>
|
||||
apiRequest<Check>(`/admin/checks/${id}/status`, { method: 'PATCH', body }),
|
||||
}
|
||||
|
||||
/* ── Cash payments ─────────────────────────────────────────── */
|
||||
|
||||
export const cashPaymentsApi = {
|
||||
list: (status?: CashPayment['status']) =>
|
||||
apiRequest<CashPayment[]>('/admin/cash-payments', { query: { status } }),
|
||||
updateStatus: (id: string, body: { status: CashPayment['status']; note?: string }) =>
|
||||
apiRequest<CashPayment>(`/admin/cash-payments/${id}/status`, { method: 'PATCH', body }),
|
||||
}
|
||||
|
||||
/* ── Shipments ─────────────────────────────────────────────── */
|
||||
|
||||
export type CreateShipmentPayload = {
|
||||
orderId: string
|
||||
carrier: string
|
||||
trackingNumber?: string
|
||||
meterage?: number
|
||||
status?: ShipmentStatus
|
||||
}
|
||||
|
||||
export const shipmentsApi = {
|
||||
list: (status?: ShipmentStatus) =>
|
||||
apiRequest<Shipment[]>('/admin/shipments', { query: { status } }),
|
||||
create: (body: CreateShipmentPayload) => apiRequest<Shipment>('/admin/shipments', { body }),
|
||||
update: (id: string, body: Partial<CreateShipmentPayload>) =>
|
||||
apiRequest<Shipment>(`/admin/shipments/${id}`, { method: 'PATCH', body }),
|
||||
advanceStatus: (id: string, body?: { status?: ShipmentStatus; trackingNumber?: string }) =>
|
||||
apiRequest<Shipment>(`/admin/shipments/${id}/status`, { method: 'PATCH', body: body ?? {} }),
|
||||
}
|
||||
|
||||
/* ── Admin users ───────────────────────────────────────────── */
|
||||
|
||||
export type CreateAdminUserPayload = {
|
||||
fullName: string
|
||||
phone: string
|
||||
email?: string
|
||||
password?: string
|
||||
role?: UserRole
|
||||
isActive?: boolean
|
||||
}
|
||||
|
||||
export type UpdateAdminUserPayload = Partial<CreateAdminUserPayload>
|
||||
|
||||
export const adminUsersApi = {
|
||||
list: () => apiRequest<AdminUser[]>('/admin/users'),
|
||||
create: (body: CreateAdminUserPayload) => apiRequest<AdminUser>('/admin/users', { body }),
|
||||
update: (id: string, body: UpdateAdminUserPayload) =>
|
||||
apiRequest<AdminUser>(`/admin/users/${id}`, { method: 'PATCH', body }),
|
||||
setActive: (id: string, isActive: boolean) =>
|
||||
apiRequest<AdminUser>(`/admin/users/${id}/active`, { method: 'PATCH', body: { isActive } }),
|
||||
}
|
||||
|
||||
/* ── Dashboard & reports ───────────────────────────────────── */
|
||||
|
||||
export type DashboardStats = {
|
||||
newOrders: number
|
||||
pendingReview: number
|
||||
pendingCheck: number
|
||||
finalApproval: number
|
||||
todaySales: number
|
||||
todaySalesGrowth: number
|
||||
monthSales: number
|
||||
}
|
||||
|
||||
export type DashboardResponse = {
|
||||
stats: DashboardStats
|
||||
recentOrders: ApiOrder[]
|
||||
salesChartData: number[]
|
||||
chartAxisLabels: string[]
|
||||
checkDeadlines: CheckDeadline[]
|
||||
notifications?: AdminNotification[]
|
||||
messages?: InternalMessage[]
|
||||
}
|
||||
|
||||
export type ReportsResponse = {
|
||||
reportSalesByCompany: {
|
||||
companyName: string
|
||||
orderCount: number
|
||||
totalAmount: number
|
||||
meterage: number
|
||||
}[]
|
||||
reportOrdersByStatus: { status: string; count: number }[]
|
||||
reportSalesByMonth?: {
|
||||
month: string
|
||||
orderCount: number
|
||||
totalAmount: number
|
||||
meterage: number
|
||||
}[]
|
||||
reportSalesByProduct?: {
|
||||
productId: string
|
||||
productName: string
|
||||
productCode: string
|
||||
orderCount: number
|
||||
meterage: number
|
||||
totalAmount: number
|
||||
}[]
|
||||
reportSalesByCustomer?: {
|
||||
customerName: string
|
||||
orderCount: number
|
||||
totalAmount: number
|
||||
meterage: number
|
||||
}[]
|
||||
reportCustomers?: {
|
||||
active: number
|
||||
inactive: number
|
||||
newLast30Days: number
|
||||
noPurchase: number
|
||||
noPurchase90Days: number
|
||||
withAgent: number
|
||||
}
|
||||
}
|
||||
|
||||
export const dashboardApi = {
|
||||
get: () => apiRequest<DashboardResponse>('/admin/dashboard'),
|
||||
reports: () => apiRequest<ReportsResponse>('/admin/reports'),
|
||||
}
|
||||
|
||||
/* ── System settings ──────────────────────────────────────── */
|
||||
|
||||
export type SystemSettings = {
|
||||
id: string
|
||||
maxMeteragePerInvoice: number
|
||||
cashInvoiceValidityHours: number
|
||||
defaultSettlementCheck: boolean
|
||||
defaultSettlementCash: boolean
|
||||
minOrderRollsPerProduct?: number
|
||||
showCatalogPublicPrice?: boolean
|
||||
allowAgentPricing?: boolean
|
||||
agentMinUnitPrice?: number | null
|
||||
smsOrderRegistered: boolean
|
||||
smsPricingReady: boolean
|
||||
smsPaymentReminder: boolean
|
||||
smsCheckReview: boolean
|
||||
smsShippingUpdate: boolean
|
||||
supportPhone: string | null
|
||||
supportEmail: string | null
|
||||
categoryImagesJson?: Record<string, string>
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export type UpdateSettingsPayload = Partial<Omit<SystemSettings, 'id' | 'updatedAt'>>
|
||||
|
||||
export const settingsApi = {
|
||||
get: () => apiRequest<SystemSettings>('/admin/settings'),
|
||||
update: (body: UpdateSettingsPayload) =>
|
||||
apiRequest<SystemSettings>('/admin/settings', { method: 'PATCH', body }),
|
||||
}
|
||||
|
||||
/* ── Admin notifications ──────────────────────────────────── */
|
||||
|
||||
export const notificationsApi = {
|
||||
markRead: (id: string) =>
|
||||
apiRequest<{ ok: boolean }>(`/admin/notifications/${id}/read`, { method: 'PATCH' }),
|
||||
markAllRead: () =>
|
||||
apiRequest<{ ok: boolean }>('/admin/notifications/read-all', { method: 'PATCH' }),
|
||||
broadcast: (body: { title: string; body: string; customerIds?: string[]; tone?: string }) =>
|
||||
apiRequest<{ sent: number }>('/admin/notifications/broadcast', { method: 'POST', body }),
|
||||
}
|
||||
|
||||
/* ── Catalog requests (physical) ──────────────────────────── */
|
||||
|
||||
export type CatalogRequestRow = {
|
||||
id: string
|
||||
customerId: string
|
||||
customerName: string
|
||||
customerPhone: string
|
||||
status: string
|
||||
note?: string
|
||||
createdAt: string
|
||||
items: {
|
||||
id: string
|
||||
productId: string
|
||||
productName: string
|
||||
productCode: string
|
||||
quantity: number
|
||||
holderType: string
|
||||
holderCustomerId?: string | null
|
||||
movements?: {
|
||||
id: string
|
||||
fromHolder: string
|
||||
toHolder: string
|
||||
note?: string
|
||||
movedAt: string
|
||||
}[]
|
||||
}[]
|
||||
}
|
||||
|
||||
export const catalogRequestsApi = {
|
||||
list: (status?: string) =>
|
||||
apiRequest<CatalogRequestRow[]>('/admin/catalog-requests', {
|
||||
query: { status },
|
||||
}),
|
||||
updateStatus: (id: string, status: string) =>
|
||||
apiRequest<CatalogRequestRow>(`/admin/catalog-requests/${id}/status`, {
|
||||
method: 'PATCH',
|
||||
body: { status },
|
||||
}),
|
||||
updateHolder: (
|
||||
itemId: string,
|
||||
body: { holderType: string; holderCustomerId?: string | null; note?: string },
|
||||
) =>
|
||||
apiRequest(`/admin/catalog-request-items/${itemId}/holder`, {
|
||||
method: 'PATCH',
|
||||
body,
|
||||
}),
|
||||
}
|
||||
|
||||
/* ── Brands ───────────────────────────────────────────────── */
|
||||
|
||||
export type Brand = {
|
||||
id: string
|
||||
name: string
|
||||
slug?: string
|
||||
description?: string
|
||||
isActive: boolean
|
||||
displayOrder: number
|
||||
customersCount?: number
|
||||
}
|
||||
|
||||
export type AppBanner = {
|
||||
id: string
|
||||
slot: string
|
||||
title: string
|
||||
subtitle: string
|
||||
ctaLabel: string
|
||||
ctaLink: string
|
||||
imageUrl: string
|
||||
displayOrder: number
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
export type BannerPayload = {
|
||||
slot: string
|
||||
title: string
|
||||
subtitle?: string | null
|
||||
ctaLabel?: string | null
|
||||
ctaLink?: string | null
|
||||
imageUrl?: string | null
|
||||
displayOrder?: number
|
||||
isActive?: boolean
|
||||
}
|
||||
|
||||
export const bannersApi = {
|
||||
list: (slot?: string) =>
|
||||
apiRequest<AppBanner[]>('/admin/banners', { query: { slot } }),
|
||||
create: (body: BannerPayload) =>
|
||||
apiRequest<AppBanner>('/admin/banners', { body }),
|
||||
update: (id: string, body: Partial<BannerPayload>) =>
|
||||
apiRequest<AppBanner>(`/admin/banners/${id}`, { method: 'PATCH', body }),
|
||||
remove: (id: string) =>
|
||||
apiRequest<{ ok: boolean }>(`/admin/banners/${id}`, { method: 'DELETE' }),
|
||||
}
|
||||
|
||||
export const brandsApi = {
|
||||
list: () => apiRequest<Brand[]>('/admin/brands'),
|
||||
create: (body: Partial<Brand> & { name: string }) =>
|
||||
apiRequest<Brand>('/admin/brands', { body }),
|
||||
update: (id: string, body: Partial<Brand>) =>
|
||||
apiRequest<Brand>(`/admin/brands/${id}`, { method: 'PATCH', body }),
|
||||
}
|
||||
|
||||
/* ── Levels ───────────────────────────────────────────────── */
|
||||
|
||||
export type CustomerLevel = {
|
||||
id: string
|
||||
code: string
|
||||
label: string
|
||||
sortOrder: number
|
||||
isActive: boolean
|
||||
customersCount: number
|
||||
benefits: { id?: string; title: string; description?: string }[]
|
||||
}
|
||||
|
||||
export const levelsApi = {
|
||||
list: () => apiRequest<CustomerLevel[]>('/admin/levels'),
|
||||
create: (body: {
|
||||
code: string
|
||||
label: string
|
||||
sortOrder?: number
|
||||
benefits?: { title: string; description?: string }[]
|
||||
}) => apiRequest<CustomerLevel>('/admin/levels', { body }),
|
||||
update: (id: string, body: Partial<CustomerLevel> & { benefits?: { title: string; description?: string }[] }) =>
|
||||
apiRequest<CustomerLevel>(`/admin/levels/${id}`, { method: 'PATCH', body }),
|
||||
}
|
||||
|
||||
/* ── Suggestions ──────────────────────────────────────────── */
|
||||
|
||||
export type ProductSuggestion = {
|
||||
id: string
|
||||
customerId: string
|
||||
customerName: string
|
||||
productId: string
|
||||
productName: string
|
||||
productCode: string
|
||||
note?: string
|
||||
isActive: boolean
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export const suggestionsApi = {
|
||||
list: (customerId?: string) =>
|
||||
apiRequest<ProductSuggestion[]>('/admin/suggestions', { query: { customerId } }),
|
||||
create: (body: { customerId: string; productId: string; note?: string }) =>
|
||||
apiRequest<ProductSuggestion>('/admin/suggestions', { body }),
|
||||
setActive: (id: string, isActive: boolean) =>
|
||||
apiRequest<ProductSuggestion>(`/admin/suggestions/${id}/active`, {
|
||||
method: 'PATCH',
|
||||
body: { isActive },
|
||||
}),
|
||||
}
|
||||
|
||||
/* ── Stock alerts ─────────────────────────────────────────── */
|
||||
|
||||
export type StockAlert = {
|
||||
id: string
|
||||
customerName: string
|
||||
customerPhone: string
|
||||
productName: string
|
||||
productCode: string
|
||||
stock: number | null
|
||||
active: boolean
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export const stockAlertsApi = {
|
||||
list: () => apiRequest<StockAlert[]>('/admin/stock-alerts'),
|
||||
notify: (id: string) =>
|
||||
apiRequest<StockAlert>(`/admin/stock-alerts/${id}/notify`, { method: 'PATCH' }),
|
||||
}
|
||||
Reference in New Issue
Block a user