update: dashboard now calculate domains
This commit is contained in:
@@ -118,6 +118,41 @@ export default function Dashboard() {
|
||||
}))
|
||||
.filter((item) => item.subItems.length > 0);
|
||||
|
||||
const adminFilteredItems = filteredMenuItems.filter(
|
||||
(item) => item.en === "admin",
|
||||
);
|
||||
const nonAdminFilteredItems = filteredMenuItems.filter(
|
||||
(item) => item.en !== "admin",
|
||||
);
|
||||
|
||||
const permissionDomainMap = new Map<string, string>();
|
||||
(profile?.permissions || []).forEach((permission: any) => {
|
||||
if (permission?.page_name) {
|
||||
permissionDomainMap.set(
|
||||
permission.page_name,
|
||||
permission?.domain_fa_name || "سایر حوزه ها",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const groupedFilteredItems = nonAdminFilteredItems.reduce(
|
||||
(acc, item) => {
|
||||
const firstSubItem = item.subItems?.find((sub) =>
|
||||
permissionDomainMap.has(sub.name),
|
||||
);
|
||||
const domainTitle = firstSubItem
|
||||
? permissionDomainMap.get(firstSubItem.name) || "سایر حوزه ها"
|
||||
: "سایر حوزه ها";
|
||||
if (!acc[domainTitle]) {
|
||||
acc[domainTitle] = [];
|
||||
}
|
||||
acc[domainTitle].push(item);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, ItemWithSubItems[]>,
|
||||
);
|
||||
const showDomainGrouping = Object.keys(groupedFilteredItems).length > 1;
|
||||
|
||||
function findSubItemByPath(
|
||||
items: ItemWithSubItems[],
|
||||
path: string,
|
||||
@@ -243,110 +278,322 @@ export default function Dashboard() {
|
||||
}`}
|
||||
>
|
||||
{checkIsMobile()
|
||||
? filteredMenuItems.map(({ fa, icon: Icon, subItems }, index) => {
|
||||
const filteredSubItems = subItems.filter(
|
||||
(item) =>
|
||||
!item.path.includes("$") &&
|
||||
getFaPermissions(item.name).includes(search.trim()),
|
||||
);
|
||||
|
||||
if (filteredSubItems.length === 0) return null;
|
||||
|
||||
return (
|
||||
<section
|
||||
key={index}
|
||||
className="w-full space-y-5 border border-gray-200 dark:border-dark-600 bg-white dark:bg-dark-800 rounded-2xl p-6 shadow-sm"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Icon className="w-6 h-6 text-primary-600 dark:text-primary-400" />
|
||||
<h2 className="text-xl font-bold text-dark-900 dark:text-white">
|
||||
{fa}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 xl:grid-cols-5 gap-4">
|
||||
{filteredSubItems.map((sub, subIndex) => (
|
||||
<motion.button
|
||||
key={subIndex}
|
||||
onClick={() => navigate({ to: sub.path })}
|
||||
whileHover={{ scale: 1.03 }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
className="flex items-center gap-2 cursor-pointer bg-gray-50 dark:bg-dark-700 text-right border border-gray-200 dark:border-dark-600 hover:border-primary-500 dark:hover:border-primary-400 shadow-sm rounded-xl p-3 transition-all duration-200"
|
||||
>
|
||||
<ArrowRightCircleIcon className="w-5 h-5 text-primary-500 dark:text-primary-400" />
|
||||
<span className="text-sm font-medium text-dark-800 dark:text-white">
|
||||
{getFaPermissions(sub.name)}
|
||||
</span>
|
||||
</motion.button>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
})
|
||||
: filteredMenuItems.map(({ fa, icon: Icon, subItems }, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: index * 0.05 }}
|
||||
className="flex-none w-48 backdrop-blur-xl bg-white/20 dark:bg-dark-800/80 rounded-xl shadow-lg border border-white/30 dark:border-dark-700/30 overflow-hidden"
|
||||
>
|
||||
<div className="backdrop-blur-sm bg-white/30 dark:bg-dark-700/30 px-3 py-2 border-b border-white/20 dark:border-dark-600/20">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="p-1.5 rounded-md backdrop-blur-sm bg-primary-500/20 dark:bg-primary-400/20">
|
||||
<Icon className="w-3.5 h-3.5 text-primary-600 dark:text-primary-400" />
|
||||
? (
|
||||
<>
|
||||
{adminFilteredItems.map(({ fa, icon: Icon, subItems }, index) => (
|
||||
<section
|
||||
key={`admin-mobile-${index}`}
|
||||
className="w-full space-y-5 border border-gray-200 dark:border-dark-600 bg-white dark:bg-dark-800 rounded-2xl p-6 shadow-sm"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Icon className="w-6 h-6 text-primary-600 dark:text-primary-400" />
|
||||
<h2 className="text-xl font-bold text-dark-900 dark:text-white">
|
||||
{fa}
|
||||
</h2>
|
||||
</div>
|
||||
<span className="text-xs font-semibold text-dark-800 dark:text-dark-100 truncate">
|
||||
{fa}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-1.5 space-y-0.5">
|
||||
{subItems.map((sub, subIndex) => {
|
||||
const isActive = tabs.some(
|
||||
(tab) =>
|
||||
tab.path === sub.path && activeTabId === tab.id,
|
||||
);
|
||||
return (
|
||||
<motion.div
|
||||
key={subIndex}
|
||||
initial={{ opacity: 0, x: -5 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{
|
||||
delay: index * 0.05 + subIndex * 0.02,
|
||||
}}
|
||||
whileHover={{ x: 1 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
onClick={() => openTab(sub)}
|
||||
className={`flex items-center gap-1.5 px-2 py-1.5 text-xs rounded-md cursor-pointer transition-all duration-200 focus:outline-none ${
|
||||
isActive
|
||||
? "backdrop-blur-sm bg-primary-500/20 dark:bg-primary-400/20 text-primary-700 dark:text-primary-300 "
|
||||
: "hover:backdrop-blur-sm hover:bg-white/30 dark:hover:bg-dark-600/30 border-none"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`w-1.5 h-1.5 rounded-full ${
|
||||
isActive
|
||||
? "bg-primary-600 dark:bg-primary-400"
|
||||
: "bg-dark-400 dark:bg-dark-500"
|
||||
}`}
|
||||
/>
|
||||
<span
|
||||
className={`truncate ${
|
||||
isActive
|
||||
? "text-primary-700 dark:text-white font-medium"
|
||||
: "text-dark-600 dark:text-dark-200/80"
|
||||
}`}
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 xl:grid-cols-5 gap-4">
|
||||
{subItems.map((sub, subIndex) => (
|
||||
<motion.button
|
||||
key={subIndex}
|
||||
onClick={() => navigate({ to: sub.path })}
|
||||
whileHover={{ scale: 1.03 }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
className="flex items-center gap-2 cursor-pointer bg-gray-50 dark:bg-dark-700 text-right border border-gray-200 dark:border-dark-600 hover:border-primary-500 dark:hover:border-primary-400 shadow-sm rounded-xl p-3 transition-all duration-200"
|
||||
>
|
||||
{getFaPermissions(sub.name)}
|
||||
<ArrowRightCircleIcon className="w-5 h-5 text-primary-500 dark:text-primary-400" />
|
||||
<span className="text-sm font-medium text-dark-800 dark:text-white">
|
||||
{getFaPermissions(sub.name)}
|
||||
</span>
|
||||
</motion.button>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
{showDomainGrouping
|
||||
? Object.entries(groupedFilteredItems).map(
|
||||
([domainTitle, domainItems], domainIndex) => (
|
||||
<section
|
||||
key={`domain-mobile-${domainTitle}-${domainIndex}`}
|
||||
className="w-full space-y-3 border border-gray-200 dark:border-dark-600 bg-white dark:bg-dark-800 rounded-2xl p-4 shadow-sm"
|
||||
>
|
||||
<div className="flex items-center gap-2 pb-2 border-b border-gray-200 dark:border-dark-600">
|
||||
<Squares2X2Icon className="w-4 h-4 text-primary-500" />
|
||||
<h2 className="text-sm font-bold text-primary-700 dark:text-primary-300">
|
||||
{domainTitle}
|
||||
</h2>
|
||||
</div>
|
||||
{domainItems.map(({ fa, icon: Icon, subItems }, index) => (
|
||||
<section
|
||||
key={`domain-item-mobile-${domainTitle}-${index}`}
|
||||
className="w-full space-y-3 border border-gray-200 dark:border-dark-600 bg-gray-50 dark:bg-dark-700 rounded-xl p-4"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon className="w-5 h-5 text-primary-600 dark:text-primary-400" />
|
||||
<h3 className="text-base font-bold text-dark-900 dark:text-white">
|
||||
{fa}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
|
||||
{subItems.map((sub, subIndex) => (
|
||||
<motion.button
|
||||
key={subIndex}
|
||||
onClick={() => navigate({ to: sub.path })}
|
||||
whileHover={{ scale: 1.03 }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
className="flex items-center gap-2 cursor-pointer bg-white dark:bg-dark-800 text-right border border-gray-200 dark:border-dark-600 hover:border-primary-500 dark:hover:border-primary-400 shadow-sm rounded-xl p-3 transition-all duration-200"
|
||||
>
|
||||
<ArrowRightCircleIcon className="w-5 h-5 text-primary-500 dark:text-primary-400" />
|
||||
<span className="text-sm font-medium text-dark-800 dark:text-white">
|
||||
{getFaPermissions(sub.name)}
|
||||
</span>
|
||||
</motion.button>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</section>
|
||||
),
|
||||
)
|
||||
: nonAdminFilteredItems.map(({ fa, icon: Icon, subItems }, index) => (
|
||||
<section
|
||||
key={`plain-mobile-${index}`}
|
||||
className="w-full space-y-5 border border-gray-200 dark:border-dark-600 bg-white dark:bg-dark-800 rounded-2xl p-6 shadow-sm"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Icon className="w-6 h-6 text-primary-600 dark:text-primary-400" />
|
||||
<h2 className="text-xl font-bold text-dark-900 dark:text-white">
|
||||
{fa}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 xl:grid-cols-5 gap-4">
|
||||
{subItems.map((sub, subIndex) => (
|
||||
<motion.button
|
||||
key={subIndex}
|
||||
onClick={() => navigate({ to: sub.path })}
|
||||
whileHover={{ scale: 1.03 }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
className="flex items-center gap-2 cursor-pointer bg-gray-50 dark:bg-dark-700 text-right border border-gray-200 dark:border-dark-600 hover:border-primary-500 dark:hover:border-primary-400 shadow-sm rounded-xl p-3 transition-all duration-200"
|
||||
>
|
||||
<ArrowRightCircleIcon className="w-5 h-5 text-primary-500 dark:text-primary-400" />
|
||||
<span className="text-sm font-medium text-dark-800 dark:text-white">
|
||||
{getFaPermissions(sub.name)}
|
||||
</span>
|
||||
</motion.button>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
{adminFilteredItems.map(({ fa, icon: Icon, subItems }, index) => (
|
||||
<motion.div
|
||||
key={`admin-desktop-${index}`}
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: index * 0.05 }}
|
||||
className="flex-none w-48 backdrop-blur-xl bg-white/20 dark:bg-dark-800/80 rounded-xl shadow-lg border border-white/30 dark:border-dark-700/30 overflow-hidden"
|
||||
>
|
||||
<div className="backdrop-blur-sm bg-white/30 dark:bg-dark-700/30 px-3 py-2 border-b border-white/20 dark:border-dark-600/20">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="p-1.5 rounded-md backdrop-blur-sm bg-primary-500/20 dark:bg-primary-400/20">
|
||||
<Icon className="w-3.5 h-3.5 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<span className="text-xs font-semibold text-dark-800 dark:text-dark-100 truncate">
|
||||
{fa}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-1.5 space-y-0.5">
|
||||
{subItems.map((sub, subIndex) => {
|
||||
const isActive = tabs.some(
|
||||
(tab) => tab.path === sub.path && activeTabId === tab.id,
|
||||
);
|
||||
return (
|
||||
<motion.div
|
||||
key={subIndex}
|
||||
initial={{ opacity: 0, x: -5 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: index * 0.05 + subIndex * 0.02 }}
|
||||
whileHover={{ x: 1 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
onClick={() => openTab(sub)}
|
||||
className={`flex items-center gap-1.5 px-2 py-1.5 text-xs rounded-md cursor-pointer transition-all duration-200 focus:outline-none ${
|
||||
isActive
|
||||
? "backdrop-blur-sm bg-primary-500/20 dark:bg-primary-400/20 text-primary-700 dark:text-primary-300 "
|
||||
: "hover:backdrop-blur-sm hover:bg-white/30 dark:hover:bg-dark-600/30 border-none"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`w-1.5 h-1.5 rounded-full ${
|
||||
isActive
|
||||
? "bg-primary-600 dark:bg-primary-400"
|
||||
: "bg-dark-400 dark:bg-dark-500"
|
||||
}`}
|
||||
/>
|
||||
<span
|
||||
className={`truncate ${
|
||||
isActive
|
||||
? "text-primary-700 dark:text-white font-medium"
|
||||
: "text-dark-600 dark:text-dark-200/80"
|
||||
}`}
|
||||
>
|
||||
{getFaPermissions(sub.name)}
|
||||
</span>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
{showDomainGrouping
|
||||
? Object.entries(groupedFilteredItems).map(
|
||||
([domainTitle, domainItems], domainIndex) => (
|
||||
<div
|
||||
key={`domain-desktop-${domainTitle}-${domainIndex}`}
|
||||
className="flex-none w-56 space-y-2"
|
||||
>
|
||||
<div className="flex items-center gap-2 px-1">
|
||||
<Squares2X2Icon className="w-4 h-4 text-primary-500" />
|
||||
<span className="text-xs font-bold text-primary-700 dark:text-primary-300">
|
||||
{domainTitle}
|
||||
</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{domainItems.map(({ fa, icon: Icon, subItems }, index) => (
|
||||
<motion.div
|
||||
key={`domain-item-desktop-${domainTitle}-${index}`}
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: index * 0.05 }}
|
||||
className="w-full backdrop-blur-xl bg-white/20 dark:bg-dark-800/80 rounded-xl shadow-lg border border-white/30 dark:border-dark-700/30 overflow-hidden"
|
||||
>
|
||||
<div className="backdrop-blur-sm bg-white/30 dark:bg-dark-700/30 px-3 py-2 border-b border-white/20 dark:border-dark-600/20">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="p-1.5 rounded-md backdrop-blur-sm bg-primary-500/20 dark:bg-primary-400/20">
|
||||
<Icon className="w-3.5 h-3.5 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<span className="text-xs font-semibold text-dark-800 dark:text-dark-100 truncate">
|
||||
{fa}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-1.5 space-y-0.5">
|
||||
{subItems.map((sub, subIndex) => {
|
||||
const isActive = tabs.some(
|
||||
(tab) =>
|
||||
tab.path === sub.path &&
|
||||
activeTabId === tab.id,
|
||||
);
|
||||
return (
|
||||
<motion.div
|
||||
key={subIndex}
|
||||
initial={{ opacity: 0, x: -5 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{
|
||||
delay: index * 0.05 + subIndex * 0.02,
|
||||
}}
|
||||
whileHover={{ x: 1 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
onClick={() => openTab(sub)}
|
||||
className={`flex items-center gap-1.5 px-2 py-1.5 text-xs rounded-md cursor-pointer transition-all duration-200 focus:outline-none ${
|
||||
isActive
|
||||
? "backdrop-blur-sm bg-primary-500/20 dark:bg-primary-400/20 text-primary-700 dark:text-primary-300 "
|
||||
: "hover:backdrop-blur-sm hover:bg-white/30 dark:hover:bg-dark-600/30 border-none"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`w-1.5 h-1.5 rounded-full ${
|
||||
isActive
|
||||
? "bg-primary-600 dark:bg-primary-400"
|
||||
: "bg-dark-400 dark:bg-dark-500"
|
||||
}`}
|
||||
/>
|
||||
<span
|
||||
className={`truncate ${
|
||||
isActive
|
||||
? "text-primary-700 dark:text-white font-medium"
|
||||
: "text-dark-600 dark:text-dark-200/80"
|
||||
}`}
|
||||
>
|
||||
{getFaPermissions(sub.name)}
|
||||
</span>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
)
|
||||
: nonAdminFilteredItems.map(({ fa, icon: Icon, subItems }, index) => (
|
||||
<motion.div
|
||||
key={`plain-desktop-${index}`}
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: index * 0.05 }}
|
||||
className="flex-none w-48 backdrop-blur-xl bg-white/20 dark:bg-dark-800/80 rounded-xl shadow-lg border border-white/30 dark:border-dark-700/30 overflow-hidden"
|
||||
>
|
||||
<div className="backdrop-blur-sm bg-white/30 dark:bg-dark-700/30 px-3 py-2 border-b border-white/20 dark:border-dark-600/20">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="p-1.5 rounded-md backdrop-blur-sm bg-primary-500/20 dark:bg-primary-400/20">
|
||||
<Icon className="w-3.5 h-3.5 text-primary-600 dark:text-primary-400" />
|
||||
</div>
|
||||
<span className="text-xs font-semibold text-dark-800 dark:text-dark-100 truncate">
|
||||
{fa}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-1.5 space-y-0.5">
|
||||
{subItems.map((sub, subIndex) => {
|
||||
const isActive = tabs.some(
|
||||
(tab) =>
|
||||
tab.path === sub.path && activeTabId === tab.id,
|
||||
);
|
||||
return (
|
||||
<motion.div
|
||||
key={subIndex}
|
||||
initial={{ opacity: 0, x: -5 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{
|
||||
delay: index * 0.05 + subIndex * 0.02,
|
||||
}}
|
||||
whileHover={{ x: 1 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
onClick={() => openTab(sub)}
|
||||
className={`flex items-center gap-1.5 px-2 py-1.5 text-xs rounded-md cursor-pointer transition-all duration-200 focus:outline-none ${
|
||||
isActive
|
||||
? "backdrop-blur-sm bg-primary-500/20 dark:bg-primary-400/20 text-primary-700 dark:text-primary-300 "
|
||||
: "hover:backdrop-blur-sm hover:bg-white/30 dark:hover:bg-dark-600/30 border-none"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`w-1.5 h-1.5 rounded-full ${
|
||||
isActive
|
||||
? "bg-primary-600 dark:bg-primary-400"
|
||||
: "bg-dark-400 dark:bg-dark-500"
|
||||
}`}
|
||||
/>
|
||||
<span
|
||||
className={`truncate ${
|
||||
isActive
|
||||
? "text-primary-700 dark:text-white font-medium"
|
||||
: "text-dark-600 dark:text-dark-200/80"
|
||||
}`}
|
||||
>
|
||||
{getFaPermissions(sub.name)}
|
||||
</span>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user