Files
projet_gestion_commande/frontend-admin/src/api/api_admin.ts
T
Xor290 d7d7496a66
Backend - Build & Lint / build (push) Successful in 32m38s
Frontend Admin - EAS Build / build (push) Successful in 1h34m28s
Frontend Client - EAS Build / build (push) Successful in 1h30m42s
Frontend Web - Build & Lint / build (push) Canceled after 12m2s
chore: build
2026-09-13 16:11:30 +02:00

1385 lines
38 KiB
TypeScript

import apiClient from "./client";
import { API_BASE_URL } from "./client";
import type {
AuthResponse,
ClientResponse,
CommandResponse,
Product,
DeliveryPerson,
DeliveryPersonsStats,
Alert,
} from "./types";
const V2 = `${API_BASE_URL}/api/v2`;
const CABINE_URL = `${API_BASE_URL}/api/v1/cabine`;
const V1_PUBLIC = `${API_BASE_URL}/api/v1`;
export const loginAdmin = async (
username: string,
password: string,
): Promise<AuthResponse> => {
try {
const { data } = await apiClient.post(`${V2}/admin/auth/login`, {
username,
password,
});
if (!data.access_token) {
return { success: false, message: "Token non reçu du serveur" };
}
return {
success: true,
message: "Connexion réussie",
access_token: data.access_token,
token_type: data.token_type,
expires_in: data.expires_in,
user: data.user,
};
} catch (error: any) {
return {
success: false,
message:
error.response?.data?.error ||
error.message ||
"Erreur de connexion",
};
}
};
export const logoutAdmin = async (): Promise<void> => {
try {
await apiClient.post(`${V2}/admin/auth/logout`);
} catch {
/* ignore */
}
};
export interface StatsSummary {
total_orders: number;
total_revenue: number;
total_promo_discount: number;
promo_orders_count: number;
peak_weekday: string;
top_product: string;
avg_per_day: number;
}
export interface WeekdayStat {
weekday: string;
count: number;
}
export interface DayStat {
day: string;
label: string;
count: number;
}
export interface DayRevenueStat {
day: string;
label: string;
revenue: number;
}
export interface HourStat {
hour: number;
label: string;
count: number;
revenue: number;
}
export interface ProductStat {
product_id: number;
name: string;
quantity: number;
order_count: number;
revenue: number;
category: string;
category_color: string;
}
export interface QuantityStat {
quantity: number;
order_count: number;
total_sold: number;
revenue: number;
}
export interface ProductQuantityBreakdown {
product_id: number;
name: string;
category_color: string;
total_orders: number;
quantities: QuantityStat[];
}
export interface DailyProductItem {
product_id: number;
name: string;
quantity: number;
order_count: number;
revenue: number;
}
export interface DailyCategoryDetail {
category: string;
category_color: string;
total_quantity: number;
total_revenue: number;
products: DailyProductItem[];
}
export interface DailyDetail {
date: string;
total_orders: number;
total_quantity: number;
total_revenue: number;
categories: DailyCategoryDetail[];
}
export type StatSection =
| "commandes"
| "revenus"
| "produits"
| "heures"
| "jours"
| "doses";
export interface AdminStats {
summary: StatsSummary;
by_weekday: WeekdayStat[];
by_day_30: DayStat[];
by_day_revenue: DayRevenueStat[];
by_hour: HourStat[];
top_products: ProductStat[];
by_quantity: ProductQuantityBreakdown[];
daily_detail?: DailyDetail;
reset_at_commandes?: string;
reset_at_revenus?: string;
reset_at_produits?: string;
reset_at_heures?: string;
reset_at_jours?: string;
reset_at_doses?: string;
}
export const getAdminStats = async (): Promise<AdminStats> => {
const { data } = await apiClient.get(`${V2}/admin/protected/stats`);
return data;
};
export const getAdminDailyDetail = async (
date: string,
): Promise<DailyDetail> => {
const { data } = await apiClient.get(`${V2}/admin/protected/stats/daily`, {
params: { date },
});
return data;
};
export const resetAdminStats = async (
section: StatSection,
): Promise<{ success: boolean; reset_at: string }> => {
const { data } = await apiClient.post(
`${V2}/admin/protected/stats/reset/${section}`,
);
return data;
};
// ============================================
// STATISTIQUES MENSUELLES (jour par jour)
// ============================================
export interface MonthlyDayStat {
day: string; // "2026-06-05"
label: string; // "05/06"
count: number;
revenue: number;
quantity: number;
}
export interface MonthlyStatsSummary {
total_orders: number;
total_revenue: number;
total_quantity: number;
}
export interface MonthlyStats {
month: string; // "2026-06"
summary: MonthlyStatsSummary;
by_day: MonthlyDayStat[];
}
export const getAdminStatsByMonth = async (
month?: string,
): Promise<MonthlyStats> => {
const { data } = await apiClient.get(
`${V2}/admin/protected/stats/monthly`,
{
params: month ? { month } : undefined,
},
);
return data;
};
export const getAllClients = async (): Promise<ClientResponse[]> => {
const { data } = await apiClient.get(`${V2}/admin/protected/all/clients`);
return data.clients || [];
};
export const getAllUsers = async () => {
const { data } = await apiClient.get(`${V2}/admin/protected/all/users`);
return data.users || [];
};
export const updateClientByAdmin = async (
clientId: number,
updates: Record<string, any>,
) => {
const { data } = await apiClient.put(
`${V2}/admin/protected/clients/${clientId}`,
updates,
);
return { success: true, message: data.message, client: data.client };
};
export const updateUserByAdmin = async (
userId: number,
updates: Record<string, any>,
) => {
const { data } = await apiClient.put(
`${V2}/admin/protected/users/${userId}`,
updates,
);
return { success: true, message: data.message, user: data.user };
};
export const getAllCommands = async (status?: string, username?: string) => {
let url = `${V2}/admin/protected/orders`;
const params: string[] = [];
if (status) params.push(`status=${status}`);
if (username) params.push(`username=${username}`);
if (params.length) url += `?${params.join("&")}`;
const { data } = await apiClient.get(url);
return {
success: true,
commands: data.commands || [],
count: data.count || 0,
};
};
export const getCommandByID = async (commandId: number) => {
const { data } = await apiClient.get(
`${V2}/admin/protected/orders/${commandId}`,
);
return { success: true, command: data.command };
};
export const getCommandItems = async (commandId: number) => {
const { data } = await apiClient.get(
`${V2}/admin/protected/orders/${commandId}/items`,
);
return {
success: true,
items: data.items || [],
count: data.count || 0,
command_info: data.command_info,
client_info: data.client_info,
};
};
export const exportApprovedCommandsCSV = async (): Promise<string> => {
const { data } = await apiClient.get(
`${V2}/admin/protected/orders/export/csv`,
{
responseType: "text",
},
);
return data as string;
};
export const deleteCommand = async (commandId: number) => {
const { data } = await apiClient.delete(
`${V2}/admin/protected/orders/${commandId}`,
);
return { success: true, message: data.message };
};
export const deleteCommandItem = async (commandId: number, itemId: number) => {
const { data } = await apiClient.delete(
`${V2}/admin/protected/orders/${commandId}/items/${itemId}`,
);
return { success: true, message: data.message };
};
export const updateCommandStatus = async (
commandId: number,
newStatus: string,
) => {
const { data } = await apiClient.put(
`${V2}/admin/protected/orders/${commandId}/status`,
{ status: newStatus },
);
return { success: true, message: data.message };
};
export const updateCommandAddress = async (
commandId: number,
newAddress: string,
) => {
const { data } = await apiClient.put(
`${V2}/admin/protected/orders/${commandId}/address`,
{ delivery_address: newAddress },
);
return { success: true, message: data.message };
};
export const validateCommand = async (commandId: number) => {
const { data } = await apiClient.post(
`${V2}/admin/protected/orders/${commandId}/force-validate`,
{ command_id: commandId },
);
const validated = (data.validated ?? []) as {
command_id: number;
points_awarded: number;
}[];
const points =
validated.find((v) => v.command_id === commandId)?.points_awarded ?? 0;
return {
success: true,
points_awarded: points,
validated_count: data.validated_count ?? 0,
};
};
export const proposeAddressChangeAdmin = async (
commandId: number,
proposedAddress: string,
) => {
const { data } = await apiClient.post(
`${V2}/admin/protected/orders/${commandId}/propose-address`,
{ proposed_address: proposedAddress },
);
return { success: true, message: data.message };
};
export const notifyClientToDescend = async (commandId: number) => {
const { data } = await apiClient.post(
`${V2}/admin/protected/orders/${commandId}/notify-client`,
);
return {
success: true,
message: data.message,
client_username: data.client_username,
};
};
// ============================================
export const getAvailableDeliveryPersons = async () => {
try {
const { data } = await apiClient.get(
`${V2}/admin/protected/delivery-persons`,
);
return {
success: true,
livreurs: data.livreurs || [],
count: data.count || 0,
};
} catch {
return { success: false, livreurs: [], count: 0 };
}
};
export const assignDeliveryPerson = async (
commandId: number,
deliveryUsername: string,
) => {
const { data } = await apiClient.post(
`${V2}/admin/protected/delivery-persons/${deliveryUsername}/assign/${commandId}`,
);
return { success: true, message: data.message };
};
export const getDeliveryPersonDetails = async (username: string) => {
const { data } = await apiClient.get(
`${V2}/admin/protected/delivery-persons/${username}`,
);
return data.deliveryman || data;
};
export const getLivreurRatings = async (
username: string,
): Promise<{
ratings: {
id: number;
order_id: number;
client_username: string;
rating: number;
comment: string;
created_at: string;
}[];
average: number;
count: number;
}> => {
try {
const { data } = await apiClient.get(
`${V2}/admin/protected/delivery-persons/${username}/ratings`,
);
return data;
} catch {
return { ratings: [], average: 0, count: 0 };
}
};
export interface LoginHistoryEntry {
id: number;
username: string;
created_at: string;
}
export interface LoginHistoryWeek {
week: number;
entries: LoginHistoryEntry[];
}
export const getLivreurLoginHistory = async (
username: string,
year?: number,
month?: number,
): Promise<{
success: boolean;
year: number;
month: number;
weeks: LoginHistoryWeek[];
count: number;
}> => {
try {
const { data } = await apiClient.get(
`${V2}/admin/protected/delivery-persons/${username}/login-history`,
{ params: { year, month } },
);
return {
success: true,
year: data.year,
month: data.month,
weeks: data.weeks || [],
count: data.count || 0,
};
} catch {
return {
success: false,
year: year ?? new Date().getFullYear(),
month: month ?? new Date().getMonth() + 1,
weeks: [],
count: 0,
};
}
};
const parseStatus = (status: any): "available" | "busy" | "offline" => {
if (!status) return "offline";
if (status === "available" || status === "busy" || status === "offline")
return status;
if (typeof status === "string" && status.startsWith("{")) {
try {
const obj = JSON.parse(status);
if (
obj.status === "available" ||
obj.status === "busy" ||
obj.status === "offline"
)
return obj.status;
} catch {
/* ignore */
}
}
return "offline";
};
export const getAllDeliveryPersonsWithDetails = async (): Promise<{
success: boolean;
deliveryPersons: DeliveryPerson[];
count: number;
stats: DeliveryPersonsStats;
}> => {
try {
const { data } = await apiClient.get(
`${V2}/admin/protected/delivery-persons`,
);
const livreurs = data.livreurs || [];
const enriched = await Promise.all(
livreurs.map(async (l: any): Promise<DeliveryPerson> => {
try {
const details = await getDeliveryPersonDetails(l.username);
const parsedStatus = parseStatus(details.status);
const hasLoc =
details.location?.latitude &&
details.location?.longitude;
return {
id: l.id,
username: l.username,
status: parsedStatus,
location: {
latitude: hasLoc ? details.location.latitude : 0,
longitude: hasLoc ? details.location.longitude : 0,
last_update: details.location?.last_update
? new Date(
details.location.last_update * 1000,
).toISOString()
: new Date().toISOString(),
is_recent: details.location?.is_recent || false,
},
stats: {
total_deliveries: details.total_deliveries || 0,
completed_today: details.completed_deliveries || 0,
queue_size: details.queue_size || 0,
current_command: details.current_command || null,
},
};
} catch {
return {
id: l.id,
username: l.username,
status: "offline",
location: {
latitude: 0,
longitude: 0,
last_update: new Date().toISOString(),
is_recent: false,
},
stats: {
total_deliveries: 0,
completed_today: 0,
queue_size: 0,
current_command: null,
},
};
}
}),
);
const stats: DeliveryPersonsStats = {
total: enriched.length,
available: enriched.filter((d) => d.status === "available").length,
busy: enriched.filter((d) => d.status === "busy").length,
offline: enriched.filter((d) => d.status === "offline").length,
active_deliveries: enriched.filter(
(d) => d.stats.current_command !== null,
).length,
};
return {
success: true,
deliveryPersons: enriched,
count: enriched.length,
stats,
};
} catch {
return {
success: false,
deliveryPersons: [],
count: 0,
stats: {
total: 0,
available: 0,
busy: 0,
offline: 0,
active_deliveries: 0,
},
};
}
};
export const getDeliverymanLocationForCommand = async (commandId: number) => {
try {
const { data } = await apiClient.get(
`${V2}/admin/protected/commands/${commandId}/deliveryman/location`,
);
return { success: true, data: data.data };
} catch (error: any) {
return {
success: false,
error: error.response?.data?.error || "Erreur",
};
}
};
export const getAllProductsAdmin = async (): Promise<{
success: boolean;
data: Product[];
count: number;
}> => {
try {
const { data } = await apiClient.get(`${V2}/admin/protected/products`);
return { success: true, data: data.data || [], count: data.count || 0 };
} catch {
return { success: false, data: [], count: 0 };
}
};
export const createProductAdmin = async (formData: FormData) => {
const { data } = await apiClient.post(
`${V2}/admin/protected/products`,
formData,
{
headers: { "Content-Type": "multipart/form-data" },
timeout: 120000,
},
);
return { success: true, data: data.data, message: data.message };
};
export const updateProductAdmin = async (
productId: number,
updates: Record<string, any>,
) => {
const { data } = await apiClient.put(
`${V2}/admin/protected/products/${productId}`,
updates,
);
return { success: true, data: data.data, message: data.message };
};
export const deleteProductAdmin = async (productId: number) => {
const { data } = await apiClient.delete(
`${V2}/admin/protected/products/${productId}`,
);
return { success: true, message: data.message };
};
// ============================================
// CREATION UTILISATEURS / CLIENTS
// ============================================
export const createClientByAdmin = async (data: {
username: string;
password: string;
nom: string;
prenom: string;
telephone: string;
}) => {
const { data: res } = await apiClient.post(
`${V2}/admin/protected/clients`,
data,
);
return {
success: true,
message: res.message || "Client créé",
client: res.client,
};
};
export const createUserByAdmin = async (data: {
username: string;
password: string;
role: string;
}) => {
const { data: res } = await apiClient.post(
`${V2}/admin/protected/users`,
data,
);
return {
success: true,
message: res.message || "Utilisateur créé",
user: res.user,
};
};
// ============================================
// SUPPRESSION UTILISATEURS / CLIENTS
// ============================================
export const deleteUserAdmin = async (userId: number) => {
const { data } = await apiClient.delete(
`${V2}/admin/protected/users/${userId}`,
);
return { success: true, message: data.message };
};
export const deleteClientAdmin = async (clientId: number) => {
const { data } = await apiClient.delete(
`${V2}/admin/protected/clients/${clientId}`,
);
return { success: true, message: data.message };
};
// ============================================
// PARRAINAGE ADMIN
// ============================================
export const setClientParrain = async (
username: string,
parrain: string,
): Promise<{ success: boolean; error?: string }> => {
try {
await apiClient.post(
`${V2}/admin/protected/client/${username}/parrain/set`,
{ parrain },
);
return { success: true };
} catch (e: any) {
return { success: false, error: e.response?.data?.error || "Erreur" };
}
};
export const getClientParrain = async (
username: string,
): Promise<{ success: boolean; parrain?: string | null; error?: string }> => {
try {
const { data } = await apiClient.get(
`${V2}/admin/protected/client/${username}/parrain`,
);
return { success: true, parrain: data.parrain ?? null };
} catch (e: any) {
return { success: false, error: e.response?.data?.error || "Erreur" };
}
};
export const creditClientReferral = async (
username: string,
amount: number,
): Promise<{ success: boolean; balance?: number; message?: string }> => {
try {
const { data } = await apiClient.post(
`${V2}/admin/protected/client/${username}/referral/credit`,
{ amount },
);
return { success: true, balance: data.balance, message: data.message };
} catch (error: any) {
return {
success: false,
message: error.response?.data?.error || "Erreur crédit parrainage",
};
}
};
export const getClientReferralAdmin = async (
username: string,
): Promise<{ success: boolean; balance?: number; message?: string }> => {
try {
const { data } = await apiClient.get(
`${V2}/admin/protected/client/${username}/referral`,
);
return { success: true, balance: data.balance };
} catch (error: any) {
return {
success: false,
message: error.response?.data?.error || "Erreur récupération",
};
}
};
export const resetClientReferral = async (
username: string,
): Promise<{ success: boolean; message?: string }> => {
try {
const { data } = await apiClient.delete(
`${V2}/admin/protected/client/${username}/referral/reset`,
);
return { success: true, message: data.message };
} catch (error: any) {
return {
success: false,
message: error.response?.data?.error || "Erreur reset parrainage",
};
}
};
export interface CancelledOrder {
id: number;
username: string;
total_prix: number;
created_at: string;
updated_at: string;
items_count: number;
cancel_reason: string;
cancellation?: {
cancelled_at: string;
cancelled_by: string;
reason: string;
};
}
export const getClientCancelledOrders = async (
username: string,
): Promise<{ success: boolean; orders: CancelledOrder[] }> => {
try {
const { data } = await apiClient.get(
`${V2}/admin/protected/orders/cancelled`,
{ params: { username } },
);
return {
success: true,
orders: data.data?.cancelled_orders ?? [],
};
} catch {
return { success: false, orders: [] };
}
};
// ============================================
// AMENDES & POINTS CLIENT (Admin)
// ============================================
export const applyClientPenalty = async (
username: string,
points: number,
reason: string,
): Promise<{ success: boolean; compensated?: boolean; error?: string }> => {
try {
const { data } = await apiClient.post(`${V2}/admin/protected/penalty`, {
username,
points,
reason,
});
return { success: true, compensated: data?.compensated === true };
} catch (e: any) {
return { success: false, error: e.response?.data?.error || "Erreur" };
}
};
export const resetClientPenalties = async (
username: string,
resetCancellationsCount = false,
): Promise<{ success: boolean; error?: string }> => {
try {
await apiClient.post(
`${V2}/admin/protected/client/${username}/penalties/reset`,
{ reset_cancellations_count: resetCancellationsCount },
);
return { success: true };
} catch (e: any) {
return { success: false, error: e.response?.data?.error || "Erreur" };
}
};
export const resetClientPoints = async (
username: string,
pool = -1,
): Promise<{ success: boolean; error?: string }> => {
try {
await apiClient.post(
`${V2}/admin/protected/client/${username}/point/reset`,
{ pool },
);
return { success: true };
} catch (e: any) {
return { success: false, error: e.response?.data?.error || "Erreur" };
}
};
export const addClientPoints = async (
username: string,
poolKey: string,
points: number,
): Promise<{ success: boolean; error?: string }> => {
try {
await apiClient.post(
`${V2}/admin/protected/client/${username}/points/add`,
{ pool_key: poolKey, points },
);
return { success: true };
} catch (e: any) {
return { success: false, error: e.response?.data?.error || "Erreur" };
}
};
export const subtractClientPoints = async (
username: string,
poolKey: string,
points: number,
): Promise<{ success: boolean; error?: string }> => {
try {
await apiClient.post(
`${V2}/admin/protected/client/${username}/points/subtract`,
{ pool_key: poolKey, points },
);
return { success: true };
} catch (e: any) {
return { success: false, error: e.response?.data?.error || "Erreur" };
}
};
// ============================================
// ALERTES
// ============================================
export const getAdminAlerts = async (): Promise<{
success: boolean;
alerts: Alert[];
count: number;
}> => {
try {
const { data } = await apiClient.get(`${CABINE_URL}/alerts`);
return {
success: true,
alerts: data.alerts || [],
count: data.count || 0,
};
} catch {
return { success: false, alerts: [], count: 0 };
}
};
// ============================================
// MEDIAS PRODUITS
// ============================================
export const uploadProductMediaAdmin = async (
productId: number,
fileUri: string,
fileName: string,
fileType: string,
mediaType: "image" | "video",
) => {
const fd = new FormData();
fd.append("file", { uri: fileUri, name: fileName, type: fileType } as any);
fd.append("type", mediaType);
const { data } = await apiClient.post(
`${V2}/admin/protected/products/${productId}/media`,
fd,
{ headers: { "Content-Type": "multipart/form-data" }, timeout: 120000 },
);
return { success: true, media: data.media, message: data.message };
};
export const deleteProductMediaAdmin = async (
productId: number,
mediaId: number,
) => {
const { data } = await apiClient.delete(
`${V2}/admin/protected/products/${productId}/media/${mediaId}`,
);
return { success: true, message: data.message };
};
export const activateProductPrice = async (priceId: number) => {
const { data } = await apiClient.post(
`${V2}/admin/protected/active/product/price/${priceId}`,
);
return { success: true, message: data.message };
};
export const deactivateProductPrice = async (priceId: number) => {
const { data } = await apiClient.post(
`${V2}/admin/protected/desactive/product/price/${priceId}`,
);
return { success: true, message: data.message };
};
// ============================================
// ADDRESSES
// ============================================
export const addAddress = async (
invalidAddress: string,
correctAddress: string,
): Promise<{ success: boolean; message: string }> => {
const { data } = await apiClient.post(`${V2}/admin/protected/add/address`, {
invalid_address: invalidAddress,
correct_address: correctAddress,
});
return { success: true, message: data.message };
};
export const deleteAddress = async (
invalidAddress: string,
correctAddress: string,
): Promise<{ success: boolean; message: string }> => {
const { data } = await apiClient.delete(
`${V2}/admin/protected/delete/address`,
{
data: {
invalid_address: invalidAddress,
correct_address: correctAddress,
},
},
);
return { success: true, message: data.message };
};
export const getAllAddresses = async (): Promise<
{ invalid_address: string; correct_address: string }[]
> => {
const { data } = await apiClient.get(`${V2}/admin/protected/addresses`);
return (data.addresses ?? []).map((a: any) => ({
invalid_address: a.invalid_address ?? a.InvalidAddress ?? "",
correct_address: a.correct_address ?? a.CorrectAddress ?? "",
}));
};
// ============================================
// STATS HELPERS
// ============================================
export const getCommandCountByStatus = async (
status: string,
): Promise<number> => {
try {
const result = await getAllCommands(status);
return result.commands.length;
} catch {
return 0;
}
};
// ============================================
// CATÉGORIES
// ============================================
export interface Category {
id: number;
name: string;
color: string;
is_coming_soon: boolean;
position: number;
created_at: string;
}
export const getCategories = async (): Promise<Category[]> => {
try {
const { data } = await apiClient.get(`${V1_PUBLIC}/categories`);
return data.categories || [];
} catch {
return [];
}
};
export const createCategoryAdmin = async (
name: string,
color: string,
isComingSoon: boolean = false,
): Promise<{ success: boolean; category?: Category; error?: string }> => {
try {
const { data } = await apiClient.post(
`${V2}/admin/protected/categories`,
{ name, color, is_coming_soon: isComingSoon },
);
return { success: true, category: data.category };
} catch (error: any) {
return {
success: false,
error: error.response?.data?.error || "Erreur",
};
}
};
export const updateCategoryAdmin = async (
id: number,
name: string,
color: string,
isComingSoon: boolean = false,
): Promise<{ success: boolean; category?: Category; error?: string }> => {
try {
const { data } = await apiClient.put(
`${V2}/admin/protected/categories/${id}`,
{ name, color, is_coming_soon: isComingSoon },
);
return { success: true, category: data.category };
} catch (error: any) {
return {
success: false,
error: error.response?.data?.error || "Erreur",
};
}
};
export const deleteCategoryAdmin = async (
id: number,
): Promise<{ success: boolean; error?: string }> => {
try {
await apiClient.delete(`${V2}/admin/protected/categories/${id}`);
return { success: true };
} catch (error: any) {
return {
success: false,
error: error.response?.data?.error || "Erreur",
};
}
};
export const reorderCategoriesAdmin = async (
ids: number[],
): Promise<{ success: boolean; error?: string }> => {
try {
await apiClient.put(`${V2}/admin/protected/categories/reorder`, {
ids,
});
return { success: true };
} catch (error: any) {
return {
success: false,
error: error.response?.data?.error || "Erreur",
};
}
};
// ============================================
// PARAMÈTRES GLOBAUX
// ============================================
export interface PointsTier {
min: number;
max: number; // 0 = illimité
points: number;
}
export interface RewardProductQuantity {
product_id: number;
quantity: number; // quantité individuelle de ce produit (palier de prix catalogue, ex: 1g)
}
export interface RewardCategoryConfig {
category: string;
type: "free_product" | "half_price_product";
all_products: boolean;
quantity: number; // quantité uniforme si all_products = true
products: RewardProductQuantity[]; // produits + quantité individuelle si all_products = false
}
export interface PointsReward {
threshold: number;
description: string;
category_configs: RewardCategoryConfig[];
}
export interface PromotionProductQuantity {
product_id: number;
quantity: number; // quantité individuelle de ce produit (palier de prix catalogue)
}
export interface CategoryPromotionConfig {
category: string;
discount_percent: number; // pourcentage de réduction libre (ex: 10, 20, 33.5)
all_products: boolean;
quantity: number; // quantité uniforme si all_products = true
products: PromotionProductQuantity[]; // produits + quantité individuelle si all_products = false
}
export interface FreeGiftTier {
buy_quantity: number; // quantité à acheter pour déclencher l'offre
free_quantity: number; // quantité offerte du même produit
}
export interface FreeGiftProductQuantity {
product_id: number;
tiers: FreeGiftTier[]; // seuils propres à ce produit
}
export interface CategoryFreeGiftConfig {
category: string;
all_products: boolean;
tiers: FreeGiftTier[]; // seuils uniformes si all_products = true
products: FreeGiftProductQuantity[]; // produits + seuils individuels si all_products = false
}
export interface PointsPool {
key: string;
name: string;
categories: string[];
tiers: PointsTier[];
}
export interface PostalZone {
name: string;
min_amount: number;
codes: string[];
}
export interface DaySchedule {
enabled: boolean;
open_time: string; // "09:00"
close_time: string; // "20:00"
}
export interface DeliverySchedule {
monday: DaySchedule;
tuesday: DaySchedule;
wednesday: DaySchedule;
thursday: DaySchedule;
friday: DaySchedule;
saturday: DaySchedule;
sunday: DaySchedule;
}
export const DEFAULT_POSTAL_ZONES: PostalZone[] = [
{
name: "Zone 30€",
min_amount: 30,
codes: ["44000", "44100", "44200", "44300"],
},
{
name: "Zone 50€",
min_amount: 50,
codes: [
"44400",
"44880",
"44120",
"44230",
"44115",
"44980",
"44470",
"44240",
"44700",
"44800",
"44340",
"44620",
"44830",
],
},
{
name: "Zone 100€",
min_amount: 100,
codes: ["44860", "44220", "44118", "44710", "44690", "44119"],
},
];
export const DEFAULT_DAY: DaySchedule = {
enabled: true,
open_time: "09:00",
close_time: "20:00",
};
export const DEFAULT_DELIVERY_SCHEDULE: DeliverySchedule = {
monday: { ...DEFAULT_DAY },
tuesday: { ...DEFAULT_DAY },
wednesday: { ...DEFAULT_DAY },
thursday: { ...DEFAULT_DAY },
friday: { ...DEFAULT_DAY },
saturday: { ...DEFAULT_DAY },
sunday: { ...DEFAULT_DAY },
};
export interface CategoryRoute {
deliveryman_username: string;
categories: string[];
}
export interface DeliveryModeConfig {
mode: "single" | "category_based";
category_routes: CategoryRoute[];
}
export interface PenaltyTier {
min_cancel: number;
amount: number;
}
export interface AppSettings {
penalties_enabled: boolean;
show_amende_score: boolean;
penalty_tiers: PenaltyTier[];
points_enabled: boolean;
points_pools: PointsPool[];
points_reward?: PointsReward | null;
promotions_enabled: boolean;
promotions: CategoryPromotionConfig[];
free_gifts_enabled: boolean;
free_gifts: CategoryFreeGiftConfig[];
referral_enabled: boolean;
delivery_schedule: DeliverySchedule;
postal_zones: PostalZone[];
crypto_payment_enabled: boolean;
crypto_only: boolean;
nowpayments_api_key: string;
nowpayments_ipn_secret: string;
nowpayments_currencies: string[];
telegram_bot_token: string;
telegram_bot_username: string;
telegram_notifications_enabled: boolean;
telegram_2fa_enabled: boolean;
delivery_mode: DeliveryModeConfig;
shop_name: string;
contact_telegram: string;
admin_color_primary: string;
admin_color_secondary: string;
admin_color_success: string;
admin_color_danger: string;
admin_color_warning: string;
client_color_primary: string;
client_color_secondary: string;
client_color_success: string;
client_color_danger: string;
client_color_warning: string;
client_title_gradient_from: string;
client_title_gradient_to: string;
}
export const getSettings = async (): Promise<{
success: boolean;
settings?: AppSettings;
error?: string;
}> => {
try {
const { data } = await apiClient.get(`${V2}/admin/protected/settings`);
return { success: true, settings: data.settings };
} catch (error: any) {
return {
success: false,
error: error.response?.data?.error || "Erreur",
};
}
};
export interface AppNotification {
command_id: number;
type: string;
message: string;
created_at: string;
read: boolean;
}
export const getAdminNotifications = async (): Promise<{
notifications: AppNotification[];
unread_count: number;
}> => {
const { data } = await apiClient.get(`${V2}/admin/protected/notifications`);
return data;
};
export const markAdminNotificationsRead = async (): Promise<void> => {
await apiClient.post(`${V2}/admin/protected/notifications/read`);
};
// ============================================
// TELEGRAM — ADMIN
// ============================================
export const getAdminTelegramStatus = async (): Promise<{
linked: boolean;
enabled: boolean;
}> => {
try {
const { data } = await apiClient.get(
`${V2}/admin/protected/telegram/status`,
);
return data;
} catch {
return { linked: false, enabled: false };
}
};
export const generateAdminLinkToken = async (): Promise<{
link_url?: string;
error?: string;
}> => {
try {
const { data } = await apiClient.post(
`${V2}/admin/protected/telegram/link-token`,
);
return data;
} catch (e: any) {
return { error: e?.response?.data?.error || "Erreur" };
}
};
export const unlinkAdminTelegram = async (): Promise<void> => {
try {
await apiClient.delete(`${V2}/admin/protected/telegram/unlink`);
} catch {
/* ignore */
}
};
export const updateSettings = async (
settings: AppSettings,
): Promise<{ success: boolean; settings?: AppSettings; error?: string }> => {
try {
const { data } = await apiClient.put(
`${V2}/admin/protected/settings`,
settings,
);
return { success: true, settings: data.settings };
} catch (error: any) {
return {
success: false,
error: error.response?.data?.error || "Erreur",
};
}
};