102 lines
3.6 KiB
TypeScript
102 lines
3.6 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
import Link from "next/link";
|
||
import { AdminPageHeader, cardClass } from "@/components/admin/controls";
|
||
import { commercialGet } from "../adapters/http";
|
||
import {
|
||
COMMERCIAL_ADMIN_RESOURCES,
|
||
type CommercialAdminResourceDef,
|
||
} from "../admin/resources";
|
||
import { humanizeCommercialCode } from "../presentation";
|
||
|
||
type CatalogEntry = {
|
||
kind: string;
|
||
path?: string;
|
||
display_name?: string;
|
||
description?: string | null;
|
||
href?: string;
|
||
generic_href?: string;
|
||
};
|
||
|
||
/**
|
||
* Admin hub — prefers live GET /api/v1/commercial/catalog.
|
||
* Static resource defs are path bindings only (no row data); used as label fallback.
|
||
*/
|
||
export function AdminCommercialHub() {
|
||
const [entries, setEntries] = useState<
|
||
Array<{ key: string; label: string; description: string; href: string; listPath: string }>
|
||
>([]);
|
||
|
||
useEffect(() => {
|
||
void (async () => {
|
||
const res = await commercialGet<{ registries?: CatalogEntry[] }>("/api/v1/commercial/catalog");
|
||
if (res.ok && Array.isArray(res.data.registries) && res.data.registries.length) {
|
||
const byPath = new Map(COMMERCIAL_ADMIN_RESOURCES.map((r) => [r.listPath, r]));
|
||
setEntries(
|
||
res.data.registries.map((r) => {
|
||
const path = r.href || `/api/v1/commercial/${r.path || r.kind}`;
|
||
const binding = byPath.get(path) || byPath.get(`/api/v1/commercial/${r.path}`);
|
||
const key = binding?.key || r.path || r.kind;
|
||
return {
|
||
key,
|
||
label: humanizeCommercialCode(r.kind, r.display_name || binding?.label),
|
||
description: `مدیریت و انتشار ${humanizeCommercialCode(r.kind, r.display_name || binding?.label)}`,
|
||
href: `/admin/commercial/${key}`,
|
||
listPath: path,
|
||
};
|
||
})
|
||
);
|
||
return;
|
||
}
|
||
setEntries(
|
||
COMMERCIAL_ADMIN_RESOURCES.map((r: CommercialAdminResourceDef) => ({
|
||
key: r.key,
|
||
label: r.label,
|
||
description: `مدیریت و انتشار ${r.label}`,
|
||
href: `/admin/commercial/${r.key}`,
|
||
listPath: r.listPath,
|
||
}))
|
||
);
|
||
})();
|
||
}, []);
|
||
|
||
return (
|
||
<div className="space-y-8">
|
||
<AdminPageHeader
|
||
title="مدیریت تجاری"
|
||
subtitle="محصولات، بستهها، قیمتها و اشتراکها را از یک فضای ساده مدیریت و منتشر کنید."
|
||
/>
|
||
|
||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||
{entries.map((r) => (
|
||
<Link
|
||
key={r.key}
|
||
href={r.href}
|
||
className={`${cardClass} transition-shadow hover:shadow-md`}
|
||
>
|
||
<p className="font-semibold text-secondary">{r.label}</p>
|
||
<p className="mt-1 text-xs text-gray-500">{r.description}</p>
|
||
</Link>
|
||
))}
|
||
</div>
|
||
|
||
<section>
|
||
<h2 className="mb-3 text-sm font-bold text-secondary">مشتریان و فعالسازی</h2>
|
||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||
{COMMERCIAL_ADMIN_RESOURCES.filter((r) => r.group === "tenant").map((r) => (
|
||
<Link
|
||
key={r.key}
|
||
href={`/admin/commercial/${r.key}`}
|
||
className={`${cardClass} transition-shadow hover:shadow-md`}
|
||
>
|
||
<p className="font-semibold text-secondary">{r.label}</p>
|
||
<p className="mt-1 text-xs text-gray-500">مدیریت و پیگیری {r.label}</p>
|
||
</Link>
|
||
))}
|
||
</div>
|
||
</section>
|
||
</div>
|
||
);
|
||
}
|