Add Oudelaa dashboard API integration
فشلت بعض الفحوصات
Deploy To Ghaymah / deploy (push) Has been cancelled

هذا الالتزام موجود في:
boutmoun123
2026-05-25 20:36:52 +03:00
الأصل 367fce6557
التزام 8863f61d00
90 ملفات معدلة مع 16694 إضافات و1 حذوفات

عرض الملف

@@ -0,0 +1,954 @@
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import { NoPermissionState } from "@/components/auth/no-permission-state";
import { useSuperAdminSession } from "@/components/auth/session-context";
import { PageHeader } from "@/components/dashboard/page-header";
import { PaginationControls } from "@/components/dashboard/pagination-controls";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { EmptyState } from "@/components/ui/empty-state";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Textarea } from "@/components/ui/textarea";
import { useToast } from "@/components/ui/toast";
import { getItems, getPagination } from "@/lib/api/core";
import { listPlatformAdmins } from "@/lib/api/admin-users";
import {
createMarketplaceInstrumentForSuperAdmin,
createMarketplaceListingForSuperAdmin,
createMarketplaceRepairShopForSuperAdmin,
deleteModerationListing,
deleteModerationRepairShop,
getMarketplaceHome,
listModerationListings,
listModerationRepairShops,
updateMarketplaceShopProfileForSuperAdmin,
updateModerationListingStatus,
updateModerationRepairShopStatus,
} from "@/lib/api/marketplace";
import { formatCurrency } from "@/lib/format";
import { SUPERADMIN_PERMISSIONS, hasPermission } from "@/lib/permissions";
import type {
ApiUser,
MarketplaceHomeResponse,
MarketplaceListing,
MarketplaceRepairShop,
MarketplaceRepairShopResponse,
MarketplaceResponse,
UsersResponse,
} from "@/types/api";
type CreateMode = "listing" | "instrument" | "repair_shop" | "shop_profile";
type ListingFormState = {
title: string;
description: string;
price: string;
currency: string;
quantity: string;
listingCategory: string;
condition: string;
instrumentType: string;
isActive: boolean;
imageFiles: File[];
};
type RepairShopFormState = {
name: string;
description: string;
services: string;
phone: string;
whatsapp: string;
location: string;
latitude: string;
longitude: string;
isActive: boolean;
imageFiles: File[];
};
type ShopProfileFormState = {
shopName: string;
shopDescription: string;
shopLocation: string;
shopLatitude: string;
shopLongitude: string;
shopImageFiles: File[];
};
const defaultListingForm: ListingFormState = {
title: "",
description: "",
price: "",
currency: "SAR",
quantity: "1",
listingCategory: "other",
condition: "used",
instrumentType: "",
isActive: true,
imageFiles: [],
};
const defaultRepairShopForm: RepairShopFormState = {
name: "",
description: "",
services: "",
phone: "",
whatsapp: "",
location: "",
latitude: "",
longitude: "",
isActive: true,
imageFiles: [],
};
const defaultShopProfileForm: ShopProfileFormState = {
shopName: "",
shopDescription: "",
shopLocation: "",
shopLatitude: "",
shopLongitude: "",
shopImageFiles: [],
};
function toNumber(value: string) {
return Number(value.trim());
}
function toOptionalNumber(value: string) {
const trimmed = value.trim();
return trimmed ? Number(trimmed) : undefined;
}
function parseCommaSeparated(value: string) {
return value
.split(",")
.map((entry) => entry.trim())
.filter(Boolean);
}
function getAdminLabel(admin: ApiUser) {
return admin.shopName || admin.stageName || admin.name || admin.username || admin.email;
}
function promptReason(actionLabel: string) {
if (typeof window === "undefined") {
return "";
}
const result = window.prompt(`Optional reason for ${actionLabel}`, "");
if (result === null) {
return null;
}
return result.trim();
}
export default function MarketplacePage() {
const { permissions } = useSuperAdminSession();
const [query, setQuery] = useState("");
const [listingCategory, setListingCategory] = useState("all");
const [activeFilter, setActiveFilter] = useState("all");
const [listingsPage, setListingsPage] = useState(1);
const [shopsPage, setShopsPage] = useState(1);
const [home, setHome] = useState<MarketplaceHomeResponse | null>(null);
const [listingsResponse, setListingsResponse] = useState<MarketplaceResponse | null>(null);
const [shopsResponse, setShopsResponse] = useState<MarketplaceRepairShopResponse | null>(null);
const [adminsResponse, setAdminsResponse] = useState<UsersResponse | null>(null);
const [ownerAdminId, setOwnerAdminId] = useState("");
const [createMode, setCreateMode] = useState<CreateMode>("listing");
const [listingForm, setListingForm] = useState<ListingFormState>(defaultListingForm);
const [repairShopForm, setRepairShopForm] = useState<RepairShopFormState>(defaultRepairShopForm);
const [shopProfileForm, setShopProfileForm] = useState<ShopProfileFormState>(defaultShopProfileForm);
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const { toast } = useToast();
const filtersRef = useRef({ query, listingCategory, activeFilter });
const canManageMarketplace = hasPermission(
permissions,
SUPERADMIN_PERMISSIONS.MARKETPLACE_MANAGE,
);
const canReadUsers = hasPermission(permissions, SUPERADMIN_PERMISSIONS.USERS_READ);
filtersRef.current = { query, listingCategory, activeFilter };
const loadMarketplace = useCallback(async () => {
if (!canManageMarketplace) {
setLoading(false);
return;
}
setLoading(true);
try {
const {
query: currentQuery,
listingCategory: currentListingCategory,
activeFilter: currentActiveFilter,
} = filtersRef.current;
const isActive =
currentActiveFilter === "all" ? undefined : currentActiveFilter === "active" ? true : false;
const [homeResponse, listings, shops] = await Promise.all([
getMarketplaceHome(),
listModerationListings({
page: listingsPage,
limit: 10,
q: currentQuery || undefined,
listingCategory: currentListingCategory === "all" ? undefined : currentListingCategory,
isActive,
sortBy: "createdAt",
sortOrder: "desc",
}),
listModerationRepairShops({
page: shopsPage,
limit: 10,
q: currentQuery || undefined,
isActive,
sortBy: "createdAt",
sortOrder: "desc",
}),
]);
setHome(homeResponse);
setListingsResponse(listings);
setShopsResponse(shops);
} catch (error) {
toast({
title: "تعذر تحميل بيانات الماركت",
description: String(error),
variant: "danger",
});
} finally {
setLoading(false);
}
}, [canManageMarketplace, listingsPage, shopsPage, toast]);
const loadAdmins = useCallback(async () => {
if (!canReadUsers) {
setAdminsResponse({ items: [], data: [] });
return;
}
try {
const response = await listPlatformAdmins(1, 100);
setAdminsResponse(response);
const admins = getItems(response) as ApiUser[];
setOwnerAdminId((current) => current || admins[0]?._id || "");
} catch (error) {
toast({
title: "تعذر تحميل قائمة الأدمنز",
description: String(error),
variant: "danger",
});
}
}, [canReadUsers, toast]);
useEffect(() => {
void loadMarketplace();
}, [loadMarketplace]);
useEffect(() => {
void loadAdmins();
}, [loadAdmins]);
const admins = getItems(adminsResponse) as ApiUser[];
const listings = getItems(listingsResponse) as MarketplaceListing[];
const shops = getItems(shopsResponse) as MarketplaceRepairShop[];
const applyFilters = () => {
const shouldReloadDirectly = listingsPage === 1 && shopsPage === 1;
setListingsPage(1);
setShopsPage(1);
if (shouldReloadDirectly) {
void loadMarketplace();
}
};
const handleCreate = async () => {
if (!canReadUsers) {
toast({
title: "Missing permission",
description: "Choosing the owner admin requires users.read.",
variant: "danger",
});
return;
}
if (!ownerAdminId) {
toast({
title: "اختر الأدمن المالك أولًا",
description: "السوبر أدمن ينشئ العنصر على حساب أدمن محدد.",
variant: "danger",
});
return;
}
setSubmitting(true);
try {
if (createMode === "shop_profile") {
await updateMarketplaceShopProfileForSuperAdmin(ownerAdminId, {
shopName: shopProfileForm.shopName,
shopDescription: shopProfileForm.shopDescription,
shopLocation: shopProfileForm.shopLocation,
shopLatitude: toOptionalNumber(shopProfileForm.shopLatitude),
shopLongitude: toOptionalNumber(shopProfileForm.shopLongitude),
shopImageFiles: shopProfileForm.shopImageFiles,
});
setShopProfileForm(defaultShopProfileForm);
toast({ title: "تم تحديث ملف المتجر", description: "أصبح الأدمن جاهزًا لامتلاك عناصر ماركت." });
} else if (createMode === "repair_shop") {
await createMarketplaceRepairShopForSuperAdmin(ownerAdminId, {
name: repairShopForm.name,
description: repairShopForm.description,
services: parseCommaSeparated(repairShopForm.services),
phone: repairShopForm.phone,
whatsapp: repairShopForm.whatsapp,
location: repairShopForm.location,
latitude: toOptionalNumber(repairShopForm.latitude),
longitude: toOptionalNumber(repairShopForm.longitude),
isActive: repairShopForm.isActive,
imageFiles: repairShopForm.imageFiles,
});
setRepairShopForm(defaultRepairShopForm);
toast({ title: "تم إنشاء متجر الصيانة", description: "أضيف المتجر بنجاح تحت الأدمن المحدد." });
} else {
const payload = {
title: listingForm.title,
description: listingForm.description,
price: toNumber(listingForm.price),
currency: listingForm.currency,
quantity: toNumber(listingForm.quantity),
condition: listingForm.condition,
instrumentType: listingForm.instrumentType,
isActive: listingForm.isActive,
imageFiles: listingForm.imageFiles,
...(createMode === "instrument"
? { listingCategory: "musical_instrument" }
: { listingCategory: listingForm.listingCategory }),
};
if (createMode === "instrument") {
await createMarketplaceInstrumentForSuperAdmin(ownerAdminId, payload);
toast({ title: "تم إنشاء الآلة الموسيقية", description: "أضيفت الآلة تحت الأدمن المحدد." });
} else {
await createMarketplaceListingForSuperAdmin(ownerAdminId, payload);
toast({ title: "تم إنشاء العنصر العام", description: "أضيف العنصر إلى الماركت بنجاح." });
}
setListingForm({
...defaultListingForm,
listingCategory: createMode === "listing" ? "other" : "musical_instrument",
});
}
await loadMarketplace();
} catch (error) {
toast({
title: "فشل تنفيذ العملية",
description: String(error),
variant: "danger",
});
} finally {
setSubmitting(false);
}
};
if (!canManageMarketplace) {
return (
<div className="space-y-5 pb-8">
<PageHeader
title="Marketplace management"
subtitle="Central marketplace moderation for listings, repair shops, and owner-managed creation flows."
/>
<NoPermissionState description="This page needs the marketplace.manage permission." />
</div>
);
}
return (
<div className="space-y-5 pb-8">
<PageHeader
title="إدارة الماركت"
subtitle="إشراف مركزي على عناصر البيع والمتاجر وورش الصيانة، مع قدرة مباشرة للسوبر أدمن على الإنشاء باسم أي أدمن."
actions={
<Button
variant="outline"
onClick={applyFilters}
>
تحديث البيانات
</Button>
}
/>
<section className="grid gap-4 md:grid-cols-3">
<Card>
<CardHeader>
<CardTitle>عناصر السوق العامة</CardTitle>
</CardHeader>
<CardContent className="text-3xl font-bold text-foreground">
{home?.summary.activeListings ?? 0}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>الآلات الموسيقية</CardTitle>
</CardHeader>
<CardContent className="text-3xl font-bold text-foreground">
{home?.summary.activeMusicalInstruments ?? 0}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>ورش ومتاجر الصيانة</CardTitle>
</CardHeader>
<CardContent className="text-3xl font-bold text-foreground">
{home?.summary.activeRepairShops ?? 0}
</CardContent>
</Card>
</section>
<Card>
<CardHeader>
<CardTitle>إنشاء باسم الأدمن</CardTitle>
<CardDescription>
اختر الأدمن المالك ثم أنشئ متجرًا أو آلة أو عنصرًا عامًا أو حدّث ملف متجره. كل الرفع يتم عبر
FormData للصور.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-3 md:grid-cols-2">
<div className="space-y-2">
<p className="text-sm font-medium text-foreground">الأدمن المالك</p>
<Select value={ownerAdminId} onValueChange={setOwnerAdminId}>
<SelectTrigger>
<SelectValue placeholder="اختر الأدمن" />
</SelectTrigger>
<SelectContent>
{admins.map((admin) => (
<SelectItem key={admin._id} value={admin._id}>
{getAdminLabel(admin)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<p className="text-sm font-medium text-foreground">نوع العملية</p>
<Select
value={createMode}
onValueChange={(value) => setCreateMode(value as CreateMode)}
>
<SelectTrigger>
<SelectValue placeholder="اختر نوع العملية" />
</SelectTrigger>
<SelectContent>
<SelectItem value="listing">عنصر ماركت عام</SelectItem>
<SelectItem value="instrument">آلة موسيقية</SelectItem>
<SelectItem value="repair_shop">متجر/ورشة صيانة</SelectItem>
<SelectItem value="shop_profile">ملف المتجر</SelectItem>
</SelectContent>
</Select>
</div>
</div>
{(createMode === "listing" || createMode === "instrument") && (
<div className="grid gap-3 md:grid-cols-2">
<Input
value={listingForm.title}
onChange={(event) =>
setListingForm((current) => ({ ...current, title: event.target.value }))
}
placeholder="العنوان"
/>
<Input
value={listingForm.instrumentType}
onChange={(event) =>
setListingForm((current) => ({ ...current, instrumentType: event.target.value }))
}
placeholder="نوع الآلة أو المنتج"
/>
<Input
value={listingForm.price}
onChange={(event) =>
setListingForm((current) => ({ ...current, price: event.target.value }))
}
placeholder="السعر"
inputMode="decimal"
/>
<Input
value={listingForm.quantity}
onChange={(event) =>
setListingForm((current) => ({ ...current, quantity: event.target.value }))
}
placeholder="الكمية"
inputMode="numeric"
/>
<Input
value={listingForm.currency}
onChange={(event) =>
setListingForm((current) => ({ ...current, currency: event.target.value.toUpperCase() }))
}
placeholder="العملة"
/>
<Select
value={listingForm.condition}
onValueChange={(value) =>
setListingForm((current) => ({ ...current, condition: value }))
}
>
<SelectTrigger>
<SelectValue placeholder="الحالة" />
</SelectTrigger>
<SelectContent>
<SelectItem value="new">new</SelectItem>
<SelectItem value="used">used</SelectItem>
<SelectItem value="like_new">like_new</SelectItem>
<SelectItem value="refurbished">refurbished</SelectItem>
</SelectContent>
</Select>
{createMode === "listing" && (
<Select
value={listingForm.listingCategory}
onValueChange={(value) =>
setListingForm((current) => ({ ...current, listingCategory: value }))
}
>
<SelectTrigger>
<SelectValue placeholder="فئة العنصر" />
</SelectTrigger>
<SelectContent>
<SelectItem value="accessory">accessory</SelectItem>
<SelectItem value="audio_gear">audio_gear</SelectItem>
<SelectItem value="sheet_music">sheet_music</SelectItem>
<SelectItem value="other">other</SelectItem>
<SelectItem value="musical_instrument">musical_instrument</SelectItem>
</SelectContent>
</Select>
)}
<label className="flex items-center justify-between rounded-lg border border-input bg-background/40 px-3 py-2 text-sm text-foreground">
<span>نشط عند الإنشاء</span>
<Switch
checked={listingForm.isActive}
onCheckedChange={(value) =>
setListingForm((current) => ({ ...current, isActive: value }))
}
/>
</label>
<div className="md:col-span-2">
<Textarea
value={listingForm.description}
onChange={(event) =>
setListingForm((current) => ({ ...current, description: event.target.value }))
}
placeholder="الوصف"
/>
</div>
<div className="space-y-2 md:col-span-2">
<p className="text-sm font-medium text-foreground">صور العنصر</p>
<Input
type="file"
multiple
accept="image/*"
onChange={(event) =>
setListingForm((current) => ({
...current,
imageFiles: Array.from(event.target.files ?? []),
}))
}
/>
<p className="text-xs text-muted-foreground">
الملفات المختارة: {listingForm.imageFiles.length}
</p>
</div>
</div>
)}
{createMode === "repair_shop" && (
<div className="grid gap-3 md:grid-cols-2">
<Input
value={repairShopForm.name}
onChange={(event) =>
setRepairShopForm((current) => ({ ...current, name: event.target.value }))
}
placeholder="اسم المتجر أو الورشة"
/>
<Input
value={repairShopForm.location}
onChange={(event) =>
setRepairShopForm((current) => ({ ...current, location: event.target.value }))
}
placeholder="الموقع"
/>
<Input
value={repairShopForm.phone}
onChange={(event) =>
setRepairShopForm((current) => ({ ...current, phone: event.target.value }))
}
placeholder="الهاتف"
/>
<Input
value={repairShopForm.whatsapp}
onChange={(event) =>
setRepairShopForm((current) => ({ ...current, whatsapp: event.target.value }))
}
placeholder="واتساب"
/>
<Input
value={repairShopForm.latitude}
onChange={(event) =>
setRepairShopForm((current) => ({ ...current, latitude: event.target.value }))
}
placeholder="خط العرض"
inputMode="decimal"
/>
<Input
value={repairShopForm.longitude}
onChange={(event) =>
setRepairShopForm((current) => ({ ...current, longitude: event.target.value }))
}
placeholder="خط الطول"
inputMode="decimal"
/>
<div className="md:col-span-2">
<Input
value={repairShopForm.services}
onChange={(event) =>
setRepairShopForm((current) => ({ ...current, services: event.target.value }))
}
placeholder="الخدمات مفصولة بفواصل، مثال: Oud Repair, Violin Setup"
/>
</div>
<div className="md:col-span-2">
<Textarea
value={repairShopForm.description}
onChange={(event) =>
setRepairShopForm((current) => ({ ...current, description: event.target.value }))
}
placeholder="وصف المتجر"
/>
</div>
<label className="flex items-center justify-between rounded-lg border border-input bg-background/40 px-3 py-2 text-sm text-foreground">
<span>نشط عند الإنشاء</span>
<Switch
checked={repairShopForm.isActive}
onCheckedChange={(value) =>
setRepairShopForm((current) => ({ ...current, isActive: value }))
}
/>
</label>
<div className="space-y-2 md:col-span-2">
<p className="text-sm font-medium text-foreground">صور المتجر</p>
<Input
type="file"
multiple
accept="image/*"
onChange={(event) =>
setRepairShopForm((current) => ({
...current,
imageFiles: Array.from(event.target.files ?? []),
}))
}
/>
<p className="text-xs text-muted-foreground">
الملفات المختارة: {repairShopForm.imageFiles.length}
</p>
</div>
</div>
)}
{createMode === "shop_profile" && (
<div className="grid gap-3 md:grid-cols-2">
<Input
value={shopProfileForm.shopName}
onChange={(event) =>
setShopProfileForm((current) => ({ ...current, shopName: event.target.value }))
}
placeholder="اسم المتجر"
/>
<Input
value={shopProfileForm.shopLocation}
onChange={(event) =>
setShopProfileForm((current) => ({ ...current, shopLocation: event.target.value }))
}
placeholder="موقع المتجر"
/>
<Input
value={shopProfileForm.shopLatitude}
onChange={(event) =>
setShopProfileForm((current) => ({ ...current, shopLatitude: event.target.value }))
}
placeholder="خط العرض"
inputMode="decimal"
/>
<Input
value={shopProfileForm.shopLongitude}
onChange={(event) =>
setShopProfileForm((current) => ({ ...current, shopLongitude: event.target.value }))
}
placeholder="خط الطول"
inputMode="decimal"
/>
<div className="md:col-span-2">
<Textarea
value={shopProfileForm.shopDescription}
onChange={(event) =>
setShopProfileForm((current) => ({
...current,
shopDescription: event.target.value,
}))
}
placeholder="وصف المتجر"
/>
</div>
<div className="space-y-2 md:col-span-2">
<p className="text-sm font-medium text-foreground">صور ملف المتجر</p>
<Input
type="file"
multiple
accept="image/*"
onChange={(event) =>
setShopProfileForm((current) => ({
...current,
shopImageFiles: Array.from(event.target.files ?? []),
}))
}
/>
<p className="text-xs text-muted-foreground">
الملفات المختارة: {shopProfileForm.shopImageFiles.length}
</p>
</div>
</div>
)}
<div className="flex flex-wrap items-center gap-3">
<Button onClick={() => void handleCreate()} disabled={submitting}>
{submitting ? "جارٍ التنفيذ..." : "تنفيذ العملية"}
</Button>
<p className="text-sm text-muted-foreground">
ملاحظة: متجر الصيانة يبقى واحدًا فقط لكل أدمن، بينما العناصر يمكن إضافتها بلا مشكلة بعد تجهيز
ملف المتجر.
</p>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>فلاتر الإشراف</CardTitle>
</CardHeader>
<CardContent className="grid gap-3 md:grid-cols-3">
<Input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="ابحث بالاسم أو المتجر"
/>
<Select value={listingCategory} onValueChange={setListingCategory}>
<SelectTrigger>
<SelectValue placeholder="الفئة" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">كل الفئات</SelectItem>
<SelectItem value="musical_instrument">musical_instrument</SelectItem>
<SelectItem value="accessory">accessory</SelectItem>
<SelectItem value="audio_gear">audio_gear</SelectItem>
<SelectItem value="sheet_music">sheet_music</SelectItem>
<SelectItem value="other">other</SelectItem>
</SelectContent>
</Select>
<Select value={activeFilter} onValueChange={setActiveFilter}>
<SelectTrigger>
<SelectValue placeholder="الحالة" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">كل الحالات</SelectItem>
<SelectItem value="active">نشط</SelectItem>
<SelectItem value="inactive">موقوف</SelectItem>
</SelectContent>
</Select>
</CardContent>
</Card>
<section className="grid gap-4 xl:grid-cols-12">
<Card className="xl:col-span-7">
<CardHeader>
<CardTitle>عناصر البيع</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{!listings.length && !loading ? (
<EmptyState
title="لا توجد عناصر"
description="لم يرجع الخادم أي عناصر بهذه الفلاتر."
/>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead>العنوان</TableHead>
<TableHead>الفئة</TableHead>
<TableHead>المتجر</TableHead>
<TableHead>السعر</TableHead>
<TableHead>الحالة</TableHead>
<TableHead>إجراءات</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{listings.map((listing) => (
<TableRow key={listing._id}>
<TableCell>{listing.title}</TableCell>
<TableCell>{listing.listingCategory ?? "-"}</TableCell>
<TableCell>{listing.storeName ?? "-"}</TableCell>
<TableCell>{formatCurrency(listing.price, listing.currency ?? "SAR")}</TableCell>
<TableCell>
<Badge variant={listing.isActive ? "success" : "danger"}>
{listing.isActive ? "نشط" : "موقوف"}
</Badge>
</TableCell>
<TableCell className="flex flex-wrap gap-2">
<Button
size="sm"
variant="outline"
onClick={async () => {
const reason = promptReason(
listing.isActive ? "listing deactivation" : "listing activation",
);
if (reason === null) return;
try {
await updateModerationListingStatus(
listing._id,
!listing.isActive,
reason || undefined,
);
await loadMarketplace();
} catch (error) {
toast({
title: "فشل تحديث العنصر",
description: String(error),
variant: "danger",
});
}
}}
>
{listing.isActive ? "إيقاف" : "تفعيل"}
</Button>
<Button
size="sm"
variant="danger"
onClick={async () => {
try {
await deleteModerationListing(listing._id);
await loadMarketplace();
} catch (error) {
toast({
title: "فشل حذف العنصر",
description: String(error),
variant: "danger",
});
}
}}
>
حذف
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
<PaginationControls
pagination={getPagination(listingsResponse)}
loading={loading}
onPageChange={setListingsPage}
/>
</CardContent>
</Card>
<Card className="xl:col-span-5">
<CardHeader>
<CardTitle>المتاجر وورش الصيانة</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{!shops.length && !loading ? (
<EmptyState
title="لا توجد متاجر"
description="لا توجد نتائج مطابقة حاليًا."
/>
) : (
<div className="space-y-3">
{shops.map((shop) => (
<div key={shop._id} className="rounded-xl border border-border/70 bg-secondary/20 p-4">
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-sm font-medium text-foreground">{shop.name}</p>
<p className="mt-1 text-xs text-muted-foreground">{shop.location ?? "-"}</p>
</div>
<Badge variant={shop.isActive ? "success" : "danger"}>
{shop.isActive ? "نشط" : "موقوف"}
</Badge>
</div>
<p className="mt-3 text-sm text-muted-foreground">{shop.description ?? "-"}</p>
<div className="mt-3 flex flex-wrap gap-2">
<Button
size="sm"
variant="outline"
onClick={async () => {
const reason = promptReason(
shop.isActive ? "repair shop deactivation" : "repair shop activation",
);
if (reason === null) return;
try {
await updateModerationRepairShopStatus(
shop._id,
!shop.isActive,
reason || undefined,
);
await loadMarketplace();
} catch (error) {
toast({
title: "فشل تحديث المتجر",
description: String(error),
variant: "danger",
});
}
}}
>
{shop.isActive ? "إيقاف" : "تفعيل"}
</Button>
<Button
size="sm"
variant="danger"
onClick={async () => {
try {
await deleteModerationRepairShop(shop._id);
await loadMarketplace();
} catch (error) {
toast({
title: "فشل حذف المتجر",
description: String(error),
variant: "danger",
});
}
}}
>
حذف
</Button>
</div>
</div>
))}
</div>
)}
<PaginationControls
pagination={getPagination(shopsResponse)}
loading={loading}
onPageChange={setShopsPage}
/>
</CardContent>
</Card>
</section>
</div>
);
}