Compare commits

...

71 Commits

Author SHA1 Message Date
4138ea652a version changed to 02.79 2026-02-25 15:49:02 +03:30
313364a304 add: inquiry tracking code 2026-02-25 11:43:16 +03:30
472d0e66cc feat: check tracking code component 2026-02-25 11:43:04 +03:30
5572ed070e feat: units inventory 2026-02-25 10:59:40 +03:30
11bb774b9c add: domain key 2026-02-25 10:09:08 +03:30
88ec9ed3b6 feat: value field function 2026-02-25 09:33:59 +03:30
ff6089bfb0 update: dashboard now calculate domains 2026-02-25 08:59:06 +03:30
25c8171f87 version changed to 02.78 2026-02-24 17:06:08 +03:30
9ab4977b50 add: domain on paths 2026-02-24 17:05:59 +03:30
defa1e3e08 update: placement of elements 2026-02-24 17:05:46 +03:30
e6ff022335 feat: domains in menu and sidebar 2026-02-24 15:36:18 +03:30
043ad77b83 fix: icon issues 2026-02-24 15:35:23 +03:30
0743d6c86f add: domain in access and page 2026-02-24 12:12:40 +03:30
2829f68831 version changed to 02.77 2026-02-23 16:33:40 +03:30
d7d4848f2a feat: domains management 2026-02-23 16:33:34 +03:30
b8ae9757d4 feat: admin category 2026-02-23 15:32:16 +03:30
e7f4c55bfe refactor: organized components based on domain 2026-02-23 14:38:30 +03:30
1f763a33ac refactor: organized pages based on domain 2026-02-23 12:30:27 +03:30
32246fd0cc version changed to 02.76 2026-02-22 14:05:09 +03:30
78c7bb22e3 add: livestock active state filter 2026-02-22 08:47:24 +03:30
a3c9163787 version changed to 02.75 2026-02-21 11:29:45 +03:30
741385e282 add: manual entering sms receiver user 2026-02-21 11:29:34 +03:30
9d87907f81 version changed to 02.74 2026-02-21 10:28:59 +03:30
561ecafe0f version changed to 02.73 2026-02-21 10:06:14 +03:30
26c13018e7 version changed to 02.72 2026-02-21 09:58:43 +03:30
b3892b6a8a add: page and access for send sms 2026-02-21 09:58:34 +03:30
44037758ed version changed to 02.71 2026-02-18 16:03:44 +03:30
c55e1f907a add: two new keys 2026-02-18 15:45:51 +03:30
b3dde3c0ec add: active state 2026-02-18 14:01:17 +03:30
4245dce136 feat: tag batch otp auth 2026-02-18 12:25:09 +03:30
d9aed4d215 version changed to 02.70 2026-02-17 16:40:56 +03:30
4a5bdd7f89 feat: opt auth established 2026-02-17 16:40:45 +03:30
21dcc7dca8 add: otp status 2026-02-17 15:47:31 +03:30
e507d08835 add: page and access filter to tags tab 2026-02-16 10:59:31 +03:30
225b2d874d update: move tagging pages in one page 2026-02-16 10:57:25 +03:30
c7b3eb80ec chore: rename permission 2026-02-16 10:40:11 +03:30
33233cf9e0 version changed to 02.69 2026-02-15 10:17:54 +03:30
abe53b885a add: one time purchase limit 2026-02-15 10:17:42 +03:30
c0b8472c72 chore: rename tagging names 2026-02-15 08:49:05 +03:30
f473c729fe version changed to 02.68 2026-02-14 14:20:25 +03:30
e8a75fcc74 feat: allocate incentive plan to rancher 2026-02-14 13:20:21 +03:30
ec087e59bc feat: allocate incentive plan to rancher 2026-02-14 13:20:10 +03:30
468ad9079e version changed to 02.67 2026-02-10 16:50:13 +03:30
28f7d5c991 feat: inquiry unique id 2026-02-10 16:50:04 +03:30
763d5b79d3 add: quaternaryKey 2026-02-10 16:49:48 +03:30
f8e8e7f1f1 fix: unique unit id validation 2026-02-10 15:16:53 +03:30
63aed5572c fix: AutoComplete click event on open icon 2026-02-10 15:11:45 +03:30
59ae3b76d9 update: add address text color 2026-02-10 14:52:23 +03:30
93867ce7ee version changed to 02.66 2026-02-09 16:23:19 +03:30
4210dbd7e2 add: organization type filter 2026-02-09 14:38:32 +03:30
3624b3bc70 add: size prop 2026-02-09 13:53:43 +03:30
4d00b0d492 add: unique_unit_identity 2026-02-09 12:34:56 +03:30
ed7b257ed8 add: limit size for upload doc and removed validFiles limit 2026-02-09 10:23:35 +03:30
03136f5f30 add: limit size 2026-02-09 10:22:34 +03:30
908a69ce0e version changed to 02.65 2026-02-08 16:53:18 +03:30
a818683247 add: multiple addresses 2026-02-08 16:52:55 +03:30
95780cfbc9 add: document upload and sign 2026-02-08 16:52:26 +03:30
071c3e159b feat: document operation 2026-02-08 16:52:00 +03:30
90f51c6899 feat: edit distribution from distribution 2026-02-08 11:20:10 +03:30
e967329108 chore: fix grammar mistake 2026-02-03 10:24:28 +03:30
f8d2da4f28 version changed to 02.64 2026-02-02 16:34:27 +03:30
bb1d5b3315 update: tag distribution details 2026-02-02 16:34:21 +03:30
d8d415a8f5 fix: remaining number 2026-02-02 14:50:44 +03:30
f58c8e6c58 add: new keys 2026-02-02 14:49:12 +03:30
3a17fcb448 version changed to 02.63 2026-02-02 12:14:52 +03:30
6e219aca1a feat: distribute from distribution 2026-02-02 12:14:48 +03:30
9b74be078f version changed to 02.62 2026-02-02 11:03:09 +03:30
5fd55c4b10 fix: filter error 2026-02-02 11:03:01 +03:30
6b5276ce36 add: new tagging key 2026-02-02 08:40:06 +03:30
e465843eb9 version changed to 02.61 2026-01-28 17:42:11 +03:30
e5402f9037 add: tag dist detail 2026-01-28 17:41:53 +03:30
125 changed files with 4662 additions and 1713 deletions

View File

@@ -32,7 +32,7 @@ export default function Dashboard() {
useDashboardTabStore();
const menuItems: ItemWithSubItems[] = getUserPermissions(
profile?.permissions
profile?.permissions,
);
const [tabs, setTabs] = useState<Tab[]>(dashboarTabs || []);
@@ -55,7 +55,7 @@ export default function Dashboard() {
hour: "2-digit",
minute: "2-digit",
hour12: false,
})
}),
);
useEffect(() => {
@@ -65,7 +65,7 @@ export default function Dashboard() {
hour: "2-digit",
minute: "2-digit",
hour12: false,
})
}),
);
}, 60000);
return () => clearInterval(interval);
@@ -96,7 +96,7 @@ export default function Dashboard() {
if (activeTabId === id) {
setActiveTabId(
newTabs.length > 0 ? newTabs[newTabs.length - 1].id : null
newTabs.length > 0 ? newTabs[newTabs.length - 1].id : null,
);
}
};
@@ -113,14 +113,49 @@ export default function Dashboard() {
(subItem) =>
!subItem.path.includes("$") &&
(search.trim() === "" ||
getFaPermissions(subItem.name).includes(search.trim()))
getFaPermissions(subItem.name).includes(search.trim())),
),
}))
.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
path: string,
): ItemWithSubItems["subItems"][0] | null {
for (const item of items) {
for (const subItem of item.subItems) {
@@ -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>

84
src/Pages/Domains.tsx Normal file
View File

@@ -0,0 +1,84 @@
import { useEffect, useState } from "react";
import { useApiRequest } from "../utils/useApiRequest";
import { useModalStore } from "../context/zustand-store/appStore";
import Table from "../components/Table/Table";
import { Grid } from "../components/Grid/Grid";
import Button from "../components/Button/Button";
import { AddDomain } from "../partials/Admin/AddDomain";
import { Popover } from "../components/PopOver/PopOver";
import { Tooltip } from "../components/Tooltip/Tooltip";
import { DeleteButtonForPopOver } from "../components/PopOverButtons/PopOverButtons";
export default function Domains() {
const { openModal } = useModalStore();
const [domainsInfo, setDomainsInfo] = useState({ page: 1, page_size: 10 });
const [domainsTableData, setDomainsTableData] = useState([]);
const { data: domainsData, refetch } = useApiRequest({
api: "/core/domain/",
method: "get",
params: domainsInfo,
queryKey: ["domains", domainsInfo],
});
useEffect(() => {
if (domainsData?.results) {
const formattedData = domainsData.results.map((item: any, i: number) => {
return [
domainsInfo.page === 1
? i + 1
: i + domainsInfo.page_size * (domainsInfo.page - 1) + 1,
item?.fa_name,
item?.code,
item?.name,
<Popover key={i}>
<Tooltip title="ویرایش" position="right">
<Button
variant="edit"
onClick={() => {
openModal({
title: "ویرایش حوزه",
content: <AddDomain item={item} getData={refetch} />,
});
}}
/>
</Tooltip>
<DeleteButtonForPopOver
api={`core/domain/${item?.id}/`}
getData={refetch}
/>
</Popover>,
];
});
setDomainsTableData(formattedData);
}
}, [domainsData, domainsInfo]);
return (
<Grid container column>
<Grid container className="items-center gap-2">
<Button
size="small"
variant="submit"
onClick={() => {
openModal({
title: "ایجاد حوزه",
content: <AddDomain getData={refetch} />,
});
}}
>
ایجاد حوزه
</Button>
</Grid>
<Table
className="mt-2"
onChange={setDomainsInfo}
count={domainsData?.count || 10}
isPaginated
title="حوزه ها"
columns={["ردیف", "نام حوزه", "کد حوزه", "نام انگلیسی", "عملیات"]}
rows={domainsTableData}
/>
</Grid>
);
}

View File

@@ -1,16 +1,16 @@
import { useEffect, useState } from "react";
import { useApiRequest } from "../utils/useApiRequest";
import { Grid } from "../components/Grid/Grid";
import Table from "../components/Table/Table";
import { useApiRequest } from "../../utils/useApiRequest";
import { Grid } from "../../components/Grid/Grid";
import Table from "../../components/Table/Table";
import { useNavigate, useParams } from "@tanstack/react-router";
import { Popover } from "../components/PopOver/PopOver";
import { Tooltip } from "../components/Tooltip/Tooltip";
import Button from "../components/Button/Button";
import { useModalStore } from "../context/zustand-store/appStore";
import { LIVESTOCK_FARMERS } from "../routes/paths";
import { TableButton } from "../components/TableButton/TableButton";
import { CooperativesDashboardDetails } from "../partials/cooperatives/CooperativesDashboardDetails";
import { DeleteButtonForPopOver } from "../components/PopOverButtons/PopOverButtons";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import Button from "../../components/Button/Button";
import { useModalStore } from "../../context/zustand-store/appStore";
import { LIVESTOCK_FARMERS } from "../../routes/paths";
import { TableButton } from "../../components/TableButton/TableButton";
import { CooperativesDashboardDetails } from "../../partials/LiveStock/cooperatives/CooperativesDashboardDetails";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
export default function CooperativeRanchers() {
const { openModal } = useModalStore();
@@ -60,10 +60,10 @@ export default function CooperativeRanchers() {
item?.rancher?.activity === "V"
? "روستایی"
: item?.rancher?.activity === "I"
? "صنعتی"
: item?.rancher?.activity === "R"
? "عشایری"
: "-",
? "صنعتی"
: item?.rancher?.activity === "R"
? "عشایری"
: "-",
item?.rancher?.province?.name || "-",
item?.rancher?.city?.name || "-",
item?.rancher?.address,
@@ -98,7 +98,7 @@ export default function CooperativeRanchers() {
/>
</Popover>,
];
}
},
);
setCooperativesTableData(tableData);
}

View File

@@ -1,19 +1,19 @@
import { useEffect, useState } from "react";
import { useApiRequest } from "../utils/useApiRequest";
import { Grid } from "../components/Grid/Grid";
import Table from "../components/Table/Table";
import { useApiRequest } from "../../utils/useApiRequest";
import { Grid } from "../../components/Grid/Grid";
import Table from "../../components/Table/Table";
import { useNavigate, useParams } from "@tanstack/react-router";
import { Popover } from "../components/PopOver/PopOver";
import { Tooltip } from "../components/Tooltip/Tooltip";
import Button from "../components/Button/Button";
import { useModalStore } from "../context/zustand-store/appStore";
import { ChildOrganizations } from "../partials/cooperatives/ChildOrganizations";
import { COOPERATIVE_LIST } from "../routes/paths";
import { TableButton } from "../components/TableButton/TableButton";
import { CooperativesDashboardDetails } from "../partials/cooperatives/CooperativesDashboardDetails";
import { AddActivityType } from "../partials/cooperatives/AddActivityType";
import ShowMoreInfo from "../components/ShowMoreInfo/ShowMoreInfo";
import ShowStringList from "../components/ShowStringList/ShowStringList";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import Button from "../../components/Button/Button";
import { useModalStore } from "../../context/zustand-store/appStore";
import { ChildOrganizations } from "../../partials/LiveStock/cooperatives/ChildOrganizations";
import { COOPERATIVE_LIST } from "../../routes/paths";
import { TableButton } from "../../components/TableButton/TableButton";
import { CooperativesDashboardDetails } from "../../partials/LiveStock/cooperatives/CooperativesDashboardDetails";
import { AddActivityType } from "../../partials/LiveStock/cooperatives/AddActivityType";
import ShowMoreInfo from "../../components/ShowMoreInfo/ShowMoreInfo";
import ShowStringList from "../../components/ShowStringList/ShowStringList";
export default function Cooperatives() {
const { openModal } = useModalStore();
@@ -64,7 +64,7 @@ export default function Cooperatives() {
<ShowStringList
showSearch={false}
strings={item.org_service_area.map(
(city: any) => city.name
(city: any) => city.name,
)}
/>
</Grid>
@@ -75,8 +75,8 @@ export default function Cooperatives() {
item?.org_purchase_policy === "INTERNAL_ONLY"
? "بر اساس تعاونی"
: item?.org_purchase_policy === "CROSS_COOP"
? "برای کل استان"
: "-",
? "برای کل استان"
: "-",
<Popover key={i}>
<Tooltip title="دامداران تعاونی" position="right">
<Button
@@ -131,7 +131,7 @@ export default function Cooperatives() {
/>
</Tooltip>,
];
}
},
);
setCooperativesTableData(formattedData);
}

View File

@@ -1,21 +1,21 @@
import { useEffect, useState } from "react";
import { Grid } from "../components/Grid/Grid";
import Table from "../components/Table/Table";
import { useApiRequest } from "../utils/useApiRequest";
import { Popover } from "../components/PopOver/PopOver";
import { DeleteButtonForPopOver } from "../components/PopOverButtons/PopOverButtons";
import { Tooltip } from "../components/Tooltip/Tooltip";
import Button from "../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Table from "../../components/Table/Table";
import { useApiRequest } from "../../utils/useApiRequest";
import { Popover } from "../../components/PopOver/PopOver";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import Button from "../../components/Button/Button";
import {
useDrawerStore,
useModalStore,
} from "../context/zustand-store/appStore";
import { LiveStockAddHerd } from "../partials/live-stock/LiveStockAddHerd";
} from "../../context/zustand-store/appStore";
import { LiveStockAddHerd } from "../../partials/LiveStock/live-stock/LiveStockAddHerd";
import { useNavigate, useParams } from "@tanstack/react-router";
import { LIVESTOCKS } from "../routes/paths";
import { LiveStockAddLiveStock } from "../partials/live-stock/LiveStockAddLiveStock";
import { TableButton } from "../components/TableButton/TableButton";
import { LiveStockHerdDetails } from "../partials/live-stock/LiveStockHerdDetails";
import { LIVESTOCKS } from "../../routes/paths";
import { LiveStockAddLiveStock } from "../../partials/LiveStock/live-stock/LiveStockAddLiveStock";
import { TableButton } from "../../components/TableButton/TableButton";
import { LiveStockHerdDetails } from "../../partials/LiveStock/live-stock/LiveStockHerdDetails";
export default function LiveStocks() {
const [pagesInfo, setPagesInfo] = useState({ page: 1, page_size: 10 });
@@ -38,7 +38,7 @@ export default function LiveStocks() {
? `/herd/web/api/v1/rancher/${farmid}/rancher_dashboard/`
: "/herd/web/api/v1/rancher/rancher_main_dashboard/",
queryKey: ["HerdsDashboard"],
}
},
);
const handleUpdate = () => {
@@ -63,10 +63,10 @@ export default function LiveStocks() {
item?.activity === "I"
? "صنعتی"
: item?.activity === "V"
? "روستایی"
: item?.activity === "N"
? "عشایری"
: "-",
? "روستایی"
: item?.activity === "N"
? "عشایری"
: "-",
item?.epidemiologic,
parseInt(item?.light_livestock_number)?.toLocaleString(),
parseInt(item?.heavy_livestock_number)?.toLocaleString(),
@@ -156,6 +156,8 @@ export default function LiveStocks() {
"تعداد دام سنگین",
"تعداد دام سبک",
"مجموع وزن خرید از سهمیه ها",
"تعداد دام های فعال",
"تعداد دام های غیرفعال",
"جزئیات",
]
: [
@@ -186,6 +188,10 @@ export default function LiveStocks() {
"0",
DashboardData?.total_purchase_weight?.toLocaleString() ||
"0",
DashboardData?.total_active_livestock_count?.toLocaleString() ||
"0",
DashboardData?.total_deactive_livestock_count?.toLocaleString() ||
"0",
<TableButton
size="small"
key={DashboardData}

View File

@@ -1,15 +1,15 @@
import { useEffect, useState } from "react";
import { Popover } from "../components/PopOver/PopOver";
import { Tooltip } from "../components/Tooltip/Tooltip";
import Button from "../components/Button/Button";
import { DeleteButtonForPopOver } from "../components/PopOverButtons/PopOverButtons";
import { useModalStore } from "../context/zustand-store/appStore";
import { useApiRequest } from "../utils/useApiRequest";
import { Grid } from "../components/Grid/Grid";
import Table from "../components/Table/Table";
import { formatJustDate } from "../utils/formatTime";
import { AddIncentivePlan } from "../partials/quota/AddIncentivePlan";
import AutoComplete from "../components/AutoComplete/AutoComplete";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import Button from "../../components/Button/Button";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
import { useModalStore } from "../../context/zustand-store/appStore";
import { useApiRequest } from "../../utils/useApiRequest";
import { Grid } from "../../components/Grid/Grid";
import Table from "../../components/Table/Table";
import { formatJustDate } from "../../utils/formatTime";
import { AddIncentivePlan } from "../../partials/LiveStock/quota/AddIncentivePlan";
import AutoComplete from "../../components/AutoComplete/AutoComplete";
export default function IncentivePlans() {
const { openModal } = useModalStore();
@@ -47,8 +47,8 @@ export default function IncentivePlans() {
item?.group === "rural"
? "روستایی"
: item?.group === "nomadic"
? "عشایری"
: "صنعتی",
? "عشایری"
: "صنعتی",
item?.is_time_unlimited ? "دارد" : "ندارد",
formatJustDate(item?.start_date_limit),
formatJustDate(item?.end_date_limit),

View File

@@ -1,11 +1,11 @@
import { useState } from "react";
import { Grid } from "../components/Grid/Grid";
import Tabs from "../components/Tab/Tab";
import { useUserProfileStore } from "../context/zustand-store/userStore";
import { InventoryStakeHolderAllocations } from "../partials/inventory/InventoryStakeHolderAllocations";
import { InventoryWarehouseEntryTab } from "../partials/inventory/InventoryWarehouseEntryTab";
import { Grid } from "../../components/Grid/Grid";
import Tabs from "../../components/Tab/Tab";
import { useUserProfileStore } from "../../context/zustand-store/userStore";
import { InventoryStakeHolderAllocations } from "../../partials/LiveStock/inventory/InventoryStakeHolderAllocations";
import { InventoryWarehouseEntryTab } from "../../partials/LiveStock/inventory/InventoryWarehouseEntryTab";
import { useParams } from "@tanstack/react-router";
import { InventoryEntriesList } from "../partials/inventory/InventoryEntriesList";
import { InventoryEntriesList } from "../../partials/LiveStock/inventory/InventoryEntriesList";
export default function Inventory() {
const [selectedTab, setSelectedTab] = useState<number>(0);

View File

@@ -1,21 +1,21 @@
import { useEffect, useState } from "react";
import { Grid } from "../components/Grid/Grid";
import Table from "../components/Table/Table";
import { useApiRequest } from "../utils/useApiRequest";
import Button from "../components/Button/Button";
import { LiveStockAddRancher } from "../partials/live-stock/LiveStockAddRancher";
import { Grid } from "../../components/Grid/Grid";
import Table from "../../components/Table/Table";
import { useApiRequest } from "../../utils/useApiRequest";
import Button from "../../components/Button/Button";
import { LiveStockAddRancher } from "../../partials/LiveStock/live-stock/LiveStockAddRancher";
import {
useDrawerStore,
useModalStore,
} from "../context/zustand-store/appStore";
import { Popover } from "../components/PopOver/PopOver";
import { Tooltip } from "../components/Tooltip/Tooltip";
import { DeleteButtonForPopOver } from "../components/PopOverButtons/PopOverButtons";
import { LiveStockAddHerd } from "../partials/live-stock/LiveStockAddHerd";
import { LiveStockAllocateCooperative } from "../partials/live-stock/LiveStockAllocateCooperative";
} from "../../context/zustand-store/appStore";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
import { LiveStockAddHerd } from "../../partials/LiveStock/live-stock/LiveStockAddHerd";
import { LiveStockAllocateCooperative } from "../../partials/LiveStock/live-stock/LiveStockAllocateCooperative";
import { useNavigate } from "@tanstack/react-router";
import { LIVESTOCK_FARMERS } from "../routes/paths";
import { LiveStockFarmersDashboardResponse } from "../types/LiveStockFarmers";
import { LIVESTOCK_FARMERS } from "../../routes/paths";
import { LiveStockFarmersDashboardResponse } from "../../types/LiveStockFarmers";
export default function LiveStockFarmers() {
const [pagesInfo, setPagesInfo] = useState({ page: 1, page_size: 10 });
@@ -61,10 +61,10 @@ export default function LiveStockFarmers() {
item?.activity === "V"
? "روستایی"
: item?.activity === "I"
? "صنعتی"
: item?.activity === "R"
? "عشایری"
: "-",
? "صنعتی"
: item?.activity === "R"
? "عشایری"
: "-",
item?.province?.name || "-",
item?.city?.name || "-",
item?.address,
@@ -137,7 +137,7 @@ export default function LiveStockFarmers() {
"/plans/" +
item.id +
"/" +
item?.ranching_farm;
`${item?.first_name.replace(":", " ") || ""} ${item?.last_name || ""} ${item?.ranching_farm ? " (" + item?.ranching_farm + ") " : ""}`;
navigate({ to: path });
}}
/>

View File

@@ -1,30 +1,47 @@
import { useEffect, useState } from "react";
import { Grid } from "../components/Grid/Grid";
import Table from "../components/Table/Table";
import { useApiRequest } from "../utils/useApiRequest";
import { Popover } from "../components/PopOver/PopOver";
import { DeleteButtonForPopOver } from "../components/PopOverButtons/PopOverButtons";
import { Grid } from "../../components/Grid/Grid";
import Table from "../../components/Table/Table";
import { useApiRequest } from "../../utils/useApiRequest";
import { Popover } from "../../components/PopOver/PopOver";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
import { useParams } from "@tanstack/react-router";
import { formatAgeCalcuation, formatJustDate } from "../utils/formatTime";
import { Tooltip } from "../components/Tooltip/Tooltip";
import Button from "../components/Button/Button";
import { LiveStockAddLiveStock } from "../partials/live-stock/LiveStockAddLiveStock";
import { useDrawerStore } from "../context/zustand-store/appStore";
import { formatAgeCalcuation, formatJustDate } from "../../utils/formatTime";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import Button from "../../components/Button/Button";
import { LiveStockAddLiveStock } from "../../partials/LiveStock/live-stock/LiveStockAddLiveStock";
import { useDrawerStore } from "../../context/zustand-store/appStore";
import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/outline";
import AutoComplete from "../../components/AutoComplete/AutoComplete";
export default function LiveStocks() {
const [pagesInfo, setPagesInfo] = useState({ page: 1, page_size: 10 });
const [pagesTableData, setPagesTableData] = useState([]);
const [selectedStatusKeys, setSelectedStatusKeys] = useState<
(string | number)[]
>([]);
const { openDrawer } = useDrawerStore();
const statusItems = [
{ key: "", value: "همه" },
{ key: "true", value: "فعال" },
{ key: "false", value: "غیرفعال" },
];
const { herdid, name } = useParams({ strict: false });
const activeParam =
selectedStatusKeys.length && selectedStatusKeys[0] !== ""
? { active: selectedStatusKeys[0] }
: {};
const { data: pagesData, refetch } = useApiRequest({
api: herdid
? `herd/web/api/v1/herd/${herdid}/live_stocks/`
: "/livestock/web/api/v1/livestock/",
method: "get",
params: pagesInfo,
queryKey: ["LiveStockFarmers", pagesInfo],
params: { ...pagesInfo, ...activeParam },
queryKey: ["LiveStockFarmers", pagesInfo, selectedStatusKeys],
});
useEffect(() => {
@@ -42,6 +59,19 @@ export default function LiveStocks() {
item?.gender === 1 ? "نر" : "ماده",
// item?.species?.name,
item?.weight_type === "L" ? "سبک" : "سنگین",
<span className="flex items-center gap-1">
{item?.active ? (
<>
<span className="text-green-500">فعال</span>
<CheckCircleIcon className="w-5 h-5 text-green-500" />
</>
) : (
<>
<span className="text-red-500">غیرفعال</span>
<XCircleIcon className="w-5 h-5 text-red-500" />
</>
)}
</span>,
<Popover key={i}>
<Tooltip title="ویرایش دام" position="right">
<Button
@@ -77,6 +107,18 @@ export default function LiveStocks() {
return (
<Grid container column>
<Grid container className="items-center gap-2">
<Grid>
<AutoComplete
inPage
size="small"
data={statusItems}
selectedKeys={selectedStatusKeys}
onChange={setSelectedStatusKeys}
title="فیلتر وضعیت"
/>
</Grid>
</Grid>
<Table
className="mt-2"
onChange={(e) => {
@@ -95,6 +137,7 @@ export default function LiveStocks() {
"جنسیت",
// "گونه",
"دسته وزنی",
"وضعیت",
"عملیات",
]}
rows={pagesTableData}

View File

@@ -1,17 +1,17 @@
import { Grid } from "../components/Grid/Grid";
import { Grid } from "../../components/Grid/Grid";
import { useState } from "react";
import Tabs from "../components/Tab/Tab";
import { OrganizationsList } from "../partials/management/OrganizationsList";
import { OrganizationsTypes } from "../partials/management/OrganizationsTypes";
import Tabs from "../../components/Tab/Tab";
import { OrganizationsList } from "../../partials/LiveStock/management/OrganizationsList";
import { OrganizationsTypes } from "../../partials/LiveStock/management/OrganizationsTypes";
export default function Organizations() {
const tabItems = [
{ label: "سازمان ها" },
{
label: "نهاد",
page: "organizations",
access: "Show-Organization-Type",
},
{ label: "سازمان ها" },
];
const [selectedTab, setSelectedTab] = useState<number>(0);
@@ -22,7 +22,7 @@ export default function Organizations() {
return (
<Grid container column className="gap-2">
<Tabs tabs={tabItems} onChange={handleTabChange} size="medium" />
{selectedTab === 0 ? <OrganizationsList /> : <OrganizationsTypes />}
{selectedTab === 0 ? <OrganizationsTypes /> : <OrganizationsList />}
</Grid>
);
}

View File

@@ -1,18 +1,18 @@
import { useEffect, useState } from "react";
import { useApiRequest } from "../utils/useApiRequest";
import { Grid } from "../components/Grid/Grid";
import Table from "../components/Table/Table";
import Button from "../components/Button/Button";
import { useModalStore } from "../context/zustand-store/appStore";
import { AddPos } from "../partials/pos/AddPos";
import { Popover } from "../components/PopOver/PopOver";
import { Tooltip } from "../components/Tooltip/Tooltip";
import { DeleteButtonForPopOver } from "../components/PopOverButtons/PopOverButtons";
import { useUserProfileStore } from "../context/zustand-store/userStore";
import { useApiRequest } from "../../utils/useApiRequest";
import { Grid } from "../../components/Grid/Grid";
import Table from "../../components/Table/Table";
import Button from "../../components/Button/Button";
import { useModalStore } from "../../context/zustand-store/appStore";
import { AddPos } from "../../partials/LiveStock/pos/AddPos";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
import { useUserProfileStore } from "../../context/zustand-store/userStore";
import { useNavigate, useParams } from "@tanstack/react-router";
import { AllocatePos } from "../partials/pos/AllocatePos";
import { AllocatePos } from "../../partials/LiveStock/pos/AllocatePos";
import { CreditCardIcon } from "@heroicons/react/24/outline";
import { POS_POS_LIST } from "../routes/paths";
import { POS_POS_LIST } from "../../routes/paths";
export default function Pos() {
const { openModal } = useModalStore();

View File

@@ -1,18 +1,18 @@
import { useEffect, useState } from "react";
import { useApiRequest } from "../utils/useApiRequest";
import { Grid } from "../components/Grid/Grid";
import Table from "../components/Table/Table";
import Button from "../components/Button/Button";
import { useModalStore } from "../context/zustand-store/appStore";
import { AddPos } from "../partials/pos/AddPos";
import { Popover } from "../components/PopOver/PopOver";
import { Tooltip } from "../components/Tooltip/Tooltip";
import { DeleteButtonForPopOver } from "../components/PopOverButtons/PopOverButtons";
import { useApiRequest } from "../../utils/useApiRequest";
import { Grid } from "../../components/Grid/Grid";
import Table from "../../components/Table/Table";
import Button from "../../components/Button/Button";
import { useModalStore } from "../../context/zustand-store/appStore";
import { AddPos } from "../../partials/LiveStock/pos/AddPos";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
import { useParams } from "@tanstack/react-router";
import { AllocatePos } from "../partials/pos/AllocatePos";
import { PosAllocateOrganizationAccount } from "../partials/pos/PosAllocateOrganizationAccount";
import { AllocateAccountToBroker } from "../partials/pos/AllocateAccountToBroker";
import { BooleanQuestion } from "../components/BooleanQuestion/BooleanQuestion";
import { AllocatePos } from "../../partials/LiveStock/pos/AllocatePos";
import { PosAllocateOrganizationAccount } from "../../partials/LiveStock/pos/PosAllocateOrganizationAccount";
import { AllocateAccountToBroker } from "../../partials/LiveStock/pos/AllocateAccountToBroker";
import { BooleanQuestion } from "../../components/BooleanQuestion/BooleanQuestion";
export default function PosAccounts() {
const { openModal } = useModalStore();
@@ -102,7 +102,7 @@ export default function PosAccounts() {
/>
</Popover>,
];
}
},
);
setAccountsTableData(formattedData);
}

View File

@@ -1,12 +1,12 @@
import { useEffect, useState } from "react";
import { Grid } from "../components/Grid/Grid";
import Table from "../components/Table/Table";
import { useApiRequest } from "../utils/useApiRequest";
import { Popover } from "../components/PopOver/PopOver";
import { Tooltip } from "../components/Tooltip/Tooltip";
import Button from "../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Table from "../../components/Table/Table";
import { useApiRequest } from "../../utils/useApiRequest";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import Button from "../../components/Button/Button";
import { useNavigate } from "@tanstack/react-router";
import { POS_COMPANIES } from "../routes/paths";
import { POS_COMPANIES } from "../../routes/paths";
export default function PosCompanies() {
const [pagesInfo, setPagesInfo] = useState({ page: 1, page_size: 10 });
@@ -35,10 +35,10 @@ export default function PosCompanies() {
item?.field_of_activity === "CO"
? "کشور"
: item?.field_of_activity === "PR"
? "استان"
: item?.field_of_activity === "CI"
? "شهرستان"
: "نامشخص",
? "استان"
: item?.field_of_activity === "CI"
? "شهرستان"
: "نامشخص",
<Popover key={i}>
<Tooltip title="نمایش کارتخوان ها" position="right">
<Button

View File

@@ -1,9 +1,9 @@
import { useState } from "react";
import { Grid } from "../components/Grid/Grid";
import Tabs from "../components/Tab/Tab";
import { Attributes } from "../partials/feed-input/Attributes";
import { Brokers } from "../partials/feed-input/Brokers";
import { SaleUnits } from "../partials/feed-input/SaleUnits";
import { Grid } from "../../components/Grid/Grid";
import Tabs from "../../components/Tab/Tab";
import { Attributes } from "../../partials/LiveStock/feed-input/Attributes";
import { Brokers } from "../../partials/LiveStock/feed-input/Brokers";
import { SaleUnits } from "../../partials/LiveStock/feed-input/SaleUnits";
const tabItems = [
{ label: "مولفه" },
{ label: "کارگزار" },

View File

@@ -1,21 +1,21 @@
import { motion } from "framer-motion";
import { useApiRequest } from "../utils/useApiRequest";
import sabos from "../assets/images/products/saboos.png";
import jo from "../assets/images/products/jo.png";
import soya from "../assets/images/products/soya.png";
import zorat from "../assets/images/products/zorat.png";
import goosfandi from "../assets/images/products/constantre-goosfandi.png";
import parvari from "../assets/images/products/constantre-parvari.png";
import porTolid from "../assets/images/products/constantre-gave-shiri-por-tolid.png";
import shiriMotevaset from "../assets/images/products/constantre-gave-shiri-motevaset.png";
import defaultImage from "../assets/images/products/default.png";
import Button from "../components/Button/Button";
import { useApiRequest } from "../../utils/useApiRequest";
import sabos from "../../assets/images/products/saboos.png";
import jo from "../../assets/images/products/jo.png";
import soya from "../../assets/images/products/soya.png";
import zorat from "../../assets/images/products/zorat.png";
import goosfandi from "../../assets/images/products/constantre-goosfandi.png";
import parvari from "../../assets/images/products/constantre-parvari.png";
import porTolid from "../../assets/images/products/constantre-gave-shiri-por-tolid.png";
import shiriMotevaset from "../../assets/images/products/constantre-gave-shiri-motevaset.png";
import defaultImage from "../../assets/images/products/default.png";
import Button from "../../components/Button/Button";
import { PencilIcon, TrashIcon } from "@heroicons/react/24/outline";
import { Grid } from "../components/Grid/Grid";
import { useModalStore } from "../context/zustand-store/appStore";
import { AddProduct } from "../partials/feed-input/AddProduct";
import { getAbleToSee } from "../utils/getAbleToSee";
import { DeleteProduct } from "../partials/feed-input/DeleteProduct";
import { Grid } from "../../components/Grid/Grid";
import { useModalStore } from "../../context/zustand-store/appStore";
import { AddProduct } from "../../partials/LiveStock/feed-input/AddProduct";
import { getAbleToSee } from "../../utils/getAbleToSee";
import { DeleteProduct } from "../../partials/LiveStock/feed-input/DeleteProduct";
interface Category {
id: number;
@@ -232,7 +232,7 @@ export default function Products() {
<button
className={`${getAbleToSee(
"feed_input_products",
"Edit-Product"
"Edit-Product",
)} rounded-full text-primary-600 text-sm`}
onClick={() => {
openModal({
@@ -252,7 +252,7 @@ export default function Products() {
}}
className={`${getAbleToSee(
"feed_input_products",
"Delete-Product"
"Delete-Product",
)} text-red-400 rounded-lg text-sm`}
>
<TrashIcon className="w-5" />

View File

@@ -1,13 +1,13 @@
import { useEffect, useState } from "react";
import { useModalStore } from "../context/zustand-store/appStore";
import { useApiRequest } from "../utils/useApiRequest";
import { Popover } from "../components/PopOver/PopOver";
import { Tooltip } from "../components/Tooltip/Tooltip";
import Button from "../components/Button/Button";
import { DeleteButtonForPopOver } from "../components/PopOverButtons/PopOverButtons";
import { Grid } from "../components/Grid/Grid";
import Table from "../components/Table/Table";
import { AddProductCategory } from "../partials/feed-input/AddProductCategory";
import { useModalStore } from "../../context/zustand-store/appStore";
import { useApiRequest } from "../../utils/useApiRequest";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import Button from "../../components/Button/Button";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
import { Grid } from "../../components/Grid/Grid";
import Table from "../../components/Table/Table";
import { AddProductCategory } from "../../partials/LiveStock/feed-input/AddProductCategory";
export const ProductsCategories = () => {
const { openModal } = useModalStore();

View File

@@ -1,11 +1,11 @@
import { Grid } from "../components/Grid/Grid";
import { Grid } from "../../components/Grid/Grid";
import { useState } from "react";
import Tabs from "../components/Tab/Tab";
import { QuotaActives } from "../partials/quota/QuotaActives";
import { QuotaClosed } from "../partials/quota/QuotaClosed";
import Tabs from "../../components/Tab/Tab";
import { QuotaActives } from "../../partials/LiveStock/quota/QuotaActives";
import { QuotaClosed } from "../../partials/LiveStock/quota/QuotaClosed";
import { useParams } from "@tanstack/react-router";
import { QuotaDistributions } from "../partials/quota/QuotaDistributions";
import { QuotaAllDistributions } from "../partials/quota/QuotaAllDistributions";
import { QuotaDistributions } from "../../partials/LiveStock/quota/QuotaDistributions";
import { QuotaAllDistributions } from "../../partials/LiveStock/quota/QuotaAllDistributions";
export default function Quota() {
const [selectedTab, setSelectedTab] = useState<number>(0);

View File

@@ -1,13 +1,12 @@
import { useEffect, useState } from "react";
import { Popover } from "../components/PopOver/PopOver";
import { Tooltip } from "../components/Tooltip/Tooltip";
import Button from "../components/Button/Button";
import { DeleteButtonForPopOver } from "../components/PopOverButtons/PopOverButtons";
import { useModalStore } from "../context/zustand-store/appStore";
import { useApiRequest } from "../utils/useApiRequest";
import { Grid } from "../components/Grid/Grid";
import Table from "../components/Table/Table";
import { AddIncentivePlan } from "../partials/quota/AddIncentivePlan";
import { Popover } from "../../components/PopOver/PopOver";
import Button from "../../components/Button/Button";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
import { useModalStore } from "../../context/zustand-store/appStore";
import { useApiRequest } from "../../utils/useApiRequest";
import { Grid } from "../../components/Grid/Grid";
import Table from "../../components/Table/Table";
import { LiveStockRancherAllocateIncentivePlan } from "../../partials/LiveStock/live-stock/LiveStockRancherAllocateIncentivePlan";
import { useParams } from "@tanstack/react-router";
export default function RancherPlans() {
@@ -34,25 +33,11 @@ export default function RancherPlans() {
item?.livestock_type_name,
item?.allowed_quantity?.toLocaleString(),
<Popover key={i}>
<Tooltip title="ویرایش طرح" position="right">
<Button
page="farmer_plans"
access="Edit-Rancher-Incentive-Plan"
variant="edit"
onClick={() => {
openModal({
title: "ویرایش طرح",
content: <AddIncentivePlan getData={refetch} item={item} />,
});
}}
/>
</Tooltip>
<DeleteButtonForPopOver
page="farmer_plans"
access="Delete-Rancher-Incentive-Plan"
title="از حذف طرح تشویقی اطمینان دارید؟"
api={`/product/web/api/v1/incentive_plan/${item?.id}/`}
api={`/product/web/api/v1/rancher_incentive_plan/${item?.id}/`}
getData={refetch}
/>
</Popover>,
@@ -67,7 +52,6 @@ export default function RancherPlans() {
<Grid container className="items-center gap-2">
<Grid>
<Button
className="hidden"
size="small"
page="farmer_plans"
access="Post-Rancher-Incentive-Plan"
@@ -75,7 +59,12 @@ export default function RancherPlans() {
onClick={() =>
openModal({
title: "تخصیص طرح تشویقی",
content: <AddIncentivePlan getData={refetch} />,
content: (
<LiveStockRancherAllocateIncentivePlan
getData={refetch}
rancher={farmid}
/>
),
})
}
>
@@ -95,13 +84,7 @@ export default function RancherPlans() {
title={`طرح های تشویقی دامدار (${name})`}
isPaginated
count={apiData?.count || 10}
columns={[
"ردیف",
"نام طرح",
"نوع دام",
"تعداد راس مجاز",
// , "عملیات"
]}
columns={["ردیف", "نام طرح", "نوع دام", "تعداد راس مجاز", "عملیات"]}
rows={tableData}
/>
</Grid>

View File

@@ -1,10 +1,10 @@
import { useState } from "react";
import { Grid } from "../components/Grid/Grid";
import Tabs from "../components/Tab/Tab";
import { QuotaReportingProducts } from "../partials/quota/QuotaReportingProducts";
import { Grid } from "../../components/Grid/Grid";
import Tabs from "../../components/Tab/Tab";
import { QuotaReportingProducts } from "../../partials/LiveStock/quota/QuotaReportingProducts";
import { useParams } from "@tanstack/react-router";
import { QuotaReportingProductDetails } from "../partials/quota/QuotaReportingProductDetails";
import { QuotaReportingQuotaDistributions } from "../partials/quota/QuotaReportingQuotaDistributions";
import { QuotaReportingProductDetails } from "../../partials/LiveStock/quota/QuotaReportingProductDetails";
import { QuotaReportingQuotaDistributions } from "../../partials/LiveStock/quota/QuotaReportingQuotaDistributions";
const tabItems = [
{ label: "محصول" },

View File

@@ -1,17 +1,17 @@
import { useEffect, useState } from "react";
import { useModalStore } from "../context/zustand-store/appStore";
import { useApiRequest } from "../utils/useApiRequest";
import { Popover } from "../components/PopOver/PopOver";
import { Tooltip } from "../components/Tooltip/Tooltip";
import Button from "../components/Button/Button";
import { DeleteButtonForPopOver } from "../components/PopOverButtons/PopOverButtons";
import { Grid } from "../components/Grid/Grid";
import Table from "../components/Table/Table";
import { AddRole } from "../partials/management/AddRole";
import { getFaPermissions } from "../utils/getFaPermissions";
import ShowStringList from "../components/ShowStringList/ShowStringList";
import ShowMoreInfo from "../components/ShowMoreInfo/ShowMoreInfo";
import Typography from "../components/Typography/Typography";
import { useModalStore } from "../../context/zustand-store/appStore";
import { useApiRequest } from "../../utils/useApiRequest";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import Button from "../../components/Button/Button";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
import { Grid } from "../../components/Grid/Grid";
import Table from "../../components/Table/Table";
import { AddRole } from "../../partials/LiveStock/management/AddRole";
import { getFaPermissions } from "../../utils/getFaPermissions";
import ShowStringList from "../../components/ShowStringList/ShowStringList";
import ShowMoreInfo from "../../components/ShowMoreInfo/ShowMoreInfo";
import Typography from "../../components/Typography/Typography";
export default function Roles() {
const { openModal } = useModalStore();
@@ -40,7 +40,7 @@ export default function Roles() {
acc[item.page].names.push(item.name);
acc[item.page].descriptions.push(item.description);
return acc;
}, {})
}, {}),
);
return [

View File

@@ -1,10 +1,10 @@
import { useState } from "react";
import { Grid } from "../components/Grid/Grid";
import Tabs from "../components/Tab/Tab";
import SettingCard from "../components/SettingCard/SettingCard";
import { Grid } from "../../components/Grid/Grid";
import Tabs from "../../components/Tab/Tab";
import SettingCard from "../../components/SettingCard/SettingCard";
import { ShieldExclamationIcon, MapPinIcon } from "@heroicons/react/24/outline";
import { useModalStore } from "../context/zustand-store/appStore";
import { CooperativesSettingsTable } from "../partials/units/CooperativesSettingsTable";
import { useModalStore } from "../../context/zustand-store/appStore";
import { CooperativesSettingsTable } from "../../partials/LiveStock/units/CooperativesSettingsTable";
const tabItems = [
{ label: "اتحادیه ها", visible: false },

View File

@@ -1,8 +1,8 @@
import { useState } from "react";
import { Grid } from "../components/Grid/Grid";
import Tabs from "../components/Tab/Tab";
import TagActiveDistributions from "../partials/tagging/TagActiveDistributions";
import TagCanceledDistributions from "../partials/tagging/TagCanceledDistributions";
import { Grid } from "../../components/Grid/Grid";
import Tabs from "../../components/Tab/Tab";
import TagActiveDistributions from "../../partials/LiveStock/tagging/TagActiveDistributions";
import TagCanceledDistributions from "../../partials/LiveStock/tagging/TagCanceledDistributions";
export default function TagDistribtution() {
const [selectedTab, setSelectedTab] = useState<number>(0);

View File

@@ -0,0 +1,412 @@
import { useEffect, useState } from "react";
import { useParams } from "@tanstack/react-router";
import { Bars3Icon, CubeIcon, SparklesIcon } from "@heroicons/react/24/outline";
import { useApiRequest } from "../../utils/useApiRequest";
import { useModalStore } from "../../context/zustand-store/appStore";
import { formatJustDate, formatJustTime } from "../../utils/formatTime";
import ShowMoreInfo from "../../components/ShowMoreInfo/ShowMoreInfo";
import { Grid } from "../../components/Grid/Grid";
import Typography from "../../components/Typography/Typography";
import Table from "../../components/Table/Table";
import { Popover } from "../../components/PopOver/PopOver";
import Button from "../../components/Button/Button";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import { DistributeFromDistribution } from "../../partials/LiveStock/tagging/DistributeFromDistribution";
import { DocumentOperation } from "../../components/DocumentOperation/DocumentOperation";
import { DocumentDownloader } from "../../components/DocumentDownloader/DocumentDownloader";
import { BooleanQuestion } from "../../components/BooleanQuestion/BooleanQuestion";
import { useUserProfileStore } from "../../context/zustand-store/userStore";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
const speciesMap: Record<number, string> = {
1: "گاو",
2: "گاومیش",
3: "شتر",
4: "گوسفند",
5: "بز",
};
export default function TagDistribtutionDetails() {
const { id } = useParams({ strict: false });
const { openModal } = useModalStore();
const [childTableInfo, setChildTableInfo] = useState({
page: 1,
page_size: 10,
});
const [childTableData, setChildTableData] = useState([]);
const { data, refetch: refetchData } = useApiRequest({
api: `/tag/web/api/v1/tag_distribution_batch/${id}/`,
method: "get",
queryKey: ["tagBatchInnerDashboard", id],
enabled: !!id,
});
const { data: childData, refetch: refetchChildList } = useApiRequest({
api: `/tag/web/api/v1/tag_distribution_batch/${id}/child_list/`,
method: "get",
queryKey: ["tagDistributionChildList", id, childTableInfo],
params: {
...childTableInfo,
},
enabled: !!id,
});
const { profile } = useUserProfileStore();
const showAssignDocColumn =
childData?.results?.some(
(item: any) =>
profile?.role?.type?.key === "ADM" ||
profile?.organization?.id === item?.assigned_org?.id,
) ?? false;
const AbleToSeeAssignDoc = (item: any) => {
if (
profile?.role?.type?.key === "ADM" ||
profile?.organization?.id === item?.assigned_org?.id
) {
return (
<DocumentOperation
key={item?.id}
downloadLink={`/tag/web/api/v1/tag_distribution_batch/${item?.id}/distribution_pdf_view/`}
payloadKey="dist_exit_document"
validFiles={["pdf"]}
page="tag_distribution"
access="Upload-Assign-Document"
uploadLink={`/tag/web/api/v1/tag_distribution_batch/${item?.id}/assign_document/`}
onUploadSuccess={handleUpdate}
/>
);
} else {
return "-";
}
};
const handleUpdate = () => {
refetchData();
refetchChildList();
};
useEffect(() => {
if (childData?.results) {
const formattedData = childData.results.map(
(item: any, index: number) => {
const dist = item?.distributions;
return [
childTableInfo.page === 1
? index + 1
: index +
childTableInfo.page_size * (childTableInfo.page - 1) +
1,
item?.dist_batch_identity ?? "-",
`${formatJustDate(item?.create_date) ?? "-"} (${
formatJustDate(item?.create_date)
? (formatJustTime(item?.create_date) ?? "-")
: "-"
})`,
item?.assigner_org?.name ?? "-",
item?.assigned_org?.name ?? "-",
item?.total_tag_count?.toLocaleString() ?? "-",
item?.total_distributed_tag_count?.toLocaleString() ?? "-",
item?.remaining_tag_count?.toLocaleString() ?? "-",
item?.distribution_type === "batch"
? "توزیع گروهی"
: "توزیع تصادفی",
<ShowMoreInfo key={item?.id} title="جزئیات توزیع">
<Grid container column className="gap-4 w-full">
{dist?.map((opt: any, idx: number) => (
<Grid
key={idx}
container
column
className="gap-3 w-full rounded-xl border border-gray-200 dark:border-gray-700 p-4"
>
<Grid container className="gap-2 items-center">
<SparklesIcon className="w-5 h-5 text-gray-500 dark:text-gray-300" />
<Typography variant="body2" className="font-medium">
گونه:
</Typography>
<Typography
variant="body2"
className="text-gray-700 dark:text-gray-300"
>
{speciesMap[opt?.species_code] ?? "-"}
</Typography>
</Grid>
{item?.distribution_type === "batch" &&
opt?.serial_from != null && (
<Grid container className="gap-2 items-center">
<Bars3Icon className="w-5 h-5 text-gray-500 dark:text-gray-300" />
<Typography variant="body2" className="font-medium">
بازه سریال:
</Typography>
<Typography
variant="body2"
className="text-gray-600 dark:text-gray-400"
>
از {opt?.serial_from ?? "-"} تا{" "}
{opt?.serial_to ?? "-"}
</Typography>
</Grid>
)}
<Grid container className="gap-2 items-center">
<CubeIcon className="w-5 h-5 text-gray-500 dark:text-gray-300" />
<Typography variant="body2" className="font-medium">
تعداد پلاک:
</Typography>
<Typography
variant="body2"
className="text-gray-700 dark:text-gray-300"
>
{opt?.total_tag_count?.toLocaleString() ?? "-"}
</Typography>
</Grid>
<Grid container className="gap-2 items-center">
<CubeIcon className="w-5 h-5 text-gray-500 dark:text-gray-300" />
<Typography variant="body2" className="font-medium">
پلاک های توزیع شده:
</Typography>
<Typography
variant="body2"
className="text-gray-700 dark:text-gray-300"
>
{opt?.distributed_number?.toLocaleString() ?? "-"}
</Typography>
</Grid>
<Grid container className="gap-2 items-center">
<CubeIcon className="w-5 h-5 text-gray-500 dark:text-gray-300" />
<Typography variant="body2" className="font-medium">
پلاک های باقیمانده:
</Typography>
<Typography
variant="body2"
className="text-gray-700 dark:text-gray-300"
>
{opt?.remaining_number?.toLocaleString() ?? "-"}
</Typography>
</Grid>
</Grid>
))}
</Grid>
</ShowMoreInfo>,
...(showAssignDocColumn ? [AbleToSeeAssignDoc(item)] : []),
<DocumentDownloader
key={`doc-${item?.id}`}
link={item?.warehouse_exit_doc}
title="دانلود"
/>,
item?.exit_doc_status ? (
"تایید شده"
) : (
<Button
key={`btn-${item?.id}`}
page="tag_distribution"
access="Accept-Assign-Document"
size="small"
disabled={item?.exit_doc_status}
onClick={() => {
openModal({
title: "تایید سند خروج",
content: (
<BooleanQuestion
api={`/tag/web/api/v1/tag_distribution_batch/${item?.id}/accept_exit_doc/`}
method="post"
getData={handleUpdate}
title="آیا از تایید سند خروج مطمئنید؟"
/>
),
});
}}
>
تایید سند خروج
</Button>
),
<Popover key={`popover-${item?.id}`}>
<Tooltip title="ویرایش توزیع" position="right">
<Button
variant="edit"
page="tag_distribution"
access="Submit-Tag-Distribution"
onClick={() => {
openModal({
title: "ویرایش توزیع",
content: (
<DistributeFromDistribution
getData={handleUpdate}
item={item}
isEdit
parentDistributions={data?.distributions}
/>
),
});
}}
/>
</Tooltip>
<DeleteButtonForPopOver
page="tag_distribution"
access="Delete-Tag-Distribution"
api={`/tag/web/api/v1/tag_distribution_batch/${item?.id}/`}
getData={handleUpdate}
/>
</Popover>,
];
},
);
setChildTableData(formattedData);
} else {
setChildTableData([]);
}
}, [childData, childTableInfo]);
const dist = data?.distributions;
return (
<Grid container column className="gap-4 mt-2">
<Grid isDashboard>
<Table
isDashboard
title="مشخصات توزیع پلاک"
noSearch
noPagination
columns={[
"شناسه توزیع",
"تاریخ ثبت",
"توزیع کننده",
"دریافت کننده",
"تعداد کل پلاک",
"پلاک های توزیع شده",
"پلاک های باقیمانده",
"نوع توزیع",
"جزئیات توزیع",
]}
rows={[
[
data?.dist_batch_identity ?? "-",
`${formatJustDate(data?.create_date) ?? "-"} (${
formatJustDate(data?.create_date)
? (formatJustTime(data?.create_date) ?? "-")
: "-"
})`,
data?.assigner_org?.name ?? "-",
data?.assigned_org?.name ?? "-",
data?.total_tag_count?.toLocaleString() ?? "-",
data?.total_distributed_tag_count?.toLocaleString() ?? "-",
data?.remaining_tag_count?.toLocaleString() ?? "-",
data?.distribution_type === "batch"
? "توزیع گروهی"
: "توزیع تصادفی",
<ShowMoreInfo key={data?.id} title="جزئیات توزیع">
<Grid container column className="gap-4 w-full">
{dist?.map((opt: any, index: number) => (
<Grid
key={index}
container
column
className="gap-3 w-full rounded-xl border border-gray-200 dark:border-gray-700 p-4"
>
<Grid container className="gap-2 items-center">
<SparklesIcon className="w-5 h-5 text-gray-500 dark:text-gray-300" />
<Typography variant="body2" className="font-medium">
گونه:
</Typography>
<Typography
variant="body2"
className="text-gray-700 dark:text-gray-300"
>
{speciesMap[opt?.species_code] ?? "-"}
</Typography>
</Grid>
{data?.distribution_type === "batch" &&
opt?.serial_from != null && (
<Grid container className="gap-2 items-center">
<Bars3Icon className="w-5 h-5 text-gray-500 dark:text-gray-300" />
<Typography variant="body2" className="font-medium">
بازه سریال:
</Typography>
<Typography
variant="body2"
className="text-gray-600 dark:text-gray-400"
>
از {opt?.serial_from ?? "-"} تا{" "}
{opt?.serial_to ?? "-"}
</Typography>
</Grid>
)}
<Grid container className="gap-2 items-center">
<CubeIcon className="w-5 h-5 text-gray-500 dark:text-gray-300" />
<Typography variant="body2" className="font-medium">
تعداد پلاک:
</Typography>
<Typography
variant="body2"
className="text-gray-700 dark:text-gray-300"
>
{opt?.total_tag_count?.toLocaleString() ?? "-"}
</Typography>
</Grid>
<Grid container className="gap-2 items-center">
<CubeIcon className="w-5 h-5 text-gray-500 dark:text-gray-300" />
<Typography variant="body2" className="font-medium">
پلاک های توزیع شده:
</Typography>
<Typography
variant="body2"
className="text-gray-700 dark:text-gray-300"
>
{opt?.distributed_number?.toLocaleString() ?? "-"}
</Typography>
</Grid>
<Grid container className="gap-2 items-center">
<CubeIcon className="w-5 h-5 text-gray-500 dark:text-gray-300" />
<Typography variant="body2" className="font-medium">
پلاک های باقیمانده:
</Typography>
<Typography
variant="body2"
className="text-gray-700 dark:text-gray-300"
>
{opt?.remaining_number?.toLocaleString() ?? "-"}
</Typography>
</Grid>
</Grid>
))}
</Grid>
</ShowMoreInfo>,
],
]}
/>
</Grid>
<Table
className="mt-2"
onChange={setChildTableInfo}
count={childData?.count || 0}
isPaginated
title="توزیع های مجدد"
columns={[
"ردیف",
"شناسه توزیع",
"تاریخ ثبت",
"توزیع کننده",
"دریافت کننده",
"تعداد کل پلاک",
"پلاک های توزیع شده",
"پلاک های باقیمانده",
"نوع توزیع",
"جزئیات توزیع",
...(showAssignDocColumn ? ["امضا سند خروج از انبار"] : []),
"سند خروج از انبار",
"تایید سند خروج",
"عملیات",
]}
rows={childTableData}
/>
</Grid>
);
}

View File

@@ -0,0 +1,27 @@
import { useState } from "react";
import { Grid } from "../../components/Grid/Grid";
import Tabs from "../../components/Tab/Tab";
import Taggings from "../../partials/LiveStock/tagging/Taggings";
import Tags from "../../partials/LiveStock/tagging/Tags";
const tabItems = [
{ label: "ثبت پلاک" },
{ label: "پلاک ها", page: "tagging_detail", access: "Show-Tagging-Detail" },
];
export default function Tagging() {
const [selectedTab, setSelectedTab] = useState<number>(0);
const handleTabChange = (index: number) => {
setSelectedTab(index);
};
return (
<Grid container column className="justify-center mt-2">
<Tabs tabs={tabItems} onChange={handleTabChange} size="medium" />
<Grid container column className="mt-2">
{selectedTab === 0 && <Taggings />}
{selectedTab === 1 && <Tags />}
</Grid>
</Grid>
);
}

View File

@@ -1,16 +1,19 @@
import { useEffect, useState } from "react";
import Table from "../components/Table/Table";
import { Grid } from "../components/Grid/Grid";
import { useApiRequest } from "../utils/useApiRequest";
import { formatJustDate, formatJustTime } from "../utils/formatTime";
import { TableButton } from "../components/TableButton/TableButton";
import { useModalStore } from "../context/zustand-store/appStore";
import TransactionDetails from "../partials/transactions/TransactionDetails";
import { DashboardResponse, ProductSummaryItem } from "../types/transactions";
import { ProductSummaryModal } from "../partials/transactions/ProductSummaryModal";
import { PaginationParameters } from "../components/PaginationParameters/PaginationParameters";
import TransactionSharingDetails from "../partials/transactions/TransactionSharingDetails";
import { convertNumberToPersian } from "../utils/convertNumberToPersian";
import Table from "../../components/Table/Table";
import { Grid } from "../../components/Grid/Grid";
import { useApiRequest } from "../../utils/useApiRequest";
import { formatJustDate, formatJustTime } from "../../utils/formatTime";
import { TableButton } from "../../components/TableButton/TableButton";
import { useModalStore } from "../../context/zustand-store/appStore";
import TransactionDetails from "../../partials/LiveStock/transactions/TransactionDetails";
import {
DashboardResponse,
ProductSummaryItem,
} from "../../types/transactions";
import { ProductSummaryModal } from "../../partials/LiveStock/transactions/ProductSummaryModal";
import { PaginationParameters } from "../../components/PaginationParameters/PaginationParameters";
import TransactionSharingDetails from "../../partials/LiveStock/transactions/TransactionSharingDetails";
import { convertNumberToPersian } from "../../utils/convertNumberToPersian";
type TransactionResponse = {
results?: any[];
@@ -72,7 +75,7 @@ export default function Transactions() {
.filter((unit: any) => unit);
const totalWeight = items.reduce(
(sum: number, p: any) => sum + (p?.weight || 0),
0
0,
);
let weightNature;
@@ -111,12 +114,12 @@ export default function Transactions() {
item?.transaction_status === "waiting"
? "درحال انتظار"
: item?.transaction_status === "success"
? "موفق"
: item?.transaction_status === "failed"
? `ناموفق ( ${item?.result_text || "-"} ${
item?.transaction_status_code || ""
} )`
: "-",
? "موفق"
: item?.transaction_status === "failed"
? `ناموفق ( ${item?.result_text || "-"} ${
item?.transaction_status_code || ""
} )`
: "-",
<TableButton
size="small"
key={i}
@@ -210,7 +213,7 @@ export default function Transactions() {
DashboardData?.transaction_summary?.total_amount?.toLocaleString() ||
0,
convertNumberToPersian(
DashboardData?.transaction_summary?.total_amount || 0
DashboardData?.transaction_summary?.total_amount || 0,
),
DashboardData?.transaction_summary?.total_weight?.toLocaleString() ||
0,

View File

@@ -1,11 +1,11 @@
import { useEffect, useState } from "react";
import { useApiRequest } from "../utils/useApiRequest";
import { Grid } from "../components/Grid/Grid";
import Table from "../components/Table/Table";
import { Popover } from "../components/PopOver/PopOver";
import { Tooltip } from "../components/Tooltip/Tooltip";
import Button from "../components/Button/Button";
import { COOPERATIVE_LIST } from "../routes/paths";
import { useApiRequest } from "../../utils/useApiRequest";
import { Grid } from "../../components/Grid/Grid";
import Table from "../../components/Table/Table";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import Button from "../../components/Button/Button";
import { COOPERATIVE_LIST } from "../../routes/paths";
import { useNavigate } from "@tanstack/react-router";
export default function Unions() {

View File

@@ -0,0 +1,81 @@
import { useEffect, useState } from "react";
import { Grid } from "../../components/Grid/Grid";
import Table from "../../components/Table/Table";
import { useApiRequest } from "../../utils/useApiRequest";
import { formatJustDate } from "../../utils/formatTime";
import CheckTrackingCode from "../../components/CheckTrackingCode/CheckTrackingCode";
type UnitsInventoryResponse = {
results?: any[];
count?: number;
};
export default function UnitsInventory() {
const [params, setParams] = useState({ page: 1, page_size: 10 });
const [tableData, setTableData] = useState<any[]>([]);
const { data } = useApiRequest<UnitsInventoryResponse>({
api: "/rsi/web/api/v1/org_rsi_data/",
method: "get",
params,
queryKey: ["units_inventory", params],
});
useEffect(() => {
if (data?.results) {
const formattedData = data.results.map((item: any, i: number) => [
params.page === 1
? i + 1
: i + params.page_size * (params.page - 1) + 1,
<CheckTrackingCode key={`${item?.id}-tracking`} tracking={item?.tracking} />,
item?.record_id || "-",
formatJustDate(item?.data) || "-",
item?.product || "-",
item?.quantity ? Number(item.quantity).toLocaleString() : "-",
item?.unit || "-",
item?.origin_province || "-",
item?.destination_province || "-",
item?.origin_city || "-",
item?.destination_city || "-",
item?.origin || "-",
item?.destination || "-",
item?.driver_name || "-",
item?.car_tracking_code || "-",
]);
setTableData(formattedData);
} else {
setTableData([]);
}
}, [data]);
return (
<Grid container column>
<Table
className="mt-2"
onChange={setParams}
count={data?.count || 0}
isPaginated
title="انبار واحدها"
columns={[
"ردیف",
"کد رهگیری",
"شماره رکورد",
"تاریخ",
"محصول",
"مقدار",
"واحد",
"استان مبدا",
"استان مقصد",
"شهرستان مبدا",
"شهرستان مقصد",
"مبدا",
"مقصد",
"راننده",
"کد رهگیری خودرو",
]}
rows={tableData}
/>
</Grid>
);
}

View File

@@ -1,22 +1,22 @@
import Table from "../components/Table/Table";
import { Grid } from "../components/Grid/Grid";
import { useApiRequest } from "../utils/useApiRequest";
import Table from "../../components/Table/Table";
import { Grid } from "../../components/Grid/Grid";
import { useApiRequest } from "../../utils/useApiRequest";
import { useEffect, useState } from "react";
import { getFaPermissions } from "../utils/getFaPermissions";
import { Popover } from "../components/PopOver/PopOver";
import { Tooltip } from "../components/Tooltip/Tooltip";
import Button from "../components/Button/Button";
import { DeleteButtonForPopOver } from "../components/PopOverButtons/PopOverButtons";
import { getFaPermissions } from "../../utils/getFaPermissions";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import Button from "../../components/Button/Button";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
import {
useDrawerStore,
useModalStore,
} from "../context/zustand-store/appStore";
import { EditAccess } from "../partials/management/EditAccess";
import { AddUser } from "../partials/management/AddUser";
import ShowStringList from "../components/ShowStringList/ShowStringList";
import ShowMoreInfo from "../components/ShowMoreInfo/ShowMoreInfo";
import AutoComplete from "../components/AutoComplete/AutoComplete";
import { useUserProfileStore } from "../context/zustand-store/userStore";
} from "../../context/zustand-store/appStore";
import { EditAccess } from "../../partials/Admin/EditAccess";
import { AddUser } from "../../partials/LiveStock/management/AddUser";
import ShowStringList from "../../components/ShowStringList/ShowStringList";
import ShowMoreInfo from "../../components/ShowMoreInfo/ShowMoreInfo";
import AutoComplete from "../../components/AutoComplete/AutoComplete";
import { useUserProfileStore } from "../../context/zustand-store/userStore";
type PermissionType = {
page_name: string;
@@ -95,7 +95,7 @@ export default function Users() {
>
<ShowStringList
strings={item?.permissions?.map((option: PermissionType) =>
getFaPermissions(option?.page_name)
getFaPermissions(option?.page_name),
)}
/>
</ShowMoreInfo>,

View File

@@ -1,9 +1,9 @@
import { useState } from "react";
import { Grid } from "../components/Grid/Grid";
import Tabs from "../components/Tab/Tab";
import Pages from "../partials/management/Pages";
import Access from "../partials/management/Access";
import UnusedAccess from "../partials/management/UnusedAccess";
import Pages from "../partials/Admin/Pages";
import Access from "../partials/Admin/Access";
import UnusedAccess from "../partials/Admin/UnusedAccess";
const tabItems = [
{ label: "صفحات" },

View File

@@ -7,7 +7,11 @@ import { useUserProfileStore } from "../context/zustand-store/userStore";
import { getUserPermissions } from "../utils/getUserAvalableItems";
import { ItemWithSubItems } from "../types/userPermissions";
import { useNavigate } from "@tanstack/react-router";
import { Bars3Icon, QueueListIcon } from "@heroicons/react/24/outline";
import {
Bars3Icon,
QueueListIcon,
Squares2X2Icon,
} from "@heroicons/react/24/outline";
import { getFaPermissions } from "../utils/getFaPermissions";
import SVGImage from "../components/SvgImage/SvgImage";
@@ -58,13 +62,13 @@ export const Menu = () => {
const { profile } = useUserProfileStore();
const menuItems: ItemWithSubItems[] = getUserPermissions(
profile?.permissions
profile?.permissions,
);
const getOpenedItem = () => {
if (window.location.pathname !== "/") {
const matchedIndex = menuItems.findIndex((item) =>
item.subItems.some((sub) => sub.path === window.location.pathname)
item.subItems.some((sub) => sub.path === window.location.pathname),
);
return matchedIndex;
} else {
@@ -73,12 +77,62 @@ export const Menu = () => {
};
const [openIndex, setOpenIndex] = useState<number | null>(getOpenedItem());
const [openDomains, setOpenDomains] = useState<Record<string, boolean>>({});
const navigate = useNavigate();
const adminItems = menuItems
.map((item, index) => ({ ...item, originalIndex: index }))
.filter((item) => item.en === "admin");
const indexedNonAdminItems = menuItems
.map((item, index) => ({ ...item, originalIndex: index }))
.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 groupedItems = indexedNonAdminItems.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 & { originalIndex: number })[]
>,
);
const showDomainGrouping = Object.keys(groupedItems).length > 1;
const toggleSubmenu = (index: number) => {
setOpenIndex((prev) => (prev === index ? null : index));
};
const isDomainOpen = (domainTitle: string) =>
openDomains[domainTitle] ?? true;
const toggleDomain = (domainTitle: string) => {
setOpenDomains((prev) => ({
...prev,
[domainTitle]: !(prev[domainTitle] ?? true),
}));
};
return (
<Grid
container
@@ -99,14 +153,14 @@ export const Menu = () => {
animate="visible"
className="flex flex-col items-center gap-4 w-full"
>
{menuItems.map(({ fa, icon: Icon, subItems }, index) => (
{adminItems.map(({ fa, icon: Icon, subItems, originalIndex }) => (
<motion.div
key={index}
key={`admin-${originalIndex}`}
variants={itemVariants}
className="w-full max-w-sm"
>
<motion.button
onClick={() => toggleSubmenu(index)}
onClick={() => toggleSubmenu(originalIndex)}
whileTap={{ scale: 0.97 }}
className="w-full flex justify-between items-center gap-3 px-4 py-3 rounded-lg shadow-xs dark:shadow-sm shadow-dark-300 dark:shadow-dark-500 backdrop-blur-md bg-gradient-to-r from-transparent to-transparent dark:via-gray-800 via-gray-100 border border-dark-200 dark:border-dark-700 text-dark-800 dark:text-dark-100 transition-all duration-200"
>
@@ -120,13 +174,13 @@ export const Menu = () => {
</div>
<ChevronDownIcon
className={`w-5 h-5 text-dark-500 dark:text-dark-300 transition-transform duration-300 ${
openIndex === index ? "rotate-180" : ""
openIndex === originalIndex ? "rotate-180" : ""
}`}
/>
</motion.button>
<AnimatePresence>
{openIndex === index && (
{openIndex === originalIndex && (
<motion.div
key="submenu"
variants={submenuVariants}
@@ -156,6 +210,146 @@ export const Menu = () => {
</AnimatePresence>
</motion.div>
))}
{showDomainGrouping
? Object.entries(groupedItems).map(([domainTitle, domainItems]) => (
<div key={domainTitle} className="w-full max-w-sm">
<button
onClick={() => toggleDomain(domainTitle)}
className="w-full px-1 py-1 text-primary-700 dark:text-primary-200 text-sm font-bold flex items-center justify-between cursor-pointer"
>
<div className="flex items-center gap-2">
<Squares2X2Icon className="w-4 h-4 text-primary-600 dark:text-primary-300" />
<span>{domainTitle}</span>
</div>
<ChevronDownIcon
className={`w-4 h-4 transition-transform duration-300 ${
isDomainOpen(domainTitle) ? "rotate-180" : ""
}`}
/>
</button>
{(isDomainOpen(domainTitle) || !domainTitle) && (
<div className="mt-1 mr-2 border-r-2 border-primary-200 dark:border-primary-500/40 pr-2 flex flex-col gap-3">
{domainItems.map(
({ fa, icon: Icon, subItems, originalIndex }) => (
<motion.div
key={`group-${domainTitle}-${originalIndex}`}
variants={itemVariants}
className="w-full"
>
<motion.button
onClick={() => toggleSubmenu(originalIndex)}
whileTap={{ scale: 0.97 }}
className="w-full flex justify-between items-center gap-3 px-4 py-3 rounded-lg shadow-xs dark:shadow-sm shadow-dark-300 dark:shadow-dark-500 backdrop-blur-md bg-gradient-to-r from-transparent to-transparent dark:via-gray-800 via-gray-100 border border-dark-200 dark:border-dark-700 text-dark-800 dark:text-dark-100 transition-all duration-200"
>
<div className="flex items-center gap-3">
<SVGImage
src={Icon}
className={` text-primary-800 dark:text-primary-100`}
/>
<span className="text-base font-medium">{fa}</span>
</div>
<ChevronDownIcon
className={`w-5 h-5 text-dark-500 dark:text-dark-300 transition-transform duration-300 ${
openIndex === originalIndex ? "rotate-180" : ""
}`}
/>
</motion.button>
<AnimatePresence>
{openIndex === originalIndex && (
<motion.div
key="submenu"
variants={submenuVariants}
initial="hidden"
animate="visible"
exit="exit"
className="mt-2 mr-4 border-r-2 border-primary-500 dark:border-primary-400 pr-4 flex flex-col gap-2"
>
{subItems
.filter((item) => !item?.path.includes("$"))
?.map((sub, subIndex) => (
<motion.button
onClick={() => {
navigate({ to: sub.path });
}}
key={subIndex}
whileTap={{ scale: 0.97 }}
className="text-sm flex items-center gap-2 text-dark-700 dark:text-dark-200 bg-white dark:bg-dark-700 shadow-sm px-3 py-2 rounded-lg w-full text-right"
>
<QueueListIcon className="w-3" />
{getFaPermissions(sub.name)}
</motion.button>
))}
</motion.div>
)}
</AnimatePresence>
</motion.div>
),
)}
</div>
)}
</div>
))
: indexedNonAdminItems.map(
({ fa, icon: Icon, subItems, originalIndex }) => (
<motion.div
key={`plain-${originalIndex}`}
variants={itemVariants}
className="w-full max-w-sm"
>
<motion.button
onClick={() => toggleSubmenu(originalIndex)}
whileTap={{ scale: 0.97 }}
className="w-full flex justify-between items-center gap-3 px-4 py-3 rounded-lg shadow-xs dark:shadow-sm shadow-dark-300 dark:shadow-dark-500 backdrop-blur-md bg-gradient-to-r from-transparent to-transparent dark:via-gray-800 via-gray-100 border border-dark-200 dark:border-dark-700 text-dark-800 dark:text-dark-100 transition-all duration-200"
>
<div className="flex items-center gap-3">
<SVGImage
src={Icon}
className={` text-primary-800 dark:text-primary-100`}
/>
<span className="text-base font-medium">{fa}</span>
</div>
<ChevronDownIcon
className={`w-5 h-5 text-dark-500 dark:text-dark-300 transition-transform duration-300 ${
openIndex === originalIndex ? "rotate-180" : ""
}`}
/>
</motion.button>
<AnimatePresence>
{openIndex === originalIndex && (
<motion.div
key="submenu"
variants={submenuVariants}
initial="hidden"
animate="visible"
exit="exit"
className="mt-2 mr-4 border-r-2 border-primary-500 dark:border-primary-400 pr-4 flex flex-col gap-2"
>
{subItems
.filter((item) => !item?.path.includes("$"))
?.map((sub, subIndex) => (
<motion.button
onClick={() => {
navigate({ to: sub.path });
}}
key={subIndex}
whileTap={{ scale: 0.97 }}
className="text-sm flex items-center gap-2 text-dark-700 dark:text-dark-200 bg-white dark:bg-dark-700 shadow-sm px-3 py-2 rounded-lg w-full text-right"
>
<QueueListIcon className="w-3" />
{getFaPermissions(sub.name)}
</motion.button>
))}
</motion.div>
)}
</AnimatePresence>
</motion.div>
),
)}
</motion.div>
</Grid>
);

View File

@@ -16,7 +16,7 @@ import { Grid } from "../components/Grid/Grid";
import { useDarkMode } from "../hooks/useDarkMode";
import clsx from "clsx";
import { useModalStore } from "../context/zustand-store/appStore";
import { Logout } from "../partials/auth/Logout";
import { Logout } from "../partials/Auth/Logout";
import { useUserProfileStore } from "../context/zustand-store/userStore";
import { formatJustDate } from "../utils/formatTime";
import bg from "../assets/images/profile-bg.png";
@@ -46,14 +46,14 @@ const ProfileCard = ({
return (
<Grid
className={clsx(
"group relative p-4 transition-all duration-500 hover:-translate-y-1 hover:scale-[1.01]"
"group relative p-4 transition-all duration-500 hover:-translate-y-1 hover:scale-[1.01]",
)}
>
<Grid className="relative flex items-start gap-4">
<Grid
className={clsx(
"rounded-xl p-3 shadow-lg group-hover:shadow-xl transition-all duration-500 group-hover:scale-110",
"bg-primary-600"
"bg-primary-600",
)}
>
<Icon className="h-5 w-5 text-white" />
@@ -128,12 +128,12 @@ export default function UserProfile() {
<button
onClick={() => setIsDark(!isDark)}
className={clsx(
"group relative flex items-center gap-3 rounded-2xl transition-all duration-500 cursor-pointer"
"group relative flex items-center gap-3 rounded-2xl transition-all duration-500 cursor-pointer",
)}
>
<Grid
className={clsx(
"rounded-xl transition-all duration-500 group-hover:scale-110 bg-transparent"
"rounded-xl transition-all duration-500 group-hover:scale-110 bg-transparent",
)}
>
{isDark ? (
@@ -153,12 +153,12 @@ export default function UserProfile() {
navigate({ to: path });
}}
className={clsx(
"group relative flex items-center gap-3 rounded-2xl transition-all duration-500 cursor-pointer"
"group relative flex items-center gap-3 rounded-2xl transition-all duration-500 cursor-pointer",
)}
>
<Grid
className={clsx(
"rounded-xl transition-all duration-500 group-hover:scale-110 bg-transparent"
"rounded-xl transition-all duration-500 group-hover:scale-110 bg-transparent",
)}
>
<BookOpenIcon className="h-5 w-5 text-green-500" />
@@ -176,12 +176,12 @@ export default function UserProfile() {
});
}}
className={clsx(
"group relative flex items-center gap-3 rounded-2xl transition-all duration-500 cursor-pointer"
"group relative flex items-center gap-3 rounded-2xl transition-all duration-500 cursor-pointer",
)}
>
<Grid
className={clsx(
"rounded-xl transition-all duration-500 group-hover:scale-110 bg-transparent"
"rounded-xl transition-all duration-500 group-hover:scale-110 bg-transparent",
)}
>
<ArrowLeftStartOnRectangleIcon className="h-5 w-5 text-red-700" />

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="currentColor" width="800px" height="800px" viewBox="0 0 1920 1920" xmlns="http://www.w3.org/2000/svg">
<path d="M276.941 440.584v565.722c0 422.4 374.174 625.468 674.71 788.668l8.02 4.292 8.131-4.292c300.537-163.2 674.71-366.268 674.71-788.668V440.584l-682.84-321.657L276.94 440.584Zm682.73 1479.529c-9.262 0-18.523-2.372-26.993-6.89l-34.9-18.974C588.095 1726.08 164 1495.906 164 1006.306V404.78c0-21.91 12.65-41.788 32.414-51.162L935.727 5.42c15.134-7.228 32.866-7.228 48 0l739.313 348.2c19.765 9.374 32.414 29.252 32.414 51.162v601.525c0 489.6-424.207 719.774-733.779 887.943l-34.899 18.975c-8.47 4.517-17.731 6.889-27.105 6.889Zm467.158-547.652h-313.412l-91.595-91.482v-83.803H905.041v-116.78h-83.69l-58.503-58.504c-1.92.113-3.84.113-5.76.113-176.075 0-319.285-143.21-319.285-319.285 0-176.075 143.21-319.398 319.285-319.398 176.075 0 319.285 143.323 319.285 319.398 0 1.92 0 3.84-.113 5.647l350.57 350.682v313.412Zm-266.654-112.941h153.713v-153.713L958.462 750.155l3.953-37.27c1.017-123.897-91.595-216.621-205.327-216.621S550.744 588.988 550.744 702.72c0 113.845 92.612 206.344 206.344 206.344l47.21-5.309 63.811 63.7h149.873v116.78h116.781v149.986l25.412 25.299Zm-313.4-553.57c0 46.758-37.949 84.706-84.706 84.706-46.758 0-84.706-37.948-84.706-84.706s37.948-84.706 84.706-84.706c46.757 0 84.706 37.948 84.706 84.706" fill-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -196,11 +196,11 @@ const AutoComplete: React.FC<AutoCompleteProps> = ({
if (window.visualViewport) {
window.visualViewport.addEventListener(
"resize",
handleVisualViewportResize
handleVisualViewportResize,
);
window.visualViewport.addEventListener(
"scroll",
handleVisualViewportScroll
handleVisualViewportScroll,
);
}
const inputElement = inputRef.current;
@@ -222,11 +222,11 @@ const AutoComplete: React.FC<AutoCompleteProps> = ({
if (window.visualViewport) {
window.visualViewport.removeEventListener(
"resize",
handleVisualViewportResize
handleVisualViewportResize,
);
window.visualViewport.removeEventListener(
"scroll",
handleVisualViewportScroll
handleVisualViewportScroll,
);
}
const inputElement = inputRef.current;
@@ -247,7 +247,7 @@ const AutoComplete: React.FC<AutoCompleteProps> = ({
target.closest(".select-group") && !clickedInsideCurrent;
const clickedOnPortalDropdown = target.closest(
`[data-autocomplete-portal="${uniqueId}"]`
`[data-autocomplete-portal="${uniqueId}"]`,
);
if (clickedOnAnotherAutocomplete) {
@@ -318,7 +318,7 @@ const AutoComplete: React.FC<AutoCompleteProps> = ({
const preventTouchMove = (e: TouchEvent) => {
const target = e.target as HTMLElement;
const dropdown = document.querySelector(
`[data-autocomplete-portal="${uniqueId}"]`
`[data-autocomplete-portal="${uniqueId}"]`,
);
if (dropdown) {
@@ -326,7 +326,7 @@ const AutoComplete: React.FC<AutoCompleteProps> = ({
if (touch) {
const elementAtPoint = document.elementFromPoint(
touch.clientX,
touch.clientY
touch.clientY,
);
if (
elementAtPoint &&
@@ -375,12 +375,12 @@ const AutoComplete: React.FC<AutoCompleteProps> = ({
setLocalInputValue(value);
setIsTyping(true);
const filtered = data.filter((item) =>
item.value.toLowerCase().includes(value.toLowerCase())
item.value.toLowerCase().includes(value.toLowerCase()),
);
setFilteredData(filtered);
setShowOptions(true);
},
[data]
[data],
);
const handleChange = useCallback(
@@ -390,7 +390,7 @@ const AutoComplete: React.FC<AutoCompleteProps> = ({
if (onChangeValue && newSelectedKeys.length > 0) {
const selectedItem = data.find(
(item) => item.key === newSelectedKeys[0]
(item) => item.key === newSelectedKeys[0],
);
if (selectedItem) {
onChangeValue({
@@ -400,7 +400,7 @@ const AutoComplete: React.FC<AutoCompleteProps> = ({
}
}
},
[onChange, onChangeValue, data]
[onChange, onChangeValue, data],
);
const handleOptionClick = useCallback(
@@ -430,7 +430,7 @@ const AutoComplete: React.FC<AutoCompleteProps> = ({
setShowOptions(false);
}
},
[multiselect, handleChange]
[multiselect, handleChange],
);
const handleInputClick = useCallback(() => {
@@ -507,8 +507,8 @@ const AutoComplete: React.FC<AutoCompleteProps> = ({
item.originalGroupKey !== undefined
? item.originalGroupKey
: String(item.key).startsWith("__group__")
? String(item.key).slice(11)
: item.key;
? String(item.key).slice(11)
: item.key;
onGroupHeaderClick(groupKey);
} else if (!item.disabled) {
handleOptionClick(item.key);
@@ -523,8 +523,8 @@ const AutoComplete: React.FC<AutoCompleteProps> = ({
isGroupHeader && onGroupHeaderClick
? "cursor-pointer opacity-55 hover:bg-gray-100 text-dark-800 dark:text-dark-100 dark:hover:bg-primary-900/90 font-semibold bg-gray-200 dark:bg-primary-900"
: item.disabled
? "text-gray-400 dark:text-dark-500 cursor-not-allowed"
: "cursor-pointer hover:bg-primary-100 text-dark-800 dark:text-dark-100 dark:hover:bg-dark-700"
? "text-gray-400 dark:text-dark-500 cursor-not-allowed"
: "cursor-pointer hover:bg-primary-100 text-dark-800 dark:text-dark-100 dark:hover:bg-dark-700"
}
${
isSelected && !isGroupHeader
@@ -602,7 +602,7 @@ const AutoComplete: React.FC<AutoCompleteProps> = ({
>
{dropdownOptions}
</motion.ul>,
document.body
document.body,
);
}, [
showOptions,
@@ -638,6 +638,7 @@ const AutoComplete: React.FC<AutoCompleteProps> = ({
placeholder={title || "انتخاب کنید..."}
/>
<ChevronDownIcon
onClick={handleInputClick}
className={`absolute left-3 text-dark-400 dark:text-dark-100 transition-transform duration-200 ${
showOptions ? "transform rotate-180" : ""
} ${getSizeStyles(size).icon}`}

View File

@@ -7,6 +7,7 @@ import React, {
} from "react";
import clsx from "clsx";
import {
ArrowUpCircleIcon,
ChartBarIcon,
DocumentChartBarIcon,
EyeIcon,
@@ -56,7 +57,8 @@ type ButtonProps = {
| "view"
| "info"
| "chart"
| "set";
| "set"
| "share";
page?: string;
access?: string;
height?: string | number;
@@ -161,6 +163,10 @@ const Button: React.FC<ButtonProps> = ({
return (
<WrenchIcon className="w-5 h-5 text-purple-400 dark:text-purple-100" />
);
case "share":
return (
<ArrowUpCircleIcon className="w-5 h-5 text-purple-400 dark:text-purple-100" />
);
default:
return null;
}
@@ -181,7 +187,7 @@ const Button: React.FC<ButtonProps> = ({
return true;
} else {
const finded = profile?.permissions?.find(
(item: any) => item.page_name === page
(item: any) => item.page_name === page,
);
if (finded && finded.page_access.includes(access)) {
return true;
@@ -237,7 +243,7 @@ const Button: React.FC<ButtonProps> = ({
sizeStyles.padding,
sizeStyles.text,
className,
checkIsMobile() && !icon && !variant && children && mobileBorders
checkIsMobile() && !icon && !variant && children && mobileBorders,
)}
style={{ height }}
>
@@ -256,7 +262,7 @@ const Button: React.FC<ButtonProps> = ({
.then((response) => {
closeBackdrop();
const url = window.URL.createObjectURL(
new Blob([response.data])
new Blob([response.data]),
);
const link = document.createElement("a");

View File

@@ -0,0 +1,44 @@
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
import { useRef } from "react";
type CheckTrackingCodeProps = {
tracking?: string | number | null;
className?: string;
};
export default function CheckTrackingCode({
tracking,
className = "",
}: CheckTrackingCodeProps) {
const formRef = useRef<HTMLFormElement>(null);
const safeTracking = tracking ? String(tracking) : "";
const handleSubmit = () => {
if (!safeTracking) return;
formRef.current?.submit();
};
if (!safeTracking) {
return <span>-</span>;
}
return (
<form
action="https://e.ivo.ir/Rahgiri/Gidprnt.aspx"
method="post"
target="_blank"
ref={formRef}
className={`inline-flex ${className}`}
>
<input name="gid" type="hidden" value={safeTracking} />
<button
type="button"
onClick={handleSubmit}
className="group inline-flex cursor-pointer items-center gap-1.5 rounded-xl border border-primary-500/30 bg-gradient-to-r from-primary-500/10 to-primary-500/20 px-3 py-1.5 text-xs font-medium text-primary-700 transition-all duration-200 hover:from-primary-500/20 hover:to-primary-500/30 hover:shadow-sm dark:border-primary-200/20 dark:from-primary-100/10 dark:to-primary-100/20 dark:text-primary-200"
>
<span className="tracking-wide">{safeTracking}</span>
<ArrowTopRightOnSquareIcon className="h-3.5 w-3.5 transition-transform duration-200 group-hover:-translate-y-0.5 group-hover:translate-x-0.5" />
</button>
</form>
);
}

View File

@@ -0,0 +1,236 @@
import React, { useRef, useState, useEffect, ChangeEvent } from "react";
import {
ArrowDownTrayIcon,
ArrowUpTrayIcon,
CheckCircleIcon,
} from "@heroicons/react/24/outline";
import api from "../../utils/axios";
import { useBackdropStore } from "../../context/zustand-store/appStore";
import { useToast } from "../../hooks/useToast";
import { useUserProfileStore } from "../../context/zustand-store/userStore";
import { RolesContextMenu } from "../Button/RolesContextMenu";
interface DocumentOperationProps {
downloadLink: string;
uploadLink: string;
validFiles?: string[];
payloadKey: string;
onUploadSuccess?: () => void;
page?: string;
access?: string;
limitSize?: number;
}
const buildAcceptString = (extensions: string[]): string => {
const mimeTypes: string[] = [];
extensions.forEach((ext) => {
const lower = ext.toLowerCase().replace(".", "");
if (lower === "img" || lower === "image") {
mimeTypes.push("image/*");
} else {
mimeTypes.push(`.${lower}`);
}
});
return mimeTypes.join(",");
};
export const DocumentOperation = ({
downloadLink,
uploadLink,
validFiles = [],
payloadKey,
onUploadSuccess,
page = "",
access = "",
limitSize,
}: DocumentOperationProps) => {
const { openBackdrop, closeBackdrop } = useBackdropStore();
const showToast = useToast();
const fileInputRef = useRef<HTMLInputElement>(null);
const [uploadedFileName, setUploadedFileName] = useState<string>("");
const { profile } = useUserProfileStore();
const [contextMenu, setContextMenu] = useState<{
x: number;
y: number;
} | null>(null);
const isAdmin = profile?.role?.type?.key === "ADM";
const ableToSee = () => {
if (!access || !page) {
return true;
}
const found = profile?.permissions?.find(
(item: any) => item.page_name === page,
);
if (found && found.page_access.includes(access)) {
return true;
}
return false;
};
const handleContextMenu = (e: React.MouseEvent<HTMLDivElement>) => {
if (isAdmin && page && access) {
e.preventDefault();
e.stopPropagation();
setContextMenu({
x: e.clientX,
y: e.clientY,
});
}
};
useEffect(() => {
const handleClick = () => {
if (contextMenu) {
setContextMenu(null);
}
};
if (contextMenu) {
document.addEventListener("click", handleClick);
}
return () => {
document.removeEventListener("click", handleClick);
};
}, [contextMenu]);
const handleDownload = async () => {
if (!downloadLink) return;
openBackdrop();
try {
const response = await api.get(downloadLink, {
responseType: "blob",
});
const contentDisposition = response.headers["content-disposition"];
let fileName = "document";
if (contentDisposition) {
const match = contentDisposition.match(
/filename\*?=(?:UTF-8''|"?)([^";]+)/i,
);
if (match?.[1]) {
fileName = decodeURIComponent(match[1].replace(/"/g, ""));
}
} else {
const urlParts = downloadLink.split("/").filter(Boolean);
const lastPart = urlParts[urlParts.length - 1];
if (lastPart && lastPart.includes(".")) {
fileName = lastPart;
}
}
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", fileName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
showToast("فایل با موفقیت دانلود شد", "success");
} catch {
showToast("خطا در دانلود فایل", "error");
} finally {
closeBackdrop();
}
};
const handleUploadClick = () => {
fileInputRef.current?.click();
};
const handleFileChange = async (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
if (fileInputRef.current) {
fileInputRef.current.value = "";
}
if (limitSize && file.size > limitSize * 1024 * 1024) {
showToast(`حداکثر حجم فایل ${limitSize} مگابایت است`, "error");
return;
}
openBackdrop();
try {
const formData = new FormData();
formData.append(payloadKey, file);
await api.post(uploadLink, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});
setUploadedFileName(file.name);
showToast("فایل با موفقیت آپلود شد", "success");
onUploadSuccess?.();
} catch {
showToast("خطا در آپلود فایل", "error");
} finally {
closeBackdrop();
}
};
const acceptString =
validFiles.length > 0 ? buildAcceptString(validFiles) : undefined;
return (
<>
<div
className="inline-flex items-center"
onContextMenu={handleContextMenu}
>
<input
type="file"
ref={fileInputRef}
onChange={handleFileChange}
className="hidden"
accept={acceptString}
/>
<button
type="button"
onClick={handleDownload}
disabled={!downloadLink || !ableToSee()}
className="flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-r-lg border border-l-0 border-primary-300 dark:border-dark-400 bg-primary-50 dark:bg-dark-600 text-primary-700 dark:text-primary-200 hover:bg-primary-100 dark:hover:bg-dark-500 transition-colors duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
>
<ArrowDownTrayIcon className="w-4 h-4" />
<span>دانلود</span>
</button>
<button
type="button"
onClick={handleUploadClick}
disabled={!uploadLink || !ableToSee()}
className="flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-l-lg border border-primary-300 dark:border-dark-400 bg-primary-600 dark:bg-primary-700 text-white hover:bg-primary-500 dark:hover:bg-primary-800 transition-colors duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
>
{uploadedFileName ? (
<CheckCircleIcon className="w-4 h-4 text-green-300" />
) : (
<ArrowUpTrayIcon className="w-4 h-4" />
)}
<span>آپلود</span>
</button>
</div>
{contextMenu && page && access && (
<RolesContextMenu
page={page}
access={access}
position={contextMenu}
onClose={() => setContextMenu(null)}
/>
)}
</>
);
};

View File

@@ -7,6 +7,7 @@ import { getNestedValue } from "../../utils/getNestedValue";
type FormEnterLocationsProps = {
title: string;
api: string;
size?: "small" | "medium" | "large";
error?: boolean;
errorMessage?: any;
multiple?: boolean;
@@ -15,6 +16,7 @@ type FormEnterLocationsProps = {
keyField?: string;
secondaryKey?: string | string[];
tertiaryKey?: string | string[];
quaternaryKey?: string | string[];
valueField?: string | string[];
valueField2?: string | string[];
valueField3?: string | string[];
@@ -25,12 +27,14 @@ type FormEnterLocationsProps = {
valueTemplateProps?: Array<{ [key: string]: "string" | "number" }>;
groupBy?: string | string[];
groupFunction?: (item: any) => string;
valueFieldFunction?: (item: any) => any;
selectField?: boolean;
};
export const FormApiBasedAutoComplete = ({
title,
api,
size,
error,
errorMessage,
onChange,
@@ -38,6 +42,7 @@ export const FormApiBasedAutoComplete = ({
keyField = "id",
secondaryKey,
tertiaryKey,
quaternaryKey,
valueField = "name",
valueField2,
valueField3,
@@ -49,6 +54,7 @@ export const FormApiBasedAutoComplete = ({
valueTemplateProps,
groupBy,
groupFunction,
valueFieldFunction,
selectField = false,
}: FormEnterLocationsProps) => {
const [data, setData] = useState<any>([]);
@@ -79,6 +85,18 @@ export const FormApiBasedAutoComplete = ({
return value;
};
const getPrimaryValue = (option: any) => {
if (valueFieldFunction) {
return valueFieldFunction(option);
}
return valueField === "page"
? getFaPermissions(option[valueField])
: typeof valueField === "string"
? option[valueField]
: getNestedValue(option, valueField);
};
const { data: apiData } = useApiRequest({
api: api,
method: "get",
@@ -94,7 +112,7 @@ export const FormApiBasedAutoComplete = ({
if (filterAddress && filterValue) {
data = apiData.results?.filter(
(item: any) =>
!filterValue.includes(getNestedValue(item, filterAddress))
!filterValue.includes(getNestedValue(item, filterAddress)),
);
} else {
data = apiData.results;
@@ -111,19 +129,14 @@ export const FormApiBasedAutoComplete = ({
? option[tertiaryKey]
: getNestedValue(option, tertiaryKey)
: undefined,
quaternaryKey: quaternaryKey
? typeof quaternaryKey === "string"
? option[quaternaryKey]
: getNestedValue(option, quaternaryKey)
: undefined,
value: valueTemplate
? valueTemplate
.replace(
/v1/g,
formatValue(
valueField === "page"
? getFaPermissions(option[valueField])
: typeof valueField === "string"
? option[valueField]
: getNestedValue(option, valueField),
"v1"
)
)
.replace(/v1/g, formatValue(getPrimaryValue(option), "v1"))
.replace(
/v2/g,
formatValue(
@@ -132,8 +145,8 @@ export const FormApiBasedAutoComplete = ({
? option[valueField2]
: getNestedValue(option, valueField2)
: "",
"v2"
)
"v2",
),
)
.replace(
/v3/g,
@@ -143,16 +156,10 @@ export const FormApiBasedAutoComplete = ({
? option[valueField3]
: getNestedValue(option, valueField3)
: "",
"v3"
)
"v3",
),
)
: `${
valueField === "page"
? getFaPermissions(option[valueField])
: typeof valueField === "string"
? option[valueField]
: getNestedValue(option, valueField)
} ${
: `${getPrimaryValue(option)} ${
valueField2
? " - " +
(typeof valueField2 === "string"
@@ -208,7 +215,7 @@ export const FormApiBasedAutoComplete = ({
setData(finalData);
const actualDataItems = finalData.filter(
(item: any) => !item.isGroupHeader && !item.disabled
(item: any) => !item.isGroupHeader && !item.disabled,
);
if (defaultKey !== undefined && defaultKey !== null) {
@@ -218,10 +225,10 @@ export const FormApiBasedAutoComplete = ({
setSelectedKeys([]);
} else {
const defaultIds = defaultKey.map((item: any) =>
typeof item === "object" ? item[keyField] : item
typeof item === "object" ? item[keyField] : item,
);
const defaultItems = actualDataItems.filter((item: any) =>
defaultIds.includes(item.key)
defaultIds.includes(item.key),
);
setSelectedKeys(defaultItems.map((item: any) => item.key));
if (onChange) {
@@ -237,12 +244,17 @@ export const FormApiBasedAutoComplete = ({
key3: item?.tertiaryKey,
}
: {}),
...(quaternaryKey
? {
key4: item?.quaternaryKey,
}
: {}),
};
})
}),
);
if (onChangeValue) {
onChangeValue(
defaultItems.map((item: any) => item.value.trim())
defaultItems.map((item: any) => item.value.trim()),
);
}
} else {
@@ -258,7 +270,7 @@ export const FormApiBasedAutoComplete = ({
? defaultKey[keyField]
: defaultKey;
const defaultItem = actualDataItems.find(
(item: any) => item.key === keyToFind
(item: any) => item.key === keyToFind,
);
if (defaultItem) {
setSelectedKeys([keyToFind]);
@@ -268,6 +280,9 @@ export const FormApiBasedAutoComplete = ({
key1: defaultItem.key,
key2: defaultItem.secondaryKey,
...(tertiaryKey ? { key3: defaultItem.tertiaryKey } : {}),
...(quaternaryKey
? { key4: defaultItem.quaternaryKey }
: {}),
});
} else {
onChange(keyToFind);
@@ -278,6 +293,7 @@ export const FormApiBasedAutoComplete = ({
key1: defaultItem.key,
key2: defaultItem.secondaryKey,
...(tertiaryKey ? { key3: defaultItem.tertiaryKey } : {}),
...(quaternaryKey ? { key4: defaultItem.quaternaryKey } : {}),
value: defaultItem.value.trim(),
});
}
@@ -296,18 +312,18 @@ export const FormApiBasedAutoComplete = ({
const groupItemKeys = groupItems.map((item: any) => item.key);
const allGroupItemsSelected = groupItemKeys.every((key) =>
selectedKeys.includes(key)
selectedKeys.includes(key),
);
let newSelectedKeys: (string | number)[];
if (allGroupItemsSelected) {
newSelectedKeys = selectedKeys.filter(
(key) => !groupItemKeys.includes(key)
(key) => !groupItemKeys.includes(key),
);
} else {
const newKeys = groupItemKeys.filter(
(key) => !selectedKeys.includes(key)
(key) => !selectedKeys.includes(key),
);
newSelectedKeys = [...selectedKeys, ...newKeys];
}
@@ -320,7 +336,7 @@ export const FormApiBasedAutoComplete = ({
(item: any) =>
newSelectedKeys.includes(item.key) &&
!item.isGroupHeader &&
!item.disabled
!item.disabled,
);
if (secondaryKey) {
@@ -329,7 +345,8 @@ export const FormApiBasedAutoComplete = ({
key1: item.key,
key2: item.secondaryKey,
...(tertiaryKey ? { key3: item.tertiaryKey } : {}),
}))
...(quaternaryKey ? { key4: item.quaternaryKey } : {}),
})),
);
if (onChangeValue) {
onChangeValue(selectedItems.map((item: any) => item.value.trim()));
@@ -344,6 +361,7 @@ export const FormApiBasedAutoComplete = ({
<AutoComplete
multiselect={multiple}
selectField={selectField}
size={size}
data={data}
selectedKeys={selectedKeys}
onChange={(newSelectedKeys) => {
@@ -357,7 +375,7 @@ export const FormApiBasedAutoComplete = ({
(item: any) =>
newSelectedKeys.includes(item.key) &&
!item.isGroupHeader &&
!item.disabled
!item.disabled,
);
onChange(
@@ -365,16 +383,17 @@ export const FormApiBasedAutoComplete = ({
key1: item.key,
key2: item.secondaryKey,
...(tertiaryKey ? { key3: item.tertiaryKey } : {}),
}))
...(quaternaryKey ? { key4: item.quaternaryKey } : {}),
})),
);
if (onChangeValue) {
onChangeValue(
selectedItems.map((item: any) => item.value.trim())
selectedItems.map((item: any) => item.value.trim()),
);
}
} else {
const validKeys = newSelectedKeys.filter(
(key) => !String(key).startsWith("__group__")
(key) => !String(key).startsWith("__group__"),
);
onChange(validKeys);
}
@@ -384,7 +403,7 @@ export const FormApiBasedAutoComplete = ({
(item: any) =>
item.key === newSelectedKeys[0] &&
!item.isGroupHeader &&
!item.disabled
!item.disabled,
);
if (onChangeValue) {
onChangeValue({
@@ -394,6 +413,9 @@ export const FormApiBasedAutoComplete = ({
...(tertiaryKey
? { key3: selectedItem?.tertiaryKey ?? "" }
: {}),
...(quaternaryKey
? { key4: selectedItem?.quaternaryKey ?? "" }
: {}),
});
}
@@ -402,6 +424,9 @@ export const FormApiBasedAutoComplete = ({
key1: selectedItem.key,
key2: selectedItem.secondaryKey,
...(tertiaryKey ? { key3: selectedItem.tertiaryKey } : {}),
...(quaternaryKey
? { key4: selectedItem.quaternaryKey }
: {}),
});
}
} else {

View File

@@ -8,9 +8,16 @@ interface UseUserProfileStore {
clearProfile: () => void;
}
type UserPermission = {
page_name: string;
domain_name?: string;
domain_fa_name?: string;
page_access: string[];
};
const arePermissionsEqual = (
permissions1?: Array<{ page_name: string; page_access: string[] }>,
permissions2?: Array<{ page_name: string; page_access: string[] }>
permissions1?: UserPermission[],
permissions2?: UserPermission[]
): boolean => {
if (!permissions1 && !permissions2) return true;
if (!permissions1 || !permissions2) return false;
@@ -20,11 +27,13 @@ const arePermissionsEqual = (
const map2 = new Map<string, Set<string>>();
permissions1.forEach((perm) => {
map1.set(perm.page_name, new Set(perm.page_access.sort()));
const key = `${perm.domain_name || ""}::${perm.domain_fa_name || ""}::${perm.page_name}`;
map1.set(key, new Set([...(perm.page_access || [])].sort()));
});
permissions2.forEach((perm) => {
map2.set(perm.page_name, new Set(perm.page_access.sort()));
const key = `${perm.domain_name || ""}::${perm.domain_fa_name || ""}::${perm.page_name}`;
map2.set(key, new Set([...(perm.page_access || [])].sort()));
});
if (map1.size !== map2.size) return false;

View File

@@ -37,6 +37,7 @@ export default function Access() {
pagesInfo.page === 1
? i + 1
: i + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
item?.domain?.fa_name || item?.page_data?.domain?.fa_name || "-",
item?.name,
item?.description,
`${getFaPermissions(item?.page)} (${item?.page})`,
@@ -119,6 +120,7 @@ export default function Access() {
title="مدیریت دسترسی ها"
columns={[
"ردیف",
"حوزه",
"دسترسی",
"توضیحات",
"صفحه",

View File

@@ -3,15 +3,14 @@ import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import { useForm, Controller } from "react-hook-form";
import { zValidateString } from "../../data/getFormTypeErrors";
import { zValidateNumber, zValidateString } from "../../data/getFormTypeErrors";
import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import AutoComplete from "../../components/AutoComplete/AutoComplete";
import { useEffect, useState } from "react";
import { getFaPermissions } from "../../utils/getFaPermissions";
import Checkbox from "../../components/CheckBox/CheckBox";
import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete";
import { getFaPermissions } from "../../utils/getFaPermissions";
type FormValues = z.infer<typeof schema>;
type AddAccessProps = {
@@ -22,9 +21,8 @@ type AddAccessProps = {
const schema = z.object({
access: zValidateString("نام صفحه"),
description: zValidateString("توضیحات"),
selectedPageId: z
.array(z.union([z.string(), z.number()]))
.min(1, { message: "لطفاً یک صفحه انتخاب کنید." }),
selectedPageId: zValidateNumber("صفحه"),
domain: z.union([z.string(), z.number()]).optional(),
modify_state: z.boolean(),
});
@@ -32,80 +30,28 @@ export const AddAccess = ({ getData, item }: AddAccessProps) => {
const showToast = useToast();
const { closeModal } = useModalStore();
const [selectedKeys, setSelectedKeys] = useState<(string | number)[]>([]);
const [data, setData] = useState<any>([]);
const [pagesData, setPagesData] = useState<any>(null);
const {
control,
handleSubmit,
setValue,
trigger,
formState: { errors },
} = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: {
access: item?.name || "",
description: item?.description || "",
selectedPageId: [],
selectedPageId: item?.page_data?.id || "",
domain: item?.domain?.id || item?.page_data?.domain?.id || "",
modify_state: item?.modify_state || false,
},
});
useEffect(() => {
if (selectedKeys.length > 0) {
setValue("selectedPageId", selectedKeys);
}
}, [selectedKeys, setValue]);
useEffect(() => {
if (pagesData?.results && item?.page) {
const matchingPage = pagesData.results.find(
(page: any) => page.name === item.page
);
if (matchingPage) {
const keys = [matchingPage.id];
setSelectedKeys(keys);
setValue("selectedPageId", keys);
}
}
}, [pagesData, item, setValue]);
const handleChangeComplete = (newSelectedKeys: (number | string)[]) => {
setSelectedKeys(newSelectedKeys);
};
const mutationPages = useApiMutation({
api: "/auth/api/v1/page/",
method: "get",
});
const mutation = useApiMutation({
api: `/auth/api/v1/permission/${item ? item?.id + "/" : ""}`,
method: item ? "put" : "post",
});
const getPages = async () => {
const data = await mutationPages.mutateAsync({
page: 1,
page_size: 1000,
});
setPagesData(data);
};
useEffect(() => {
getPages();
}, []);
useEffect(() => {
if (pagesData?.results) {
const d = pagesData.results.map((page: any) => ({
key: page.id,
value: `${getFaPermissions(page.name)} (${page.name})`,
}));
setData(d);
}
}, [pagesData]);
const onSubmit = async (data: FormValues) => {
try {
await mutation.mutateAsync({
@@ -113,7 +59,8 @@ export const AddAccess = ({ getData, item }: AddAccessProps) => {
description: data.description,
category: "api",
meta: {},
page: selectedKeys[0],
page: data.selectedPageId,
domain: data.domain,
modify_state: data?.modify_state,
});
showToast("عملیات با موفقیت انجام شد", "success");
@@ -132,17 +79,21 @@ export const AddAccess = ({ getData, item }: AddAccessProps) => {
<Controller
name="selectedPageId"
control={control}
render={({ field }) => (
<AutoComplete
data={data}
selectedKeys={selectedKeys}
onChange={(newSelectedKeys) => {
handleChangeComplete(newSelectedKeys);
field.onChange(newSelectedKeys);
}}
render={() => (
<FormApiBasedAutoComplete
defaultKey={item?.page_id}
title="انتخاب صفحه"
api="auth/api/v1/page/"
keyField="id"
valueFieldFunction={(item) => getFaPermissions(item?.name)}
secondaryKey={["domain", "id"]}
onChange={(selectedPage) => {
setValue("selectedPageId", selectedPage?.key1 || "");
setValue("domain", selectedPage?.key2 || "");
trigger("selectedPageId");
}}
error={!!errors.selectedPageId}
helperText={errors.selectedPageId?.message}
errorMessage={errors.selectedPageId?.message}
/>
)}
/>

View File

@@ -0,0 +1,125 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import { useForm, Controller } from "react-hook-form";
import {
zValidateString,
zValidateEnglishString,
} from "../../data/getFormTypeErrors";
import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import { getToastResponse } from "../../data/getToastResponse";
const schema = z.object({
code: zValidateString("کد حوزه"),
name: zValidateEnglishString("نام انگلیسی حوزه"),
fa_name: zValidateString("نام حوزه"),
});
type AddDomainProps = {
getData: () => void;
item?: any;
};
type FormValues = z.infer<typeof schema>;
export const AddDomain = ({ getData, item }: AddDomainProps) => {
const showToast = useToast();
const { closeModal } = useModalStore();
const {
control,
handleSubmit,
formState: { errors },
} = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: {
code: item?.code || "",
name: item?.name || "",
fa_name: item?.fa_name || "",
},
});
const mutation = useApiMutation({
api: `/core/domain/${item ? item?.id + "/" : ""}`,
method: item ? "put" : "post",
});
const onSubmit = async (data: FormValues) => {
try {
await mutation.mutateAsync({
code: data.code,
name: data.name,
fa_name: data.fa_name,
});
showToast(getToastResponse(item, "حوزه"), "success");
closeModal();
getData();
} catch (error: any) {
if (error?.status === 403) {
showToast(
error?.response?.data?.message || "این مورد تکراری است!",
"error",
);
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error",
);
}
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Grid container column className="gap-2">
<Controller
name="code"
control={control}
render={({ field }) => (
<Textfield
fullWidth
placeholder="کد حوزه"
value={field.value}
onChange={field.onChange}
error={!!errors.code}
helperText={errors.code?.message}
/>
)}
/>
<Controller
name="name"
control={control}
render={({ field }) => (
<Textfield
fullWidth
placeholder="نام انگلیسی حوزه"
value={field.value}
onChange={field.onChange}
error={!!errors.name}
helperText={errors.name?.message}
/>
)}
/>
<Controller
name="fa_name"
control={control}
render={({ field }) => (
<Textfield
fullWidth
placeholder="نام حوزه"
value={field.value}
onChange={field.onChange}
error={!!errors.fa_name}
helperText={errors.fa_name?.message}
/>
)}
/>
<Button type="submit">ثبت</Button>
</Grid>
</form>
);
};

View File

@@ -3,14 +3,19 @@ import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import { useForm, Controller } from "react-hook-form";
import { zValidateEnglishString } from "../../data/getFormTypeErrors";
import {
zValidateEnglishString,
zValidateNumber,
} from "../../data/getFormTypeErrors";
import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete";
const schema = z.object({
page: zValidateEnglishString("نام صفحه"),
domain: zValidateNumber("حوزه"),
});
type AddPageProps = {
@@ -27,11 +32,13 @@ export const AddPage = ({ getData, item }: AddPageProps) => {
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: {
page: item?.name || "",
domain: item?.domain?.id,
},
});
@@ -45,6 +52,7 @@ export const AddPage = ({ getData, item }: AddPageProps) => {
const payload = {
name: data.page,
code: data.page + ".view",
domain: data.domain,
};
if (item) {
@@ -69,6 +77,25 @@ export const AddPage = ({ getData, item }: AddPageProps) => {
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Grid container column className="gap-2">
<Controller
name="domain"
control={control}
render={() => (
<FormApiBasedAutoComplete
selectField
defaultKey={item?.domain?.id}
title="انتخاب حوزه"
api="/core/domain/"
keyField="id"
valueField="fa_name"
error={!!errors.domain}
errorMessage={errors.domain?.message}
onChange={(value) => {
setValue("domain", value);
}}
/>
)}
/>
<Controller
name="page"
control={control}
@@ -83,6 +110,7 @@ export const AddPage = ({ getData, item }: AddPageProps) => {
/>
)}
/>
<Button type="submit">ثبت</Button>
</Grid>
</form>

View File

@@ -52,11 +52,11 @@ export const EditAccess = ({ getData, item }: AddAccessProps) => {
useEffect(() => {
if (accessData?.results && item?.permissions) {
const permissionPageAccesses = item.permissions.flatMap(
(option: any) => option.page_access || []
(option: any) => option.page_access || [],
);
const matchingPages = accessData.results.filter((page: any) =>
permissionPageAccesses.includes(page.name)
permissionPageAccesses.includes(page.name),
);
const matchingIds = matchingPages.map((page: any) => page.id);

View File

@@ -32,6 +32,7 @@ export default function Pages() {
? i + 1
: i + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
getFaPermissions(item?.name),
item?.domain?.fa_name || "-",
item?.name,
item?.permissions?.map((option: any) => option.name)?.join(" - "),
<Popover key={i}>
@@ -110,7 +111,7 @@ export default function Pages() {
count={pagesData?.count || 10}
isPaginated
title="صفحات سامانه"
columns={["ردیف", "صفحه", "کلید", "دسترسی ها", "عملیات"]}
columns={["ردیف", "صفحه", "حوزه", "کلید", "دسترسی ها", "عملیات"]}
rows={pagesTableData}
/>
</Grid>

View File

@@ -29,6 +29,7 @@ export default function UnusedAccess() {
pagesInfo.page === 1
? i + 1
: i + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
item?.domain?.fa_name || item?.page_data?.domain?.fa_name || "-",
item?.name,
item?.description,
`${getFaPermissions(item?.page)} (${item?.page})`,
@@ -63,7 +64,7 @@ export default function UnusedAccess() {
count={pagesData?.count || 10}
isPaginated
title="دسترسی های غیر فعال"
columns={["ردیف", "دسترسی", "توضیحات", "صفحه", "عملیات"]}
columns={["ردیف", "حوزه", "دسترسی", "توضیحات", "صفحه", "عملیات"]}
rows={pagesTableData}
/>
</Grid>

View File

@@ -1,17 +1,17 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Button from "../../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import { useForm, Controller } from "react-hook-form";
import {
zValidateAutoComplete,
zValidateString,
} from "../../data/getFormTypeErrors";
} from "../../../data/getFormTypeErrors";
import { z } from "zod";
import { useApiMutation, useApiRequest } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import AutoComplete from "../../components/AutoComplete/AutoComplete";
import { getToastResponse } from "../../data/getToastResponse";
import { useApiMutation, useApiRequest } from "../../../utils/useApiRequest";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
import AutoComplete from "../../../components/AutoComplete/AutoComplete";
import { getToastResponse } from "../../../data/getToastResponse";
import { useState, useEffect } from "react";
const schema = z.object({
@@ -107,7 +107,7 @@ export const AddActivityType = ({ getData, item }: AddActivityTypeProps) => {
} catch (error: any) {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error"
"error",
);
}
};

View File

@@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
import { useApiRequest } from "../../utils/useApiRequest";
import { Grid } from "../../components/Grid/Grid";
import Table from "../../components/Table/Table";
import { useApiRequest } from "../../../utils/useApiRequest";
import { Grid } from "../../../components/Grid/Grid";
import Table from "../../../components/Table/Table";
interface ChildOrganizationsProps {
orgId: number;
@@ -40,7 +40,7 @@ export const ChildOrganizations: React.FC<ChildOrganizationsProps> = ({
item?.national_unique_id || "-",
item?.address || "-",
];
}
},
);
setChildOrgsTableData(formattedData);
}

View File

@@ -1,8 +1,8 @@
import { useEffect, useState } from "react";
import { useApiRequest } from "../../utils/useApiRequest";
import Table from "../../components/Table/Table";
import { Grid } from "../../components/Grid/Grid";
import { ShowWeight } from "../../components/ShowWeight/ShowWeight";
import { useApiRequest } from "../../../utils/useApiRequest";
import Table from "../../../components/Table/Table";
import { Grid } from "../../../components/Grid/Grid";
import { ShowWeight } from "../../../components/ShowWeight/ShowWeight";
interface QuotaDashboardByProduct {
quotas_count: string;

View File

@@ -3,19 +3,19 @@ import {
zValidateAutoComplete,
zValidateAutoCompleteOptional,
zValidateString,
} from "../../data/getFormTypeErrors";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
} from "../../../data/getFormTypeErrors";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { zodResolver } from "@hookform/resolvers/zod";
import { Controller, useForm } from "react-hook-form";
import { useApiMutation } from "../../utils/useApiRequest";
import { getToastResponse } from "../../data/getToastResponse";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import Button from "../../components/Button/Button";
import { RadioGroup } from "../../components/RadioButton/RadioGroup";
import { useApiMutation } from "../../../utils/useApiRequest";
import { getToastResponse } from "../../../data/getToastResponse";
import { Grid } from "../../../components/Grid/Grid";
import Textfield from "../../../components/Textfeild/Textfeild";
import Button from "../../../components/Button/Button";
import { RadioGroup } from "../../../components/RadioButton/RadioGroup";
import { useState } from "react";
import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
type Props = {
getData: () => void;
@@ -82,7 +82,7 @@ export const AddAttribute = ({ getData, item }: Props) => {
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error"
"error",
);
}
}

View File

@@ -1,7 +1,7 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import Button from "../../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import Textfield from "../../../components/Textfeild/Textfeild";
import { useForm, Controller } from "react-hook-form";
import {
zValidateAutoComplete,
@@ -9,17 +9,17 @@ import {
zValidateNumber,
zValidateNumberOptional,
zValidateString,
} from "../../data/getFormTypeErrors";
} from "../../../data/getFormTypeErrors";
import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import { getToastResponse } from "../../data/getToastResponse";
import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete";
import { RadioGroup } from "../../components/RadioButton/RadioGroup";
import { useApiMutation } from "../../../utils/useApiRequest";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { getToastResponse } from "../../../data/getToastResponse";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
import { RadioGroup } from "../../../components/RadioButton/RadioGroup";
import { useState } from "react";
import { checkAccess } from "../../utils/checkAccess";
import Checkbox from "../../components/CheckBox/CheckBox";
import { checkAccess } from "../../../utils/checkAccess";
import Checkbox from "../../../components/CheckBox/CheckBox";
type AddPageProps = {
getData: () => void;
@@ -47,7 +47,7 @@ export const AddBroker = ({ getData, item }: AddPageProps) => {
const { closeModal } = useModalStore();
const [isGlobal, setIsGlobal] = useState(
item?.broker_type === "exclusive" ? false : true
item?.broker_type === "exclusive" ? false : true,
);
const [isRequired, setIsRequired] = useState(item ? item?.required : true);
@@ -114,12 +114,12 @@ export const AddBroker = ({ getData, item }: AddPageProps) => {
if (error?.status === 403) {
showToast(
error?.response?.data?.message || "این مورد تکراری است!",
"error"
"error",
);
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error"
"error",
);
}
}

View File

@@ -1,21 +1,21 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import Button from "../../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import Textfield from "../../../components/Textfeild/Textfeild";
import { useForm, Controller } from "react-hook-form";
import {
zValidateAutoComplete,
zValidateNumber,
zValidateString,
} from "../../data/getFormTypeErrors";
} from "../../../data/getFormTypeErrors";
import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import { getToastResponse } from "../../data/getToastResponse";
import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete";
import AutoComplete from "../../components/AutoComplete/AutoComplete";
import { ImageUploader } from "../../components/ImageUploader/ImageUploader";
import { useApiMutation } from "../../../utils/useApiRequest";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { getToastResponse } from "../../../data/getToastResponse";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
import AutoComplete from "../../../components/AutoComplete/AutoComplete";
import { ImageUploader } from "../../../components/ImageUploader/ImageUploader";
import { useState } from "react";
const schema = z.object({
@@ -74,12 +74,12 @@ export const AddProduct = ({ getData, item }: AddPageProps) => {
if (error?.status === 403) {
showToast(
error?.response?.data?.message || "این مورد تکراری است!",
"error"
"error",
);
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error"
"error",
);
}
}

View File

@@ -1,14 +1,14 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import Button from "../../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import Textfield from "../../../components/Textfeild/Textfeild";
import { useForm, Controller } from "react-hook-form";
import { zValidateString } from "../../data/getFormTypeErrors";
import { zValidateString } from "../../../data/getFormTypeErrors";
import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import { getToastResponse } from "../../data/getToastResponse";
import { useApiMutation } from "../../../utils/useApiRequest";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { getToastResponse } from "../../../data/getToastResponse";
const schema = z.object({
name: zValidateString("نام "),
@@ -53,12 +53,12 @@ export const AddProductCategory = ({ getData, item }: AddPageProps) => {
if (error?.status === 403) {
showToast(
error?.response?.data?.message || "این مورد تکراری است!",
"error"
"error",
);
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error"
"error",
);
}
}

View File

@@ -1,13 +1,13 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import Button from "../../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import Textfield from "../../../components/Textfeild/Textfeild";
import { useForm, Controller } from "react-hook-form";
import { zValidateString } from "../../data/getFormTypeErrors";
import { zValidateString } from "../../../data/getFormTypeErrors";
import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import { useApiMutation } from "../../../utils/useApiRequest";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
const schema = z.object({
unit: zValidateString("نام واحد فروش"),

View File

@@ -1,16 +1,16 @@
import { useApiRequest } from "../../utils/useApiRequest";
import { useModalStore } from "../../context/zustand-store/appStore";
import { Grid } from "../../components/Grid/Grid";
import Button from "../../components/Button/Button";
import { useApiRequest } from "../../../utils/useApiRequest";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { Grid } from "../../../components/Grid/Grid";
import Button from "../../../components/Button/Button";
import { motion } from "framer-motion";
import SVGImage from "../../components/SvgImage/SvgImage";
import editIcon from "../../assets/images/svg/edit.svg?react";
import trashIcon from "../../assets/images/svg/trash.svg?react";
import SVGImage from "../../../components/SvgImage/SvgImage";
import editIcon from "../../../assets/images/svg/edit.svg?react";
import trashIcon from "../../../assets/images/svg/trash.svg?react";
import { AddAttribute } from "./AddAttribute";
import { NoData } from "../../components/NoData/NoData";
import { PageTitle } from "../../components/PageTitle/PageTitle";
import { checkAccess } from "../../utils/checkAccess";
import { BooleanQuestion } from "../../components/BooleanQuestion/BooleanQuestion";
import { NoData } from "../../../components/NoData/NoData";
import { PageTitle } from "../../../components/PageTitle/PageTitle";
import { checkAccess } from "../../../utils/checkAccess";
import { BooleanQuestion } from "../../../components/BooleanQuestion/BooleanQuestion";
export const Attributes = () => {
const { openModal } = useModalStore();

View File

@@ -1,16 +1,16 @@
import { useApiRequest } from "../../utils/useApiRequest";
import { useModalStore } from "../../context/zustand-store/appStore";
import { Grid } from "../../components/Grid/Grid";
import Button from "../../components/Button/Button";
import { useApiRequest } from "../../../utils/useApiRequest";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { Grid } from "../../../components/Grid/Grid";
import Button from "../../../components/Button/Button";
import { motion } from "framer-motion";
import SVGImage from "../../components/SvgImage/SvgImage";
import editIcon from "../../assets/images/svg/edit.svg?react";
import SVGImage from "../../../components/SvgImage/SvgImage";
import editIcon from "../../../assets/images/svg/edit.svg?react";
import { AddBroker } from "./AddBroker";
import { NoData } from "../../components/NoData/NoData";
import { PageTitle } from "../../components/PageTitle/PageTitle";
import { checkAccess } from "../../utils/checkAccess";
import { BooleanQuestion } from "../../components/BooleanQuestion/BooleanQuestion";
import trashIcon from "../../assets/images/svg/trash.svg?react";
import { NoData } from "../../../components/NoData/NoData";
import { PageTitle } from "../../../components/PageTitle/PageTitle";
import { checkAccess } from "../../../utils/checkAccess";
import { BooleanQuestion } from "../../../components/BooleanQuestion/BooleanQuestion";
import trashIcon from "../../../assets/images/svg/trash.svg?react";
export const Brokers = () => {
const { openModal } = useModalStore();

View File

@@ -1,9 +1,9 @@
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Button from "../../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import { motion } from "framer-motion";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import { useApiMutation } from "../../../utils/useApiRequest";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
type Props = {
item: any;

View File

@@ -1,16 +1,16 @@
import { useApiRequest } from "../../utils/useApiRequest";
import { useModalStore } from "../../context/zustand-store/appStore";
import { Grid } from "../../components/Grid/Grid";
import Button from "../../components/Button/Button";
import { useApiRequest } from "../../../utils/useApiRequest";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { Grid } from "../../../components/Grid/Grid";
import Button from "../../../components/Button/Button";
import { motion } from "framer-motion";
import SVGImage from "../../components/SvgImage/SvgImage";
import editIcon from "../../assets/images/svg/edit.svg?react";
import SVGImage from "../../../components/SvgImage/SvgImage";
import editIcon from "../../../assets/images/svg/edit.svg?react";
import { AddSaleUnit } from "./AddSaleUnit";
import { NoData } from "../../components/NoData/NoData";
import { PageTitle } from "../../components/PageTitle/PageTitle";
import { checkAccess } from "../../utils/checkAccess";
import { BooleanQuestion } from "../../components/BooleanQuestion/BooleanQuestion";
import trashIcon from "../../assets/images/svg/trash.svg?react";
import { NoData } from "../../../components/NoData/NoData";
import { PageTitle } from "../../../components/PageTitle/PageTitle";
import { checkAccess } from "../../../utils/checkAccess";
import { BooleanQuestion } from "../../../components/BooleanQuestion/BooleanQuestion";
import trashIcon from "../../../assets/images/svg/trash.svg?react";
export const SaleUnits = () => {
const { openModal } = useModalStore();

View File

@@ -1,17 +1,17 @@
import { useParams } from "@tanstack/react-router";
import { Grid } from "../../components/Grid/Grid";
import { Grid } from "../../../components/Grid/Grid";
import { useEffect, useState } from "react";
import { useApiRequest } from "../../utils/useApiRequest";
import Table from "../../components/Table/Table";
import { formatJustDate, formatJustTime } from "../../utils/formatTime";
import { ShowWeight } from "../../components/ShowWeight/ShowWeight";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import Button from "../../components/Button/Button";
import { useApiRequest } from "../../../utils/useApiRequest";
import Table from "../../../components/Table/Table";
import { formatJustDate, formatJustTime } from "../../../utils/formatTime";
import { ShowWeight } from "../../../components/ShowWeight/ShowWeight";
import { Popover } from "../../../components/PopOver/PopOver";
import { Tooltip } from "../../../components/Tooltip/Tooltip";
import Button from "../../../components/Button/Button";
import { QuotaDistributionEntryInventory } from "../quota/QuotaDistributionEntryInventory";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
import { useModalStore } from "../../context/zustand-store/appStore";
import { DocumentDownloader } from "../../components/DocumentDownloader/DocumentDownloader";
import { DeleteButtonForPopOver } from "../../../components/PopOverButtons/PopOverButtons";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { DocumentDownloader } from "../../../components/DocumentDownloader/DocumentDownloader";
const formatGroupNames = (groups?: any[]) =>
groups
@@ -19,8 +19,8 @@ const formatGroupNames = (groups?: any[]) =>
group === "rural"
? "روستایی"
: group === "industrial"
? "صنعتی"
: "عشایری"
? "صنعتی"
: "عشایری",
)
.join(", ");
@@ -28,10 +28,10 @@ const formatDeviceSaleType = (value?: string) =>
value === "all"
? "بر اساس تعداد راس دام و وزن"
: value === "weight"
? "بر اساس وزن"
: value === "count"
? "بر اساس تعداد راس دام"
: "-";
? "بر اساس وزن"
: value === "count"
? "بر اساس تعداد راس دام"
: "-";
export const InventoryEntriesList = () => {
const params = useParams({ strict: false });
@@ -73,7 +73,7 @@ export const InventoryEntriesList = () => {
? i + 1
: i + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
`${formatJustDate(item?.create_date)} (${formatJustTime(
item?.create_date
item?.create_date,
)})`,
<ShowWeight
key={i}

View File

@@ -1,16 +1,16 @@
import { useEffect, useState } from "react";
import { useApiRequest } from "../../utils/useApiRequest";
import { formatJustDate, formatJustTime } from "../../utils/formatTime";
import { Grid } from "../../components/Grid/Grid";
import Table from "../../components/Table/Table";
import { ShowWeight } from "../../components/ShowWeight/ShowWeight";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import Button from "../../components/Button/Button";
import { useApiRequest } from "../../../utils/useApiRequest";
import { formatJustDate, formatJustTime } from "../../../utils/formatTime";
import { Grid } from "../../../components/Grid/Grid";
import Table from "../../../components/Table/Table";
import { ShowWeight } from "../../../components/ShowWeight/ShowWeight";
import { Tooltip } from "../../../components/Tooltip/Tooltip";
import Button from "../../../components/Button/Button";
import { BarsArrowUpIcon } from "@heroicons/react/24/outline";
import { QuotaAllocateToStakeHolders } from "../quota/QuotaAllocateToStakeHolders";
import { Popover } from "../../components/PopOver/PopOver";
import { useModalStore } from "../../context/zustand-store/appStore";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
import { Popover } from "../../../components/PopOver/PopOver";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { DeleteButtonForPopOver } from "../../../components/PopOverButtons/PopOverButtons";
export const InventoryStakeHolderAllocations = () => {
const { openModal } = useModalStore();
@@ -34,7 +34,7 @@ export const InventoryStakeHolderAllocations = () => {
item?.quota_distribution?.distribution_id,
item?.quota_distribution?.quota?.quota_id,
`${formatJustDate(item?.create_date)} (${formatJustTime(
item?.quota_distribution?.create_date
item?.quota_distribution?.create_date,
)})`,
item?.quota_distribution?.assigner_organization?.organization,
item?.quota_distribution?.assigned_organization?.organization,

View File

@@ -1,24 +1,24 @@
import { useEffect, useState } from "react";
import { useApiRequest } from "../../utils/useApiRequest";
import Button from "../../components/Button/Button";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import { ShowWeight } from "../../components/ShowWeight/ShowWeight";
import { Grid } from "../../components/Grid/Grid";
import Table from "../../components/Table/Table";
import { useApiRequest } from "../../../utils/useApiRequest";
import Button from "../../../components/Button/Button";
import { Popover } from "../../../components/PopOver/PopOver";
import { Tooltip } from "../../../components/Tooltip/Tooltip";
import { ShowWeight } from "../../../components/ShowWeight/ShowWeight";
import { Grid } from "../../../components/Grid/Grid";
import Table from "../../../components/Table/Table";
import { ListBulletIcon } from "@heroicons/react/24/outline";
import { INVENTORY } from "../../routes/paths";
import { INVENTORY } from "../../../routes/paths";
import { useNavigate } from "@tanstack/react-router";
import { PaginationParameters } from "../../components/PaginationParameters/PaginationParameters";
import { PaginationParameters } from "../../../components/PaginationParameters/PaginationParameters";
const formatDeviceSaleType = (value?: string) =>
value === "all"
? "بر اساس تعداد راس دام و وزن"
: value === "weight"
? "بر اساس وزن"
: value === "count"
? "بر اساس تعداد راس دام"
: "-";
? "بر اساس وزن"
: value === "count"
? "بر اساس تعداد راس دام"
: "-";
export const InventoryWarehouseEntryTab = () => {
const navigate = useNavigate();
@@ -62,8 +62,8 @@ export const InventoryWarehouseEntryTab = () => {
group === "rural"
? "روستایی"
: group === "industrial"
? "صنعتی"
: "عشایری"
? "صنعتی"
: "عشایری",
)
.join(", ");
@@ -103,7 +103,7 @@ export const InventoryWarehouseEntryTab = () => {
</Tooltip>
</Popover>,
];
}
},
);
setPagesTableData(formattedData);
}

View File

@@ -1,23 +1,23 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import Button from "../../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import Textfield from "../../../components/Textfeild/Textfeild";
import { useForm, Controller } from "react-hook-form";
import {
zValidateNumber,
zValidateNumberOptional,
zValidateString,
} from "../../data/getFormTypeErrors";
} from "../../../data/getFormTypeErrors";
import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useDrawerStore } from "../../context/zustand-store/appStore";
import { getToastResponse } from "../../data/getToastResponse";
import { RadioGroup } from "../../components/RadioButton/RadioGroup";
import { useApiMutation } from "../../../utils/useApiRequest";
import { useToast } from "../../../hooks/useToast";
import { useDrawerStore } from "../../../context/zustand-store/appStore";
import { getToastResponse } from "../../../data/getToastResponse";
import { RadioGroup } from "../../../components/RadioButton/RadioGroup";
import { useState } from "react";
import { FormEnterLocations } from "../../components/FormItems/FormEnterLocation";
import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete";
import { useUserProfileStore } from "../../context/zustand-store/userStore";
import { FormEnterLocations } from "../../../components/FormItems/FormEnterLocation";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
import { useUserProfileStore } from "../../../context/zustand-store/userStore";
type AddPageProps = {
getData: () => void;
@@ -71,10 +71,10 @@ export const LiveStockAddHerd = ({ getData, item, rancher }: AddPageProps) => {
const [activityType, setActivityType] = useState(item?.activity || "V");
const [activityState, setActivityState] = useState(
item ? item?.activity_state : true
item ? item?.activity_state : true,
);
const [operatingLicenseState, setOperatingLicenseState] = useState(
item ? item?.operating_license_state : true
item ? item?.operating_license_state : true,
);
const {
@@ -137,12 +137,12 @@ export const LiveStockAddHerd = ({ getData, item, rancher }: AddPageProps) => {
if (error?.status === 403) {
showToast(
error?.response?.data?.message || "این مورد تکراری است!",
"error"
"error",
);
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error"
"error",
);
}
}

View File

@@ -1,21 +1,21 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Button from "../../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import { useForm, Controller } from "react-hook-form";
import {
zValidateNumber,
zValidateNumberOptional,
zValidateString,
} from "../../data/getFormTypeErrors";
} from "../../../data/getFormTypeErrors";
import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useDrawerStore } from "../../context/zustand-store/appStore";
import { getToastResponse } from "../../data/getToastResponse";
import { RadioGroup } from "../../components/RadioButton/RadioGroup";
import { useApiMutation } from "../../../utils/useApiRequest";
import { useToast } from "../../../hooks/useToast";
import { useDrawerStore } from "../../../context/zustand-store/appStore";
import { getToastResponse } from "../../../data/getToastResponse";
import { RadioGroup } from "../../../components/RadioButton/RadioGroup";
import { useState } from "react";
import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete";
import DatePicker from "../../components/date-picker/DatePicker";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
import DatePicker from "../../../components/date-picker/DatePicker";
type AddPageProps = {
getData: () => void;
@@ -51,7 +51,7 @@ export const LiveStockAddLiveStock = ({
const { closeDrawer } = useDrawerStore();
const [gender, setGender] = useState(item?.gender || 1);
const [weightType, setWeightType] = useState(
item?.weight_type === "H" ? "H" : "L"
item?.weight_type === "H" ? "H" : "L",
);
const {
@@ -92,12 +92,12 @@ export const LiveStockAddLiveStock = ({
if (error?.status === 403) {
showToast(
error?.response?.data?.message || "این مورد تکراری است!",
"error"
"error",
);
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error"
"error",
);
}
}

View File

@@ -1,7 +1,7 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import Button from "../../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import Textfield from "../../../components/Textfeild/Textfeild";
import { useForm, Controller } from "react-hook-form";
import {
zValidateAutoComplete,
@@ -10,16 +10,16 @@ import {
zValidateNumber,
zValidateString,
zValidateStringOptional,
} from "../../data/getFormTypeErrors";
} from "../../../data/getFormTypeErrors";
import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useDrawerStore } from "../../context/zustand-store/appStore";
import { getToastResponse } from "../../data/getToastResponse";
import { FormEnterLocations } from "../../components/FormItems/FormEnterLocation";
import { RadioGroup } from "../../components/RadioButton/RadioGroup";
import { useApiMutation } from "../../../utils/useApiRequest";
import { useToast } from "../../../hooks/useToast";
import { useDrawerStore } from "../../../context/zustand-store/appStore";
import { getToastResponse } from "../../../data/getToastResponse";
import { FormEnterLocations } from "../../../components/FormItems/FormEnterLocation";
import { RadioGroup } from "../../../components/RadioButton/RadioGroup";
import { useState } from "react";
import AutoComplete from "../../components/AutoComplete/AutoComplete";
import AutoComplete from "../../../components/AutoComplete/AutoComplete";
type AddPageProps = {
getData: () => void;
@@ -67,7 +67,7 @@ export const LiveStockAddRancher = ({ getData, item }: AddPageProps) => {
{
message: "نام واحد حقوقی نمیتواند خالی باشد",
path: ["union_name"],
}
},
)
.refine(
(data) => {
@@ -79,7 +79,7 @@ export const LiveStockAddRancher = ({ getData, item }: AddPageProps) => {
{
message: "شناسه ملی واحد حقوقی نمیتواند خالی باشد",
path: ["union_code"],
}
},
);
type FormValues = z.infer<typeof schema>;
@@ -90,7 +90,7 @@ export const LiveStockAddRancher = ({ getData, item }: AddPageProps) => {
const [activityType, setActivityType] = useState(item?.activity || "V");
const [rancherHerdType, setRancherHerdType] = useState(
item ? item?.without_herd : false
item ? item?.without_herd : false,
);
const {
@@ -149,12 +149,12 @@ export const LiveStockAddRancher = ({ getData, item }: AddPageProps) => {
if (error?.status === 403) {
showToast(
error?.response?.data?.message || "این مورد تکراری است!",
"error"
"error",
);
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error"
"error",
);
}
}

View File

@@ -1,14 +1,14 @@
import { z } from "zod";
import { zValidateAutoComplete } from "../../data/getFormTypeErrors";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import { zValidateAutoComplete } from "../../../data/getFormTypeErrors";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { zodResolver } from "@hookform/resolvers/zod";
import { Controller, useForm } from "react-hook-form";
import { useApiMutation } from "../../utils/useApiRequest";
import { getToastResponse } from "../../data/getToastResponse";
import { Grid } from "../../components/Grid/Grid";
import Button from "../../components/Button/Button";
import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete";
import { useApiMutation } from "../../../utils/useApiRequest";
import { getToastResponse } from "../../../data/getToastResponse";
import { Grid } from "../../../components/Grid/Grid";
import Button from "../../../components/Button/Button";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
type Props = {
getData: () => void;
@@ -57,7 +57,7 @@ export const LiveStockAllocateCooperative = ({ getData, item }: Props) => {
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error"
"error",
);
}
}

View File

@@ -10,7 +10,7 @@ import {
TruckIcon,
UserIcon,
} from "@heroicons/react/24/outline";
import { useApiRequest } from "../../utils/useApiRequest";
import { useApiRequest } from "../../../utils/useApiRequest";
export const LiveStockHerdDetails = ({
farmid,
@@ -23,7 +23,7 @@ export const LiveStockHerdDetails = ({
Record<number, boolean>
>({});
const [expandedItems, setExpandedItems] = useState<Record<string, boolean>>(
{}
{},
);
const { data: herdData } = useApiRequest({
@@ -237,12 +237,12 @@ export const LiveStockHerdDetails = ({
{item?.by_type?.length > 0 &&
item?.by_type
.filter(
(animal: any) => animal.weight > 0
(animal: any) => animal.weight > 0,
)
.map(
(
animal: any,
animalIndex: number
animalIndex: number,
) => (
<motion.div
key={animal.name}
@@ -261,23 +261,23 @@ export const LiveStockHerdDetails = ({
</span>
<span
className={`text-xs px-2 py-1 rounded-full ${getAnimalTypeColor(
animal.type
animal.type,
)}`}
>
{getAnimalTypeText(
animal.type
animal.type,
)}
</span>
</div>
<p className="text-lg font-bold text-gray-800 dark:text-gray-200 mt-2">
{formatWeight(
animal.weight
animal.weight,
)}
</p>
</div>
</div>
</motion.div>
)
),
)}
</div>
</div>

View File

@@ -0,0 +1,207 @@
import { useState } from "react";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { useApiMutation, useApiRequest } from "../../../utils/useApiRequest";
import { Grid } from "../../../components/Grid/Grid";
import Button from "../../../components/Button/Button";
import Textfield from "../../../components/Textfeild/Textfeild";
import AutoComplete from "../../../components/AutoComplete/AutoComplete";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
type Props = {
getData: () => void;
rancher: string | number;
};
type LivestockEntry = {
livestock_type: number;
allowed_quantity: number | "";
};
type PlanAllocation = {
plan: string | number;
plan_name: string;
livestock_entries: LivestockEntry[];
};
export const LiveStockRancherAllocateIncentivePlan = ({
getData,
rancher,
}: Props) => {
const showToast = useToast();
const { closeModal } = useModalStore();
const [planAllocations, setPlanAllocations] = useState<PlanAllocation[]>([]);
const { data: speciesData } = useApiRequest({
api: "/livestock/web/api/v1/livestock_type",
method: "get",
params: { page: 1, page_size: 1000 },
queryKey: ["livestock_species"],
});
const speciesOptions = () => {
return (
speciesData?.results?.map((opt: any) => ({
key: opt?.id,
value: opt?.name,
})) ?? []
);
};
const mutation = useApiMutation({
api: "/product/web/api/v1/rancher_incentive_plan/",
method: "post",
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const payload = planAllocations.flatMap((pa) =>
pa.livestock_entries.map((entry) => ({
plan: pa.plan,
rancher: Number(rancher),
livestock_type: entry.livestock_type,
allowed_quantity: Number(entry.allowed_quantity),
})),
);
if (payload.length === 0) {
showToast("لطفاً حداقل یک طرح و نوع دام انتخاب کنید!", "error");
return;
}
try {
await mutation.mutateAsync({ data: payload });
showToast("تخصیص طرح تشویقی با موفقیت انجام شد", "success");
getData();
closeModal();
} catch (error: any) {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error",
);
}
};
return (
<form onSubmit={handleSubmit}>
<Grid container column className="gap-3">
<FormApiBasedAutoComplete
title="انتخاب طرح تشویقی"
api="product/web/api/v1/incentive_plan/active_plans/"
keyField="id"
valueField="name"
secondaryKey="name"
multiple
onChange={(items) => {
const selectedItems = Array.isArray(items) ? items : [];
setPlanAllocations((prev) =>
selectedItems.map((item: any) => {
const existing = prev.find((pa) => pa.plan === item.key1);
return (
existing || {
plan: item.key1,
plan_name: item.key2,
livestock_entries: [],
}
);
}),
);
}}
onChangeValue={(names) => {
setPlanAllocations((prev) =>
prev.map((pa, i) => ({
...pa,
plan_name: names[i] || pa.plan_name,
})),
);
}}
/>
{planAllocations.map((pa, planIndex) => (
<Grid
key={pa.plan}
container
column
className="gap-2 border p-3 rounded-lg"
>
<span className="font-bold text-sm">{pa.plan_name}</span>
{speciesData?.results && (
<AutoComplete
data={speciesOptions()}
multiselect
selectedKeys={pa.livestock_entries.map((e) => e.livestock_type)}
onChange={(keys: (string | number)[]) => {
setPlanAllocations((prev) => {
const next = [...prev];
next[planIndex] = {
...next[planIndex],
livestock_entries: keys.map((k) => {
const existing = next[planIndex].livestock_entries.find(
(e) => e.livestock_type === k,
);
return {
livestock_type: k as number,
allowed_quantity: existing?.allowed_quantity ?? "",
};
}),
};
return next;
});
}}
title="نوع دام"
/>
)}
{pa.livestock_entries.map((entry, entryIndex) => (
<Textfield
key={entry.livestock_type}
fullWidth
formattedNumber
placeholder={`تعداد مجاز ${
speciesOptions().find(
(s: any) => s.key === entry.livestock_type,
)?.value || ""
}`}
value={entry.allowed_quantity}
onChange={(e) => {
setPlanAllocations((prev) => {
const next = [...prev];
const entries = [...next[planIndex].livestock_entries];
entries[entryIndex] = {
...entries[entryIndex],
allowed_quantity: Number(e.target.value),
};
next[planIndex] = {
...next[planIndex],
livestock_entries: entries,
};
return next;
});
}}
/>
))}
</Grid>
))}
<Button
disabled={
planAllocations.length === 0 ||
planAllocations.some((pa) => pa.livestock_entries.length === 0) ||
planAllocations.some((pa) =>
pa.livestock_entries.some(
(e) =>
e.allowed_quantity === "" || Number(e.allowed_quantity) <= 0,
),
)
}
type="submit"
>
ثبت
</Button>
</Grid>
</form>
);
};

View File

@@ -1,35 +1,35 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import Button from "../../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import Textfield from "../../../components/Textfeild/Textfeild";
import { useForm, Controller } from "react-hook-form";
import {
zValidateBigNumber,
zValidateNumber,
zValidateNumberOptional,
zValidateStringOptional,
} from "../../data/getFormTypeErrors";
} from "../../../data/getFormTypeErrors";
import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import { getToastResponse } from "../../data/getToastResponse";
import Typography from "../../components/Typography/Typography";
import { useApiMutation } from "../../../utils/useApiRequest";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { getToastResponse } from "../../../data/getToastResponse";
import Typography from "../../../components/Typography/Typography";
import { useEffect } from "react";
import ansar from "../../assets/images/banks/ansar.png";
import ayandeh from "../../assets/images/banks/ayandeh.png";
import eghtesadNovin from "../../assets/images/banks/eghtesad-novin.png";
import keshavarzi from "../../assets/images/banks/keshavarzi.png";
import maskan from "../../assets/images/banks/maskan.png";
import mehriran from "../../assets/images/banks/mehriran.png";
import meli from "../../assets/images/banks/meli.png";
import mellat from "../../assets/images/banks/mellat.png";
import pasargad from "../../assets/images/banks/pasargad.png";
import saderat from "../../assets/images/banks/saderat.png";
import saman from "../../assets/images/banks/saman.png";
import sina from "../../assets/images/banks/sina.png";
import tejarat from "../../assets/images/banks/tejarat.png";
import toseeTavon from "../../assets/images/banks/tosee-tavon.png";
import ansar from "../../../assets/images/banks/ansar.png";
import ayandeh from "../../../assets/images/banks/ayandeh.png";
import eghtesadNovin from "../../../assets/images/banks/eghtesad-novin.png";
import keshavarzi from "../../../assets/images/banks/keshavarzi.png";
import maskan from "../../../assets/images/banks/maskan.png";
import mehriran from "../../../assets/images/banks/mehriran.png";
import meli from "../../../assets/images/banks/meli.png";
import mellat from "../../../assets/images/banks/mellat.png";
import pasargad from "../../../assets/images/banks/pasargad.png";
import saderat from "../../../assets/images/banks/saderat.png";
import saman from "../../../assets/images/banks/saman.png";
import sina from "../../../assets/images/banks/sina.png";
import tejarat from "../../../assets/images/banks/tejarat.png";
import toseeTavon from "../../../assets/images/banks/tosee-tavon.png";
const schema = z.object({
name: zValidateStringOptional("بانک"),
@@ -112,7 +112,7 @@ export const AddCard = ({ getData, item, target }: AddPageProps) => {
const foundBank =
cardToBank[
Object.keys(cardToBank).find((prefix) =>
cardNumber.toString().startsWith(prefix)
cardNumber.toString().startsWith(prefix),
) || ""
];
if (foundBank) setValue("name", foundBank);
@@ -145,7 +145,7 @@ export const AddCard = ({ getData, item, target }: AddPageProps) => {
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error"
"error",
);
}
}

View File

@@ -1,36 +1,42 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import Button from "../../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import Textfield from "../../../components/Textfeild/Textfeild";
import { useForm, Controller } from "react-hook-form";
import {
zValidateAutoComplete,
zValidateNumber,
zValidateNumberOptional,
zValidateString,
zValidateStringOptional,
} from "../../data/getFormTypeErrors";
} from "../../../data/getFormTypeErrors";
import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import { FormEnterLocations } from "../../components/FormItems/FormEnterLocation";
import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete";
import AutoComplete from "../../components/AutoComplete/AutoComplete";
import { getToastResponse } from "../../data/getToastResponse";
import { useUserProfileStore } from "../../context/zustand-store/userStore";
import { useApiMutation } from "../../../utils/useApiRequest";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { FormEnterLocations } from "../../../components/FormItems/FormEnterLocation";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
import AutoComplete from "../../../components/AutoComplete/AutoComplete";
import { getToastResponse } from "../../../data/getToastResponse";
import { useUserProfileStore } from "../../../context/zustand-store/userStore";
import { useState } from "react";
import Checkbox from "../../components/CheckBox/CheckBox";
import Checkbox from "../../../components/CheckBox/CheckBox";
import {
ArrowPathIcon,
CheckBadgeIcon,
PlusIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import axios from "axios";
const schema = z.object({
name: zValidateString("نام سازمان"),
national_unique_id: zValidateString("شناسه کشوری"),
address: zValidateStringOptional("آدرس"),
field_of_activity: zValidateAutoComplete("حوزه فعالیت"),
province: zValidateNumber("استان"),
city: zValidateNumber("شهر"),
organization: zValidateNumberOptional("سازمان"),
organizationType: zValidateNumber("سازمان"),
unique_unit_identity: zValidateNumberOptional("شناسه یکتا واحد"),
is_repeatable: z.boolean(),
free_visibility_by_scope: z.boolean(),
});
@@ -75,8 +81,8 @@ export const AddOrganization = ({ getData, item }: AddPageProps) => {
resolver: zodResolver(schema),
defaultValues: {
name: item?.name || "",
address: item?.address || "",
national_unique_id: item?.national_unique_id || "",
unique_unit_identity: item?.unique_unit_identity || "",
free_visibility_by_scope: item?.free_visibility_by_scope || false,
field_of_activity:
item && item?.field_of_activity !== "EM"
@@ -95,22 +101,94 @@ export const AddOrganization = ({ getData, item }: AddPageProps) => {
city: string | any;
}>({ province: "", city: "" });
const [addresses, setAddresses] = useState<
{ postal_code: string; address: string }[]
>(
item?.addresses?.length
? item.addresses.map((a: any) => ({
postal_code: a.postal_code || "",
address: a.address || "",
}))
: [{ postal_code: "", address: "" }],
);
const [isInquiryRequired, setIsInquiryRequired] = useState(false);
const [inquiryPassed, setInquiryPassed] = useState(false);
const [inquiryLoading, setInquiryLoading] = useState(false);
const handleAddAddress = () => {
setAddresses((prev) => [...prev, { postal_code: "", address: "" }]);
};
const handleRemoveAddress = (index: number) => {
setAddresses((prev) => prev.filter((_, i) => i !== index));
};
const handleAddressChange = (
index: number,
field: "postal_code" | "address",
value: string,
) => {
setAddresses((prev) =>
prev.map((item, i) => (i === index ? { ...item, [field]: value } : item)),
);
};
const handleInquiry = async () => {
const code = getValues("unique_unit_identity");
if (!code) {
showToast("لطفاً شناسه یکتا واحد را وارد کنید!", "error");
return;
}
setInquiryLoading(true);
try {
await axios.get(
`https://rsibackend.rasadyar.com/app/has_code_in_db/?code=${code}`,
);
setInquiryPassed(true);
showToast("استعلام با موفقیت انجام شد!", "success");
} catch (error: any) {
if (error?.response?.status === 404) {
setInquiryPassed(false);
showToast("شناسه موجود نیست!", "error");
} else {
showToast("خطا در استعلام!", "error");
}
} finally {
setInquiryLoading(false);
}
};
const onSubmit = async (data: FormValues) => {
if (isInquiryRequired && !data.unique_unit_identity) {
showToast("شناسه یکتا واحد الزامی است!", "error");
return;
}
if (isInquiryRequired && !inquiryPassed) {
showToast("لطفاً ابتدا استعلام شناسه یکتا واحد را انجام دهید!", "error");
return;
}
try {
await mutation.mutateAsync({
addresses: addresses.filter(
(a) => a.postal_code.trim() || a.address.trim(),
),
organization: {
name: `${data?.name} ${
data?.is_repeatable
? ""
: data.field_of_activity[0] === "CI"
? LocationValues.city
: LocationValues.province
? LocationValues.city
: LocationValues.province
}`,
...(data.organizationType !== undefined && {
type: data.organizationType,
}),
national_unique_id: data?.national_unique_id,
...(data?.unique_unit_identity && {
unique_unit_identity: data.unique_unit_identity,
}),
province: data?.province,
city: data?.city,
...(data.organization && {
@@ -118,7 +196,6 @@ export const AddOrganization = ({ getData, item }: AddPageProps) => {
}),
field_of_activity: data.field_of_activity[0],
free_visibility_by_scope: data.free_visibility_by_scope,
address: data.address,
},
});
showToast(getToastResponse(item, "سازمان"), "success");
@@ -128,12 +205,12 @@ export const AddOrganization = ({ getData, item }: AddPageProps) => {
if (error?.status === 403) {
showToast(
error?.response?.data?.message || "این سازمان تکراری است!",
"error"
"error",
);
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error"
"error",
);
}
}
@@ -153,6 +230,7 @@ export const AddOrganization = ({ getData, item }: AddPageProps) => {
keyField="id"
secondaryKey="is_repeatable"
tertiaryKey="org_type_field"
quaternaryKey="key"
valueField="name"
error={!!errors.organizationType}
errorMessage={errors.organizationType?.message}
@@ -163,6 +241,12 @@ export const AddOrganization = ({ getData, item }: AddPageProps) => {
trigger(["organizationType"]);
}}
onChangeValue={(r) => {
if (r.key4 === "U" || r.key4 === "CO") {
setIsInquiryRequired(true);
} else {
setIsInquiryRequired(false);
setInquiryPassed(false);
}
if (!r.key2) {
setValue("name", r.value);
} else {
@@ -223,6 +307,62 @@ export const AddOrganization = ({ getData, item }: AddPageProps) => {
)}
/>
<Controller
name="unique_unit_identity"
control={control}
render={({ field }) => (
<div className="flex items-start gap-2 w-full">
<Textfield
fullWidth
placeholder={
isInquiryRequired
? "شناسه یکتا واحد"
: "شناسه یکتا واحد (اختیاری)"
}
value={field.value}
onChange={(e) => {
field.onChange(e);
setInquiryPassed(false);
}}
error={
!!errors.unique_unit_identity ||
(isInquiryRequired && !inquiryPassed && !!field.value)
}
helperText={
errors.unique_unit_identity?.message ||
(isInquiryRequired && inquiryPassed
? "استعلام تایید شده"
: undefined)
}
/>
{isInquiryRequired && (
<button
type="button"
onClick={handleInquiry}
disabled={inquiryLoading || !field.value}
className={`shrink-0 flex items-center gap-1 mt-[2px] px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
inquiryPassed
? "bg-green-500 text-white hover:bg-green-600"
: "bg-blue-500 text-white hover:bg-blue-600"
} disabled:opacity-50 disabled:cursor-not-allowed`}
>
{inquiryLoading
? "..."
: inquiryPassed
? "تایید شده"
: "استعلام"}
{inquiryPassed && !inquiryLoading ? (
<CheckBadgeIcon className="w-4 h-4 text-white" />
) : (
<ArrowPathIcon className="w-4 h-4 text-white" />
)}
</button>
)}
</div>
)}
/>
<Controller
name="province"
control={control}
@@ -258,7 +398,7 @@ export const AddOrganization = ({ getData, item }: AddPageProps) => {
defaultKey={item?.parent_organization?.id}
title="سازمان والد (اختیاری)"
api={`auth/api/v1/organization/organizations_by_province?province=${getValues(
"province"
"province",
)}`}
keyField="id"
valueField="name"
@@ -273,20 +413,53 @@ export const AddOrganization = ({ getData, item }: AddPageProps) => {
)}
/>
<Controller
name="address"
control={control}
render={({ field }) => (
<Textfield
fullWidth
placeholder="آدرس (اختیاری)"
value={field.value}
onChange={field.onChange}
error={!!errors.address}
helperText={errors.address?.message}
/>
)}
/>
<div className="flex flex-col gap-2 w-full">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-gray-700">آدرسها</span>
<button
type="button"
onClick={handleAddAddress}
className="flex items-center gap-1 text-sm text-blue-500 dark:text-blue-300 hover:text-blue-800 transition-colors"
>
<PlusIcon className="w-4 h-4" />
افزودن آدرس
</button>
</div>
{addresses.map((addr, index) => (
<div
key={index}
className="flex items-start gap-2 p-3 border border-gray-200 rounded-lg"
>
<div className="flex flex-col gap-2 flex-1">
<Textfield
fullWidth
placeholder="کد پستی"
value={addr.postal_code}
onChange={(e) =>
handleAddressChange(index, "postal_code", e.target.value)
}
/>
<Textfield
fullWidth
placeholder="آدرس"
value={addr.address}
onChange={(e) =>
handleAddressChange(index, "address", e.target.value)
}
/>
</div>
{addresses.length > 1 && (
<button
type="button"
onClick={() => handleRemoveAddress(index)}
className="mt-2 text-red-500 hover:text-red-700 transition-colors shrink-0"
>
<TrashIcon className="w-5 h-5" />
</button>
)}
</div>
))}
</div>
<Controller
name="free_visibility_by_scope"

View File

@@ -1,21 +1,21 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import Button from "../../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import Textfield from "../../../components/Textfeild/Textfeild";
import { useForm, Controller } from "react-hook-form";
import {
zValidateAutoComplete,
zValidateNumberOptional,
zValidateString,
} from "../../data/getFormTypeErrors";
} from "../../../data/getFormTypeErrors";
import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import { getToastResponse } from "../../data/getToastResponse";
import AutoComplete from "../../components/AutoComplete/AutoComplete";
import Checkbox from "../../components/CheckBox/CheckBox";
import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete";
import { useApiMutation } from "../../../utils/useApiRequest";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { getToastResponse } from "../../../data/getToastResponse";
import AutoComplete from "../../../components/AutoComplete/AutoComplete";
import Checkbox from "../../../components/CheckBox/CheckBox";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
const schema = z.object({
name: zValidateString("نام نهاد "),
@@ -81,12 +81,12 @@ export const AddOrganizationType = ({ getData, item }: AddPageProps) => {
if (error?.status === 403) {
showToast(
error?.response?.data?.message || "این مورد تکراری است!",
"error"
"error",
);
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error"
"error",
);
}
}

View File

@@ -1,7 +1,7 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import Button from "../../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import Textfield from "../../../components/Textfeild/Textfeild";
import { useForm, Controller } from "react-hook-form";
import {
zValidateAutoComplete,
@@ -9,15 +9,15 @@ import {
zValidateNumberOptional,
zValidateString,
zValidateStringOptional,
} from "../../data/getFormTypeErrors";
} from "../../../data/getFormTypeErrors";
import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import { getToastResponse } from "../../data/getToastResponse";
import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete";
import { useFetchProfile } from "../../hooks/useFetchProfile";
import { getFaPermissions } from "../../utils/getFaPermissions";
import { useApiMutation } from "../../../utils/useApiRequest";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { getToastResponse } from "../../../data/getToastResponse";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
import { useFetchProfile } from "../../../hooks/useFetchProfile";
import { getFaPermissions } from "../../../utils/getFaPermissions";
const schema = z.object({
name: zValidateString("نام سازمان"),
@@ -79,7 +79,7 @@ export const AddRole = ({ getData, item }: AddPageProps) => {
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error"
"error",
);
}
}

View File

@@ -6,23 +6,23 @@ import {
zValidateNumber,
zValidateString,
zValidateStringOptional,
} from "../../data/getFormTypeErrors";
} from "../../../data/getFormTypeErrors";
import { z } from "zod";
import { Grid } from "../../components/Grid/Grid";
import { Grid } from "../../../components/Grid/Grid";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import Textfield from "../../components/Textfeild/Textfeild";
import Button from "../../components/Button/Button";
import DatePicker from "../../components/date-picker/DatePicker";
import AutoComplete from "../../components/AutoComplete/AutoComplete";
import { FormEnterLocations } from "../../components/FormItems/FormEnterLocation";
import { useApiMutation } from "../../utils/useApiRequest";
import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete";
import { useToast } from "../../hooks/useToast";
import { useDrawerStore } from "../../context/zustand-store/appStore";
import Typography from "../../components/Typography/Typography";
import { useFetchProfile } from "../../hooks/useFetchProfile";
import { useUserProfileStore } from "../../context/zustand-store/userStore";
import Textfield from "../../../components/Textfeild/Textfeild";
import Button from "../../../components/Button/Button";
import DatePicker from "../../../components/date-picker/DatePicker";
import AutoComplete from "../../../components/AutoComplete/AutoComplete";
import { FormEnterLocations } from "../../../components/FormItems/FormEnterLocation";
import { useApiMutation } from "../../../utils/useApiRequest";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
import { useToast } from "../../../hooks/useToast";
import { useDrawerStore } from "../../../context/zustand-store/appStore";
import Typography from "../../../components/Typography/Typography";
import { useFetchProfile } from "../../../hooks/useFetchProfile";
import { useUserProfileStore } from "../../../context/zustand-store/userStore";
type AddAccessProps = {
getData: () => void;
@@ -71,7 +71,7 @@ export const AddUser = ({ getData, item }: AddAccessProps) => {
{
message: "نام واحد حقوقی نمیتواند خالی باشد",
path: ["unit_name"],
}
},
)
.refine(
(data) => {
@@ -83,7 +83,7 @@ export const AddUser = ({ getData, item }: AddAccessProps) => {
{
message: "شناسه ملی واحد حقوقی نمیتواند خالی باشد",
path: ["unit_national_id"],
}
},
);
type FormValues = z.infer<typeof schema>;
@@ -398,7 +398,7 @@ export const AddUser = ({ getData, item }: AddAccessProps) => {
defaultKey={item?.organization?.id}
title="سازمان"
api={`auth/api/v1/organization/organizations_by_province?province=${getValues(
"province"
"province",
)}`}
keyField="id"
valueField="name"

View File

@@ -1,24 +1,28 @@
import { useEffect, useState } from "react";
import { Grid } from "../../components/Grid/Grid";
import Button from "../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import Button from "../../../components/Button/Button";
import { AddOrganization } from "./AddOrganization";
import AutoComplete from "../../components/AutoComplete/AutoComplete";
import Table from "../../components/Table/Table";
import { useModalStore } from "../../context/zustand-store/appStore";
import { useApiRequest } from "../../utils/useApiRequest";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import { Popover } from "../../components/PopOver/PopOver";
import AutoComplete from "../../../components/AutoComplete/AutoComplete";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
import Table from "../../../components/Table/Table";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { useApiRequest } from "../../../utils/useApiRequest";
import { DeleteButtonForPopOver } from "../../../components/PopOverButtons/PopOverButtons";
import { Tooltip } from "../../../components/Tooltip/Tooltip";
import { Popover } from "../../../components/PopOver/PopOver";
import { AddCard } from "./AddCard";
import ShowMoreInfo from "../../components/ShowMoreInfo/ShowMoreInfo";
import { ShowCardsStringList } from "../../components/ShowCardsStringList/ShowCardsStringList";
import { useUserProfileStore } from "../../context/zustand-store/userStore";
import ShowMoreInfo from "../../../components/ShowMoreInfo/ShowMoreInfo";
import { ShowCardsStringList } from "../../../components/ShowCardsStringList/ShowCardsStringList";
import { useUserProfileStore } from "../../../context/zustand-store/userStore";
export const OrganizationsList = () => {
const { openModal } = useModalStore();
const [selectedProvinceKeys, setSelectedProvinceKeys] = useState<
(string | number)[]
>([]);
const [selectedOrganizationType, setSelectedOrganizationType] = useState<
string | number
>("");
const [params, setParams] = useState({ page: 1, page_size: 10 });
const [tableData, setTableData] = useState([]);
const { profile } = useUserProfileStore();
@@ -32,11 +36,16 @@ export const OrganizationsList = () => {
const { data: apiData, refetch } = useApiRequest({
api: selectedProvinceKeys?.length
? `/auth/api/v1/organization/organizations_by_province?province=${selectedProvinceKeys[0]}`
: "/auth/api/v1/organization/",
? `/auth/api/v1/organization/organizations_by_province?province=${selectedProvinceKeys[0]}${selectedOrganizationType ? `&org_type=${selectedOrganizationType}` : ""}`
: `/auth/api/v1/organization/${selectedOrganizationType ? `?org_type=${selectedOrganizationType}` : ""}`,
method: "get",
params: params,
queryKey: ["organizations", params, selectedProvinceKeys],
queryKey: [
"organizations",
params,
selectedProvinceKeys,
selectedOrganizationType,
],
});
useEffect(() => {
@@ -50,16 +59,24 @@ export const OrganizationsList = () => {
`${item?.type?.name}`,
item?.parent_organization?.name,
item?.national_unique_id,
item?.unique_unit_identity || "-",
item?.field_of_activity === "CO"
? "کشور"
: item?.field_of_activity === "PR"
? "استان"
: item?.field_of_activity === "CI"
? "شهرستان"
: "نامشخص",
? "استان"
: item?.field_of_activity === "CI"
? "شهرستان"
: "نامشخص",
item?.province?.name,
item?.city?.name,
item?.address || "-",
<ShowMoreInfo
key={`address-${i}`}
title="آدرس‌ها"
disabled={!item?.addresses?.length}
data={item?.addresses}
columns={["کد پستی", "آدرس"]}
accessKeys={[["postal_code"], ["address"]]}
/>,
<ShowMoreInfo
key={i}
title="اطلاعات حساب"
@@ -151,6 +168,16 @@ export const OrganizationsList = () => {
ایجاد سازمان
</Button>
</Grid>
<Grid>
<FormApiBasedAutoComplete
size="small"
title="فیلتر نهاد"
api={`auth/api/v1/organization-type`}
keyField="id"
valueField="name"
onChange={(r) => setSelectedOrganizationType(r)}
/>
</Grid>
{profile?.organization?.type?.org_type_field === "CO" && (
<Grid>
<AutoComplete
@@ -177,6 +204,7 @@ export const OrganizationsList = () => {
"نهاد",
"سازمان والد",
"شناسه کشوری",
"شناسه یکتا واحد",
"حوزه فعالیت",
"استان",
"شهر",

View File

@@ -1,14 +1,14 @@
import { useEffect, useState } from "react";
import { Grid } from "../../components/Grid/Grid";
import Button from "../../components/Button/Button";
import Table from "../../components/Table/Table";
import { useModalStore } from "../../context/zustand-store/appStore";
import { useApiRequest } from "../../utils/useApiRequest";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import { Popover } from "../../components/PopOver/PopOver";
import { Grid } from "../../../components/Grid/Grid";
import Button from "../../../components/Button/Button";
import Table from "../../../components/Table/Table";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { useApiRequest } from "../../../utils/useApiRequest";
import { DeleteButtonForPopOver } from "../../../components/PopOverButtons/PopOverButtons";
import { Tooltip } from "../../../components/Tooltip/Tooltip";
import { Popover } from "../../../components/PopOver/PopOver";
import { AddOrganizationType } from "./AddOrganizationType";
import AutoComplete from "../../components/AutoComplete/AutoComplete";
import AutoComplete from "../../../components/AutoComplete/AutoComplete";
export const OrganizationsTypes = () => {
const { openModal } = useModalStore();
@@ -55,10 +55,10 @@ export const OrganizationsTypes = () => {
item?.org_type_field === "CO"
? "کشور"
: item?.org_type_field === "PR"
? "استان"
: item?.org_type_field === "CI"
? "شهرستان"
: "نامشخص",
? "استان"
: item?.org_type_field === "CI"
? "شهرستان"
: "نامشخص",
item?.is_repeatable ? "دارد" : "ندارد",
<Popover key={i}>
<Tooltip title="ویرایش نهاد" position="right">

View File

@@ -1,21 +1,21 @@
import { Controller, useForm } from "react-hook-form";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import Button from "../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import Textfield from "../../../components/Textfeild/Textfeild";
import Button from "../../../components/Button/Button";
import { z } from "zod";
import {
zValidateAutoComplete,
zValidateAutoCompleteOptional,
zValidateEnglishString,
zValidateString,
} from "../../data/getFormTypeErrors";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
} from "../../../data/getFormTypeErrors";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { zodResolver } from "@hookform/resolvers/zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { getToastResponse } from "../../data/getToastResponse";
import { useUserProfileStore } from "../../context/zustand-store/userStore";
import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete";
import { useApiMutation } from "../../../utils/useApiRequest";
import { getToastResponse } from "../../../data/getToastResponse";
import { useUserProfileStore } from "../../../context/zustand-store/userStore";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
type AddPosProps = {
getData: () => void;
@@ -80,12 +80,12 @@ export const AddPos = ({ getData, item }: AddPosProps) => {
if (error?.status === 403) {
showToast(
error?.response?.data?.message || "این مورد تکراری است!",
"error"
"error",
);
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error"
"error",
);
}
}

View File

@@ -1,14 +1,14 @@
import { Controller, useForm } from "react-hook-form";
import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete";
import { useModalStore } from "../../context/zustand-store/appStore";
import { zValidateAutoComplete } from "../../data/getFormTypeErrors";
import { useToast } from "../../hooks/useToast";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { zValidateAutoComplete } from "../../../data/getFormTypeErrors";
import { useToast } from "../../../hooks/useToast";
import { zodResolver } from "@hookform/resolvers/zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { getToastResponse } from "../../data/getToastResponse";
import { useApiMutation } from "../../../utils/useApiRequest";
import { getToastResponse } from "../../../data/getToastResponse";
import { z } from "zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Button from "../../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
type Props = {
getData: () => void;
@@ -59,7 +59,7 @@ export const AllocateAccountToBroker = ({ getData, item }: Props) => {
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error"
"error",
);
}
}

View File

@@ -1,22 +1,22 @@
import { Controller, useForm } from "react-hook-form";
import { Grid } from "../../components/Grid/Grid";
import Button from "../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import Button from "../../../components/Button/Button";
import { z } from "zod";
import {
zValidateAutoComplete,
zValidateString,
zValidateStringOptional,
} from "../../data/getFormTypeErrors";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
} from "../../../data/getFormTypeErrors";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { zodResolver } from "@hookform/resolvers/zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { getToastResponse } from "../../data/getToastResponse";
import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete";
import { RadioGroup } from "../../components/RadioButton/RadioGroup";
import { useApiMutation } from "../../../utils/useApiRequest";
import { getToastResponse } from "../../../data/getToastResponse";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
import { RadioGroup } from "../../../components/RadioButton/RadioGroup";
import { useState } from "react";
import Textfield from "../../components/Textfeild/Textfeild";
import Divider from "../../components/Divider/Divider";
import Textfield from "../../../components/Textfeild/Textfeild";
import Divider from "../../../components/Divider/Divider";
type AddPosProps = {
getData: () => void;
@@ -96,12 +96,12 @@ export const AllocatePos = ({ getData, item }: AddPosProps) => {
if (error?.status === 403) {
showToast(
error?.response?.data?.message || "این مورد تکراری است!",
"error"
"error",
);
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error"
"error",
);
}
}

View File

@@ -1,14 +1,14 @@
import { Controller, useForm } from "react-hook-form";
import { Grid } from "../../components/Grid/Grid";
import Button from "../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import Button from "../../../components/Button/Button";
import { z } from "zod";
import { zValidateAutoComplete } from "../../data/getFormTypeErrors";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import { zValidateAutoComplete } from "../../../data/getFormTypeErrors";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { zodResolver } from "@hookform/resolvers/zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { getToastResponse } from "../../data/getToastResponse";
import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete";
import { useApiMutation } from "../../../utils/useApiRequest";
import { getToastResponse } from "../../../data/getToastResponse";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
type AddPosProps = {
getData: () => void;
@@ -66,12 +66,12 @@ export const PosAllocateOrganizationAccount = ({
if (error?.status === 403) {
showToast(
error?.response?.data?.message || "این مورد تکراری است!",
"error"
"error",
);
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error"
"error",
);
}
}

View File

@@ -0,0 +1,235 @@
import { z } from "zod";
import {
zValidateAutoComplete,
zValidateString,
zValidateStringOptional,
} from "../../../data/getFormTypeErrors";
import { useState } from "react";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useApiMutation } from "../../../utils/useApiRequest";
import { getToastResponse } from "../../../data/getToastResponse";
import Button from "../../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import Textfield from "../../../components/Textfeild/Textfeild";
import { RadioGroup } from "../../../components/RadioButton/RadioGroup";
import AutoComplete from "../../../components/AutoComplete/AutoComplete";
import DatePicker from "../../../components/date-picker/DatePicker";
type Props = {
getData: () => void;
item?: any;
};
const groupTypes = [
{ key: "rural", value: "روستایی", disabled: false },
{ key: "industrial", value: "صنعتی", disabled: false },
{ key: "nomadic", value: "عشایری", disabled: false },
];
const planTypes = [
{ key: "ILQ", value: "افزایش سهمیه دام", disabled: false },
{ key: "SM", value: "آماری / پایشی", disabled: true },
];
const limitTimeTypes = [
{ label: "دارد", value: true },
{
label: "ندارد",
value: false,
},
];
export const AddIncentivePlan = ({ getData, item }: Props) => {
const showToast = useToast();
const { closeModal } = useModalStore();
const [isTimeUnlimited, setIsTimeUnlimited] = useState(
item ? item?.is_time_unlimited : false,
);
const schema = z.object({
name: zValidateString("نام طرح"),
description: zValidateStringOptional("توضیحات"),
plan_type: zValidateAutoComplete("نوع طرح"),
group: zValidateAutoComplete("گروه"),
// is_time_unlimited: zValidateNumber("شهر"),
start_date_limit: isTimeUnlimited
? zValidateString("تاریخ شروع محدودیت")
: zValidateStringOptional("تاریخ شروع محدودیت"),
end_date_limit: isTimeUnlimited
? zValidateString("تاریخ اتمام محدودیت")
: zValidateStringOptional("تاریخ اتمام محدودیت"),
});
type FormValues = z.infer<typeof schema>;
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: {
name: item?.name || "",
description: item?.description || "",
group: item?.group ? [item?.group] : [],
plan_type: item?.plan_type ? [item?.plan_type] : [],
start_date_limit: item?.start_date_limit,
end_date_limit: item?.end_date_limit,
},
});
const mutation = useApiMutation({
api: `/product/web/api/v1/incentive_plan/${item ? item?.id + "/" : ""}`,
method: item ? "put" : "post",
});
const onSubmit = async (data: FormValues) => {
try {
await mutation.mutateAsync({
name: data.name,
description: data.description,
plan_type: data.plan_type[0],
group: data.group[0],
is_time_unlimited: isTimeUnlimited,
...(isTimeUnlimited
? {
start_date_limit: data?.start_date_limit,
end_date_limit: data?.end_date_limit,
}
: {}),
...(item
? { registering_organization: item?.registering_organization }
: {}),
});
showToast(getToastResponse(item, ""), "success");
getData();
closeModal();
} catch (error: any) {
if (error?.status === 403) {
showToast(
error?.response?.data?.message || "این مورد تکراری است!",
"error",
);
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error",
);
}
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Grid container column className="gap-2">
<Controller
name="name"
control={control}
render={({ field }) => (
<Textfield
fullWidth
placeholder="نام طرح "
value={field.value}
onChange={field.onChange}
error={!!errors.name}
helperText={errors.name?.message}
/>
)}
/>
<Controller
name="description"
control={control}
render={({ field }) => (
<Textfield
fullWidth
placeholder="توضیحات (اختیاری)"
value={field.value}
onChange={field.onChange}
error={!!errors.description}
helperText={errors.description?.message}
/>
)}
/>
<Controller
name="plan_type"
control={control}
render={({ field }) => (
<AutoComplete
data={planTypes}
selectedKeys={field.value}
onChange={(keys: (string | number)[]) => {
setValue("plan_type", keys);
}}
error={!!errors.plan_type}
helperText={errors.plan_type?.message}
title="نوع طرح"
/>
)}
/>
<Controller
name="group"
control={control}
render={({ field }) => (
<AutoComplete
data={groupTypes}
selectedKeys={field.value}
onChange={(keys: (string | number)[]) => {
setValue("group", keys);
}}
error={!!errors.group}
helperText={errors.group?.message}
title="گروه"
/>
)}
/>
<RadioGroup
groupTitle="محدودیت زمانی"
className="mr-2 mt-2"
direction="row"
options={limitTimeTypes}
name="دریافت تعرفه"
value={isTimeUnlimited}
onChange={(e) =>
e.target.value === "true"
? setIsTimeUnlimited(true)
: setIsTimeUnlimited(false)
}
/>
{isTimeUnlimited && (
<>
<DatePicker
value={item?.start_date_limit || ""}
label="تاریخ شروع طرح"
size="medium"
onChange={(r) => {
setValue("start_date_limit", r);
}}
/>
<DatePicker
value={item?.end_date_limit || ""}
label="تاریخ اتمام طرح"
size="medium"
onChange={(r) => {
setValue("end_date_limit", r);
}}
/>
</>
)}
<Button type="submit">ثبت</Button>
</Grid>
</form>
);
};

View File

@@ -1,15 +1,15 @@
import { Grid } from "../../components/Grid/Grid";
import { Stepper } from "../../components/Stepper/Stepper";
import { Grid } from "../../../components/Grid/Grid";
import { Stepper } from "../../../components/Stepper/Stepper";
import { useEffect, useState } from "react";
import { QuotaLevel1 } from "./QuotaLevel1";
import Button from "../../components/Button/Button";
import Button from "../../../components/Button/Button";
import { QuotaLevel2 } from "./QuotaLevel2";
import { QuotaLevel3 } from "./QuotaLevel3";
import { QuotaLevel4 } from "./QuotaLevel4";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast, useConfirmToast } from "../../hooks/useToast";
import { getToastResponse } from "../../data/getToastResponse";
import { useModalStore } from "../../context/zustand-store/appStore";
import { useApiMutation } from "../../../utils/useApiRequest";
import { useToast, useConfirmToast } from "../../../hooks/useToast";
import { getToastResponse } from "../../../data/getToastResponse";
import { useModalStore } from "../../../context/zustand-store/appStore";
type Props = {
item?: any;
@@ -70,7 +70,7 @@ export const AddQuota = ({ item, getData }: Props) => {
const handleSubmitForm = async () => {
if (item && item?.quota_distributed > 0) {
const confirmed = await showConfirmToast(
"اطلاعات ویرایش شده بر روی تمام توزیع های زیر مجموعه اعمال میشود!"
"اطلاعات ویرایش شده بر روی تمام توزیع های زیر مجموعه اعمال میشود!",
);
if (!confirmed) {
return;
@@ -96,15 +96,16 @@ export const AddQuota = ({ item, getData }: Props) => {
broker_data: formData?.broker_data,
pos_sale_type: formData?.pos_sale_type,
livestock_allocation_data: formData?.livestockTypes?.filter(
(opt: { quantity_kg: number }) => opt?.quantity_kg > 0
(opt: { quantity_kg: number }) => opt?.quantity_kg > 0,
),
livestock_age_limitations: formData?.livestock_age_limitations,
pre_sale: formData?.pre_sale,
free_sale: formData?.free_sale,
one_time_purchase_limit: formData?.one_time_purchase_limit,
};
let filterEmptyKeys = Object.fromEntries(
Object.entries(submitData).filter(([, v]) => v != null)
Object.entries(submitData).filter(([, v]) => v != null),
);
try {

View File

@@ -1,14 +1,14 @@
import { useEffect, useState } from "react";
import { useApiRequest } from "../../utils/useApiRequest";
import { useModalStore } from "../../context/zustand-store/appStore";
import Table from "../../components/Table/Table";
import { Grid } from "../../components/Grid/Grid";
import Button from "../../components/Button/Button";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import { useApiRequest } from "../../../utils/useApiRequest";
import { useModalStore } from "../../../context/zustand-store/appStore";
import Table from "../../../components/Table/Table";
import { Grid } from "../../../components/Grid/Grid";
import Button from "../../../components/Button/Button";
import { Popover } from "../../../components/PopOver/PopOver";
import { Tooltip } from "../../../components/Tooltip/Tooltip";
import { AddQuota } from "./AddQuota";
import { QuotaView } from "./QuotaView";
import { PopoverCustomModalOperation } from "../../components/PopOverCustomModalOperation/PopoverCustomModalOperation";
import { PopoverCustomModalOperation } from "../../../components/PopOverCustomModalOperation/PopoverCustomModalOperation";
import {
ArrowDownOnSquareIcon,
ArrowUpOnSquareIcon,
@@ -16,14 +16,14 @@ import {
XMarkIcon,
} from "@heroicons/react/24/outline";
import { useNavigate } from "@tanstack/react-router";
import { QUOTAS } from "../../routes/paths";
import { QUOTAS } from "../../../routes/paths";
import { getQuotaTableColumns, getQuotaTableRowData } from "./quotaTableUtils";
import { QuotaAllocateToStakeHolders } from "./QuotaAllocateToStakeHolders";
import { QuotaDistributionEntryInventory } from "./QuotaDistributionEntryInventory";
import { useUserProfileStore } from "../../context/zustand-store/userStore";
import { TableButton } from "../../components/TableButton/TableButton";
import { useUserProfileStore } from "../../../context/zustand-store/userStore";
import { TableButton } from "../../../components/TableButton/TableButton";
import { QuotaActivesDashboardDetails } from "./QuotaActivesDashboardDetails";
import { PaginationParameters } from "../../components/PaginationParameters/PaginationParameters";
import { PaginationParameters } from "../../../components/PaginationParameters/PaginationParameters";
export const QuotaActives = () => {
const { openModal } = useModalStore();

View File

@@ -1,8 +1,8 @@
import { useEffect, useState } from "react";
import { useApiRequest } from "../../utils/useApiRequest";
import Table from "../../components/Table/Table";
import { Grid } from "../../components/Grid/Grid";
import { ShowWeight } from "../../components/ShowWeight/ShowWeight";
import { useApiRequest } from "../../../utils/useApiRequest";
import Table from "../../../components/Table/Table";
import { Grid } from "../../../components/Grid/Grid";
import { ShowWeight } from "../../../components/ShowWeight/ShowWeight";
interface QuotaDashboardByProduct {
quotas_count: string;

View File

@@ -1,16 +1,16 @@
import { Grid } from "../../components/Grid/Grid";
import { Grid } from "../../../components/Grid/Grid";
import { useEffect, useState } from "react";
import { useApiRequest } from "../../utils/useApiRequest";
import Table from "../../components/Table/Table";
import { formatJustDate, formatJustTime } from "../../utils/formatTime";
import { ShowWeight } from "../../components/ShowWeight/ShowWeight";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import Button from "../../components/Button/Button";
import { useApiRequest } from "../../../utils/useApiRequest";
import Table from "../../../components/Table/Table";
import { formatJustDate, formatJustTime } from "../../../utils/formatTime";
import { ShowWeight } from "../../../components/ShowWeight/ShowWeight";
import { Popover } from "../../../components/PopOver/PopOver";
import { Tooltip } from "../../../components/Tooltip/Tooltip";
import Button from "../../../components/Button/Button";
import { QuotaDistribution } from "./QuotaDistribution";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
import { useModalStore } from "../../context/zustand-store/appStore";
import { PaginationParameters } from "../../components/PaginationParameters/PaginationParameters";
import { DeleteButtonForPopOver } from "../../../components/PopOverButtons/PopOverButtons";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { PaginationParameters } from "../../../components/PaginationParameters/PaginationParameters";
export const QuotaAllDistributions = () => {
const [params, setParams] = useState({ page: 1, page_size: 10 });
@@ -55,7 +55,7 @@ export const QuotaAllDistributions = () => {
item?.quota?.quota_id,
item?.quota?.product?.product,
`${formatJustDate(item?.create_date)} (${formatJustTime(
item?.create_date
item?.create_date,
)})`,
`${item?.assigner_organization?.organization} (${item?.creator_info})`,
item?.assigned_organization?.organization,

View File

@@ -1,19 +1,19 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import Button from "../../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import Textfield from "../../../components/Textfeild/Textfeild";
import { useForm, Controller } from "react-hook-form";
import {
zValidateNumber,
zValidateStringOptional,
} from "../../data/getFormTypeErrors";
} from "../../../data/getFormTypeErrors";
import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import { getToastResponse } from "../../data/getToastResponse";
import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete";
import Typography from "../../components/Typography/Typography";
import { useApiMutation } from "../../../utils/useApiRequest";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { getToastResponse } from "../../../data/getToastResponse";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
import Typography from "../../../components/Typography/Typography";
type Props = {
getData: () => void;
@@ -31,16 +31,16 @@ export const QuotaAllocateToStakeHolders = ({
const cooperativeValue = isSubmit
? item?.quota?.brokers?.find(
(broker: any) => broker?.broker_name === "تعاونی"
(broker: any) => broker?.broker_name === "تعاونی",
)?.value
: item?.quota_distribution?.quota?.brokers?.find(
(broker: any) => broker?.broker_name === "تعاونی"
(broker: any) => broker?.broker_name === "تعاونی",
)?.value;
const schema = z.object({
share_amount: zValidateNumber("سهم از تعرفه").max(
cooperativeValue,
`سهم از تعرفه نمی‌تواند بیشتر از ${cooperativeValue?.toLocaleString()} باشد!`
`سهم از تعرفه نمی‌تواند بیشتر از ${cooperativeValue?.toLocaleString()} باشد!`,
),
organization: zValidateNumber("سازمان"),
assigned_organization: zValidateNumber("سازمان تخصیص دهنده"),
@@ -88,7 +88,7 @@ export const QuotaAllocateToStakeHolders = ({
await mutation.mutateAsync(payload as any);
showToast(
getToastResponse(isSubmit ? false : true, "تخصیص به زیر مجموعه"),
"success"
"success",
);
getData();
closeModal();
@@ -96,18 +96,18 @@ export const QuotaAllocateToStakeHolders = ({
if (error?.status === 400) {
showToast(
error?.response?.data?.detail || error?.response?.data?.message,
"error"
"error",
);
closeModal();
} else if (error?.status === 403) {
showToast(
error?.response?.data?.message || "این مورد تکراری است!",
"error"
"error",
);
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error"
"error",
);
}
}

View File

@@ -1,12 +1,12 @@
import { useEffect, useState } from "react";
import { useApiRequest } from "../../utils/useApiRequest";
import Table from "../../components/Table/Table";
import { Grid } from "../../components/Grid/Grid";
import { Popover } from "../../components/PopOver/PopOver";
import { PopoverCustomModalOperation } from "../../components/PopOverCustomModalOperation/PopoverCustomModalOperation";
import { useApiRequest } from "../../../utils/useApiRequest";
import Table from "../../../components/Table/Table";
import { Grid } from "../../../components/Grid/Grid";
import { Popover } from "../../../components/PopOver/PopOver";
import { PopoverCustomModalOperation } from "../../../components/PopOverCustomModalOperation/PopoverCustomModalOperation";
import { ArrowUturnDownIcon } from "@heroicons/react/24/outline";
import { getQuotaTableColumns, getQuotaTableRowData } from "./quotaTableUtils";
import { PaginationParameters } from "../../components/PaginationParameters/PaginationParameters";
import { PaginationParameters } from "../../../components/PaginationParameters/PaginationParameters";
export const QuotaClosed = () => {
const [pagesInfo, setPagesInfo] = useState({ page: 1, page_size: 10 });

View File

@@ -1,21 +1,21 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import Button from "../../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import Textfield from "../../../components/Textfeild/Textfeild";
import { useForm, Controller } from "react-hook-form";
import {
zValidateAutoComplete,
zValidateNumber,
zValidateStringOptional,
} from "../../data/getFormTypeErrors";
} from "../../../data/getFormTypeErrors";
import { z } from "zod";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import { getToastResponse } from "../../data/getToastResponse";
import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete";
import { useApiMutation } from "../../utils/useApiRequest";
import Typography from "../../components/Typography/Typography";
import Checkbox from "../../components/CheckBox/CheckBox";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { getToastResponse } from "../../../data/getToastResponse";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
import { useApiMutation } from "../../../utils/useApiRequest";
import Typography from "../../../components/Typography/Typography";
import Checkbox from "../../../components/CheckBox/CheckBox";
import { useState, useEffect } from "react";
type Props = {
@@ -100,17 +100,17 @@ export const QuotaDistribution = ({
if (editPriceComponents) {
const currentAttributeSum = Object.values(attributeValues).reduce(
(sum, val) => sum + val,
0
0,
);
const currentBrokerSum = Object.values(brokerValues).reduce(
(sum, val) => sum + val,
0
0,
);
if (currentAttributeSum !== initialAttributeSum) {
showToast(
`مجموع قیمت مولفه های قیمت گذاری باید برابر ${initialAttributeSum.toLocaleString()} باشد. مجموع فعلی: ${currentAttributeSum.toLocaleString()}`,
"error"
"error",
);
return;
}
@@ -118,7 +118,7 @@ export const QuotaDistribution = ({
if (currentBrokerSum !== initialBrokerSum) {
showToast(
`مجموع قیمت کارگزاران باید برابر ${initialBrokerSum.toLocaleString()} باشد. مجموع فعلی: ${currentBrokerSum.toLocaleString()}`,
"error"
"error",
);
return;
}
@@ -136,7 +136,7 @@ export const QuotaDistribution = ({
(key) => ({
attribute: parseInt(key),
value: attributeValues[parseInt(key)],
})
}),
);
payload.broker_data = Object.keys(brokerValues).map((key) => ({
broker: parseInt(key),
@@ -152,18 +152,18 @@ export const QuotaDistribution = ({
if (error?.status === 400) {
showToast(
error?.response?.data?.detail || error?.response?.data?.message,
"error"
"error",
);
closeModal();
} else if (error?.status === 403) {
showToast(
error?.response?.data?.message || "این مورد تکراری است!",
"error"
"error",
);
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error"
"error",
);
}
}
@@ -273,7 +273,7 @@ export const QuotaDistribution = ({
className={
Object.values(attributeValues).reduce(
(sum, val) => sum + val,
0
0,
) === initialAttributeSum
? "text-green-500"
: "text-red-500"
@@ -320,7 +320,7 @@ export const QuotaDistribution = ({
className={
Object.values(brokerValues).reduce(
(sum, val) => sum + val,
0
0,
) === initialBrokerSum
? "text-green-500"
: "text-red-500"

View File

@@ -1,20 +1,20 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import Button from "../../../components/Button/Button";
import { Grid } from "../../../components/Grid/Grid";
import Textfield from "../../../components/Textfeild/Textfeild";
import { useForm, Controller } from "react-hook-form";
import {
zValidateBase64Optional,
zValidateNumber,
zValidateStringOptional,
} from "../../data/getFormTypeErrors";
} from "../../../data/getFormTypeErrors";
import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import { getToastResponse } from "../../data/getToastResponse";
import FileUploader from "../../components/FIleUploader/FileUploader";
import Typography from "../../components/Typography/Typography";
import { useApiMutation } from "../../../utils/useApiRequest";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { getToastResponse } from "../../../data/getToastResponse";
import FileUploader from "../../../components/FIleUploader/FileUploader";
import Typography from "../../../components/Typography/Typography";
type Props = {
getData: () => void;
@@ -81,18 +81,18 @@ export const QuotaDistributionEntryInventory = ({
if (error?.status === 400) {
showToast(
error?.response?.data?.detail || error?.response?.data?.message,
"error"
"error",
);
closeModal();
} else if (error?.status === 403) {
showToast(
error?.response?.data?.message || "این مورد تکراری است!",
"error"
"error",
);
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error"
"error",
);
}
}

View File

@@ -1,5 +1,5 @@
import { Grid } from "../../components/Grid/Grid";
import { useApiRequest } from "../../utils/useApiRequest";
import { Grid } from "../../../components/Grid/Grid";
import { useApiRequest } from "../../../utils/useApiRequest";
import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";
import {
@@ -10,7 +10,7 @@ import {
DocumentTextIcon,
TruckIcon,
} from "@heroicons/react/24/outline";
import { formatJustDate } from "../../utils/formatTime";
import { formatJustDate } from "../../../utils/formatTime";
const formatWeight = (value: number | string | undefined, unit?: string) => {
if (value === null || value === undefined || value === "") return "-";
@@ -136,7 +136,7 @@ const DistributionNode = ({
مانده:{" "}
{formatWeight(
item?.warehouse_balance,
item?.sale_unit?.unit
item?.sale_unit?.unit,
)}
</span>
<span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-amber-50 text-amber-700 dark:bg-amber-900/30 dark:text-amber-200">
@@ -155,7 +155,7 @@ const DistributionNode = ({
<button
onClick={() =>
setIsWarehouseEntriesExpanded(
!isWarehouseEntriesExpanded
!isWarehouseEntriesExpanded,
)
}
className="w-full flex items-center justify-between px-1.5 py-1 rounded text-xs text-gray-600 bg-gray-50 hover:bg-gray-100 transition-colors dark:bg-gray-700/40 dark:text-gray-100 dark:hover:bg-gray-700/60"
@@ -222,7 +222,7 @@ const DistributionNode = ({
وزن:{" "}
{formatWeight(
entry?.weight,
item?.sale_unit?.unit
item?.sale_unit?.unit,
)}
</span>
{entry?.lading_number && (
@@ -249,7 +249,7 @@ const DistributionNode = ({
)}
</div>
</motion.div>
)
),
)}
{!isLoadingWarehouseEntries &&

View File

@@ -1,22 +1,22 @@
import { useParams } from "@tanstack/react-router";
import { Grid } from "../../components/Grid/Grid";
import { Grid } from "../../../components/Grid/Grid";
import { useEffect, useState } from "react";
import { useApiRequest } from "../../utils/useApiRequest";
import Table from "../../components/Table/Table";
import Button from "../../components/Button/Button";
import { useApiRequest } from "../../../utils/useApiRequest";
import Table from "../../../components/Table/Table";
import Button from "../../../components/Button/Button";
import { QuotaDistribution } from "./QuotaDistribution";
import { useModalStore } from "../../context/zustand-store/appStore";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import { formatJustDate, formatJustTime } from "../../utils/formatTime";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
import { ShowWeight } from "../../components/ShowWeight/ShowWeight";
import { useModalStore } from "../../../context/zustand-store/appStore";
import { Popover } from "../../../components/PopOver/PopOver";
import { Tooltip } from "../../../components/Tooltip/Tooltip";
import { formatJustDate, formatJustTime } from "../../../utils/formatTime";
import { DeleteButtonForPopOver } from "../../../components/PopOverButtons/PopOverButtons";
import { ShowWeight } from "../../../components/ShowWeight/ShowWeight";
import {
getQuotaDashboardColumns,
getQuotaDashboardRowData,
} from "./quotaTableUtils";
import { QuotaDistributionOverview } from "./QuotaDistributionOverview";
import { useUserProfileStore } from "../../context/zustand-store/userStore";
import { useUserProfileStore } from "../../../context/zustand-store/userStore";
export const QuotaDistributions = () => {
const params = useParams({ strict: false });
@@ -49,7 +49,7 @@ export const QuotaDistributions = () => {
: i + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
item?.distribution_id,
`${formatJustDate(item?.create_date)} (${formatJustTime(
item?.create_date
item?.create_date,
)})`,
item?.assigner_organization?.organization +
" (" +

View File

@@ -1,22 +1,22 @@
import { Controller, useForm } from "react-hook-form";
import { Grid } from "../../components/Grid/Grid";
import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete";
import { Grid } from "../../../components/Grid/Grid";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
import {
zValidateAutoComplete,
zValidateAutoCompleteOptional,
zValidateNumber,
zValidateString,
} from "../../data/getFormTypeErrors";
} from "../../../data/getFormTypeErrors";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import AutoComplete from "../../components/AutoComplete/AutoComplete";
import { getMonthsList } from "../../data/getMonths";
import { RadioGroup } from "../../components/RadioButton/RadioGroup";
import AutoComplete from "../../../components/AutoComplete/AutoComplete";
import { getMonthsList } from "../../../data/getMonths";
import { RadioGroup } from "../../../components/RadioButton/RadioGroup";
import { useEffect, useRef, useState } from "react";
import ToggleButton from "../../components/ToggleButton/ToggleButton";
import Typography from "../../components/Typography/Typography";
import Textfield from "../../components/Textfeild/Textfeild";
import { useApiRequest } from "../../utils/useApiRequest";
import ToggleButton from "../../../components/ToggleButton/ToggleButton";
import Typography from "../../../components/Typography/Typography";
import Textfield from "../../../components/Textfeild/Textfeild";
import { useApiRequest } from "../../../utils/useApiRequest";
type Props = {
item: any;
@@ -45,7 +45,7 @@ const posSaleTypes = [
export const QuotaLevel1 = ({ item, onSubmit, setFormRef, visible }: Props) => {
const [hasDistributionLimit, setHasDistributionLimit] = useState(
item?.distribution_mode?.length ? true : false
item?.distribution_mode?.length ? true : false,
);
const internalRef = useRef<HTMLFormElement>(null);
const [livestockTypes, setLivestockTypes] = useState<
@@ -116,7 +116,7 @@ export const QuotaLevel1 = ({ item, onSubmit, setFormRef, visible }: Props) => {
(option: any) =>
option?.livestock_type?.weight_type === allocate?.weight_type &&
option?.livestock_group === group &&
option?.livestock_type?.name === allocate?.name
option?.livestock_type?.name === allocate?.name,
);
return result?.quantity_kg || 0;
};
@@ -153,13 +153,13 @@ export const QuotaLevel1 = ({ item, onSubmit, setFormRef, visible }: Props) => {
const findLivestockIndex = (
group: string,
weightType: string,
fa: string
fa: string,
) => {
return livestockTypes.findIndex(
(item) =>
item.livestock_group === group &&
item.weight_type === weightType &&
item.fa === fa
item.fa === fa,
);
};
@@ -394,7 +394,7 @@ export const QuotaLevel1 = ({ item, onSubmit, setFormRef, visible }: Props) => {
?.filter(
(option) =>
option.livestock_group === "rural" &&
option?.weight_type === "H"
option?.weight_type === "H",
)
.map((item, i) => {
const index = findLivestockIndex("rural", "H", item.fa);
@@ -428,7 +428,7 @@ export const QuotaLevel1 = ({ item, onSubmit, setFormRef, visible }: Props) => {
?.filter(
(option) =>
option.livestock_group === "rural" &&
option?.weight_type === "L"
option?.weight_type === "L",
)
.map((item, i) => {
const index = findLivestockIndex("rural", "L", item.fa);
@@ -475,13 +475,13 @@ export const QuotaLevel1 = ({ item, onSubmit, setFormRef, visible }: Props) => {
?.filter(
(option) =>
option.livestock_group === "industrial" &&
option?.weight_type === "H"
option?.weight_type === "H",
)
.map((item, i) => {
const index = findLivestockIndex(
"industrial",
"H",
item.fa
item.fa,
);
return (
<Textfield
@@ -513,13 +513,13 @@ export const QuotaLevel1 = ({ item, onSubmit, setFormRef, visible }: Props) => {
?.filter(
(option) =>
option.livestock_group === "industrial" &&
option?.weight_type === "L"
option?.weight_type === "L",
)
.map((item, i) => {
const index = findLivestockIndex(
"industrial",
"L",
item.fa
item.fa,
);
return (
<Textfield

View File

@@ -1,9 +1,9 @@
import { useEffect, useRef, useState } from "react";
import { useApiRequest } from "../../utils/useApiRequest";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import Typography from "../../components/Typography/Typography";
import Checkbox from "../../components/CheckBox/CheckBox";
import { useApiRequest } from "../../../utils/useApiRequest";
import { Grid } from "../../../components/Grid/Grid";
import Textfield from "../../../components/Textfeild/Textfeild";
import Typography from "../../../components/Typography/Typography";
import Checkbox from "../../../components/CheckBox/CheckBox";
type Props = {
item: any;
@@ -48,7 +48,7 @@ export const QuotaLevel2 = ({ item, onSubmit, setFormRef, visible }: Props) => {
if (livestockData) {
const d = data?.results?.map((option: PlansProps) => {
const founded = item?.incentive_plan?.find(
(itm: PlansProps) => itm?.incentive_plan === option.id
(itm: PlansProps) => itm?.incentive_plan === option.id,
);
return {
name: option?.name,
@@ -56,7 +56,7 @@ export const QuotaLevel2 = ({ item, onSubmit, setFormRef, visible }: Props) => {
active: founded ? true : false,
live_stocks: livestockData.results.flatMap((item: any) => {
const foundedLiveStock = founded?.live_stocks?.find(
(option: any) => option?.id === item.id
(option: any) => option?.id === item.id,
);
return [

View File

@@ -1,10 +1,10 @@
import { useEffect, useRef, useState } from "react";
import { useApiRequest } from "../../utils/useApiRequest";
import { Grid } from "../../components/Grid/Grid";
import Checkbox from "../../components/CheckBox/CheckBox";
import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete";
import Textfield from "../../components/Textfeild/Textfeild";
import Typography from "../../components/Typography/Typography";
import { useApiRequest } from "../../../utils/useApiRequest";
import { Grid } from "../../../components/Grid/Grid";
import Checkbox from "../../../components/CheckBox/CheckBox";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
import Textfield from "../../../components/Textfeild/Textfeild";
import Typography from "../../../components/Typography/Typography";
type Props = {
item: any;
@@ -24,13 +24,17 @@ export const QuotaLevel3 = ({ item, onSubmit, setFormRef, visible }: Props) => {
const internalRef = useRef<HTMLFormElement>(null);
const [limitByHerdSize, setLimitByHerdSize] = useState(
item?.limit_by_herd_size || false
item?.limit_by_herd_size || false,
);
const [preSale, setPreSale] = useState(item?.pre_sale || false);
const [freeSale, setFreeSale] = useState(item?.free_sale || false);
const [oneTimePurchase, setOneTimePurchase] = useState(
item?.one_time_purchase_limit || false,
);
useEffect(() => {
if (visible) {
setFormRef(internalRef.current);
@@ -66,7 +70,7 @@ export const QuotaLevel3 = ({ item, onSubmit, setFormRef, visible }: Props) => {
(option: any) =>
option?.livestock_type?.weight_type === allocate?.weight_type &&
option?.livestock_type?.id === allocate?.id &&
option?.livestock_type?.name === allocate?.name
option?.livestock_type?.name === allocate?.name,
);
if (result) {
return result.age_month;
@@ -105,6 +109,7 @@ export const QuotaLevel3 = ({ item, onSubmit, setFormRef, visible }: Props) => {
}),
pre_sale: preSale,
free_sale: freeSale,
one_time_purchase_limit: oneTimePurchase,
};
if (
@@ -234,7 +239,7 @@ export const QuotaLevel3 = ({ item, onSubmit, setFormRef, visible }: Props) => {
const originalIndex = livestockTypes.findIndex(
(type) =>
type.livestock_type === item.livestock_type &&
type.weight_type === item.weight_type
type.weight_type === item.weight_type,
);
if (originalIndex !== -1) {
handleLivestockTypeChange(originalIndex, value);
@@ -268,7 +273,7 @@ export const QuotaLevel3 = ({ item, onSubmit, setFormRef, visible }: Props) => {
const originalIndex = livestockTypes.findIndex(
(type) =>
type.livestock_type === item.livestock_type &&
type.weight_type === item.weight_type
type.weight_type === item.weight_type,
);
if (originalIndex !== -1) {
handleLivestockTypeChange(originalIndex, value);
@@ -301,6 +306,16 @@ export const QuotaLevel3 = ({ item, onSubmit, setFormRef, visible }: Props) => {
}}
/>
</Grid>
<Grid className="flex gap-2 p-2 border-1 border-gray-200 rounded-xl items-center">
<Checkbox
label="محدودیت یکبار خرید از سهیمه"
checked={oneTimePurchase}
onChange={() => {
setOneTimePurchase(!oneTimePurchase);
}}
/>
</Grid>
</Grid>
</Grid>
</form>

View File

@@ -1,10 +1,10 @@
import { useEffect, useRef, useState } from "react";
import Checkbox from "../../components/CheckBox/CheckBox";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import Typography from "../../components/Typography/Typography";
import { useApiRequest } from "../../utils/useApiRequest";
import AutoComplete from "../../components/AutoComplete/AutoComplete";
import Checkbox from "../../../components/CheckBox/CheckBox";
import { Grid } from "../../../components/Grid/Grid";
import Textfield from "../../../components/Textfeild/Textfeild";
import Typography from "../../../components/Typography/Typography";
import { useApiRequest } from "../../../utils/useApiRequest";
import AutoComplete from "../../../components/AutoComplete/AutoComplete";
type Props = {
item: any;
@@ -64,7 +64,7 @@ export const QuotaLevel4 = ({
if (visible) {
const getQuatity = (allocate: any) => {
const result = item?.attribute_values?.find(
(option: any) => option?.attribute === allocate?.id
(option: any) => option?.attribute === allocate?.id,
);
if (result) {
return result.value;
@@ -91,7 +91,7 @@ export const QuotaLevel4 = ({
if (visible) {
const getQuatity = (allocate: any) => {
const result = item?.brokers?.find(
(option: any) => option?.broker === allocate?.id
(option: any) => option?.broker === allocate?.id,
);
if (result) {
return result.value;
@@ -106,8 +106,8 @@ export const QuotaLevel4 = ({
const active = broker?.fix_broker_price_state
? true
: item
? existingValue > 0
: value > 0;
? existingValue > 0
: value > 0;
return {
broker: broker?.id,
value,
@@ -169,7 +169,7 @@ export const QuotaLevel4 = ({
fixedBrokers.forEach((broker: any) => {
const existingIndex = next.findIndex(
(selection) =>
selection.pricing_type === pt?.id && selection.name === broker.fa
selection.pricing_type === pt?.id && selection.name === broker.fa,
);
if (existingIndex === -1) {
next.push({
@@ -195,7 +195,7 @@ export const QuotaLevel4 = ({
return getPriceList()
?.filter((opt) => {
const isSelected = selectedItems.some(
(item) => item.name === opt.value
(item) => item.name === opt.value,
);
if (!isSelected) return false;
const broker = brokersData?.find((b: any) => b.fa === opt.value);
@@ -209,7 +209,7 @@ export const QuotaLevel4 = ({
const handleSubmitForm = () => {
const allTypesSelected = priceTypesResponse?.results?.every((pt: any) =>
priceSelections?.some((item) => item.pricing_type === pt?.id)
priceSelections?.some((item) => item.pricing_type === pt?.id),
);
if (
@@ -217,7 +217,7 @@ export const QuotaLevel4 = ({
!brokersData?.filter((opt: any) => opt.required && opt.value === 0).length
) {
const activeBrokersData = brokersData?.filter(
(broker: any) => broker.active
(broker: any) => broker.active,
);
const activeBrokerNames = activeBrokersData?.map((b: any) => b.fa) || [];
const filteredPriceSelections = priceSelections?.filter((item) => {
@@ -243,8 +243,8 @@ export const QuotaLevel4 = ({
...item,
value,
}
: item
)
: item,
),
);
};
@@ -313,7 +313,7 @@ export const QuotaLevel4 = ({
});
if (!newActiveState) {
setPriceSelections((prev) =>
prev.filter((selection) => selection.name !== item.fa)
prev.filter((selection) => selection.name !== item.fa),
);
}
}}
@@ -379,16 +379,16 @@ export const QuotaLevel4 = ({
onChange={(e: (string | number)[]) => {
setPriceSelections((prev) => {
const filtered = prev.filter(
(item) => item.pricing_type !== pt?.id
(item) => item.pricing_type !== pt?.id,
);
const requiredBrokers = item
? []
: brokersData?.filter(
(broker: any) => broker?.fix_broker_price_state
(broker: any) => broker?.fix_broker_price_state,
) || [];
const newSelections = e.map((selectedKey) => {
const selectedItem = getPriceList().find(
(item) => item.key === selectedKey
(item) => item.key === selectedKey,
);
return {
@@ -402,7 +402,7 @@ export const QuotaLevel4 = ({
const existingIndex = merged.findIndex(
(selection) =>
selection.pricing_type === pt?.id &&
selection.name === broker.fa
selection.name === broker.fa,
);
const brokerValue = broker.value || 0;
if (existingIndex === -1) {
@@ -424,11 +424,11 @@ export const QuotaLevel4 = ({
selectedKeys={(() => {
const filtered =
priceSelections?.filter(
(item) => item.pricing_type === pt?.id
(item) => item.pricing_type === pt?.id,
) || [];
const keys = filtered.map((item) => {
const priceItem = getPriceList().find(
(p) => p.value === item.name
(p) => p.value === item.name,
);
return priceItem?.key;
});

View File

@@ -1,12 +1,12 @@
import { useNavigate, useParams } from "@tanstack/react-router";
import { Grid } from "../../components/Grid/Grid";
import Table from "../../components/Table/Table";
import { useApiRequest } from "../../utils/useApiRequest";
import { Grid } from "../../../components/Grid/Grid";
import Table from "../../../components/Table/Table";
import { useApiRequest } from "../../../utils/useApiRequest";
import { useEffect, useState } from "react";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import Button from "../../components/Button/Button";
import { REPORTING } from "../../routes/paths";
import { Popover } from "../../../components/PopOver/PopOver";
import { Tooltip } from "../../../components/Tooltip/Tooltip";
import Button from "../../../components/Button/Button";
import { REPORTING } from "../../../routes/paths";
import { getQuotaTableColumns, getQuotaTableRowData } from "./quotaTableUtils";
export const QuotaReportingProductDetails = () => {

View File

@@ -1,14 +1,14 @@
import { useEffect, useState } from "react";
import { useApiRequest } from "../../utils/useApiRequest";
import Table from "../../components/Table/Table";
import { Grid } from "../../components/Grid/Grid";
import Button from "../../components/Button/Button";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import { useApiRequest } from "../../../utils/useApiRequest";
import Table from "../../../components/Table/Table";
import { Grid } from "../../../components/Grid/Grid";
import Button from "../../../components/Button/Button";
import { Popover } from "../../../components/PopOver/PopOver";
import { Tooltip } from "../../../components/Tooltip/Tooltip";
import { useNavigate } from "@tanstack/react-router";
import { REPORTING } from "../../routes/paths";
import { ShowWeight } from "../../components/ShowWeight/ShowWeight";
import { PaginationParameters } from "../../components/PaginationParameters/PaginationParameters";
import { REPORTING } from "../../../routes/paths";
import { ShowWeight } from "../../../components/ShowWeight/ShowWeight";
import { PaginationParameters } from "../../../components/PaginationParameters/PaginationParameters";
interface QuotaDashboardByProduct {
product_id: string;
@@ -42,7 +42,7 @@ export const QuotaReportingProducts = () => {
method: "get",
params: { ...publicParams },
queryKey: ["QuotaReportingAllProducts", publicParams],
}
},
);
const navigate = useNavigate();
@@ -89,7 +89,7 @@ export const QuotaReportingProducts = () => {
</Tooltip>
</Popover>,
];
}
},
);
setPagesTableData(tableData);
} else {

View File

@@ -1,15 +1,15 @@
import { useParams } from "@tanstack/react-router";
import { Grid } from "../../components/Grid/Grid";
import { Grid } from "../../../components/Grid/Grid";
import { useEffect, useState } from "react";
import { useApiRequest } from "../../utils/useApiRequest";
import Table from "../../components/Table/Table";
import { formatJustDate, formatJustTime } from "../../utils/formatTime";
import { getPersianMonths } from "../../utils/getPersianMonths";
import ShowMoreInfo from "../../components/ShowMoreInfo/ShowMoreInfo";
import Typography from "../../components/Typography/Typography";
import ShowStringList from "../../components/ShowStringList/ShowStringList";
import Divider from "../../components/Divider/Divider";
import { ShowWeight } from "../../components/ShowWeight/ShowWeight";
import { useApiRequest } from "../../../utils/useApiRequest";
import Table from "../../../components/Table/Table";
import { formatJustDate, formatJustTime } from "../../../utils/formatTime";
import { getPersianMonths } from "../../../utils/getPersianMonths";
import ShowMoreInfo from "../../../components/ShowMoreInfo/ShowMoreInfo";
import Typography from "../../../components/Typography/Typography";
import ShowStringList from "../../../components/ShowStringList/ShowStringList";
import Divider from "../../../components/Divider/Divider";
import { ShowWeight } from "../../../components/ShowWeight/ShowWeight";
export const QuotaReportingQuotaDistributions = () => {
const params = useParams({ strict: false });
@@ -39,7 +39,7 @@ export const QuotaReportingQuotaDistributions = () => {
: i + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
item?.distribution_id,
`${formatJustDate(item?.create_date)} (${formatJustTime(
item?.create_date
item?.create_date,
)})`,
item?.assigner_organization?.organization +
" (" +
@@ -129,8 +129,8 @@ export const QuotaReportingQuotaDistributions = () => {
DashboardData?.group === "rural"
? "روستایی"
: DashboardData?.group === "industrial"
? "صنعتی"
: "عشایری",
? "صنعتی"
: "عشایری",
getPersianMonths(DashboardData?.month_choices).join("، "),
DashboardData?.sale_type === "gov" ? "دولتی" : "آزاد",
getPersianMonths(DashboardData?.sale_license).join("، "),
@@ -172,7 +172,7 @@ export const QuotaReportingQuotaDistributions = () => {
</Typography>
<ShowStringList
strings={DashboardData?.limit_by_organizations?.map(
(opt: { name: string }) => opt?.name
(opt: { name: string }) => opt?.name,
)}
/>
@@ -190,7 +190,7 @@ export const QuotaReportingQuotaDistributions = () => {
opt?.livestock_type?.weight_type === "L"
? "سبک"
: "سنگین"
}) با سن ${opt?.age_month}`
}) با سن ${opt?.age_month}`,
)}
/>
</Grid>
@@ -199,10 +199,10 @@ export const QuotaReportingQuotaDistributions = () => {
DashboardData?.pos_sale_type === "all"
? "بر اساس تعداد راس دام و وزن"
: DashboardData?.pos_sale_type === "weight"
? "بر اساس وزن"
: DashboardData?.pos_sale_type === "count"
? "بر اساس تعداد راس دام"
: "-",
? "بر اساس وزن"
: DashboardData?.pos_sale_type === "count"
? "بر اساس تعداد راس دام"
: "-",
<ShowMoreInfo key={DashboardData} title="طرح های تشویقی">
<div className="grid grid-cols-2 gap-1.5 p-2 max-h-[400px] overflow-y-auto w-full">
{DashboardData?.incentive_plan?.map(
@@ -250,12 +250,12 @@ export const QuotaReportingQuotaDistributions = () => {
</Typography>
</div>
</Grid>
)
),
)}
</Grid>
)}
</Grid>
)
),
)}
</div>
</ShowMoreInfo>,

View File

@@ -1,11 +1,11 @@
import React from "react";
import { Grid } from "../../components/Grid/Grid";
import Typography from "../../components/Typography/Typography";
import { ShowWeight } from "../../components/ShowWeight/ShowWeight";
import ShowStringList from "../../components/ShowStringList/ShowStringList";
import Divider from "../../components/Divider/Divider";
import { formatJustDate } from "../../utils/formatTime";
import { getPersianMonths } from "../../utils/getPersianMonths";
import { Grid } from "../../../components/Grid/Grid";
import Typography from "../../../components/Typography/Typography";
import { ShowWeight } from "../../../components/ShowWeight/ShowWeight";
import ShowStringList from "../../../components/ShowStringList/ShowStringList";
import Divider from "../../../components/Divider/Divider";
import { formatJustDate } from "../../../utils/formatTime";
import { getPersianMonths } from "../../../utils/getPersianMonths";
interface QuotaViewProps {
item: any;
@@ -129,8 +129,8 @@ export const QuotaView: React.FC<QuotaViewProps> = ({ item }) => {
group === "rural"
? "روستایی"
: group === "industrial"
? "صنعتی"
: "عشایری"
? "صنعتی"
: "عشایری",
)
.join(", ") || "-"
}
@@ -145,10 +145,10 @@ export const QuotaView: React.FC<QuotaViewProps> = ({ item }) => {
item?.pos_sale_type === "all"
? "بر اساس تعداد راس دام و وزن"
: item?.pos_sale_type === "weight"
? "بر اساس وزن"
: item?.pos_sale_type === "count"
? "بر اساس تعداد راس دام"
: "-"
? "بر اساس وزن"
: item?.pos_sale_type === "count"
? "بر اساس تعداد راس دام"
: "-"
}
/>
</div>
@@ -202,8 +202,8 @@ export const QuotaView: React.FC<QuotaViewProps> = ({ item }) => {
acc[group].push(allocation);
return acc;
},
{} as Record<string, any[]>
)
{} as Record<string, any[]>,
),
) as [string, any[]][]
).map(([group, allocations]) => (
<div key={group} className="space-y-2">
@@ -215,8 +215,8 @@ export const QuotaView: React.FC<QuotaViewProps> = ({ item }) => {
{group === "rural"
? "روستایی"
: group === "industrial"
? "صنعتی"
: "سایر"}
? "صنعتی"
: "سایر"}
</Typography>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
@@ -296,6 +296,13 @@ export const QuotaView: React.FC<QuotaViewProps> = ({ item }) => {
</Typography>
</div>
<Divider />
<div>
<Typography variant="body1" sign="info" className="mb-2">
محدودیت یکبار خرید از سهیمه:{" "}
{item?.one_time_purchase_limit ? "دارد" : "ندارد"}
</Typography>
</div>
<Divider />
<div>
<Typography
variant="body2"
@@ -308,7 +315,7 @@ export const QuotaView: React.FC<QuotaViewProps> = ({ item }) => {
<ShowStringList
showSearch={false}
strings={item?.limit_by_organizations?.map(
(opt: { name: string }) => opt?.name
(opt: { name: string }) => opt?.name,
)}
/>
) : (
@@ -341,7 +348,7 @@ export const QuotaView: React.FC<QuotaViewProps> = ({ item }) => {
opt?.livestock_type?.weight_type === "L"
? "سبک"
: "سنگین"
}) با سن ${opt?.age_month}`
}) با سن ${opt?.age_month}`,
)}
/>
) : (
@@ -423,7 +430,7 @@ export const QuotaView: React.FC<QuotaViewProps> = ({ item }) => {
</Typography>
</div>
</div>
)
),
)}
</div>
)}
@@ -453,7 +460,7 @@ export const QuotaView: React.FC<QuotaViewProps> = ({ item }) => {
pricing_type_name: string;
name: string;
value: number;
}
},
) => {
const key = itm.pricing_type_name;
if (acc[key]) {
@@ -463,8 +470,8 @@ export const QuotaView: React.FC<QuotaViewProps> = ({ item }) => {
}
return acc;
},
{} as Record<string, any>
)
{} as Record<string, any>,
),
).map((priceItem: any, priceIndex: number) => (
<div
key={priceIndex}
@@ -499,8 +506,8 @@ export const QuotaView: React.FC<QuotaViewProps> = ({ item }) => {
acc[key].push(itm);
return acc;
},
{} as Record<string, any[]>
)
{} as Record<string, any[]>,
),
) as [string, any[]][]
).map(([pricingTypeName, items]) => (
<div key={pricingTypeName} className="space-y-2">

View File

@@ -1,10 +1,10 @@
import React from "react";
import { formatJustDate } from "../../utils/formatTime";
import { formatJustDate } from "../../../utils/formatTime";
import { ShieldCheckIcon } from "@heroicons/react/24/solid";
import { ShowWeight } from "../../components/ShowWeight/ShowWeight";
import ShowMoreInfo from "../../components/ShowMoreInfo/ShowMoreInfo";
import ShowStringList from "../../components/ShowStringList/ShowStringList";
import { Grid } from "../../components/Grid/Grid";
import { ShowWeight } from "../../../components/ShowWeight/ShowWeight";
import ShowMoreInfo from "../../../components/ShowMoreInfo/ShowMoreInfo";
import ShowStringList from "../../../components/ShowStringList/ShowStringList";
import { Grid } from "../../../components/Grid/Grid";
export interface PriceCalculationItem {
id: number;
@@ -114,7 +114,7 @@ const getFilteredPriceCalculationData = (
export const getQuotaTableRowData = (
item: any,
index: number,
config: QuotaTableConfig
config: QuotaTableConfig,
): any[] => {
const rowData: any[] = [];
const {
@@ -131,7 +131,7 @@ export const getQuotaTableRowData = (
rowData.push(
pagesInfo.page === 1
? index + 1
: index + pagesInfo.page_size * (pagesInfo.page - 1) + 1
: index + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
);
}
@@ -161,7 +161,7 @@ export const getQuotaTableRowData = (
key={index}
weight={item?.quota_weight}
type={item?.sale_unit?.unit}
/>
/>,
);
// وزن توزیع شده
@@ -170,7 +170,7 @@ export const getQuotaTableRowData = (
key={index}
weight={item?.quota_distributed}
type={item?.sale_unit?.unit}
/>
/>,
);
// وزن باقیمانده سهمیه
@@ -179,7 +179,7 @@ export const getQuotaTableRowData = (
key={index}
weight={item?.remaining_weight}
type={item?.sale_unit?.unit}
/>
/>,
);
// وزن فروش رفته
@@ -188,7 +188,7 @@ export const getQuotaTableRowData = (
key={index}
weight={item?.been_sold}
type={item?.sale_unit?.unit}
/>
/>,
);
// ورود به انبار
@@ -197,7 +197,7 @@ export const getQuotaTableRowData = (
key={index}
weight={item?.inventory_received}
type={item?.sale_unit?.unit}
/>
/>,
);
// مانده انبار
@@ -206,7 +206,7 @@ export const getQuotaTableRowData = (
key={index}
weight={item?.pre_sale_balance}
type={item?.sale_unit?.unit}
/>
/>,
);
// تاریخ بایگانی
@@ -224,10 +224,10 @@ export const getQuotaTableRowData = (
group === "rural"
? "روستایی"
: group === "industrial"
? "صنعتی"
: "عشایری"
? "صنعتی"
: "عشایری",
)
.join(", ")
.join(", "),
);
// مبدا
@@ -270,7 +270,7 @@ export const getQuotaTableRowData = (
},
},
]}
/>
/>,
);
rowData.push(
@@ -286,7 +286,7 @@ export const getQuotaTableRowData = (
showSearch={false}
/>
</Grid>
</ShowMoreInfo>
</ShowMoreInfo>,
);
// سهمیه و مجوز
@@ -415,10 +415,10 @@ export const getQuotaTableRowData = (
item?.pos_sale_type === "all"
? "بر اساس تعداد راس دام و وزن"
: item?.pos_sale_type === "weight"
? "بر اساس وزن"
: item?.pos_sale_type === "count"
? "بر اساس تعداد راس دام"
: "-"
? "بر اساس وزن"
: item?.pos_sale_type === "count"
? "بر اساس تعداد راس دام"
: "-",
);
// طرح های تشویقی
@@ -555,7 +555,7 @@ export const getQuotaDashboardRowData = (item: any): any[] => {
key={item?.id}
weight={item?.quota_weight}
type={item?.sale_unit?.unit}
/>
/>,
);
// وزن توزیع شده
@@ -564,7 +564,7 @@ export const getQuotaDashboardRowData = (item: any): any[] => {
key={item?.id}
weight={item?.quota_distributed}
type={item?.sale_unit?.unit}
/>
/>,
);
// وزن باقیمانده سهمیه
@@ -573,7 +573,7 @@ export const getQuotaDashboardRowData = (item: any): any[] => {
key={item?.id}
weight={item?.remaining_weight}
type={item?.sale_unit?.unit}
/>
/>,
);
// وزن فروش رفته
@@ -582,7 +582,7 @@ export const getQuotaDashboardRowData = (item: any): any[] => {
key={item?.id}
weight={item?.been_sold}
type={item?.sale_unit?.unit}
/>
/>,
);
// ورود به انبار
@@ -591,7 +591,7 @@ export const getQuotaDashboardRowData = (item: any): any[] => {
key={item?.id}
weight={item?.inventory_received}
type={item?.sale_unit?.unit}
/>
/>,
);
// مانده انبار
@@ -600,7 +600,7 @@ export const getQuotaDashboardRowData = (item: any): any[] => {
key={item?.id}
weight={item?.pre_sale_balance}
type={item?.sale_unit?.unit}
/>
/>,
);
// واحد فروش
@@ -613,10 +613,10 @@ export const getQuotaDashboardRowData = (item: any): any[] => {
group === "rural"
? "روستایی"
: group === "industrial"
? "صنعتی"
: "عشایری"
? "صنعتی"
: "عشایری",
)
.join(", ")
.join(", "),
);
// مبدا
@@ -659,7 +659,7 @@ export const getQuotaDashboardRowData = (item: any): any[] => {
},
},
]}
/>
/>,
);
// توزیع
@@ -676,7 +676,7 @@ export const getQuotaDashboardRowData = (item: any): any[] => {
showSearch={false}
/>
</Grid>
</ShowMoreInfo>
</ShowMoreInfo>,
);
// سهمیه و مجوز
@@ -805,10 +805,10 @@ export const getQuotaDashboardRowData = (item: any): any[] => {
item?.pos_sale_type === "all"
? "بر اساس تعداد راس دام و وزن"
: item?.pos_sale_type === "weight"
? "بر اساس وزن"
: item?.pos_sale_type === "count"
? "بر اساس تعداد راس دام"
: "-"
? "بر اساس وزن"
: item?.pos_sale_type === "count"
? "بر اساس تعداد راس دام"
: "-",
);
// طرح های تشویقی

View File

@@ -0,0 +1,290 @@
import { z } from "zod";
import { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Grid } from "../../../components/Grid/Grid";
import Button from "../../../components/Button/Button";
import Textfield from "../../../components/Textfeild/Textfeild";
import { FormApiBasedAutoComplete } from "../../../components/FormItems/FormApiBasedAutoComplete";
import AutoComplete from "../../../components/AutoComplete/AutoComplete";
import { zValidateAutoComplete } from "../../../data/getFormTypeErrors";
import { useApiMutation, useApiRequest } from "../../../utils/useApiRequest";
import { useToast } from "../../../hooks/useToast";
import { useModalStore } from "../../../context/zustand-store/appStore";
const schema = z.object({
organization: zValidateAutoComplete("سازمان"),
});
type FormValues = z.infer<typeof schema>;
type ParentDistItem = {
id: number;
dist_identity?: number;
batch_identity: string | number | null;
species_code: number;
maxCount: number;
label?: string;
};
export const DistributeFromDistribution = ({
item,
getData,
isEdit,
parentDistributions,
}: any) => {
const showToast = useToast();
const { closeModal } = useModalStore();
const [dists, setDists] = useState<ParentDistItem[]>([]);
const [selectedSpeciesKeys, setSelectedSpeciesKeys] = useState<
(string | number)[]
>([]);
const [counts, setCounts] = useState<Record<number, number | "">>({});
const {
control,
handleSubmit,
setValue,
trigger,
formState: { errors },
} = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: {
organization: [],
},
});
const { data: batchDetail } = useApiRequest({
api: `/tag/web/api/v1/tag_distribution_batch/${item?.id}/`,
method: "get",
queryKey: ["tagDistributionBatchDetail", item?.id],
enabled: !!item?.id && !isEdit,
});
const mutation = useApiMutation({
api: `/tag/web/api/v1/tag_distribution/${item?.id}/${isEdit ? "edit_" : ""}distribute_distribution/`,
method: isEdit ? "put" : "post",
});
const { data: speciesData } = useApiRequest({
api: "/livestock/web/api/v1/livestock_species",
method: "get",
params: { page: 1, pageSize: 1000 },
queryKey: ["species"],
});
useEffect(() => {
const sourceDistributions = isEdit
? parentDistributions
: item?.distributions?.length
? item.distributions
: batchDetail?.distributions;
if (!sourceDistributions?.length) {
setDists([]);
setCounts({});
return;
}
const parentDists: ParentDistItem[] = sourceDistributions.map((d: any) => {
const maxCount = d?.remaining_number || 0;
return {
id: d?.id ?? d?.dist_identity,
dist_identity: d?.dist_identity,
batch_identity: d?.batch_identity ?? null,
species_code: d?.species_code,
maxCount: Number(maxCount) || 0,
label:
d?.serial_from != null && d?.serial_to != null
? `از ${d.serial_from} تا ${d.serial_to}`
: undefined,
};
});
setDists(parentDists);
if (isEdit && item?.distributions?.length) {
const defaultCounts: Record<number, number | ""> = {};
const defaultSpeciesKeys: (string | number)[] = [];
parentDists.forEach((pd) => {
const childDist = item.distributions.find(
(cd: any) =>
cd.parent_tag_distribution === pd.id ||
cd.species_code === pd.species_code,
);
if (childDist) {
defaultCounts[pd.id] = childDist.total_tag_count || 0;
if (!defaultSpeciesKeys.includes(pd.species_code)) {
defaultSpeciesKeys.push(pd.species_code);
}
}
});
setCounts(defaultCounts);
setSelectedSpeciesKeys(defaultSpeciesKeys);
} else {
setCounts({});
setSelectedSpeciesKeys([]);
}
}, [item?.distributions, batchDetail?.distributions, parentDistributions]);
const speciesOptions = () =>
speciesData?.results?.map((opt: any) => ({
key: opt?.value,
value: opt?.name,
})) ?? [];
const getSpeciesName = (speciesCode: number) =>
speciesOptions().find((s: any) => s.key === speciesCode)?.value ?? "نامشخص";
const visibleDists = dists.filter((d) =>
selectedSpeciesKeys.includes(d.species_code),
);
const onSubmit = async (data: FormValues) => {
const distsPayload = visibleDists
.filter((d) => {
const c = counts[d.id];
return c !== "" && c !== undefined && c !== null && Number(c) > 0;
})
.map((d) => {
const fromItem = item?.distributions?.find(
(x: any) => x.id === d.id || x.dist_identity === d.id,
);
const batch_identity =
fromItem != null ? fromItem.batch_identity : d.batch_identity;
return {
parent_tag_distribution: d.id,
batch_identity: batch_identity != null ? batch_identity : null,
species_code: d.species_code,
count: Number(counts[d.id] ?? 0),
};
});
if (distsPayload.length === 0) {
showToast("حداقل یک گونه با تعداد معتبر وارد کنید", "error");
return;
}
try {
await mutation.mutateAsync({
assigned_org: data.organization[0],
parent_distribution_batch: item.id,
dists: distsPayload,
});
showToast(
isEdit
? "ویرایش توزیع با موفقیت انجام شد"
: "توزیع از توزیع با موفقیت انجام شد",
"success",
);
getData();
closeModal();
} catch (error: any) {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error",
);
}
};
const handleCountChange = (distId: number, value: number | "") => {
setCounts((prev) => ({ ...prev, [distId]: value }));
};
const isValidCount = (dist: ParentDistItem) => {
const c = counts[dist.id];
if (c === "" || c === undefined || c === null) return false;
const num = Number(c);
return num > 0 && num <= dist.maxCount;
};
const speciesOptionsFromParent = () => {
const uniqueSpecies = Array.from(
new Map(dists.map((d) => [d.species_code, d])).values(),
);
return uniqueSpecies.map((d) => ({
key: d.species_code,
value: getSpeciesName(d.species_code),
}));
};
const hasValidDists =
visibleDists.length > 0 && visibleDists.every((d) => isValidCount(d));
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Grid container column className="gap-3">
<Controller
name="organization"
control={control}
render={() => (
<FormApiBasedAutoComplete
defaultKey={item?.assigned_org?.id}
title="انتخاب سازمان (دریافت‌کننده)"
api={`auth/api/v1/organization/organizations_by_province?exclude=PSP&province=${item?.assigner_org?.province}`}
keyField="id"
valueField="name"
error={!!errors.organization}
errorMessage={errors.organization?.message}
onChange={(r) => {
setValue("organization", [r]);
trigger("organization");
}}
/>
)}
/>
{dists.length > 0 && speciesData?.results && (
<>
<AutoComplete
data={speciesOptionsFromParent()}
multiselect
selectedKeys={selectedSpeciesKeys}
onChange={(keys: (string | number)[]) => {
setSelectedSpeciesKeys(keys);
}}
title="گونه"
/>
{visibleDists.map((dist) => {
const countVal = counts[dist.id];
const numCount =
countVal !== "" && countVal !== undefined && countVal !== null
? Number(countVal)
: null;
const isOverMax = numCount !== null && numCount > dist.maxCount;
const isEmpty = countVal === "" || countVal === undefined;
const helperText = isOverMax
? `تعداد نباید بیشتر از ${dist.maxCount.toLocaleString()} باشد`
: isEmpty
? "لطفا تعداد را وارد کنید"
: undefined;
return (
<Textfield
key={dist.id}
fullWidth
formattedNumber
placeholder={`تعداد ${getSpeciesName(dist.species_code)}${dist.label ? ` (${dist.label})` : ""} — حداکثر: ${dist.maxCount.toLocaleString()}`}
value={counts[dist.id] ?? ""}
onChange={(e) =>
handleCountChange(dist.id, Number(e.target.value))
}
error={isOverMax}
helperText={helperText}
/>
);
})}
</>
)}
<Button disabled={!hasValidDists} type="submit">
ثبت
</Button>
</Grid>
</form>
);
};

Some files were not shown because too many files have changed in this diff Show More