first commit
4
.dockerignore
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules;
|
||||||
|
npm - debug.log.git.gitignore;
|
||||||
|
README.md.env.nyc_output;
|
||||||
|
coverage.DS_Store;
|
||||||
27
.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
54
README.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# React + TypeScript + Vite
|
||||||
|
|
||||||
|
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||||
|
|
||||||
|
Currently, two official plugins are available:
|
||||||
|
|
||||||
|
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||||
|
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||||
|
|
||||||
|
## Expanding the ESLint configuration
|
||||||
|
|
||||||
|
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default tseslint.config({
|
||||||
|
extends: [
|
||||||
|
// Remove ...tseslint.configs.recommended and replace with this
|
||||||
|
...tseslint.configs.recommendedTypeChecked,
|
||||||
|
// Alternatively, use this for stricter rules
|
||||||
|
...tseslint.configs.strictTypeChecked,
|
||||||
|
// Optionally, add this for stylistic rules
|
||||||
|
...tseslint.configs.stylisticTypeChecked,
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
// other options...
|
||||||
|
parserOptions: {
|
||||||
|
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// eslint.config.js
|
||||||
|
import reactX from 'eslint-plugin-react-x'
|
||||||
|
import reactDom from 'eslint-plugin-react-dom'
|
||||||
|
|
||||||
|
export default tseslint.config({
|
||||||
|
plugins: {
|
||||||
|
// Add the react-x and react-dom plugins
|
||||||
|
'react-x': reactX,
|
||||||
|
'react-dom': reactDom,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// other rules...
|
||||||
|
// Enable its recommended typescript rules
|
||||||
|
...reactX.configs['recommended-typescript'].rules,
|
||||||
|
...reactDom.configs.recommended.rules,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
9
docker-compose.yml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
rasaddam:
|
||||||
|
build: .
|
||||||
|
image: wixarm/rasaddam:latest
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
restart: unless-stopped
|
||||||
16
dockerfile
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
FROM node:18-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
RUN ls -la
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["npx", "vite", "preview", "--host", "0.0.0.0", "--port", "3000"]
|
||||||
28
eslint.config.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import js from "@eslint/js";
|
||||||
|
import globals from "globals";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
import pluginReact from "eslint-plugin-react";
|
||||||
|
import { defineConfig } from "eslint/config";
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
{
|
||||||
|
files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
|
||||||
|
plugins: { js },
|
||||||
|
extends: ["js/recommended"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
|
||||||
|
languageOptions: { globals: globals.browser },
|
||||||
|
},
|
||||||
|
tseslint.configs.recommended,
|
||||||
|
pluginReact.configs.flat.recommended,
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
"react/react-in-jsx-scope": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"react/prop-types": "off",
|
||||||
|
"@typescript-eslint/no-unused-expressions": "off",
|
||||||
|
"prefer-const": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
17
index.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/src/assets/images/fav.png" />
|
||||||
|
<link href="/src/styles.css" rel="stylesheet">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>سامانه رصددام</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body dir="rtl" class="bg-white dark:bg-dark-900">
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
8
liara.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"app": "rasaddam-front",
|
||||||
|
"platform": "react",
|
||||||
|
"react": {
|
||||||
|
"mirror": true,
|
||||||
|
"sourceMap": false
|
||||||
|
}
|
||||||
|
}
|
||||||
7185
package-lock.json
generated
Normal file
47
package.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "rasad-dam-system",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"bump": "node scripts/bump-version.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@heroicons/react": "^2.2.0",
|
||||||
|
"@hookform/resolvers": "^5.0.1",
|
||||||
|
"@tailwindcss/vite": "^4.1.5",
|
||||||
|
"@tanstack/react-query": "^5.76.1",
|
||||||
|
"@tanstack/react-router": "^1.119.0",
|
||||||
|
"@vitejs/plugin-react": "^4.4.1",
|
||||||
|
"axios": "^1.9.0",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"date-fns-jalali": "^4.1.0-0",
|
||||||
|
"framer-motion": "^12.10.5",
|
||||||
|
"gsap": "^3.13.0",
|
||||||
|
"jalali-moment": "^3.3.11",
|
||||||
|
"lottie-react": "^2.4.1",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"react-hook-form": "^7.56.4",
|
||||||
|
"react-toastify": "^11.0.5",
|
||||||
|
"zod": "^3.25.28",
|
||||||
|
"zustand": "^5.0.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.28.0",
|
||||||
|
"@types/react": "^19.1.4",
|
||||||
|
"@types/react-dom": "^19.1.5",
|
||||||
|
"eslint": "^9.28.0",
|
||||||
|
"eslint-plugin-react": "^7.37.5",
|
||||||
|
"globals": "^16.2.0",
|
||||||
|
"typescript": "~5.7.2",
|
||||||
|
"typescript-eslint": "^8.33.0",
|
||||||
|
"vite": "^6.3.1",
|
||||||
|
"vite-plugin-svgr": "^4.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
public/vite.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
38
scripts/bump-version.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { readFileSync, writeFileSync } from "fs";
|
||||||
|
import { execSync } from "child_process";
|
||||||
|
import { join, dirname } from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
const rootDir = join(__dirname, "..");
|
||||||
|
const versionFile = join(rootDir, "src", "version.txt");
|
||||||
|
|
||||||
|
const version = readFileSync(versionFile, "utf-8").trim();
|
||||||
|
const parts = version.split(".");
|
||||||
|
const major = parseInt(parts[0], 10);
|
||||||
|
const minor = parseInt(parts[1], 10) + 1;
|
||||||
|
const newVersion = `${major.toString().padStart(2, "0")}.${minor
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0")}`;
|
||||||
|
|
||||||
|
writeFileSync(versionFile, newVersion + "\n", "utf-8");
|
||||||
|
|
||||||
|
try {
|
||||||
|
execSync(`git add "${versionFile}"`, { cwd: rootDir, stdio: "inherit" });
|
||||||
|
execSync(`git commit -m "version changed to ${newVersion}"`, {
|
||||||
|
cwd: rootDir,
|
||||||
|
stdio: "inherit",
|
||||||
|
});
|
||||||
|
execSync(`git push`, {
|
||||||
|
cwd: rootDir,
|
||||||
|
stdio: "inherit",
|
||||||
|
});
|
||||||
|
execSync(`git pull`, {
|
||||||
|
cwd: rootDir,
|
||||||
|
stdio: "inherit",
|
||||||
|
});
|
||||||
|
console.log(`Version bumped to ${newVersion} and committed`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to commit:", error.message);
|
||||||
|
}
|
||||||
33
src/App.css
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
@import url("./assets/fonts/fonts.css");
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: "iranyekan", -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||||
|
"Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
|
||||||
|
"Helvetica Neue", sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
/* zoom: 90%; */
|
||||||
|
overflow-x: hidden;
|
||||||
|
font-family: "iranyekan" !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollbar-hide::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.scrollbar-hide {
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Toastify__toast {
|
||||||
|
font-family: "iranyekan" !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-scrollbar {
|
||||||
|
scrollbar-color: #323232 transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light-scrollbar {
|
||||||
|
scrollbar-color: #d9d9d9 transparent;
|
||||||
|
}
|
||||||
114
src/App.tsx
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import { useEffect, useMemo, useCallback } from "react";
|
||||||
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import { RouterProvider } from "@tanstack/react-router";
|
||||||
|
import { ToastContainer } from "react-toastify";
|
||||||
|
|
||||||
|
import {
|
||||||
|
useUserProfileStore,
|
||||||
|
useUserStore,
|
||||||
|
} from "./context/zustand-store/userStore";
|
||||||
|
import { makeRouter } from "./routes/routes";
|
||||||
|
import { useDarkMode } from "./hooks/useDarkMode";
|
||||||
|
import { getUserPermissions } from "./utils/getUserAvalableItems";
|
||||||
|
import { ItemWithSubItems } from "./types/userPermissions";
|
||||||
|
|
||||||
|
import versionNumber from "./version.txt";
|
||||||
|
import "./index.css";
|
||||||
|
import "react-toastify/dist/ReactToastify.css";
|
||||||
|
import { checkIsMobile } from "./utils/checkIsMobile";
|
||||||
|
|
||||||
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
const auth = useUserStore((s) => s.auth);
|
||||||
|
const { profile } = useUserProfileStore();
|
||||||
|
const [isDark] = useDarkMode();
|
||||||
|
|
||||||
|
const menuItems: ItemWithSubItems[] = useMemo(
|
||||||
|
() => getUserPermissions(profile?.permissions ?? []),
|
||||||
|
[profile?.permissions]
|
||||||
|
);
|
||||||
|
|
||||||
|
const router = useMemo(
|
||||||
|
() => makeRouter(auth ?? null, menuItems),
|
||||||
|
[auth, menuItems]
|
||||||
|
);
|
||||||
|
|
||||||
|
const hardRefresh = useCallback(() => {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
url.searchParams.set("refresh", Date.now().toString());
|
||||||
|
window.location.href = url.toString();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const runWhenIdle = useCallback((fn: () => void) => {
|
||||||
|
const ric = (window as any).requestIdleCallback;
|
||||||
|
if (typeof ric === "function") {
|
||||||
|
ric(fn);
|
||||||
|
} else {
|
||||||
|
setTimeout(fn, 300);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let aborted = false;
|
||||||
|
const controller = new AbortController();
|
||||||
|
const checkVersion = () => {
|
||||||
|
if (document.visibilityState !== "visible") return;
|
||||||
|
fetch(`${versionNumber}?_=${Date.now()}`, {
|
||||||
|
signal: controller.signal,
|
||||||
|
cache: "no-store",
|
||||||
|
})
|
||||||
|
.then((res) => res.text())
|
||||||
|
.then(async (txt) => {
|
||||||
|
if (aborted) return;
|
||||||
|
const latest = txt.trim();
|
||||||
|
const stored = localStorage.getItem("AppVersion");
|
||||||
|
if (latest && latest !== stored) {
|
||||||
|
localStorage.setItem("AppVersion", latest);
|
||||||
|
const clearAndReload = async () => {
|
||||||
|
if ("caches" in window) {
|
||||||
|
const names = await caches.keys();
|
||||||
|
for (const n of names) {
|
||||||
|
await caches.delete(n).catch(() => undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTimeout(hardRefresh, 200);
|
||||||
|
};
|
||||||
|
runWhenIdle(clearAndReload);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
};
|
||||||
|
runWhenIdle(checkVersion);
|
||||||
|
return () => {
|
||||||
|
aborted = true;
|
||||||
|
controller.abort();
|
||||||
|
};
|
||||||
|
}, [hardRefresh, runWhenIdle]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
if (url.searchParams.has("refresh")) {
|
||||||
|
url.searchParams.delete("refresh");
|
||||||
|
window.history.replaceState(
|
||||||
|
{},
|
||||||
|
document.title,
|
||||||
|
url.pathname + url.search
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
isDark ? "dark:bg-dark-900 dark-scrollbar" : "bg-white light-scrollbar"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</QueryClientProvider>
|
||||||
|
<ToastContainer position="bottom-right" />
|
||||||
|
{checkIsMobile() && <div className="h-20"></div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
218
src/Pages/Auth.tsx
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
import { motion } from "framer-motion";
|
||||||
|
import img from "../assets/images/auth-bg.png";
|
||||||
|
import logo from "../assets/images/logo.png";
|
||||||
|
import Textfield from "../components/Textfeild/Textfeild";
|
||||||
|
import Button from "../components/Button/Button";
|
||||||
|
import Typography from "../components/Typography/Typography";
|
||||||
|
import {
|
||||||
|
useUserProfileStore,
|
||||||
|
useUserStore,
|
||||||
|
} from "../context/zustand-store/userStore";
|
||||||
|
import { getBase64ImageSrc } from "../utils/getBase64ImageSrc";
|
||||||
|
import { useApiMutation } from "../utils/useApiRequest";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import noImage from "../assets/images/not-loaded-captcha.png";
|
||||||
|
import { ArrowPathIcon } from "@heroicons/react/24/outline";
|
||||||
|
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { zValidate, zValidateString } from "../data/getFormTypeErrors";
|
||||||
|
import { useToast } from "../hooks/useToast";
|
||||||
|
|
||||||
|
const containerVariants = {
|
||||||
|
hidden: {},
|
||||||
|
show: {
|
||||||
|
transition: {
|
||||||
|
staggerChildren: 0.1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const fadeInUp = {
|
||||||
|
hidden: { opacity: 0, y: 10 },
|
||||||
|
show: { opacity: 1, y: 0, transition: { duration: 0.4, ease: "easeOut" } },
|
||||||
|
};
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
nationalId: zValidateString("کد ملی"),
|
||||||
|
password: zValidateString("کلمه عبور"),
|
||||||
|
captcha: z.coerce
|
||||||
|
.string(zValidate("کپچا"))
|
||||||
|
.min(1, "کپچا الزامی است")
|
||||||
|
.min(1, "فیلد نمیتواند خالی باشد!"),
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormValues = z.infer<typeof schema>;
|
||||||
|
|
||||||
|
interface Captcha {
|
||||||
|
captcha_image: string | null;
|
||||||
|
captcha_key: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Auth = () => {
|
||||||
|
const { setUser } = useUserStore();
|
||||||
|
const { setUserProfile } = useUserProfileStore();
|
||||||
|
|
||||||
|
const mutationCaptcha = useApiMutation({
|
||||||
|
api: "/captcha/",
|
||||||
|
method: "post",
|
||||||
|
disableBackdrop: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mutationLogin = useApiMutation({
|
||||||
|
api: "/auth/api/v1/login/",
|
||||||
|
method: "post",
|
||||||
|
disableBackdrop: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mutationProfile = useApiMutation({
|
||||||
|
api: "/auth/api/v1/user/profile/",
|
||||||
|
method: "get",
|
||||||
|
disableBackdrop: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleGetCaptcha = async () => {
|
||||||
|
const data = await mutationCaptcha.mutateAsync({});
|
||||||
|
setCaptcha(data);
|
||||||
|
};
|
||||||
|
const showToast = useToast();
|
||||||
|
|
||||||
|
const [captcha, setCaptcha] = useState<Captcha>();
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
setValue,
|
||||||
|
watch,
|
||||||
|
} = useForm<FormValues>({
|
||||||
|
resolver: zodResolver(schema),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleGetCaptcha();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getProfile = async () => {
|
||||||
|
const profile = await mutationProfile.mutateAsync({});
|
||||||
|
if (typeof profile === "object") {
|
||||||
|
setUserProfile(profile);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (data: FormValues) => {
|
||||||
|
try {
|
||||||
|
const response = await mutationLogin.mutateAsync({
|
||||||
|
username: data.nationalId,
|
||||||
|
password: data.password,
|
||||||
|
captcha_code: data.captcha.toString(),
|
||||||
|
captcha_key: `rest_captcha_${captcha?.captcha_key ?? ""}.0`,
|
||||||
|
});
|
||||||
|
|
||||||
|
setUser({ auth: response?.access });
|
||||||
|
getProfile();
|
||||||
|
} catch (error: any) {
|
||||||
|
handleGetCaptcha();
|
||||||
|
if (error?.status === 403) {
|
||||||
|
setValue("captcha", "");
|
||||||
|
showToast("کپچا اشتباه است!", "error");
|
||||||
|
} else {
|
||||||
|
setValue("captcha", "");
|
||||||
|
showToast("نام کاربری یا رمز عبور اشتباه است!", "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const imageSrc = getBase64ImageSrc(captcha?.captcha_image ?? "");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="relative flex h-screen flex-col md:flex-row overflow-y-auto dark:bg-dark-900"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(${img})`,
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 bg-black opacity-0 dark:opacity-50 transition-opacity duration-300 pointer-events-none z-0" />
|
||||||
|
<motion.div
|
||||||
|
initial={{ scale: 0.95, opacity: 0 }}
|
||||||
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
className="flex w-full h-screen items-center justify-center px-6 sm:px-12 md:px-24 lg:px-40 py-8"
|
||||||
|
>
|
||||||
|
<motion.form
|
||||||
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
variants={containerVariants}
|
||||||
|
initial="hidden"
|
||||||
|
animate="show"
|
||||||
|
className="w-full p-10 bg-white rounded-xl flex flex-col max-w-sm space-y-2 items-center justify-center dark:bg-dark-800"
|
||||||
|
>
|
||||||
|
<motion.img
|
||||||
|
variants={fadeInUp}
|
||||||
|
src={logo}
|
||||||
|
alt="Logo"
|
||||||
|
className="mx-auto w-20 select-none mb-4 dark:brightness-100 dark:opacity-100"
|
||||||
|
/>
|
||||||
|
<motion.div variants={fadeInUp}>
|
||||||
|
<Typography className="mb-4 select-none">سامانه رصدام</Typography>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div variants={fadeInUp} className="w-full">
|
||||||
|
<Textfield
|
||||||
|
fullWidth
|
||||||
|
placeholder="نام کاربری"
|
||||||
|
{...register("nationalId")}
|
||||||
|
error={!!errors.nationalId}
|
||||||
|
helperText={errors.nationalId?.message}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
<motion.div variants={fadeInUp} className="w-full">
|
||||||
|
<Textfield
|
||||||
|
fullWidth
|
||||||
|
placeholder="کلمه عبور"
|
||||||
|
password
|
||||||
|
{...register("password")}
|
||||||
|
error={!!errors.password}
|
||||||
|
helperText={errors.password?.message}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div variants={fadeInUp} className="flex w-full items-center">
|
||||||
|
<Textfield
|
||||||
|
fullWidth
|
||||||
|
className="w-[100%]"
|
||||||
|
placeholder="کد امنیتی"
|
||||||
|
isNumber
|
||||||
|
{...register("captcha")}
|
||||||
|
value={watch("captcha") || ""}
|
||||||
|
error={!!errors.captcha}
|
||||||
|
helperText={errors.captcha?.message}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleGetCaptcha}
|
||||||
|
className="p-1 rounded-full hover:bg-gray-200 transition"
|
||||||
|
>
|
||||||
|
<ArrowPathIcon className="h-5 w-5 text-gray-600" />
|
||||||
|
</button>
|
||||||
|
<div className="h-10 w-[180px] overflow-hidden rounded-2xl">
|
||||||
|
<img
|
||||||
|
className="w-full h-full object-cover scale-100"
|
||||||
|
src={imageSrc.endsWith(",") ? noImage : imageSrc}
|
||||||
|
alt="captcha"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div variants={fadeInUp} className="w-full">
|
||||||
|
<Button fullWidth className="mt-1" type="submit">
|
||||||
|
ورود
|
||||||
|
</Button>
|
||||||
|
</motion.div>
|
||||||
|
</motion.form>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
185
src/Pages/CooperativeRanchers.tsx
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
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";
|
||||||
|
|
||||||
|
export default function CooperativeRanchers() {
|
||||||
|
const { openModal } = useModalStore();
|
||||||
|
const [pagesInfo, setPagesInfo] = useState({ page: 1, page_size: 10 });
|
||||||
|
const [cooperativesTableData, setCooperativesTableData] = useState([]);
|
||||||
|
const { id, name } = useParams({ strict: false });
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const { data: cooperativeRanchersData, refetch: cooperativeRanchersRefetch } =
|
||||||
|
useApiRequest({
|
||||||
|
api: `herd/web/api/v1/rancher_org_link/${id}/org_ranchers/`,
|
||||||
|
method: "get",
|
||||||
|
params: {
|
||||||
|
...pagesInfo,
|
||||||
|
},
|
||||||
|
queryKey: ["cooperativeRanchers", pagesInfo],
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: cooperativeRanchersDashboardData,
|
||||||
|
refetch: cooperativeRanchersDashboardRefetch,
|
||||||
|
} = useApiRequest({
|
||||||
|
api: `herd/web/api/v1/rancher_org_link/${id}/org_ranchers_quota_dashboard/`,
|
||||||
|
method: "get",
|
||||||
|
queryKey: ["cooperativeRanchersDashboard", id],
|
||||||
|
enabled: !!id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleUpdate = () => {
|
||||||
|
cooperativeRanchersRefetch();
|
||||||
|
cooperativeRanchersDashboardRefetch();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (cooperativeRanchersData?.results) {
|
||||||
|
const tableData = cooperativeRanchersData.results.map(
|
||||||
|
(item: any, i: number) => {
|
||||||
|
return [
|
||||||
|
pagesInfo.page === 1
|
||||||
|
? i + 1
|
||||||
|
: i + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
|
||||||
|
item?.ranching_farm,
|
||||||
|
item?.rancher?.first_name,
|
||||||
|
item?.rancher?.last_name,
|
||||||
|
item?.rancher?.national_code,
|
||||||
|
item?.rancher?.mobile,
|
||||||
|
item?.rancher?.activity === "V"
|
||||||
|
? "روستایی"
|
||||||
|
: item?.rancher?.activity === "I"
|
||||||
|
? "صنعتی"
|
||||||
|
: item?.rancher?.activity === "R"
|
||||||
|
? "عشایری"
|
||||||
|
: "-",
|
||||||
|
item?.rancher?.province?.name || "-",
|
||||||
|
item?.rancher?.city?.name || "-",
|
||||||
|
item?.rancher?.address,
|
||||||
|
item?.rancher?.without_herd ? "بدون دام" : "دامدار عادی",
|
||||||
|
item?.rancher?.rancher_type === "N" ? "حقیقی" : "حقوقی",
|
||||||
|
item?.rancher?.union_name || "-",
|
||||||
|
item?.rancher?.union_code || "-",
|
||||||
|
<Popover key={i}>
|
||||||
|
<Tooltip title="مشاهده گله ها" position="right">
|
||||||
|
<Button
|
||||||
|
variant="detail"
|
||||||
|
page="farmer_details"
|
||||||
|
access="See-Herds"
|
||||||
|
onClick={() => {
|
||||||
|
const path =
|
||||||
|
LIVESTOCK_FARMERS +
|
||||||
|
"/" +
|
||||||
|
item?.rancher?.id +
|
||||||
|
"/" +
|
||||||
|
item?.rancher?.ranching_farm;
|
||||||
|
navigate({ to: path });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<DeleteButtonForPopOver
|
||||||
|
page="farmer_details"
|
||||||
|
access="Delete-Rancher-Herd"
|
||||||
|
api={`herd/web/api/v1/rancher_org_link/${item?.id}/`}
|
||||||
|
getData={handleUpdate}
|
||||||
|
tooltipText="حذف دامدار از تعاونی"
|
||||||
|
title="از حذف دامدار از تعاونی مطمئنید؟"
|
||||||
|
/>
|
||||||
|
</Popover>,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
setCooperativesTableData(tableData);
|
||||||
|
}
|
||||||
|
}, [cooperativeRanchersData, pagesInfo, openModal]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container column className="gap-4 mt-2">
|
||||||
|
{id && (
|
||||||
|
<Grid isDashboard>
|
||||||
|
<Table
|
||||||
|
isDashboard
|
||||||
|
title="خلاصه اطلاعات"
|
||||||
|
noPagination
|
||||||
|
noSearch
|
||||||
|
columns={[
|
||||||
|
"تعداد کل سهمیه ها",
|
||||||
|
"مجموع وزن سهمیه ها (کیلوگرم)",
|
||||||
|
"مجموع وزن توزیع شده (کیلوگرم)",
|
||||||
|
"مجموع وزن باقیمانده (کیلوگرم)",
|
||||||
|
"مجموع وزن فروش رفته (کیلوگرم)",
|
||||||
|
"مجموع وزن ورود به انبار (کیلوگرم)",
|
||||||
|
"جزئیات",
|
||||||
|
]}
|
||||||
|
rows={[
|
||||||
|
[
|
||||||
|
cooperativeRanchersDashboardData?.quotas_summary?.total_quotas?.toLocaleString() ||
|
||||||
|
0,
|
||||||
|
cooperativeRanchersDashboardData?.quotas_summary?.total_amount?.toLocaleString() ||
|
||||||
|
0,
|
||||||
|
cooperativeRanchersDashboardData?.quotas_summary?.total_distributed?.toLocaleString() ||
|
||||||
|
0,
|
||||||
|
cooperativeRanchersDashboardData?.quotas_summary?.remaining_amount?.toLocaleString() ||
|
||||||
|
0,
|
||||||
|
cooperativeRanchersDashboardData?.quotas_summary?.sold_amount?.toLocaleString() ||
|
||||||
|
0,
|
||||||
|
cooperativeRanchersDashboardData?.quotas_summary?.inventory_received?.toLocaleString() ||
|
||||||
|
0,
|
||||||
|
<TableButton
|
||||||
|
size="small"
|
||||||
|
key="details"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "جزئیات",
|
||||||
|
content: <CooperativesDashboardDetails orgId={id} />,
|
||||||
|
isFullSize: true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
جزئیات
|
||||||
|
</TableButton>,
|
||||||
|
],
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Table
|
||||||
|
className="mt-2"
|
||||||
|
onChange={setPagesInfo}
|
||||||
|
count={cooperativeRanchersData?.count || 10}
|
||||||
|
isPaginated
|
||||||
|
title={id ? `دامداران ${name}` : "دامداران تعاونی"}
|
||||||
|
columns={[
|
||||||
|
"ردیف",
|
||||||
|
"نام دامداری",
|
||||||
|
"نام",
|
||||||
|
"نام خانوادگی",
|
||||||
|
"کد ملی",
|
||||||
|
"موبایل",
|
||||||
|
"نوع فعالیت",
|
||||||
|
"استان",
|
||||||
|
"شهر",
|
||||||
|
"آدرس",
|
||||||
|
"وضعیت",
|
||||||
|
"نوع دامدار",
|
||||||
|
"نام واحد حقوقی",
|
||||||
|
"شناسه ملی واحد حقوقی",
|
||||||
|
"عملیات",
|
||||||
|
]}
|
||||||
|
rows={cooperativesTableData}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
215
src/Pages/Cooperatives.tsx
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
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";
|
||||||
|
|
||||||
|
export default function Cooperatives() {
|
||||||
|
const { openModal } = useModalStore();
|
||||||
|
const [pagesInfo, setPagesInfo] = useState({ page: 1, page_size: 10 });
|
||||||
|
const [cooperativesTableData, setCooperativesTableData] = useState([]);
|
||||||
|
const { id, name } = useParams({ strict: false });
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const { data: cooperativesData, refetch } = useApiRequest({
|
||||||
|
api: `herd/web/api/v1/rancher_org_link/org_linked_rancher_list${
|
||||||
|
id ? `?org_id=${id}` : ""
|
||||||
|
}`,
|
||||||
|
method: "get",
|
||||||
|
params: {
|
||||||
|
...pagesInfo,
|
||||||
|
},
|
||||||
|
queryKey: [id ? "unioncooperatives" : "cooperatives", pagesInfo],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: cooperativesDashboardData } = useApiRequest({
|
||||||
|
api: `herd/web/api/v1/rancher_org_link/${id}/org_ranchers_quota_dashboard/`,
|
||||||
|
method: "get",
|
||||||
|
queryKey: [id ? "unionCooperativeDashboard" : "cooperativeDashboard"],
|
||||||
|
enabled: !!id,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (cooperativesData?.results) {
|
||||||
|
const formattedData = cooperativesData.results.map(
|
||||||
|
(item: any, i: number) => {
|
||||||
|
return [
|
||||||
|
pagesInfo.page === 1
|
||||||
|
? i + 1
|
||||||
|
: i + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
|
||||||
|
item?.name || "-",
|
||||||
|
item?.province || "-",
|
||||||
|
item?.city || "-",
|
||||||
|
item?.rancher_count || 0,
|
||||||
|
item?.herd_count || 0,
|
||||||
|
item?.livestock_count || 0,
|
||||||
|
item?.org_service_area?.length ? (
|
||||||
|
<ShowMoreInfo key={i} title="محدوده فعالیت">
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
column
|
||||||
|
className="gap-2 p-2 justify-start items-start w-full"
|
||||||
|
>
|
||||||
|
<ShowStringList
|
||||||
|
showSearch={false}
|
||||||
|
strings={item.org_service_area.map(
|
||||||
|
(city: any) => city.name
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</ShowMoreInfo>
|
||||||
|
) : (
|
||||||
|
"-"
|
||||||
|
),
|
||||||
|
item?.org_purchase_policy === "INTERNAL_ONLY"
|
||||||
|
? "بر اساس تعاونی"
|
||||||
|
: item?.org_purchase_policy === "CROSS_COOP"
|
||||||
|
? "برای کل استان"
|
||||||
|
: "-",
|
||||||
|
<Popover key={i}>
|
||||||
|
<Tooltip title="دامداران تعاونی" position="right">
|
||||||
|
<Button
|
||||||
|
variant="view"
|
||||||
|
page="cooperative_ranchers"
|
||||||
|
access="Show-Cooperative-Ranchers"
|
||||||
|
onClick={() => {
|
||||||
|
const path =
|
||||||
|
COOPERATIVE_LIST +
|
||||||
|
"/ranchers/" +
|
||||||
|
item?.id +
|
||||||
|
"/" +
|
||||||
|
item?.name;
|
||||||
|
navigate({ to: path });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="تعریف نوع فعالیت" position="right">
|
||||||
|
<Button
|
||||||
|
variant="set"
|
||||||
|
page="cooperatives"
|
||||||
|
access="Set-Cooperative-Activity-Type"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: `تعریف نوع فعالیت ${item?.name || ""}`,
|
||||||
|
content: (
|
||||||
|
<AddActivityType getData={refetch} item={item} />
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Popover>,
|
||||||
|
|
||||||
|
<Tooltip title="زیرمجموعه ها" position="right">
|
||||||
|
<Button
|
||||||
|
variant="detail"
|
||||||
|
page="cooperatives"
|
||||||
|
access="Show-Child-Organizations"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: `زیرمجموعه های ${item?.name || ""}`,
|
||||||
|
content: (
|
||||||
|
<ChildOrganizations
|
||||||
|
orgId={item?.id}
|
||||||
|
orgName={item?.name || ""}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
isFullSize: true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
setCooperativesTableData(formattedData);
|
||||||
|
}
|
||||||
|
}, [cooperativesData, pagesInfo, openModal, navigate, refetch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container column className="gap-4 mt-2">
|
||||||
|
{id && (
|
||||||
|
<Grid isDashboard>
|
||||||
|
<Table
|
||||||
|
isDashboard
|
||||||
|
title="خلاصه اطلاعات"
|
||||||
|
noPagination
|
||||||
|
noSearch
|
||||||
|
columns={[
|
||||||
|
"تعداد کل سهمیه ها",
|
||||||
|
"مجموع وزن سهمیه ها (کیلوگرم)",
|
||||||
|
"مجموع وزن توزیع شده (کیلوگرم)",
|
||||||
|
"مجموع وزن باقیمانده (کیلوگرم)",
|
||||||
|
"مجموع وزن فروش رفته (کیلوگرم)",
|
||||||
|
"مجموع وزن ورود به انبار (کیلوگرم)",
|
||||||
|
"جزئیات",
|
||||||
|
]}
|
||||||
|
rows={[
|
||||||
|
[
|
||||||
|
cooperativesDashboardData?.quotas_summary?.total_quotas?.toLocaleString() ||
|
||||||
|
0,
|
||||||
|
cooperativesDashboardData?.quotas_summary?.total_amount?.toLocaleString() ||
|
||||||
|
0,
|
||||||
|
cooperativesDashboardData?.quotas_summary?.total_distributed?.toLocaleString() ||
|
||||||
|
0,
|
||||||
|
cooperativesDashboardData?.quotas_summary?.remaining_amount?.toLocaleString() ||
|
||||||
|
0,
|
||||||
|
cooperativesDashboardData?.quotas_summary?.sold_amount?.toLocaleString() ||
|
||||||
|
0,
|
||||||
|
cooperativesDashboardData?.quotas_summary?.inventory_received?.toLocaleString() ||
|
||||||
|
0,
|
||||||
|
<TableButton
|
||||||
|
size="small"
|
||||||
|
key="details"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "جزئیات",
|
||||||
|
content: <CooperativesDashboardDetails orgId={id} />,
|
||||||
|
isFullSize: true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
جزئیات
|
||||||
|
</TableButton>,
|
||||||
|
],
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Table
|
||||||
|
className="mt-2"
|
||||||
|
onChange={(e) => {
|
||||||
|
setPagesInfo(e);
|
||||||
|
}}
|
||||||
|
count={cooperativesData?.count || 10}
|
||||||
|
isPaginated
|
||||||
|
title={id ? `تعاونی های ${name}` : "تعاونی ها"}
|
||||||
|
columns={[
|
||||||
|
"ردیف",
|
||||||
|
"نام",
|
||||||
|
"استان",
|
||||||
|
"شهر",
|
||||||
|
"تعداد دامدار",
|
||||||
|
"تعداد گله",
|
||||||
|
"تعداد دام",
|
||||||
|
"محدوده فعالیت",
|
||||||
|
"محدودیت دریافت نهاده",
|
||||||
|
"عملیات",
|
||||||
|
]}
|
||||||
|
rows={cooperativesTableData}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
447
src/Pages/Dashboard.tsx
Normal file
@@ -0,0 +1,447 @@
|
|||||||
|
import moment from "jalali-moment";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import Typography from "../components/Typography/Typography";
|
||||||
|
import { useUserProfileStore } from "../context/zustand-store/userStore";
|
||||||
|
import { ItemWithSubItems } from "../types/userPermissions";
|
||||||
|
import { getUserPermissions } from "../utils/getUserAvalableItems";
|
||||||
|
import { getFaPermissions } from "../utils/getFaPermissions";
|
||||||
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
|
import {
|
||||||
|
MagnifyingGlassIcon,
|
||||||
|
Squares2X2Icon,
|
||||||
|
XMarkIcon,
|
||||||
|
ClockIcon,
|
||||||
|
CalendarIcon,
|
||||||
|
ArrowRightCircleIcon,
|
||||||
|
} from "@heroicons/react/24/outline";
|
||||||
|
import { checkIsMobile } from "../utils/checkIsMobile";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
import { useDashboardTabStore } from "../context/zustand-store/dashboardTabStore";
|
||||||
|
|
||||||
|
interface Tab {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
component: React.ComponentType;
|
||||||
|
path: string;
|
||||||
|
icon?: React.ComponentType<{ className?: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Dashboard() {
|
||||||
|
const { profile } = useUserProfileStore();
|
||||||
|
const { dashboarTabs, setDashboardTabs, activeTabId, setActiveTabId } =
|
||||||
|
useDashboardTabStore();
|
||||||
|
|
||||||
|
const menuItems: ItemWithSubItems[] = getUserPermissions(
|
||||||
|
profile?.permissions
|
||||||
|
);
|
||||||
|
|
||||||
|
const [tabs, setTabs] = useState<Tab[]>(dashboarTabs || []);
|
||||||
|
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTabs(dashboarTabs || []);
|
||||||
|
}, [dashboarTabs]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDashboardTabs(tabs);
|
||||||
|
}, [tabs, setDashboardTabs]);
|
||||||
|
|
||||||
|
const persianDate = moment().locale("fa").format("dddd D MMMM YYYY");
|
||||||
|
const [time, setTime] = useState(
|
||||||
|
new Date().toLocaleTimeString("fa-IR", {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
hour12: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setTime(
|
||||||
|
new Date().toLocaleTimeString("fa-IR", {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
hour12: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, 60000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const openTab = (subItem: ItemWithSubItems["subItems"][0]) => {
|
||||||
|
const existingTab = tabs.find((tab) => tab.path === subItem.path);
|
||||||
|
|
||||||
|
if (existingTab) {
|
||||||
|
setActiveTabId(existingTab.id);
|
||||||
|
} else {
|
||||||
|
const newTab = {
|
||||||
|
id: `tab-${Date.now()}`,
|
||||||
|
title: getFaPermissions(subItem.name),
|
||||||
|
component: subItem.component,
|
||||||
|
path: subItem.path,
|
||||||
|
};
|
||||||
|
|
||||||
|
setTabs((prev) => [...prev, newTab]);
|
||||||
|
setActiveTabId(newTab.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeTab = (id: string, e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const newTabs = tabs.filter((tab) => tab.id !== id);
|
||||||
|
setTabs(newTabs);
|
||||||
|
|
||||||
|
if (activeTabId === id) {
|
||||||
|
setActiveTabId(
|
||||||
|
newTabs.length > 0 ? newTabs[newTabs.length - 1].id : null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeAllTabs = () => {
|
||||||
|
setTabs([]);
|
||||||
|
setActiveTabId(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredMenuItems = menuItems
|
||||||
|
.map((item) => ({
|
||||||
|
...item,
|
||||||
|
subItems: item.subItems.filter(
|
||||||
|
(subItem) =>
|
||||||
|
!subItem.path.includes("$") &&
|
||||||
|
(search.trim() === "" ||
|
||||||
|
getFaPermissions(subItem.name).includes(search.trim()))
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
.filter((item) => item.subItems.length > 0);
|
||||||
|
|
||||||
|
function findSubItemByPath(
|
||||||
|
items: ItemWithSubItems[],
|
||||||
|
path: string
|
||||||
|
): ItemWithSubItems["subItems"][0] | null {
|
||||||
|
for (const item of items) {
|
||||||
|
for (const subItem of item.subItems) {
|
||||||
|
if (subItem.path === path) return subItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeTabObj = tabs.find((tab) => tab.id === activeTabId);
|
||||||
|
const activeComponentItem =
|
||||||
|
activeTabObj && findSubItemByPath(menuItems, activeTabObj.path);
|
||||||
|
const ActiveComponent = activeComponentItem?.component || null;
|
||||||
|
|
||||||
|
const draggedTabIndex = useRef<number | null>(null);
|
||||||
|
|
||||||
|
const onDragStart = (e: React.DragEvent<HTMLDivElement>, index: number) => {
|
||||||
|
draggedTabIndex.current = index;
|
||||||
|
e.dataTransfer.effectAllowed = "move";
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDragOver = (e: React.DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDrop = (e: React.DragEvent, dropIndex: number) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (
|
||||||
|
draggedTabIndex.current === null ||
|
||||||
|
draggedTabIndex.current === dropIndex
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const newTabs = [...tabs];
|
||||||
|
const draggedItem = newTabs[draggedTabIndex.current];
|
||||||
|
|
||||||
|
newTabs.splice(draggedTabIndex.current, 1);
|
||||||
|
newTabs.splice(dropIndex, 0, draggedItem);
|
||||||
|
|
||||||
|
draggedTabIndex.current = null;
|
||||||
|
setTabs(newTabs);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDragEnd = () => {
|
||||||
|
draggedTabIndex.current = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full px-3 py-2 min-h-screen">
|
||||||
|
<header className="backdrop-blur-xl bg-white/20 dark:bg-dark-800/80 rounded-2xl shadow-lg border border-white/30 dark:border-dark-700/30 p-4 mb-4">
|
||||||
|
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="p-2 rounded-lg bg-primary-600 dark:bg-primary-800 backdrop-blur-sm shadow-lg">
|
||||||
|
<Squares2X2Icon className="w-4 h-4 text-white" />
|
||||||
|
</div>
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
className="text-dark-800 dark:text-dark-100 font-semibold"
|
||||||
|
>
|
||||||
|
داشبورد
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 w-full sm:w-auto">
|
||||||
|
<div className="relative w-full sm:w-48 md:w-64">
|
||||||
|
<div className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
|
||||||
|
<MagnifyingGlassIcon className="w-4 h-4 text-dark-400 dark:text-dark-100" />
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="جستجو..."
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
className="w-full pr-8 pl-3 py-2 text-xs rounded-lg backdrop-blur-sm bg-white/30 dark:bg-dark-800/30 border border-white/40 dark:border-dark-600/40 text-dark-700 dark:text-dark-200 placeholder:text-dark-400 focus:outline-none focus:ring-1 focus:ring-primary-500/50 focus:bg-white/50 dark:focus:bg-dark-700/50 transition-all duration-200"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-center sm:justify-start gap-1.5 px-2 py-1.5 rounded-lg backdrop-blur-sm bg-white/20 dark:bg-dark-800/80 border border-white/30 dark:border-dark-600/30">
|
||||||
|
<CalendarIcon className="w-3 h-3 text-primary-500" />
|
||||||
|
<span className="text-xs text-dark-600 dark:text-dark-300">
|
||||||
|
{persianDate}
|
||||||
|
</span>
|
||||||
|
<span className="mx-1 h-3 w-px bg-dark-300 dark:bg-dark-600"></span>
|
||||||
|
<ClockIcon className="w-3 h-3 text-primary-500" />
|
||||||
|
<span className="text-xs text-dark-600 dark:text-dark-300">
|
||||||
|
{time}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
{filteredMenuItems.length === 0 ? (
|
||||||
|
<div className="backdrop-blur-xl bg-white/20 dark:bg-dark-800/80 rounded-xl shadow-lg border border-white/30 dark:border-dark-700/30 p-8">
|
||||||
|
<div className="flex flex-col items-center justify-center text-center space-y-3">
|
||||||
|
<div className="p-3 rounded-full backdrop-blur-sm bg-white/30 dark:bg-dark-700/30">
|
||||||
|
<MagnifyingGlassIcon className="w-6 h-6 text-dark-400" />
|
||||||
|
</div>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
className="text-dark-600 dark:text-dark-300 font-medium"
|
||||||
|
>
|
||||||
|
موردی یافت نشد
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
className="text-dark-400 dark:text-dark-500"
|
||||||
|
>
|
||||||
|
هیچ آیتمی با عبارت "{search}" مطابقت ندارد
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
checkIsMobile()
|
||||||
|
? "space-y-3 pb-20"
|
||||||
|
: "flex overflow-x-auto pb-2 gap-3 scrollbar-thin scrollbar-thumb-dark-300 dark:scrollbar-thumb-dark-600"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{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" />
|
||||||
|
</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>
|
||||||
|
|
||||||
|
{tabs.length > 0 && !checkIsMobile() && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
className="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 mt-4"
|
||||||
|
>
|
||||||
|
<div className="backdrop-blur-sm bg-white/30 dark:bg-dark-700/30 border-b border-white/20 dark:border-dark-600/20">
|
||||||
|
<div className="flex items-center justify-between px-3 py-2">
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<div className="w-1.5 h-1.5 rounded-full bg-green-500"></div>
|
||||||
|
<span className="text-xs font-medium text-dark-600 dark:text-dark-300">
|
||||||
|
صفحات باز
|
||||||
|
</span>
|
||||||
|
<span className="text-xs text-dark-400 dark:text-dark-500 backdrop-blur-sm bg-white/40 dark:bg-dark-600/40 px-1.5 py-0.5 rounded-full">
|
||||||
|
{tabs.length}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{tabs.length > 1 && (
|
||||||
|
<button
|
||||||
|
onClick={closeAllTabs}
|
||||||
|
className="flex items-center gap-1 text-xs text-dark-500 dark:text-dark-400 hover:text-red-500 dark:hover:text-red-400 px-2 py-1 rounded-md hover:backdrop-blur-sm hover:bg-red-500/20 dark:hover:bg-red-400/20 transition-all duration-200 focus:outline-none"
|
||||||
|
>
|
||||||
|
<XMarkIcon className="w-3 h-3" />
|
||||||
|
بستن همه
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center overflow-x-auto scrollbar-hide backdrop-blur-sm bg-white/20 dark:bg-dark-700/20 border-b border-white/20 dark:border-dark-600/20">
|
||||||
|
<AnimatePresence initial={false}>
|
||||||
|
{tabs.map((tab, index) => (
|
||||||
|
<motion.div
|
||||||
|
draggable
|
||||||
|
key={tab.id}
|
||||||
|
onDragStart={(e: any) => onDragStart(e, index)}
|
||||||
|
onDragOver={onDragOver}
|
||||||
|
onDrop={(e) => onDrop(e, index)}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
initial={{ opacity: 0, x: -10 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
exit={{ opacity: 0, x: 10 }}
|
||||||
|
transition={{ duration: 0.15 }}
|
||||||
|
onClick={() => setActiveTabId(tab.id)}
|
||||||
|
className={`group flex items-center gap-1.5 px-3 py-1 cursor-pointer transition-all duration-200 border-b-2 focus:outline-none ${
|
||||||
|
activeTabId === tab.id
|
||||||
|
? "backdrop-blur-sm bg-primary-500/20 dark:bg-primary-400/20 text-primary-700 dark:text-primary-300 border-primary-500 dark:border-primary-400"
|
||||||
|
: "text-dark-600 dark:text-dark-300 hover:backdrop-blur-sm hover:bg-white/30 dark:hover:bg-dark-600/30 border-transparent hover:border-dark-300 dark:hover:border-dark-500"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{tab.icon && (
|
||||||
|
<tab.icon
|
||||||
|
className={`w-3.5 h-3.5 flex-shrink-0 ${
|
||||||
|
activeTabId === tab.id
|
||||||
|
? "text-primary-600 dark:text-primary-400"
|
||||||
|
: "text-dark-400 dark:text-dark-500"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span className="text-xs font-medium whitespace-nowrap">
|
||||||
|
{tab.title}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={(e) => closeTab(tab.id, e)}
|
||||||
|
className="opacity-0 group-hover:opacity-100 p-0.5 rounded-full hover:backdrop-blur-sm hover:bg-white/40 dark:hover:bg-dark-500/40 transition-all duration-200 focus:outline-none"
|
||||||
|
>
|
||||||
|
<XMarkIcon className="w-2.5 h-2.5" />
|
||||||
|
</button>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
{activeTabId && (
|
||||||
|
<motion.div
|
||||||
|
key={activeTabId}
|
||||||
|
initial={{ opacity: 0, y: 5 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -5 }}
|
||||||
|
transition={{ duration: 0.2, ease: "easeInOut" }}
|
||||||
|
className="p-4 backdrop-blur-sm bg-white/10 dark:bg-dark-800/10"
|
||||||
|
>
|
||||||
|
{ActiveComponent && <ActiveComponent />}
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
270
src/Pages/Herds.tsx
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
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 {
|
||||||
|
useDrawerStore,
|
||||||
|
useModalStore,
|
||||||
|
} from "../context/zustand-store/appStore";
|
||||||
|
import { LiveStockAddHerd } from "../partials/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";
|
||||||
|
|
||||||
|
export default function LiveStocks() {
|
||||||
|
const [pagesInfo, setPagesInfo] = useState({ page: 1, page_size: 10 });
|
||||||
|
const [pagesTableData, setPagesTableData] = useState([]);
|
||||||
|
const { openModal } = useModalStore();
|
||||||
|
const { farmid, name } = useParams({ strict: false });
|
||||||
|
const { openDrawer } = useDrawerStore();
|
||||||
|
const { data: pagesData, refetch } = useApiRequest({
|
||||||
|
api: farmid
|
||||||
|
? `herd/web/api/v1/rancher/${farmid}/herds`
|
||||||
|
: "/herd/web/api/v1/herd/",
|
||||||
|
method: "get",
|
||||||
|
params: pagesInfo,
|
||||||
|
queryKey: ["LiveStockFarmers", pagesInfo],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: DashboardData, refetch: refetchDashboard } = useApiRequest<any>(
|
||||||
|
{
|
||||||
|
api: farmid
|
||||||
|
? `/herd/web/api/v1/rancher/${farmid}/rancher_dashboard/`
|
||||||
|
: "/herd/web/api/v1/rancher/rancher_main_dashboard/",
|
||||||
|
queryKey: ["HerdsDashboard"],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleUpdate = () => {
|
||||||
|
refetch();
|
||||||
|
refetchDashboard();
|
||||||
|
};
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (pagesData?.results) {
|
||||||
|
const tableData = pagesData.results.map((item: any, i: number) => {
|
||||||
|
return [
|
||||||
|
pagesInfo.page === 1
|
||||||
|
? i + 1
|
||||||
|
: i + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
|
||||||
|
item?.unit_unique_id,
|
||||||
|
item?.name,
|
||||||
|
item?.cooperative?.name || "-",
|
||||||
|
item?.contractor?.name || "-",
|
||||||
|
parseInt(item?.capacity)?.toLocaleString(),
|
||||||
|
item?.activity === "I"
|
||||||
|
? "صنعتی"
|
||||||
|
: item?.activity === "V"
|
||||||
|
? "روستایی"
|
||||||
|
: item?.activity === "N"
|
||||||
|
? "عشایری"
|
||||||
|
: "-",
|
||||||
|
item?.epidemiologic,
|
||||||
|
parseInt(item?.light_livestock_number)?.toLocaleString(),
|
||||||
|
parseInt(item?.heavy_livestock_number)?.toLocaleString(),
|
||||||
|
item?.province?.name,
|
||||||
|
item?.city?.name,
|
||||||
|
item?.operating_license_state ? "دارد" : "ندارد",
|
||||||
|
item?.activity_state ? "فعال" : "غیر فعال",
|
||||||
|
item?.postal || "-",
|
||||||
|
<Popover key={i}>
|
||||||
|
<Tooltip title="ویرایش" position="right">
|
||||||
|
<Button
|
||||||
|
variant="edit"
|
||||||
|
page="livestock_farmers"
|
||||||
|
access="Edit-Rancher"
|
||||||
|
onClick={() =>
|
||||||
|
openDrawer({
|
||||||
|
title: "ویرایش گله",
|
||||||
|
content: (
|
||||||
|
<LiveStockAddHerd
|
||||||
|
getData={handleUpdate}
|
||||||
|
item={item}
|
||||||
|
rancher={item?.rancher?.id}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
isOpen: true,
|
||||||
|
direction: "left",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="ایجاد دام" position="right">
|
||||||
|
<Button
|
||||||
|
variant="submit"
|
||||||
|
page="herds"
|
||||||
|
access="Add-LiveStock"
|
||||||
|
onClick={() =>
|
||||||
|
openDrawer({
|
||||||
|
title: "ایجاد دام",
|
||||||
|
content: (
|
||||||
|
<LiveStockAddLiveStock
|
||||||
|
getData={handleUpdate}
|
||||||
|
herdId={item?.id}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
isOpen: true,
|
||||||
|
direction: "left",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="مشاهده دام ها" position="right">
|
||||||
|
<Button
|
||||||
|
variant="detail"
|
||||||
|
page="herd_livestocks"
|
||||||
|
access="Get-Herd-Livestocks"
|
||||||
|
onClick={() => {
|
||||||
|
const path = LIVESTOCKS + "/" + item?.id + "/" + item?.name;
|
||||||
|
navigate({ to: path });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<DeleteButtonForPopOver
|
||||||
|
page="farmer_details"
|
||||||
|
access="Delete-Rancher-Herd"
|
||||||
|
api={`herd/web/api/v1/herd/${item?.id}/`}
|
||||||
|
getData={handleUpdate}
|
||||||
|
/>
|
||||||
|
</Popover>,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
setPagesTableData(tableData);
|
||||||
|
}
|
||||||
|
}, [pagesData, pagesInfo]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container column>
|
||||||
|
<Grid container column isDashboard>
|
||||||
|
<Table
|
||||||
|
isDashboard
|
||||||
|
noPagination
|
||||||
|
noSearch
|
||||||
|
title="خلاصه اطلاعات"
|
||||||
|
columns={
|
||||||
|
farmid
|
||||||
|
? [
|
||||||
|
"تعداد کل گله ها",
|
||||||
|
"تعداد دام سنگین",
|
||||||
|
"تعداد دام سبک",
|
||||||
|
"مجموع وزن خرید از سهمیه ها",
|
||||||
|
"جزئیات",
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
"تعداد کل گله ها",
|
||||||
|
"تعداد کل دامداران",
|
||||||
|
"تعداد گله های صنعتی",
|
||||||
|
"تعداد گله های روستایی",
|
||||||
|
"تعداد گله های عشایری",
|
||||||
|
"تعداد دام سبک",
|
||||||
|
"تعداد دام سنگین",
|
||||||
|
"تعداد دامداران دارای گله",
|
||||||
|
"تعداد دامداران بدون گله",
|
||||||
|
"تعداد دامداران حقیقی",
|
||||||
|
"تعداد دامداران حقوقی",
|
||||||
|
"تعداد دامداران صنعتی",
|
||||||
|
"تعداد دامداران روستایی",
|
||||||
|
"تعداد دامداران عشایری",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
rows={
|
||||||
|
farmid
|
||||||
|
? [
|
||||||
|
[
|
||||||
|
DashboardData?.total_herds_count?.toLocaleString() || "0",
|
||||||
|
DashboardData?.total_heavy_livestock_count?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.total_light_livestock_count?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.total_purchase_weight?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
<TableButton
|
||||||
|
size="small"
|
||||||
|
key={DashboardData}
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "جزئیات دامدار",
|
||||||
|
content: (
|
||||||
|
<LiveStockHerdDetails farmid={farmid} name={name} />
|
||||||
|
),
|
||||||
|
isFullSize: true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
],
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
[
|
||||||
|
DashboardData?.herd_dashboard?.total_herds_count?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.rancher_dashboard?.total_ranchers_count?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.herd_dashboard?.total_industrial_herds_count?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.herd_dashboard?.total_Village_herds_count?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.herd_dashboard?.total_nomadic_herds_count?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.herd_dashboard?.total_heavy_livestock_count?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.herd_dashboard?.total_light_livestock_count?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.rancher_dashboard?.total_with_herd?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.rancher_dashboard?.total_without_herd?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.rancher_dashboard?.total_natural_ranchers?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.rancher_dashboard?.total_legal_ranchers?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.rancher_dashboard?.total_industrial_ranchers?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.rancher_dashboard?.total_village_ranchers?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.rancher_dashboard?.total_nomadic_ranchers?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Table
|
||||||
|
className="mt-2"
|
||||||
|
onChange={(e) => {
|
||||||
|
setPagesInfo(e);
|
||||||
|
}}
|
||||||
|
count={pagesData?.count || 10}
|
||||||
|
isPaginated
|
||||||
|
title={name ? `گله های ${name}` : "گله ها"}
|
||||||
|
columns={[
|
||||||
|
"ردیف",
|
||||||
|
"شناسه یکتا",
|
||||||
|
"نام گله",
|
||||||
|
"نام تعاونی",
|
||||||
|
"نام شرکت پیمانکار",
|
||||||
|
"ظرفیت",
|
||||||
|
"نوع فعالیت",
|
||||||
|
"کد اپیدمیولوژیک",
|
||||||
|
"حجم دام سبک",
|
||||||
|
"حجم دام سنگین",
|
||||||
|
"استان",
|
||||||
|
"شهر",
|
||||||
|
"مجوز فعالیت",
|
||||||
|
"وضعیت فعالیت",
|
||||||
|
"کد پستی",
|
||||||
|
"عملیات",
|
||||||
|
]}
|
||||||
|
rows={pagesTableData}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
146
src/Pages/IncentivePlans.tsx
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
export default function IncentivePlans() {
|
||||||
|
const { openModal } = useModalStore();
|
||||||
|
const [params, setParams] = useState({ page: 1, page_size: 10 });
|
||||||
|
const [tableData, setTableData] = useState([]);
|
||||||
|
const [selectedGroup, setSelectedGroup] = useState<(number | string)[]>([]);
|
||||||
|
|
||||||
|
const groupOptions = [
|
||||||
|
{ key: "", value: "همه" },
|
||||||
|
{ key: "rural", value: "روستایی" },
|
||||||
|
{ key: "industrial", value: "صنعتی" },
|
||||||
|
{ key: "nomadic", value: "عشایری" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const { data: apiData, refetch } = useApiRequest({
|
||||||
|
api: `/product/web/api/v1/incentive_plan/active_plans/`,
|
||||||
|
method: "get",
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
...(selectedGroup.length > 0 && { group: selectedGroup[0] }),
|
||||||
|
},
|
||||||
|
queryKey: ["incentivePlans", params, selectedGroup],
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (apiData?.results) {
|
||||||
|
const formattedData = apiData.results.map((item: any, i: number) => {
|
||||||
|
return [
|
||||||
|
params.page === 1
|
||||||
|
? i + 1
|
||||||
|
: i + params.page_size * (params.page - 1) + 1,
|
||||||
|
item?.name,
|
||||||
|
item?.description,
|
||||||
|
item?.plan_type,
|
||||||
|
item?.group === "rural"
|
||||||
|
? "روستایی"
|
||||||
|
: item?.group === "nomadic"
|
||||||
|
? "عشایری"
|
||||||
|
: "صنعتی",
|
||||||
|
item?.is_time_unlimited ? "دارد" : "ندارد",
|
||||||
|
formatJustDate(item?.start_date_limit),
|
||||||
|
formatJustDate(item?.end_date_limit),
|
||||||
|
<Popover key={i}>
|
||||||
|
<Tooltip title="ویرایش طرح" position="right">
|
||||||
|
<Button
|
||||||
|
page="incentive_plans"
|
||||||
|
access="Update-Incentive-Plan"
|
||||||
|
variant="edit"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "ویرایش طرح",
|
||||||
|
content: <AddIncentivePlan getData={refetch} item={item} />,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<DeleteButtonForPopOver
|
||||||
|
page="incentive_plans"
|
||||||
|
access="Delete-Incentive-Plan"
|
||||||
|
title="از حذف طرح تشویقی اطمینان دارید؟"
|
||||||
|
api={`/product/web/api/v1/incentive_plan/${item?.id}/`}
|
||||||
|
getData={refetch}
|
||||||
|
/>
|
||||||
|
</Popover>,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
setTableData(formattedData);
|
||||||
|
}
|
||||||
|
}, [apiData, params]);
|
||||||
|
|
||||||
|
const handleGroupChange = (keys: (number | string)[]) => {
|
||||||
|
setSelectedGroup(keys);
|
||||||
|
setParams({ ...params, page: 1 });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid container className="items-center gap-2">
|
||||||
|
<Grid>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
page="incentive_plans"
|
||||||
|
access="Post-Incentive-Plan"
|
||||||
|
variant="submit"
|
||||||
|
onClick={() =>
|
||||||
|
openModal({
|
||||||
|
title: "ایجاد طرح تشویقی",
|
||||||
|
content: <AddIncentivePlan getData={refetch} />,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
ایجاد طرح تشویقی
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<AutoComplete
|
||||||
|
data={groupOptions}
|
||||||
|
selectedKeys={selectedGroup}
|
||||||
|
onChange={handleGroupChange}
|
||||||
|
title="فیلتر بر اساس گروه"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid className="w-full">
|
||||||
|
<Table
|
||||||
|
className="mt-2"
|
||||||
|
excelInfo={{
|
||||||
|
link: "product/excel/incentive_plan_excel",
|
||||||
|
}}
|
||||||
|
onChange={(e) => {
|
||||||
|
setParams(e);
|
||||||
|
}}
|
||||||
|
title="طرح های تشویقی"
|
||||||
|
isPaginated
|
||||||
|
count={apiData?.count || 10}
|
||||||
|
columns={[
|
||||||
|
"ردیف",
|
||||||
|
"نام",
|
||||||
|
"توضیحات",
|
||||||
|
"نوع طرح",
|
||||||
|
"گروه",
|
||||||
|
"محدودیت زمانی",
|
||||||
|
"شروع محدودیت",
|
||||||
|
"پایان طرح",
|
||||||
|
"عملیات",
|
||||||
|
]}
|
||||||
|
rows={tableData}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
49
src/Pages/Inventory.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
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 { useParams } from "@tanstack/react-router";
|
||||||
|
import { InventoryEntriesList } from "../partials/inventory/InventoryEntriesList";
|
||||||
|
|
||||||
|
export default function Inventory() {
|
||||||
|
const [selectedTab, setSelectedTab] = useState<number>(0);
|
||||||
|
const handleTabChange = (index: number) => {
|
||||||
|
setSelectedTab(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { profile } = useUserProfileStore();
|
||||||
|
const params = useParams({ strict: false });
|
||||||
|
|
||||||
|
const tabItems = [
|
||||||
|
{
|
||||||
|
label: "ورودی به انبار",
|
||||||
|
page: "inventory",
|
||||||
|
access: "Entry-Inventory",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "توزیع به زیر مجموعه",
|
||||||
|
visible: profile?.role?.type?.key === "CO",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container column className="justify-center mt-2">
|
||||||
|
{params?.code ? (
|
||||||
|
<InventoryEntriesList />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Tabs tabs={tabItems} onChange={handleTabChange} size="medium" />
|
||||||
|
<Grid container column className="mt-2">
|
||||||
|
{selectedTab === 0 ? (
|
||||||
|
<InventoryWarehouseEntryTab />
|
||||||
|
) : (
|
||||||
|
<InventoryStakeHolderAllocations />
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
283
src/Pages/LiveStockFarmers.tsx
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
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 {
|
||||||
|
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";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
import { LIVESTOCK_FARMERS } from "../routes/paths";
|
||||||
|
import { LiveStockFarmersDashboardResponse } from "../types/LiveStockFarmers";
|
||||||
|
|
||||||
|
export default function LiveStockFarmers() {
|
||||||
|
const [pagesInfo, setPagesInfo] = useState({ page: 1, page_size: 10 });
|
||||||
|
const [pagesTableData, setPagesTableData] = useState([]);
|
||||||
|
const { openDrawer } = useDrawerStore();
|
||||||
|
const { openModal } = useModalStore();
|
||||||
|
|
||||||
|
const { data: pagesData, refetch } = useApiRequest({
|
||||||
|
api: "/herd/web/api/v1/rancher/",
|
||||||
|
method: "get",
|
||||||
|
params: pagesInfo,
|
||||||
|
queryKey: ["LiveStockFarmers", pagesInfo],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: DashboardData, refetch: refetchDashboard } =
|
||||||
|
useApiRequest<LiveStockFarmersDashboardResponse>({
|
||||||
|
api: `/herd/web/api/v1/rancher/rancher_main_dashboard/`,
|
||||||
|
queryKey: ["LiveStockFarmersDashboard"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleUpdate = () => {
|
||||||
|
refetch();
|
||||||
|
refetchDashboard();
|
||||||
|
};
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (pagesData?.results) {
|
||||||
|
const tableData = pagesData.results.map((item: any, i: number) => {
|
||||||
|
return [
|
||||||
|
pagesInfo.page === 1
|
||||||
|
? i + 1
|
||||||
|
: i + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
|
||||||
|
item?.ranching_farm,
|
||||||
|
item?.first_name,
|
||||||
|
item?.last_name,
|
||||||
|
item?.national_code,
|
||||||
|
item?.mobile,
|
||||||
|
item?.organizations
|
||||||
|
?.map((organization: any) => organization?.name)
|
||||||
|
.join(", "),
|
||||||
|
item?.activity === "V"
|
||||||
|
? "روستایی"
|
||||||
|
: item?.activity === "I"
|
||||||
|
? "صنعتی"
|
||||||
|
: item?.activity === "R"
|
||||||
|
? "عشایری"
|
||||||
|
: "-",
|
||||||
|
item?.province?.name || "-",
|
||||||
|
item?.city?.name || "-",
|
||||||
|
item?.address,
|
||||||
|
item?.without_herd ? "بدون دام" : "دامدار عادی",
|
||||||
|
item?.rancher_type === "N" ? "حقیقی" : "حقوقی",
|
||||||
|
item?.union_name || "-",
|
||||||
|
item?.union_code || "-",
|
||||||
|
<Popover key={i}>
|
||||||
|
<Tooltip title="ویرایش" position="right">
|
||||||
|
<Button
|
||||||
|
variant="edit"
|
||||||
|
page="livestock_farmers"
|
||||||
|
access="Edit-Rancher"
|
||||||
|
onClick={() =>
|
||||||
|
openDrawer({
|
||||||
|
title: "ویرایش دامدار",
|
||||||
|
content: (
|
||||||
|
<LiveStockAddRancher getData={handleUpdate} item={item} />
|
||||||
|
),
|
||||||
|
isOpen: true,
|
||||||
|
direction: "left",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="مشاهده گله ها" position="right">
|
||||||
|
<Button
|
||||||
|
variant="detail"
|
||||||
|
page="farmer_details"
|
||||||
|
access="See-Herds"
|
||||||
|
onClick={() => {
|
||||||
|
const path =
|
||||||
|
LIVESTOCK_FARMERS +
|
||||||
|
"/" +
|
||||||
|
item?.id +
|
||||||
|
"/" +
|
||||||
|
item?.ranching_farm;
|
||||||
|
navigate({ to: path });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="ایجاد گله" position="right">
|
||||||
|
<Button
|
||||||
|
variant="submit"
|
||||||
|
page="livestock_farmers"
|
||||||
|
access="Add-Herd"
|
||||||
|
onClick={() =>
|
||||||
|
openDrawer({
|
||||||
|
title: "ایجاد گله",
|
||||||
|
content: (
|
||||||
|
<LiveStockAddHerd
|
||||||
|
getData={handleUpdate}
|
||||||
|
rancher={item?.id}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
isOpen: true,
|
||||||
|
direction: "left",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="طرح های تشویقی" position="right">
|
||||||
|
<Button
|
||||||
|
variant="secondary-submit"
|
||||||
|
page="farmer_plans"
|
||||||
|
access="Get-Rancher-Plans"
|
||||||
|
onClick={() => {
|
||||||
|
const path =
|
||||||
|
LIVESTOCK_FARMERS +
|
||||||
|
"/plans/" +
|
||||||
|
item.id +
|
||||||
|
"/" +
|
||||||
|
item?.ranching_farm;
|
||||||
|
navigate({ to: path });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="تخصیص به تعاونی" position="right">
|
||||||
|
<Button
|
||||||
|
variant="submit"
|
||||||
|
page="livestock_farmers"
|
||||||
|
access="Add-Rancher-Organization"
|
||||||
|
onClick={() =>
|
||||||
|
openModal({
|
||||||
|
title: "تخصیص به تعاونی",
|
||||||
|
content: (
|
||||||
|
<LiveStockAllocateCooperative
|
||||||
|
getData={handleUpdate}
|
||||||
|
item={item}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<DeleteButtonForPopOver
|
||||||
|
page="livestock_farmers"
|
||||||
|
access="Delete-Rancher"
|
||||||
|
api={`herd/web/api/v1/rancher/${item?.id}/`}
|
||||||
|
getData={handleUpdate}
|
||||||
|
/>
|
||||||
|
</Popover>,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
setPagesTableData(tableData);
|
||||||
|
}
|
||||||
|
}, [pagesData]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container column className="gap-4">
|
||||||
|
<Grid container column isDashboard>
|
||||||
|
<Table
|
||||||
|
isDashboard
|
||||||
|
noPagination
|
||||||
|
noSearch
|
||||||
|
title="خلاصه اطلاعات"
|
||||||
|
columns={[
|
||||||
|
"تعداد کل دامداران",
|
||||||
|
"تعداد کل گله ها",
|
||||||
|
"تعداد گله های صنعتی",
|
||||||
|
"تعداد گله های روستایی",
|
||||||
|
"تعداد گله های عشایری",
|
||||||
|
"تعداد دام سبک",
|
||||||
|
"تعداد دام سنگین",
|
||||||
|
"تعداد دامداران دارای گله",
|
||||||
|
"تعداد دامداران بدون گله",
|
||||||
|
"تعداد دامداران حقیقی",
|
||||||
|
"تعداد دامداران حقوقی",
|
||||||
|
"تعداد دامداران صنعتی",
|
||||||
|
"تعداد دامداران روستایی",
|
||||||
|
"تعداد دامداران عشایری",
|
||||||
|
]}
|
||||||
|
rows={[
|
||||||
|
[
|
||||||
|
DashboardData?.rancher_dashboard?.total_ranchers_count?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.herd_dashboard?.total_herds_count?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.herd_dashboard?.total_industrial_herds_count?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.herd_dashboard?.total_Village_herds_count?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.herd_dashboard?.total_nomadic_herds_count?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.herd_dashboard?.total_heavy_livestock_count?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.herd_dashboard?.total_light_livestock_count?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.rancher_dashboard?.total_with_herd?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.rancher_dashboard?.total_without_herd?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.rancher_dashboard?.total_natural_ranchers?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.rancher_dashboard?.total_legal_ranchers?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.rancher_dashboard?.total_industrial_ranchers?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.rancher_dashboard?.total_village_ranchers?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
DashboardData?.rancher_dashboard?.total_nomadic_ranchers?.toLocaleString() ||
|
||||||
|
"0",
|
||||||
|
],
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
page="livestock_farmers"
|
||||||
|
access="Post-Farmer"
|
||||||
|
variant="submit"
|
||||||
|
onClick={() =>
|
||||||
|
openDrawer({
|
||||||
|
title: "ایجاد دامدار",
|
||||||
|
content: <LiveStockAddRancher getData={handleUpdate} />,
|
||||||
|
isOpen: true,
|
||||||
|
direction: "left",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
ایجاد دامدار
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
<Table
|
||||||
|
className="mt-2"
|
||||||
|
onChange={(e) => {
|
||||||
|
setPagesInfo(e);
|
||||||
|
}}
|
||||||
|
count={pagesData?.count || 10}
|
||||||
|
isPaginated
|
||||||
|
title="دامداران"
|
||||||
|
columns={[
|
||||||
|
"ردیف",
|
||||||
|
"نام دامداری",
|
||||||
|
"نام",
|
||||||
|
"نام خانوادگی",
|
||||||
|
"کد ملی",
|
||||||
|
"موبایل",
|
||||||
|
"تعاونی",
|
||||||
|
"نوع فعالیت",
|
||||||
|
"استان",
|
||||||
|
"شهر",
|
||||||
|
"آدرس",
|
||||||
|
"وضعیت",
|
||||||
|
"نوع دامدار",
|
||||||
|
"نام واحد حقوقی",
|
||||||
|
"شناسه ملی واحد حقوقی",
|
||||||
|
"عملیات",
|
||||||
|
]}
|
||||||
|
rows={pagesTableData}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
104
src/Pages/LiveStocks.tsx
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
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 { 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";
|
||||||
|
|
||||||
|
export default function LiveStocks() {
|
||||||
|
const [pagesInfo, setPagesInfo] = useState({ page: 1, page_size: 10 });
|
||||||
|
const [pagesTableData, setPagesTableData] = useState([]);
|
||||||
|
|
||||||
|
const { openDrawer } = useDrawerStore();
|
||||||
|
|
||||||
|
const { herdid, name } = useParams({ strict: false });
|
||||||
|
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],
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (pagesData?.results) {
|
||||||
|
const tableData = pagesData.results.map((item: any, i: number) => {
|
||||||
|
return [
|
||||||
|
pagesInfo.page === 1
|
||||||
|
? i + 1
|
||||||
|
: i + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
|
||||||
|
item?.type?.name,
|
||||||
|
// item?.use_type?.name,
|
||||||
|
formatJustDate(item?.birthdate),
|
||||||
|
formatAgeCalcuation(item?.birthdate),
|
||||||
|
item?.tag?.tag_code || "-",
|
||||||
|
item?.gender === 1 ? "نر" : "ماده",
|
||||||
|
// item?.species?.name,
|
||||||
|
item?.weight_type === "L" ? "سبک" : "سنگین",
|
||||||
|
<Popover key={i}>
|
||||||
|
<Tooltip title="ویرایش دام" position="right">
|
||||||
|
<Button
|
||||||
|
variant="edit"
|
||||||
|
page="livestocks"
|
||||||
|
access="Edit-LiveStock"
|
||||||
|
onClick={() =>
|
||||||
|
openDrawer({
|
||||||
|
title: "ویرایش دام",
|
||||||
|
content: (
|
||||||
|
<LiveStockAddLiveStock
|
||||||
|
getData={refetch}
|
||||||
|
herdId={item?.herd?.id}
|
||||||
|
item={item}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
isOpen: true,
|
||||||
|
direction: "left",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<DeleteButtonForPopOver
|
||||||
|
api={`livestock/web/api/v1/livestock/${item?.id}/`}
|
||||||
|
getData={refetch}
|
||||||
|
/>
|
||||||
|
</Popover>,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
setPagesTableData(tableData);
|
||||||
|
}
|
||||||
|
}, [pagesData, pagesInfo]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container column>
|
||||||
|
<Table
|
||||||
|
className="mt-2"
|
||||||
|
onChange={(e) => {
|
||||||
|
setPagesInfo(e);
|
||||||
|
}}
|
||||||
|
count={pagesData?.count || 10}
|
||||||
|
isPaginated
|
||||||
|
title={name ? `دام های ${name}` : "دام ها"}
|
||||||
|
columns={[
|
||||||
|
"ردیف",
|
||||||
|
"دام",
|
||||||
|
// "نوع دام",
|
||||||
|
"تاریخ تولد",
|
||||||
|
"سن",
|
||||||
|
"پلاک",
|
||||||
|
"جنسیت",
|
||||||
|
// "گونه",
|
||||||
|
"دسته وزنی",
|
||||||
|
"عملیات",
|
||||||
|
]}
|
||||||
|
rows={pagesTableData}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
30
src/Pages/Management.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
const tabItems = [
|
||||||
|
{ label: "صفحات" },
|
||||||
|
{ label: "دسترسی ها" },
|
||||||
|
{ label: "دسترسی های غیر فعال" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Management() {
|
||||||
|
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 && <Pages />}
|
||||||
|
{selectedTab === 1 && <Access />}
|
||||||
|
{selectedTab === 2 && <UnusedAccess />}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
162
src/Pages/Menu.tsx
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import { Grid } from "../components/Grid/Grid";
|
||||||
|
import Typography from "../components/Typography/Typography";
|
||||||
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
|
import { ChevronDownIcon } from "@heroicons/react/24/solid";
|
||||||
|
import { useState } from "react";
|
||||||
|
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 { getFaPermissions } from "../utils/getFaPermissions";
|
||||||
|
import SVGImage from "../components/SvgImage/SvgImage";
|
||||||
|
|
||||||
|
const containerVariants = {
|
||||||
|
hidden: {},
|
||||||
|
visible: {
|
||||||
|
transition: {
|
||||||
|
staggerChildren: 0.08,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const itemVariants = {
|
||||||
|
hidden: { opacity: 0, scale: 0.95, y: 20 },
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
scale: 1,
|
||||||
|
y: 0,
|
||||||
|
transition: {
|
||||||
|
type: "spring",
|
||||||
|
stiffness: 400,
|
||||||
|
damping: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const submenuVariants = {
|
||||||
|
hidden: { height: 0, opacity: 0 },
|
||||||
|
visible: {
|
||||||
|
height: "auto",
|
||||||
|
opacity: 1,
|
||||||
|
transition: {
|
||||||
|
type: "tween",
|
||||||
|
ease: "easeOut",
|
||||||
|
duration: 0.1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
exit: {
|
||||||
|
height: 0,
|
||||||
|
opacity: 0,
|
||||||
|
transition: {
|
||||||
|
duration: 0.1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Menu = () => {
|
||||||
|
const { profile } = useUserProfileStore();
|
||||||
|
|
||||||
|
const menuItems: ItemWithSubItems[] = getUserPermissions(
|
||||||
|
profile?.permissions
|
||||||
|
);
|
||||||
|
|
||||||
|
const getOpenedItem = () => {
|
||||||
|
if (window.location.pathname !== "/") {
|
||||||
|
const matchedIndex = menuItems.findIndex((item) =>
|
||||||
|
item.subItems.some((sub) => sub.path === window.location.pathname)
|
||||||
|
);
|
||||||
|
return matchedIndex;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const [openIndex, setOpenIndex] = useState<number | null>(getOpenedItem());
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const toggleSubmenu = (index: number) => {
|
||||||
|
setOpenIndex((prev) => (prev === index ? null : index));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
column
|
||||||
|
className="block md:hidden px-4 pt-6 pb-12 bg-gradient-to-b select-none items-start from-white to-dark-100 dark:from-dark-900 dark:to-dark-800 min-h-screen"
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
className="text-center gap-2 flex justify-center items-center text-dark-800 dark:text-dark-100 font-semibold text-xl mb-6"
|
||||||
|
>
|
||||||
|
<Bars3Icon className="w-4" />
|
||||||
|
داشبورد
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
variants={containerVariants}
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
className="flex flex-col items-center gap-4 w-full"
|
||||||
|
>
|
||||||
|
{menuItems.map(({ fa, icon: Icon, subItems }, index) => (
|
||||||
|
<motion.div
|
||||||
|
key={index}
|
||||||
|
variants={itemVariants}
|
||||||
|
className="w-full max-w-sm"
|
||||||
|
>
|
||||||
|
<motion.button
|
||||||
|
onClick={() => toggleSubmenu(index)}
|
||||||
|
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 === index ? "rotate-180" : ""
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</motion.button>
|
||||||
|
|
||||||
|
<AnimatePresence>
|
||||||
|
{openIndex === index && (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
};
|
||||||
39
src/Pages/NotFound.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { HOME } from "../routes/paths";
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return (
|
||||||
|
<main className="flex flex-col items-center justify-center h-full bg-white dark:bg-dark-900 px-6 text-center text-gray-100">
|
||||||
|
<motion.h1
|
||||||
|
className="text-7xl sm:text-8xl md:text-9xl font-bold mb-4 select-none text-gray-900 dark:text-white"
|
||||||
|
initial={{ opacity: 0, y: -40 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 1, ease: "easeOut" }}
|
||||||
|
>
|
||||||
|
۴۰۴
|
||||||
|
</motion.h1>
|
||||||
|
|
||||||
|
<motion.p
|
||||||
|
className="max-w-lg text-lg sm:text-xl mb-10 px-4 text-gray-900 dark:text-white"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay: 0.6, duration: 1 }}
|
||||||
|
>
|
||||||
|
متأسفیم، صفحهای که دنبال آن هستید یافت نشد!
|
||||||
|
</motion.p>
|
||||||
|
|
||||||
|
<motion.button
|
||||||
|
className="px-8 py-3 cursor-pointer rounded-full bg-primary-600 hover:bg-primary-500 focus:outline-none focus:ring-4 focus:ring-primary-400 text-white font-semibold shadow-lg"
|
||||||
|
initial={{ opacity: 0, scale: 0.85 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
transition={{ delay: 1, duration: 0.3, ease: "easeOut" }}
|
||||||
|
onClick={() => navigate({ to: HOME })}
|
||||||
|
aria-label="بازگشت به صفحه اصلی"
|
||||||
|
>
|
||||||
|
بازگشت به صفحه اصلی
|
||||||
|
</motion.button>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
28
src/Pages/Organizations.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
export default function Organizations() {
|
||||||
|
const tabItems = [
|
||||||
|
{ label: "سازمان ها" },
|
||||||
|
{
|
||||||
|
label: "نهاد",
|
||||||
|
page: "organizations",
|
||||||
|
access: "Show-Organization-Type",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const [selectedTab, setSelectedTab] = useState<number>(0);
|
||||||
|
const handleTabChange = (index: number) => {
|
||||||
|
setSelectedTab(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container column className="gap-2">
|
||||||
|
<Tabs tabs={tabItems} onChange={handleTabChange} size="medium" />
|
||||||
|
{selectedTab === 0 ? <OrganizationsList /> : <OrganizationsTypes />}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
156
src/Pages/Pos.tsx
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
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 { useNavigate, useParams } from "@tanstack/react-router";
|
||||||
|
import { AllocatePos } from "../partials/pos/AllocatePos";
|
||||||
|
import { CreditCardIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { POS_POS_LIST } from "../routes/paths";
|
||||||
|
|
||||||
|
export default function Pos() {
|
||||||
|
const { openModal } = useModalStore();
|
||||||
|
const [pagesInfo, setPagesInfo] = useState({ page: 1, page_size: 10 });
|
||||||
|
const [pagesTableData, setPagesTableData] = useState([]);
|
||||||
|
const { profile } = useUserProfileStore();
|
||||||
|
const { id, name } = useParams({ strict: false });
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const { data: pagesData, refetch } = useApiRequest({
|
||||||
|
api: id
|
||||||
|
? `pos_device/web/v1/pos/device/${id}/devices_by_psp/`
|
||||||
|
: "/pos_device/web/v1/pos/device/my_devices/",
|
||||||
|
method: "get",
|
||||||
|
params: {
|
||||||
|
...pagesInfo,
|
||||||
|
admin: profile?.role?.type?.key === "ADM" ? true : false,
|
||||||
|
},
|
||||||
|
queryKey: ["pos", pagesInfo],
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (pagesData?.results) {
|
||||||
|
const formattedData = pagesData.results.map((item: any, i: number) => {
|
||||||
|
return [
|
||||||
|
pagesInfo.page === 1
|
||||||
|
? i + 1
|
||||||
|
: i + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
|
||||||
|
item?.assignment?.client
|
||||||
|
? `${
|
||||||
|
item?.assignment?.client?.organization?.id ? "سازمان" : "صنف"
|
||||||
|
} ${item?.assignment?.client?.organization?.name}`
|
||||||
|
: "-",
|
||||||
|
item?.organization?.name,
|
||||||
|
item?.acceptor,
|
||||||
|
item?.serial,
|
||||||
|
item?.terminal,
|
||||||
|
item?.device_identity,
|
||||||
|
item?.is_activated ? "فعال" : "غیر فعال",
|
||||||
|
<Popover key={i}>
|
||||||
|
<Tooltip title={"تعریف حساب"} position="right">
|
||||||
|
<Button
|
||||||
|
icon={<CreditCardIcon className="w-5 h-5 text-secondary-600" />}
|
||||||
|
page={"pos_accounts"}
|
||||||
|
access="Pos-Accounts"
|
||||||
|
onClick={() => {
|
||||||
|
const path = POS_POS_LIST + "/" + item?.id;
|
||||||
|
navigate({ to: path });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
item?.assignment?.client
|
||||||
|
? "ویرایش تخصیص سازمان"
|
||||||
|
: "تخصیص سازمان"
|
||||||
|
}
|
||||||
|
position="right"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant={item?.assignment?.client ? "secondary-edit" : "submit"}
|
||||||
|
page={"pos"}
|
||||||
|
access="Pos-Allocate-Organization"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: item?.assignment?.client
|
||||||
|
? "ویرایش تخصیص سازمان"
|
||||||
|
: "تخصیص سازمان",
|
||||||
|
content: <AllocatePos getData={refetch} item={item} />,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="ویرایش" position="right">
|
||||||
|
<Button
|
||||||
|
page={name ? "pos_company_detail" : "pos"}
|
||||||
|
access="Edit-Pos"
|
||||||
|
variant="edit"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "ویرایش کارتخوان",
|
||||||
|
content: <AddPos getData={refetch} item={item} />,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<DeleteButtonForPopOver
|
||||||
|
page={name ? "pos_company_detail" : "pos"}
|
||||||
|
access="Delete-Pos-Device"
|
||||||
|
api={`pos_device/web/v1/pos/device/${item?.id}/`}
|
||||||
|
getData={refetch}
|
||||||
|
/>
|
||||||
|
</Popover>,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
setPagesTableData(formattedData);
|
||||||
|
}
|
||||||
|
}, [pagesData, pagesInfo]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container column>
|
||||||
|
{!id && (
|
||||||
|
<Grid>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="submit"
|
||||||
|
page="pos"
|
||||||
|
access="Submit-Pos-Device"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "ثبت کارتخوان",
|
||||||
|
content: <AddPos getData={refetch} />,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
ثبت کارتخوان
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
<Table
|
||||||
|
className="mt-2"
|
||||||
|
onChange={setPagesInfo}
|
||||||
|
count={pagesData?.count || 10}
|
||||||
|
isPaginated
|
||||||
|
title={`دستگاه های ${name ? name : "پرداخت"} `}
|
||||||
|
columns={[
|
||||||
|
"ردیف",
|
||||||
|
"مالک",
|
||||||
|
"شرکت psp",
|
||||||
|
"پذیرنده",
|
||||||
|
"سریال",
|
||||||
|
"ترمینال",
|
||||||
|
"کد پشتیبانی",
|
||||||
|
"وضعیت",
|
||||||
|
"عملیات",
|
||||||
|
]}
|
||||||
|
rows={pagesTableData}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
246
src/Pages/PosAccounts.tsx
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
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 { 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";
|
||||||
|
|
||||||
|
export default function PosAccounts() {
|
||||||
|
const { openModal } = useModalStore();
|
||||||
|
const [pagesInfo, setPagesInfo] = useState({ page: 1, page_size: 10 });
|
||||||
|
const [accountsTableData, setAccountsTableData] = useState([]);
|
||||||
|
|
||||||
|
const { id } = useParams({ strict: false });
|
||||||
|
|
||||||
|
const { data: posData, refetch } = useApiRequest({
|
||||||
|
api: `/pos_device/web/v1/pos/device/${id}/`,
|
||||||
|
method: "get",
|
||||||
|
queryKey: ["posAccountsDashboard", pagesInfo],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: posAccountsData, refetch: accountsRefetch } = useApiRequest({
|
||||||
|
api: `pos_device/web/v1/pos/stake_holders/${id}/list_by_device/`,
|
||||||
|
method: "get",
|
||||||
|
params: {
|
||||||
|
...pagesInfo,
|
||||||
|
},
|
||||||
|
queryKey: ["posAccounts", pagesInfo],
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (posAccountsData?.results) {
|
||||||
|
const formattedData = posAccountsData.results.map(
|
||||||
|
(item: any, i: number) => {
|
||||||
|
return [
|
||||||
|
pagesInfo.page === 1
|
||||||
|
? i + 1
|
||||||
|
: i + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
|
||||||
|
item?.organization?.name,
|
||||||
|
item?.bank_account?.card,
|
||||||
|
item?.bank_account?.account,
|
||||||
|
item?.bank_account?.sheba,
|
||||||
|
item?.default ? "حساب اصلی" : "زیر حساب",
|
||||||
|
item?.broker?.name,
|
||||||
|
<Popover key={i}>
|
||||||
|
<Tooltip title={"تخصیص به کارگزار"} position="right">
|
||||||
|
<Button
|
||||||
|
page="pos_accounts"
|
||||||
|
access="Allocate-Account-To-Broker"
|
||||||
|
variant="submit"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "تخصیص به کارگزار",
|
||||||
|
content: (
|
||||||
|
<AllocateAccountToBroker
|
||||||
|
getData={accountsRefetch}
|
||||||
|
item={item}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={"تنظیم بعنوان حساب اصلی"} position="right">
|
||||||
|
<Button
|
||||||
|
page="pos_accounts"
|
||||||
|
access="Set-Main-Account"
|
||||||
|
variant="set"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "تنظیم بعنوان حساب اصلی",
|
||||||
|
content: (
|
||||||
|
<BooleanQuestion
|
||||||
|
api="pos_device/web/v1/pos/bank_account_device_link"
|
||||||
|
method="post"
|
||||||
|
getData={handleUpdate}
|
||||||
|
title="آیا از تنظیم این حساب بعنوان حساب اصلی مطمئنید؟"
|
||||||
|
payload={{
|
||||||
|
organization: posData?.organization?.id,
|
||||||
|
bank_account: item?.bank_account?.id,
|
||||||
|
device: item?.id,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<DeleteButtonForPopOver
|
||||||
|
page="pos_accounts"
|
||||||
|
access="Delete-Account"
|
||||||
|
api={`pos_device/web/v1/pos/stake_holders/${item?.id}/`}
|
||||||
|
getData={handleUpdate}
|
||||||
|
/>
|
||||||
|
</Popover>,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
setAccountsTableData(formattedData);
|
||||||
|
}
|
||||||
|
}, [posAccountsData, pagesInfo]);
|
||||||
|
|
||||||
|
const handleUpdate = () => {
|
||||||
|
refetch();
|
||||||
|
accountsRefetch();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container column>
|
||||||
|
<Grid container column isDashboard>
|
||||||
|
<Table
|
||||||
|
noPagination
|
||||||
|
isDashboard
|
||||||
|
className="mt-2"
|
||||||
|
onChange={setPagesInfo}
|
||||||
|
count={posData?.count || 10}
|
||||||
|
isPaginated
|
||||||
|
title={`مشخصات کارتخوان`}
|
||||||
|
columns={[
|
||||||
|
"مالک",
|
||||||
|
"شرکت psp",
|
||||||
|
"پذیرنده",
|
||||||
|
"سریال",
|
||||||
|
"ترمینال",
|
||||||
|
"وضعیت",
|
||||||
|
"عملیات",
|
||||||
|
]}
|
||||||
|
rows={[
|
||||||
|
[
|
||||||
|
posData?.assignment?.client
|
||||||
|
? `${
|
||||||
|
posData?.assignment?.client?.organization?.id
|
||||||
|
? "سازمان"
|
||||||
|
: "صنف"
|
||||||
|
} ${posData?.assignment?.client?.organization?.name}`
|
||||||
|
: "-",
|
||||||
|
posData?.organization?.name,
|
||||||
|
posData?.acceptor,
|
||||||
|
posData?.serial,
|
||||||
|
posData?.terminal,
|
||||||
|
posData?.is_activated ? "فعال" : "غیر فعال",
|
||||||
|
<Popover key={posData}>
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
posData?.assignment?.client
|
||||||
|
? "ویرایش تخصیص"
|
||||||
|
: "تخصیص سازمان"
|
||||||
|
}
|
||||||
|
position="right"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="submit"
|
||||||
|
page="pos_accounts"
|
||||||
|
access="Allocate-Pos-Account"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: posData?.assignment?.client
|
||||||
|
? "ویرایش سازمان"
|
||||||
|
: "تخصیص سازمان",
|
||||||
|
content: (
|
||||||
|
<AllocatePos getData={handleUpdate} item={posData} />
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="ویرایش" position="right">
|
||||||
|
<Button
|
||||||
|
variant="edit"
|
||||||
|
page="pos_accounts"
|
||||||
|
access="Edit-Pos-Account"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "ویرایش کارتخوان",
|
||||||
|
content: (
|
||||||
|
<AddPos getData={handleUpdate} item={posData} />
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<DeleteButtonForPopOver
|
||||||
|
page="pos_accounts"
|
||||||
|
access="Delete-Pos"
|
||||||
|
api={`pos_device/web/v1/pos/device/${posData?.id}/`}
|
||||||
|
getData={handleUpdate}
|
||||||
|
/>
|
||||||
|
</Popover>,
|
||||||
|
],
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid className="mt-16" container column>
|
||||||
|
<Grid>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="submit"
|
||||||
|
page="pos_accounts"
|
||||||
|
access="Allocate-Account"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "انتخاب لیست زیر حساب",
|
||||||
|
content: (
|
||||||
|
<PosAllocateOrganizationAccount
|
||||||
|
getData={handleUpdate}
|
||||||
|
item={posData}
|
||||||
|
deviceId={id}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
انتخاب لیست زیر حساب
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
<Table
|
||||||
|
className="mt-8"
|
||||||
|
onChange={setPagesInfo}
|
||||||
|
count={posAccountsData?.count || 10}
|
||||||
|
isPaginated
|
||||||
|
title={`زیر حساب ها`}
|
||||||
|
columns={[
|
||||||
|
"ردیف",
|
||||||
|
"سازمان",
|
||||||
|
"شماره کارت",
|
||||||
|
"شماره حساب",
|
||||||
|
"شماره شبا",
|
||||||
|
"نوع",
|
||||||
|
"کارگزار",
|
||||||
|
"عملیات",
|
||||||
|
]}
|
||||||
|
rows={accountsTableData}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
82
src/Pages/PosCompanies.tsx
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
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 { useNavigate } from "@tanstack/react-router";
|
||||||
|
import { POS_COMPANIES } from "../routes/paths";
|
||||||
|
|
||||||
|
export default function PosCompanies() {
|
||||||
|
const [pagesInfo, setPagesInfo] = useState({ page: 1, page_size: 10 });
|
||||||
|
const [pagesTableData, setPagesTableData] = useState([]);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { data: pagesData } = useApiRequest({
|
||||||
|
api: "/pos_device/web/v1/pos/device/psp_organizations/",
|
||||||
|
method: "get",
|
||||||
|
params: { pagesInfo },
|
||||||
|
queryKey: ["posCompanies", pagesInfo],
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (pagesData?.results) {
|
||||||
|
const formattedData = pagesData.results.map((item: any, i: number) => {
|
||||||
|
return [
|
||||||
|
pagesInfo.page === 1
|
||||||
|
? i + 1
|
||||||
|
: i + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
|
||||||
|
item?.name,
|
||||||
|
item?.national_unique_id,
|
||||||
|
item?.province
|
||||||
|
? `${item?.province?.name} (${item?.city?.name})`
|
||||||
|
: "-",
|
||||||
|
item?.field_of_activity === "CO"
|
||||||
|
? "کشور"
|
||||||
|
: item?.field_of_activity === "PR"
|
||||||
|
? "استان"
|
||||||
|
: item?.field_of_activity === "CI"
|
||||||
|
? "شهرستان"
|
||||||
|
: "نامشخص",
|
||||||
|
<Popover key={i}>
|
||||||
|
<Tooltip title="نمایش کارتخوان ها" position="right">
|
||||||
|
<Button
|
||||||
|
page="pos_company_detail"
|
||||||
|
access="Show-Pos-Company-Devices"
|
||||||
|
variant="detail"
|
||||||
|
onClick={() => {
|
||||||
|
const path =
|
||||||
|
POS_COMPANIES + "/" + item?.id + "/" + item?.name;
|
||||||
|
navigate({ to: path });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Popover>,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
setPagesTableData(formattedData);
|
||||||
|
}
|
||||||
|
}, [pagesData, pagesInfo]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container column>
|
||||||
|
<Table
|
||||||
|
className="mt-2"
|
||||||
|
onChange={setPagesInfo}
|
||||||
|
count={pagesData?.count || 10}
|
||||||
|
isPaginated
|
||||||
|
title="شرکت های پرداخت"
|
||||||
|
columns={[
|
||||||
|
"ردیف",
|
||||||
|
"نام",
|
||||||
|
"شناسه یکتا",
|
||||||
|
"استان/شهر",
|
||||||
|
"حوزه فعالیت",
|
||||||
|
"عملیات",
|
||||||
|
]}
|
||||||
|
rows={pagesTableData}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
31
src/Pages/Pricing.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
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";
|
||||||
|
const tabItems = [
|
||||||
|
{ label: "مولفه" },
|
||||||
|
{ label: "کارگزار" },
|
||||||
|
{ label: "واحد فروش" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Pricing() {
|
||||||
|
const [selectedTab, setSelectedTab] = useState<number>(0);
|
||||||
|
const handleTabChange = (index: number) => {
|
||||||
|
setSelectedTab(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container column className="gap-2">
|
||||||
|
<Tabs tabs={tabItems} onChange={handleTabChange} size="medium" />
|
||||||
|
{selectedTab === 0 ? (
|
||||||
|
<Attributes />
|
||||||
|
) : selectedTab === 1 ? (
|
||||||
|
<Brokers />
|
||||||
|
) : (
|
||||||
|
<SaleUnits />
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
266
src/Pages/Products.tsx
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
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 { 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";
|
||||||
|
|
||||||
|
interface Category {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Product {
|
||||||
|
id: number;
|
||||||
|
create_date: string;
|
||||||
|
modify_date: string;
|
||||||
|
creator_info: string;
|
||||||
|
modifier_info: string;
|
||||||
|
trash: boolean;
|
||||||
|
name: string;
|
||||||
|
product_id: number;
|
||||||
|
type: string;
|
||||||
|
img: string;
|
||||||
|
created_by: number;
|
||||||
|
modified_by: number;
|
||||||
|
category: Category;
|
||||||
|
image: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Products() {
|
||||||
|
const { openModal } = useModalStore();
|
||||||
|
|
||||||
|
const { data: productsData, refetch } = useApiRequest({
|
||||||
|
api: "/product/web/api/v1/product/",
|
||||||
|
method: "get",
|
||||||
|
params: { page: 1, page_size: 100 },
|
||||||
|
queryKey: ["products"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getProductImage = (name: string) => {
|
||||||
|
switch (name) {
|
||||||
|
case "سبوس":
|
||||||
|
return sabos;
|
||||||
|
case "جو":
|
||||||
|
return jo;
|
||||||
|
case "سویا":
|
||||||
|
return soya;
|
||||||
|
case "ذرت":
|
||||||
|
return zorat;
|
||||||
|
case "کنسانتره گوسفندی":
|
||||||
|
return goosfandi;
|
||||||
|
case "کنسانتره گاو شیری پر تولید":
|
||||||
|
return porTolid;
|
||||||
|
case "کنسانتره پرواری":
|
||||||
|
return parvari;
|
||||||
|
case "کنسانتره گاو شیری متوسط":
|
||||||
|
return shiriMotevaset;
|
||||||
|
default:
|
||||||
|
return defaultImage;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const container = {
|
||||||
|
hidden: { opacity: 0 },
|
||||||
|
show: {
|
||||||
|
opacity: 1,
|
||||||
|
transition: {
|
||||||
|
staggerChildren: 0.1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const item = {
|
||||||
|
hidden: { y: 20, opacity: 0 },
|
||||||
|
show: {
|
||||||
|
y: 0,
|
||||||
|
opacity: 1,
|
||||||
|
transition: {
|
||||||
|
duration: 0.3,
|
||||||
|
ease: "easeOut",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className="p-1 md:p-4 w-full"
|
||||||
|
initial="hidden"
|
||||||
|
animate="show"
|
||||||
|
variants={container}
|
||||||
|
>
|
||||||
|
<Grid>
|
||||||
|
<Button
|
||||||
|
className="mb-2"
|
||||||
|
size="small"
|
||||||
|
page="feed_input_products"
|
||||||
|
access="Post-Product"
|
||||||
|
variant="submit"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "ایجاد محصول",
|
||||||
|
content: <AddProduct getData={refetch} />,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
ایجاد محصول
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
<motion.div className=" hidden md:grid grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 2xl:grid-cols-4 lg:mx-30 xl:mx-40 2xl:mx-70 gap-4 justify-items-center">
|
||||||
|
{productsData?.results?.map((product: Product) => (
|
||||||
|
<motion.div
|
||||||
|
key={product.id}
|
||||||
|
className="bg-white dark:bg-dark-600 rounded-lg shadow-md overflow-hidden flex flex-col max-w-60 w-full"
|
||||||
|
variants={item}
|
||||||
|
initial="hidden"
|
||||||
|
animate="show"
|
||||||
|
>
|
||||||
|
<div className="h-50 bg-gray-100 dark:bg-dark-700">
|
||||||
|
<img
|
||||||
|
src={
|
||||||
|
product?.img && product.img !== "empty"
|
||||||
|
? `${product.img}?t=${product.modify_date || Date.now()}`
|
||||||
|
: getProductImage(product.name)
|
||||||
|
}
|
||||||
|
alt={product.name}
|
||||||
|
className="w-full h-full object-cover dark:opacity-60"
|
||||||
|
key={`${product.id}-${product.modify_date}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-3 flex flex-col gap-1 text-sm text-gray-500 dark:text-dark-200">
|
||||||
|
<p className="font-extrabold">{product.name}</p>
|
||||||
|
<p>کد محصول: {product.product_id}</p>
|
||||||
|
<p
|
||||||
|
className={`text-xs p-1 px-2 rounded-lg w-12 text-center ${
|
||||||
|
product.type === "gov"
|
||||||
|
? "text-gray-600 bg-gray1-100"
|
||||||
|
: "text-gray-100 bg-gray-400"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{product.type === "gov" ? "دولتی" : "آزاد"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center gap-2 px-2 pb-3 mt-auto">
|
||||||
|
<Button
|
||||||
|
page="feed_input_products"
|
||||||
|
access="Edit-Product"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "ویرایش محصول",
|
||||||
|
content: <AddProduct getData={refetch} item={product} />,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
fullWidth
|
||||||
|
className="bg-primary-500 dark:bg-dark-500 hover:bg-primary-400 dark:hover:bg-dark-400 text-white px-3 w-full rounded-lg text-sm"
|
||||||
|
>
|
||||||
|
ویرایش
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "آیا از حذف محصول اطمینان دارید؟",
|
||||||
|
content: <DeleteProduct getData={refetch} item={product} />,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
page="feed_input_products"
|
||||||
|
access="Delete-Product"
|
||||||
|
size="small"
|
||||||
|
fullWidth
|
||||||
|
className="bg-white dark:bg-dark-600 text-primary-600 border w-full px-3 rounded-lg hover:bg-red-100 dark:hover:bg-dark-500 text-sm"
|
||||||
|
>
|
||||||
|
حذف
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* mobile */}
|
||||||
|
<motion.div className="grid md:hidden grid-cols-1 gap-2 justify-items-center">
|
||||||
|
{productsData?.results?.map((product: Product) => (
|
||||||
|
<motion.div
|
||||||
|
key={product.id}
|
||||||
|
className="bg-white dark:bg-dark-600 rounded-lg shadow-sm border border-gray-300 overflow-hidden flex justify-between items-center p-2 w-full"
|
||||||
|
variants={item}
|
||||||
|
initial="hidden"
|
||||||
|
animate="show"
|
||||||
|
>
|
||||||
|
<div className="h-15 bg-transparent w-1/6 dark:bg-dark-700">
|
||||||
|
<img
|
||||||
|
src={
|
||||||
|
product?.img && product.img !== "empty"
|
||||||
|
? `${product.img}?t=${product.modify_date || Date.now()}`
|
||||||
|
: getProductImage(product.name)
|
||||||
|
}
|
||||||
|
alt={product.name}
|
||||||
|
className="w-15 h-full object-cover rounded-xl dark:opacity-80"
|
||||||
|
key={`${product.id}-${product.modify_date}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-3 flex flex-col text-xs text-gray-500 dark:text-dark-200 w-3/6">
|
||||||
|
<p className="font-extrabold">{product.name}</p>
|
||||||
|
<p>کد محصول: {product.product_id}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p
|
||||||
|
className={`text-xs p-1 px-2 rounded-lg max-w-12 text-center w-1/6 ${
|
||||||
|
product.type === "gov"
|
||||||
|
? "text-gray-600 bg-gray1-100"
|
||||||
|
: "text-gray-100 bg-gray-400"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{product.type === "gov" ? "دولتی" : "آزاد"}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex justify-between items-end flex-col gap-4 w-1/6">
|
||||||
|
<button
|
||||||
|
className={`${getAbleToSee(
|
||||||
|
"feed_input_products",
|
||||||
|
"Edit-Product"
|
||||||
|
)} rounded-full text-primary-600 text-sm`}
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "ویرایش محصول",
|
||||||
|
content: <AddProduct getData={refetch} item={product} />,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PencilIcon className="w-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "آیا از حذف محصول اطمینان دارید؟",
|
||||||
|
content: <DeleteProduct getData={refetch} item={product} />,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className={`${getAbleToSee(
|
||||||
|
"feed_input_products",
|
||||||
|
"Delete-Product"
|
||||||
|
)} text-red-400 rounded-lg text-sm`}
|
||||||
|
>
|
||||||
|
<TrashIcon className="w-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
92
src/Pages/ProductsCategories.tsx
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
export const ProductsCategories = () => {
|
||||||
|
const { openModal } = useModalStore();
|
||||||
|
const [pagesInfo, setPagesInfo] = useState({ page: 1, page_size: 10 });
|
||||||
|
const [pagesTableData, setPagesTableData] = useState([]);
|
||||||
|
|
||||||
|
const { data: pagesData, refetch } = useApiRequest({
|
||||||
|
api: "/product/web/api/v1/category/",
|
||||||
|
method: "get",
|
||||||
|
params: pagesInfo,
|
||||||
|
queryKey: ["productCategories", pagesInfo],
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (pagesData?.results) {
|
||||||
|
const d = pagesData.results.map((item: any, i: number) => {
|
||||||
|
return [
|
||||||
|
pagesInfo.page === 1
|
||||||
|
? i + 1
|
||||||
|
: i + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
|
||||||
|
item?.name,
|
||||||
|
<Popover key={i}>
|
||||||
|
<Tooltip title="ویرایش" position="right">
|
||||||
|
<Button
|
||||||
|
variant="edit"
|
||||||
|
page="product_categories"
|
||||||
|
access="Post-Product-Category"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "ویرایش دسته بندی",
|
||||||
|
content: (
|
||||||
|
<AddProductCategory item={item} getData={refetch} />
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<DeleteButtonForPopOver
|
||||||
|
page="product_categories"
|
||||||
|
access="Post-Product-Category"
|
||||||
|
api={`product/web/api/v1/category/${item?.id}/`}
|
||||||
|
getData={refetch}
|
||||||
|
/>
|
||||||
|
</Popover>,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
setPagesTableData(d);
|
||||||
|
}
|
||||||
|
}, [pagesData, pagesInfo]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container column>
|
||||||
|
<Grid>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
page="product_categories"
|
||||||
|
access="Post-Product-Category"
|
||||||
|
variant="submit"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "ایجاد دسته بندی",
|
||||||
|
content: <AddProductCategory getData={refetch} />,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
ایجاد دسته بندی
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
<Table
|
||||||
|
className="mt-2"
|
||||||
|
onChange={(e) => {
|
||||||
|
setPagesInfo(e);
|
||||||
|
}}
|
||||||
|
count={pagesData?.count || 10}
|
||||||
|
isPaginated
|
||||||
|
title="دسته بندی ها"
|
||||||
|
columns={["ردیف", "نام دسته بندی", "عملیات"]}
|
||||||
|
rows={pagesTableData}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
54
src/Pages/Quota.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
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 { useParams } from "@tanstack/react-router";
|
||||||
|
import { QuotaDistributions } from "../partials/quota/QuotaDistributions";
|
||||||
|
import { QuotaAllDistributions } from "../partials/quota/QuotaAllDistributions";
|
||||||
|
|
||||||
|
export default function Quota() {
|
||||||
|
const [selectedTab, setSelectedTab] = useState<number>(0);
|
||||||
|
const handleTabChange = (index: number) => {
|
||||||
|
setSelectedTab(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
const tabItems = [
|
||||||
|
{
|
||||||
|
label: "سهمیه های فعال",
|
||||||
|
page: "quota",
|
||||||
|
access: "Active-Tab",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "لیست توزیع",
|
||||||
|
page: "quota",
|
||||||
|
access: "Distribution-Tab",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "بایگانی",
|
||||||
|
page: "quota",
|
||||||
|
access: "Closed-Tab",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const params = useParams({ strict: false });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container column className="gap-2">
|
||||||
|
{params?.code ? (
|
||||||
|
<QuotaDistributions />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Tabs tabs={tabItems} onChange={handleTabChange} size="medium" />
|
||||||
|
{selectedTab === 0 ? (
|
||||||
|
<QuotaActives />
|
||||||
|
) : selectedTab === 1 ? (
|
||||||
|
<QuotaAllDistributions />
|
||||||
|
) : (
|
||||||
|
<QuotaClosed />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
110
src/Pages/RancherPlans.tsx
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
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 { useParams } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export default function RancherPlans() {
|
||||||
|
const { openModal } = useModalStore();
|
||||||
|
const [params, setParams] = useState({ page: 1, page_size: 10 });
|
||||||
|
const [tableData, setTableData] = useState([]);
|
||||||
|
const { farmid, name } = useParams({ strict: false });
|
||||||
|
|
||||||
|
const { data: apiData, refetch } = useApiRequest({
|
||||||
|
api: `/herd/web/api/v1/rancher/${farmid}/rancher_plans/`,
|
||||||
|
method: "get",
|
||||||
|
params: params,
|
||||||
|
queryKey: ["incentivePlans", params],
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (apiData?.results) {
|
||||||
|
const formattedData = apiData.results.map((item: any, i: number) => {
|
||||||
|
return [
|
||||||
|
params.page === 1
|
||||||
|
? i + 1
|
||||||
|
: i + params.page_size * (params.page - 1) + 1,
|
||||||
|
item?.plan_name,
|
||||||
|
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}/`}
|
||||||
|
getData={refetch}
|
||||||
|
/>
|
||||||
|
</Popover>,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
setTableData(formattedData);
|
||||||
|
}
|
||||||
|
}, [apiData, params]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid container className="items-center gap-2">
|
||||||
|
<Grid>
|
||||||
|
<Button
|
||||||
|
className="hidden"
|
||||||
|
size="small"
|
||||||
|
page="farmer_plans"
|
||||||
|
access="Post-Rancher-Incentive-Plan"
|
||||||
|
variant="submit"
|
||||||
|
onClick={() =>
|
||||||
|
openModal({
|
||||||
|
title: "تخصیص طرح تشویقی",
|
||||||
|
content: <AddIncentivePlan getData={refetch} />,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
تخصیص طرح تشویقی
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid className="w-full">
|
||||||
|
<Table
|
||||||
|
className="mt-2"
|
||||||
|
excelInfo={{
|
||||||
|
link: "product/excel/incentive_plan_excel",
|
||||||
|
}}
|
||||||
|
onChange={(e) => {
|
||||||
|
setParams(e);
|
||||||
|
}}
|
||||||
|
title={`طرح های تشویقی دامدار (${name})`}
|
||||||
|
isPaginated
|
||||||
|
count={apiData?.count || 10}
|
||||||
|
columns={[
|
||||||
|
"ردیف",
|
||||||
|
"نام طرح",
|
||||||
|
"نوع دام",
|
||||||
|
"تعداد راس مجاز",
|
||||||
|
// , "عملیات"
|
||||||
|
]}
|
||||||
|
rows={tableData}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
37
src/Pages/Reporting.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Grid } from "../components/Grid/Grid";
|
||||||
|
import Tabs from "../components/Tab/Tab";
|
||||||
|
import { QuotaReportingProducts } from "../partials/quota/QuotaReportingProducts";
|
||||||
|
import { useParams } from "@tanstack/react-router";
|
||||||
|
import { QuotaReportingProductDetails } from "../partials/quota/QuotaReportingProductDetails";
|
||||||
|
import { QuotaReportingQuotaDistributions } from "../partials/quota/QuotaReportingQuotaDistributions";
|
||||||
|
|
||||||
|
const tabItems = [
|
||||||
|
{ label: "محصول" },
|
||||||
|
// { label: "نقش ها", visible: false },
|
||||||
|
// { label: "تراکنش ها", visible: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Reporting() {
|
||||||
|
const [selectedTab, setSelectedTab] = useState<number>(0);
|
||||||
|
const handleTabChange = (index: number) => {
|
||||||
|
setSelectedTab(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = useParams({ strict: false });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container column className="gap-2">
|
||||||
|
{params?.type === "quota" ? (
|
||||||
|
<QuotaReportingProductDetails />
|
||||||
|
) : params?.type === "distribution" ? (
|
||||||
|
<QuotaReportingQuotaDistributions />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Tabs tabs={tabItems} onChange={handleTabChange} size="medium" />
|
||||||
|
{selectedTab === 0 ? <QuotaReportingProducts /> : <></>}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
150
src/Pages/Roles.tsx
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
export default function Roles() {
|
||||||
|
const { openModal } = useModalStore();
|
||||||
|
const [pagesInfo, setPagesInfo] = useState({ page: 1, page_size: 10 });
|
||||||
|
const [pagesTableData, setPagesTableData] = useState([]);
|
||||||
|
|
||||||
|
const { data: pagesData, refetch } = useApiRequest({
|
||||||
|
api: "/auth/api/v1/role/",
|
||||||
|
method: "get",
|
||||||
|
params: pagesInfo,
|
||||||
|
queryKey: ["roles", pagesInfo],
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (pagesData?.results) {
|
||||||
|
const tableData = pagesData.results.map((item: any, i: number) => {
|
||||||
|
const accessList = Object.values(
|
||||||
|
item?.permissions.reduce((acc: any, item: any) => {
|
||||||
|
if (!acc[item.page]) {
|
||||||
|
acc[item.page] = {
|
||||||
|
page: item.page,
|
||||||
|
names: [],
|
||||||
|
descriptions: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
acc[item.page].names.push(item.name);
|
||||||
|
acc[item.page].descriptions.push(item.description);
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
pagesInfo.page === 1
|
||||||
|
? i + 1
|
||||||
|
: i + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
|
||||||
|
item?.role_name,
|
||||||
|
item?.parent_role?.name || "-",
|
||||||
|
item?.type?.name,
|
||||||
|
item?.type?.key || "-",
|
||||||
|
item?.description,
|
||||||
|
<ShowMoreInfo
|
||||||
|
key={i}
|
||||||
|
title="دسترسی ها"
|
||||||
|
counter={[accessList?.length.toString()]}
|
||||||
|
disabled={!accessList?.length}
|
||||||
|
>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
column
|
||||||
|
className="gap-2 p-2 justify-start items-start"
|
||||||
|
>
|
||||||
|
{accessList?.map((opt: any) => (
|
||||||
|
<Grid key={i} container column className="gap-2">
|
||||||
|
<Grid>
|
||||||
|
<Typography variant="subtitle2" sign="arrow">
|
||||||
|
{getFaPermissions(opt.page)}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<ShowStringList
|
||||||
|
strings={opt.descriptions}
|
||||||
|
showSearch={false}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>{" "}
|
||||||
|
</ShowMoreInfo>,
|
||||||
|
<Popover key={i}>
|
||||||
|
<Tooltip title="ویرایش" position="right">
|
||||||
|
<Button
|
||||||
|
variant="edit"
|
||||||
|
page="roles_management"
|
||||||
|
access="Edit-Role"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "ویرایش نقش",
|
||||||
|
content: <AddRole item={item} getData={refetch} />,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<DeleteButtonForPopOver
|
||||||
|
page="roles_management"
|
||||||
|
access="Delete-Role"
|
||||||
|
api={`auth/api/v1/role/${item?.id}/`}
|
||||||
|
getData={refetch}
|
||||||
|
/>
|
||||||
|
</Popover>,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
setPagesTableData(tableData);
|
||||||
|
}
|
||||||
|
}, [pagesData, pagesInfo]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container column>
|
||||||
|
<Grid>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
page="roles_management"
|
||||||
|
access="Post-Role"
|
||||||
|
variant="submit"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "ایجاد نقش",
|
||||||
|
content: <AddRole getData={refetch} />,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
ایجاد نقش
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
<Table
|
||||||
|
className="mt-2"
|
||||||
|
onChange={(e) => {
|
||||||
|
setPagesInfo(e);
|
||||||
|
}}
|
||||||
|
count={pagesData?.count || 10}
|
||||||
|
isPaginated
|
||||||
|
title="لیست نقش ها"
|
||||||
|
columns={[
|
||||||
|
"ردیف",
|
||||||
|
"نقش",
|
||||||
|
"نقش والد",
|
||||||
|
"نوع نقش",
|
||||||
|
"کلید",
|
||||||
|
"توضیحات",
|
||||||
|
"دسترسی ها",
|
||||||
|
"عملیات",
|
||||||
|
]}
|
||||||
|
rows={pagesTableData}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
62
src/Pages/SettingsOfUnits.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
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";
|
||||||
|
|
||||||
|
const tabItems = [
|
||||||
|
{ label: "اتحادیه ها", visible: false },
|
||||||
|
{ label: "تعاونی ها" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function SettingsOfUnits() {
|
||||||
|
const { openModal } = useModalStore();
|
||||||
|
const [selectedTab, setSelectedTab] = useState<number>(0);
|
||||||
|
const handleTabChange = (index: number) => {
|
||||||
|
setSelectedTab(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container column className="gap-2">
|
||||||
|
<Tabs tabs={tabItems} onChange={handleTabChange} size="medium" />
|
||||||
|
<Grid container column className="mt-2">
|
||||||
|
{selectedTab === 1 && (
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
|
||||||
|
<SettingCard
|
||||||
|
title="محدودیت دریافت نهاده"
|
||||||
|
description="تنظیم و مدیریت محدودیتهای دریافت نهاده برای تعاونیها"
|
||||||
|
icon={ShieldExclamationIcon}
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "محدودیت دریافت نهاده تعاونی ها",
|
||||||
|
content: (
|
||||||
|
<CooperativesSettingsTable settingsType="purchase_policy" />
|
||||||
|
),
|
||||||
|
isFullSize: true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<SettingCard
|
||||||
|
title="محدوده فعالیت تعاونی ها"
|
||||||
|
description="تعریف و مدیریت محدوده جغرافیایی فعالیت تعاونیها"
|
||||||
|
icon={MapPinIcon}
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "محدوده فعالیت تعاونی ها",
|
||||||
|
content: (
|
||||||
|
<CooperativesSettingsTable settingsType="service_area" />
|
||||||
|
),
|
||||||
|
isFullSize: true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
164
src/Pages/Tagging.tsx
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
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 { Popover } from "../components/PopOver/PopOver";
|
||||||
|
import { Tooltip } from "../components/Tooltip/Tooltip";
|
||||||
|
import { DeleteButtonForPopOver } from "../components/PopOverButtons/PopOverButtons";
|
||||||
|
import { TagDetails } from "../partials/tagging/TagDetails";
|
||||||
|
import { SubmitNewTags } from "../partials/tagging/SubmitNewTags";
|
||||||
|
|
||||||
|
export default function Tagging() {
|
||||||
|
const { openModal } = useModalStore();
|
||||||
|
const [tableInfo, setTableInfo] = useState({ page: 1, page_size: 10 });
|
||||||
|
const [tagsTableData, setTagsTableData] = useState([]);
|
||||||
|
|
||||||
|
const { data: tagsData, refetch } = useApiRequest({
|
||||||
|
api: "/tag/web/api/v1/tag/",
|
||||||
|
method: "get",
|
||||||
|
queryKey: ["tagsList", tableInfo],
|
||||||
|
params: {
|
||||||
|
...tableInfo,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: tagDashboardData, refetch: updateDashboard } = useApiRequest({
|
||||||
|
api: "/tag/web/api/v1/tag/tag_dashboard/",
|
||||||
|
method: "get",
|
||||||
|
queryKey: ["tagDashboard"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleUpdate = () => {
|
||||||
|
refetch();
|
||||||
|
updateDashboard();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tagsData?.results) {
|
||||||
|
const formattedData = tagsData.results.map((item: any, index: number) => {
|
||||||
|
return [
|
||||||
|
tableInfo.page === 1
|
||||||
|
? index + 1
|
||||||
|
: index + tableInfo.page_size * (tableInfo.page - 1) + 1,
|
||||||
|
item?.tag_code || "-",
|
||||||
|
item?.organization?.name || "بدون سازمان",
|
||||||
|
item?.species_code === 1
|
||||||
|
? "گاو"
|
||||||
|
: item?.species_code === 2
|
||||||
|
? "گاومیش"
|
||||||
|
: item?.species_code === 3
|
||||||
|
? "شتر"
|
||||||
|
: item?.species_code === 4
|
||||||
|
? "گوسفند"
|
||||||
|
: item?.species_code === 5
|
||||||
|
? "بز"
|
||||||
|
: "نامشخص",
|
||||||
|
item?.status === "F"
|
||||||
|
? "آزاد"
|
||||||
|
: item?.status === "A"
|
||||||
|
? "پلاک شده"
|
||||||
|
: item?.status === "R"
|
||||||
|
? "رزرو"
|
||||||
|
: "-",
|
||||||
|
item?.ownership_code || "-",
|
||||||
|
<Popover key={item.id}>
|
||||||
|
<Tooltip title="جزئیات پلاک" position="right">
|
||||||
|
<Button
|
||||||
|
variant="detail"
|
||||||
|
page="tagging"
|
||||||
|
access="Tag-Details"
|
||||||
|
disabled={item?.status === "F"}
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "جزئیات پلاک",
|
||||||
|
content: <TagDetails tagId={item.id} />,
|
||||||
|
isFullSize: true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<DeleteButtonForPopOver
|
||||||
|
page="tagging"
|
||||||
|
access="Delete-Tag"
|
||||||
|
api={`/tag/web/api/v1/tag/${item?.id}/`}
|
||||||
|
getData={refetch}
|
||||||
|
/>
|
||||||
|
</Popover>,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
setTagsTableData(formattedData);
|
||||||
|
}
|
||||||
|
}, [tagsData]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container column className="gap-4 mt-2">
|
||||||
|
<Grid>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="submit"
|
||||||
|
page="tagging"
|
||||||
|
access="Create-Tag"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "ایجاد پلاک جدید",
|
||||||
|
content: <SubmitNewTags getData={handleUpdate} />,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
ایجاد پلاک جدید
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid isDashboard>
|
||||||
|
<Table
|
||||||
|
isDashboard
|
||||||
|
title="خلاصه اطلاعات"
|
||||||
|
noPagination
|
||||||
|
noSearch
|
||||||
|
columns={[
|
||||||
|
"تعداد کل",
|
||||||
|
"تعداد پلاک های آزاد",
|
||||||
|
"تعداد پلاک شده",
|
||||||
|
"گاو",
|
||||||
|
"گاومیش",
|
||||||
|
"شتر",
|
||||||
|
"گوسفند",
|
||||||
|
"بز",
|
||||||
|
]}
|
||||||
|
rows={[
|
||||||
|
[
|
||||||
|
tagDashboardData?.count?.toLocaleString() || 0,
|
||||||
|
tagDashboardData?.free_count?.toLocaleString() || 0,
|
||||||
|
tagDashboardData?.assign_count?.toLocaleString() || 0,
|
||||||
|
tagDashboardData?.cow_count?.toLocaleString() || 0,
|
||||||
|
tagDashboardData?.buffalo_count?.toLocaleString() || 0,
|
||||||
|
tagDashboardData?.camel_count?.toLocaleString() || 0,
|
||||||
|
tagDashboardData?.sheep_count?.toLocaleString() || 0,
|
||||||
|
tagDashboardData?.goat_count?.toLocaleString() || 0,
|
||||||
|
],
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Table
|
||||||
|
className="mt-2"
|
||||||
|
onChange={setTableInfo}
|
||||||
|
count={tagsData?.count || 0}
|
||||||
|
isPaginated
|
||||||
|
title="پلاک کوبی"
|
||||||
|
columns={[
|
||||||
|
"ردیف",
|
||||||
|
"پلاک",
|
||||||
|
"سازمان ثبت کننده",
|
||||||
|
"کد گونه",
|
||||||
|
"وضعیت",
|
||||||
|
"کد مالکیت ثبتی",
|
||||||
|
"عملیات",
|
||||||
|
]}
|
||||||
|
rows={tagsTableData}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
118
src/Pages/Training.tsx
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { AcademicCapIcon, SparklesIcon } from "@heroicons/react/24/outline";
|
||||||
|
|
||||||
|
export default function Training() {
|
||||||
|
return (
|
||||||
|
<main className="flex flex-col items-center justify-center h-full min-h-[60vh] bg-white dark:bg-dark-900 px-6 text-center">
|
||||||
|
<motion.div
|
||||||
|
className="flex flex-col items-center justify-center"
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, ease: "easeOut" }}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
className="relative mb-6"
|
||||||
|
initial={{ scale: 0.8, opacity: 0 }}
|
||||||
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
|
transition={{
|
||||||
|
delay: 0.2,
|
||||||
|
duration: 0.5,
|
||||||
|
type: "spring",
|
||||||
|
stiffness: 200,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="relative">
|
||||||
|
<AcademicCapIcon className="w-24 h-24 sm:w-32 sm:h-32 text-primary-500 dark:text-primary-400" />
|
||||||
|
<motion.div
|
||||||
|
className="absolute -top-2 -right-2"
|
||||||
|
animate={{
|
||||||
|
scale: [1, 1.2, 1],
|
||||||
|
rotate: [0, 10, -10, 0],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 2,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SparklesIcon className="w-8 h-8 text-warning-500" />
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.h1
|
||||||
|
className="text-3xl sm:text-4xl md:text-5xl font-bold mb-4 text-gray-900 dark:text-white select-none"
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.4, duration: 0.6 }}
|
||||||
|
>
|
||||||
|
بخش آموزش
|
||||||
|
</motion.h1>
|
||||||
|
|
||||||
|
<motion.p
|
||||||
|
className="max-w-lg text-lg sm:text-xl mb-2 px-4 text-gray-600 dark:text-dark-300 font-medium"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay: 0.6, duration: 0.6 }}
|
||||||
|
>
|
||||||
|
این بخش در دست توسعه است!
|
||||||
|
</motion.p>
|
||||||
|
|
||||||
|
<motion.p
|
||||||
|
className="max-w-md text-sm sm:text-base px-4 text-gray-500 dark:text-dark-400"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay: 0.8, duration: 0.6 }}
|
||||||
|
>
|
||||||
|
به زودی ویدیوهای آموزشی سامانه رصدام در این بخش قرار خواهد گرفت.
|
||||||
|
</motion.p>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
className="mt-8 flex gap-2"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay: 1, duration: 0.6 }}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
className="w-2 h-2 rounded-full bg-primary-500"
|
||||||
|
animate={{
|
||||||
|
scale: [1, 1.3, 1],
|
||||||
|
opacity: [0.5, 1, 0.5],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 1.5,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<motion.div
|
||||||
|
className="w-2 h-2 rounded-full bg-primary-500"
|
||||||
|
animate={{
|
||||||
|
scale: [1, 1.3, 1],
|
||||||
|
opacity: [0.5, 1, 0.5],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 1.5,
|
||||||
|
repeat: Infinity,
|
||||||
|
delay: 0.2,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<motion.div
|
||||||
|
className="w-2 h-2 rounded-full bg-primary-500"
|
||||||
|
animate={{
|
||||||
|
scale: [1, 1.3, 1],
|
||||||
|
opacity: [0.5, 1, 0.5],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 1.5,
|
||||||
|
repeat: Infinity,
|
||||||
|
delay: 0.4,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
271
src/Pages/Transactions.tsx
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
type TransactionResponse = {
|
||||||
|
results?: any[];
|
||||||
|
count?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Transactions() {
|
||||||
|
const [params, setParams] = useState({ page: 1, page_size: 10 });
|
||||||
|
|
||||||
|
const [publicParams, setPublicParams] = useState({
|
||||||
|
start: null,
|
||||||
|
end: null,
|
||||||
|
search: null,
|
||||||
|
status: "success",
|
||||||
|
});
|
||||||
|
const { openModal } = useModalStore();
|
||||||
|
|
||||||
|
const [tableData, setTableData] = useState<any[]>([]);
|
||||||
|
|
||||||
|
const { data, refetch: refetchTransactions } =
|
||||||
|
useApiRequest<TransactionResponse>({
|
||||||
|
api: "/warehouse/web/api/v1/inventory_sale_transaction/",
|
||||||
|
params: { ...params, ...publicParams },
|
||||||
|
queryKey: ["transactions", params],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: DashboardData, refetch: refetchDashboard } =
|
||||||
|
useApiRequest<DashboardResponse>({
|
||||||
|
api: `/warehouse/web/api/v1/inventory_sale_transaction/transactions_dashboard/`,
|
||||||
|
params: { ...publicParams },
|
||||||
|
queryKey: ["distributions_dashboard"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const productSummary: ProductSummaryItem[] =
|
||||||
|
DashboardData?.product_summary || [];
|
||||||
|
const hasProductSummary = productSummary.length > 0;
|
||||||
|
|
||||||
|
const handleOpenProductSummaryModal = () => {
|
||||||
|
if (!hasProductSummary) return;
|
||||||
|
|
||||||
|
openModal({
|
||||||
|
title: "آمار محصولات",
|
||||||
|
content: <ProductSummaryModal products={productSummary} />,
|
||||||
|
isFullSize: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdate = () => {
|
||||||
|
refetchTransactions();
|
||||||
|
refetchDashboard();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data?.results) {
|
||||||
|
const formatted = data.results.map((item: any, i: number) => {
|
||||||
|
const items = item?.items || [];
|
||||||
|
const quotaSaleUnits = items
|
||||||
|
.map((p: any) => p?.quota_sale_unit)
|
||||||
|
.filter((unit: any) => unit);
|
||||||
|
const totalWeight = items.reduce(
|
||||||
|
(sum: number, p: any) => sum + (p?.weight || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
let weightNature;
|
||||||
|
if (quotaSaleUnits.length > 0) {
|
||||||
|
const allEqual =
|
||||||
|
items.length > 0 &&
|
||||||
|
quotaSaleUnits.length === items.length &&
|
||||||
|
new Set(quotaSaleUnits).size === 1;
|
||||||
|
weightNature = allEqual
|
||||||
|
? `${totalWeight.toLocaleString()} ${quotaSaleUnits[0] || ""}`
|
||||||
|
: "ترکیبی";
|
||||||
|
} else {
|
||||||
|
weightNature = `${totalWeight.toLocaleString()} کیلوگرم`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
params.page === 1
|
||||||
|
? i + 1
|
||||||
|
: i + params.page_size * (params.page - 1) + 1,
|
||||||
|
item?.seller_organization?.name || "-",
|
||||||
|
item?.rancher_fullname || "آزاد",
|
||||||
|
item?.rancher?.national_code || "-",
|
||||||
|
<Grid container column key={i}>
|
||||||
|
<Grid>{formatJustDate(item?.transaction_date) || "-"}</Grid>
|
||||||
|
<Grid>{formatJustTime(item?.transaction_date) || "-"}</Grid>
|
||||||
|
</Grid>,
|
||||||
|
item?.items?.map((p: any) => `${p?.name}`).join(" - ") || "-",
|
||||||
|
item?.transaction_id || "-",
|
||||||
|
item?.pos_device?.serial || "-",
|
||||||
|
item?.payer_cart || "-",
|
||||||
|
item?.product_type === "gov" ? "دولتی" : "آزاد",
|
||||||
|
item?.price_paid?.toLocaleString() ||
|
||||||
|
item?.transaction_price?.toLocaleString() ||
|
||||||
|
"-",
|
||||||
|
weightNature,
|
||||||
|
item?.transaction_status === "waiting"
|
||||||
|
? "درحال انتظار"
|
||||||
|
: item?.transaction_status === "success"
|
||||||
|
? "موفق"
|
||||||
|
: item?.transaction_status === "failed"
|
||||||
|
? `ناموفق ( ${item?.result_text || "-"} ${
|
||||||
|
item?.transaction_status_code || ""
|
||||||
|
} )`
|
||||||
|
: "-",
|
||||||
|
<TableButton
|
||||||
|
size="small"
|
||||||
|
key={i}
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "جزئیات تراکنش",
|
||||||
|
content: (
|
||||||
|
<TransactionDetails
|
||||||
|
transaction={item}
|
||||||
|
items={item?.items || []}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
isFullSize: true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
setTableData(formatted);
|
||||||
|
} else {
|
||||||
|
setTableData([]);
|
||||||
|
}
|
||||||
|
}, [data, params.page, params.page_size]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container column className="gap-4">
|
||||||
|
<PaginationParameters
|
||||||
|
excelInfo={{
|
||||||
|
link: `/warehouse/excel/inventory_sale_transaction_excel/?status=${
|
||||||
|
publicParams.status
|
||||||
|
}&start=${publicParams.start || ""}&end=${
|
||||||
|
publicParams.end || ""
|
||||||
|
}&search=${publicParams.search || ""}`,
|
||||||
|
title: "لیست تراکنش ها",
|
||||||
|
}}
|
||||||
|
getData={handleUpdate}
|
||||||
|
title="فیلتر تراکنش"
|
||||||
|
onChange={(r) => {
|
||||||
|
setPublicParams((prev) => ({ ...prev, ...(r as any) }));
|
||||||
|
setParams((prev) => ({ ...prev, page: 1 }));
|
||||||
|
}}
|
||||||
|
filters={[
|
||||||
|
{
|
||||||
|
key: "status",
|
||||||
|
data: [
|
||||||
|
{ key: "", value: "همه" },
|
||||||
|
{ key: "success", value: "موفق" },
|
||||||
|
{ key: "failed", value: "ناموفق" },
|
||||||
|
{ key: "waiting", value: "درحال انتظار" },
|
||||||
|
],
|
||||||
|
selectedKeys: [publicParams.status],
|
||||||
|
onChange: (keys) => {
|
||||||
|
setPublicParams((prev) => ({
|
||||||
|
...prev,
|
||||||
|
status: keys[0] as string,
|
||||||
|
}));
|
||||||
|
setParams((prev) => ({ ...prev, page: 1 }));
|
||||||
|
},
|
||||||
|
title: "وضعیت",
|
||||||
|
size: "small",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Grid container column isDashboard>
|
||||||
|
<Table
|
||||||
|
isDashboard
|
||||||
|
noPagination
|
||||||
|
noSearch
|
||||||
|
title="خلاصه تراکنش ها"
|
||||||
|
columns={[
|
||||||
|
"تعداد کل تراکنشها",
|
||||||
|
"تراکنشهای موفق",
|
||||||
|
"تراکنشهای ناموفق",
|
||||||
|
"تراکنشهای در انتظار",
|
||||||
|
"مجموع مبلغ (ریال)",
|
||||||
|
"مبلغ به حروف (ریال)",
|
||||||
|
"مجموع وزن (کیلوگرم)",
|
||||||
|
"جزئیات محصولات",
|
||||||
|
"جزئیات تسهیم",
|
||||||
|
]}
|
||||||
|
rows={[
|
||||||
|
[
|
||||||
|
DashboardData?.transaction_summary?.total_transactions?.toLocaleString() ||
|
||||||
|
0,
|
||||||
|
DashboardData?.transaction_summary?.success_transactions?.toLocaleString() ||
|
||||||
|
0,
|
||||||
|
DashboardData?.transaction_summary?.failed_transactions?.toLocaleString() ||
|
||||||
|
0,
|
||||||
|
DashboardData?.transaction_summary?.waiting_transactions?.toLocaleString() ||
|
||||||
|
0,
|
||||||
|
DashboardData?.transaction_summary?.total_amount?.toLocaleString() ||
|
||||||
|
0,
|
||||||
|
convertNumberToPersian(
|
||||||
|
DashboardData?.transaction_summary?.total_amount || 0
|
||||||
|
),
|
||||||
|
DashboardData?.transaction_summary?.total_weight?.toLocaleString() ||
|
||||||
|
0,
|
||||||
|
<TableButton
|
||||||
|
size="small"
|
||||||
|
key={DashboardData?.product_summary?.length}
|
||||||
|
disabled={!hasProductSummary}
|
||||||
|
onClick={handleOpenProductSummaryModal}
|
||||||
|
/>,
|
||||||
|
<TableButton
|
||||||
|
size="small"
|
||||||
|
key={DashboardData?.product_summary?.length}
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "جزئیات تسهیم",
|
||||||
|
content: (
|
||||||
|
<TransactionSharingDetails
|
||||||
|
data={DashboardData?.brokers_sharing_summary}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
isFullSize: true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
],
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid className="w-full">
|
||||||
|
<Table
|
||||||
|
onChange={setParams}
|
||||||
|
count={data?.count || 0}
|
||||||
|
isPaginated
|
||||||
|
noSearch
|
||||||
|
title="لیست تراکنش ها"
|
||||||
|
columns={[
|
||||||
|
"ردیف",
|
||||||
|
"تعاونی",
|
||||||
|
"دامدار",
|
||||||
|
"کد ملی دامدار",
|
||||||
|
"تاریخ",
|
||||||
|
"محصولات",
|
||||||
|
"شناسه تراکنش",
|
||||||
|
"سریال دستگاه",
|
||||||
|
"شماره کارت",
|
||||||
|
"نوع فروش",
|
||||||
|
"مبلغ",
|
||||||
|
"ماهیت وزن",
|
||||||
|
"وضعیت",
|
||||||
|
"جزئیات تراکنش",
|
||||||
|
]}
|
||||||
|
rows={tableData}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
82
src/Pages/Unions.tsx
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
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 { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export default function Unions() {
|
||||||
|
const [pagesInfo, setPagesInfo] = useState({ page: 1, page_size: 10 });
|
||||||
|
const [unionsTableData, setUnionsTableData] = useState([]);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const { data: unionsData } = useApiRequest({
|
||||||
|
api: "/auth/api/v1/organization/get_union_orgs/",
|
||||||
|
method: "get",
|
||||||
|
params: {
|
||||||
|
...pagesInfo,
|
||||||
|
},
|
||||||
|
queryKey: ["unions", pagesInfo],
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (unionsData?.results) {
|
||||||
|
const formattedData = unionsData.results.map((item: any, i: number) => {
|
||||||
|
return [
|
||||||
|
pagesInfo.page === 1
|
||||||
|
? i + 1
|
||||||
|
: i + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
|
||||||
|
item?.name || "-",
|
||||||
|
item?.type?.name || "-",
|
||||||
|
item?.province?.name || "-",
|
||||||
|
item?.city?.name || "-",
|
||||||
|
item?.parent_organization?.name || "-",
|
||||||
|
item?.national_unique_id || "-",
|
||||||
|
item?.address || "-",
|
||||||
|
<Popover key={i}>
|
||||||
|
<Tooltip title="نمایش تعاونی ها" position="right">
|
||||||
|
<Button
|
||||||
|
variant="detail"
|
||||||
|
page={"union_cooperatives"}
|
||||||
|
access="Show-Union-Cooperatives"
|
||||||
|
onClick={() => {
|
||||||
|
const path =
|
||||||
|
COOPERATIVE_LIST + "/" + item?.id + "/" + item?.name;
|
||||||
|
navigate({ to: path });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Popover>,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
setUnionsTableData(formattedData);
|
||||||
|
}
|
||||||
|
}, [unionsData, pagesInfo]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container column>
|
||||||
|
<Table
|
||||||
|
className="mt-2"
|
||||||
|
onChange={setPagesInfo}
|
||||||
|
count={unionsData?.count || 10}
|
||||||
|
isPaginated
|
||||||
|
title="اتحادیه ها"
|
||||||
|
columns={[
|
||||||
|
"ردیف",
|
||||||
|
"نام",
|
||||||
|
"نوع سازمان",
|
||||||
|
"استان",
|
||||||
|
"شهر",
|
||||||
|
"سازمان والد",
|
||||||
|
"شناسه کشوری",
|
||||||
|
"آدرس",
|
||||||
|
"عملیات",
|
||||||
|
]}
|
||||||
|
rows={unionsTableData}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
286
src/Pages/UserProfile.tsx
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
import {
|
||||||
|
PhoneIcon,
|
||||||
|
MapPinIcon,
|
||||||
|
MoonIcon,
|
||||||
|
SunIcon,
|
||||||
|
ArrowLeftStartOnRectangleIcon,
|
||||||
|
IdentificationIcon,
|
||||||
|
CalendarIcon,
|
||||||
|
HomeIcon,
|
||||||
|
UserIcon,
|
||||||
|
BuildingOfficeIcon,
|
||||||
|
BookOpenIcon,
|
||||||
|
InformationCircleIcon,
|
||||||
|
} from "@heroicons/react/24/outline";
|
||||||
|
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 { useUserProfileStore } from "../context/zustand-store/userStore";
|
||||||
|
import { formatJustDate } from "../utils/formatTime";
|
||||||
|
import bg from "../assets/images/profile-bg.png";
|
||||||
|
import bgdark from "../assets/images/profile-bg-dark.png";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
import { TRAINING } from "../routes/paths";
|
||||||
|
import { useFetchProfile } from "../hooks/useFetchProfile";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import versionRaw from "../version.txt?raw";
|
||||||
|
|
||||||
|
interface ProfileCardProps {
|
||||||
|
icon: React.ComponentType<{ className?: string }>;
|
||||||
|
label: string;
|
||||||
|
value: string | null;
|
||||||
|
show?: boolean;
|
||||||
|
variant?: "default" | "highlight" | "accent";
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProfileCard = ({
|
||||||
|
icon: Icon,
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
show = true,
|
||||||
|
}: ProfileCardProps) => {
|
||||||
|
if (!show) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid
|
||||||
|
className={clsx(
|
||||||
|
"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"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon className="h-5 w-5 text-white" />
|
||||||
|
</Grid>
|
||||||
|
<Grid className="flex flex-col min-w-0">
|
||||||
|
<span className="text-xs text-gray-500 dark:text-white font-semibold uppercase tracking-wider mb-2">
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
<span className="text-primary-600 font-bold text-sm leading-relaxed break-words">
|
||||||
|
{value}
|
||||||
|
</span>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function UserProfile() {
|
||||||
|
const [isDark, setIsDark] = useDarkMode();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { profile } = useUserProfileStore();
|
||||||
|
const { getProfile } = useFetchProfile();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getProfile();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const { openModal } = useModalStore();
|
||||||
|
const version = versionRaw.trim();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid className="min-h-screen bg-gray-50 dark:bg-dark-900">
|
||||||
|
<div
|
||||||
|
className="relative overflow-hidden bg-cover bg-center bg-no-repeat"
|
||||||
|
style={{ backgroundImage: `url(${isDark ? bgdark : bg})` }}
|
||||||
|
>
|
||||||
|
<Grid className="relative px-6 py-12 md:px-12 md:py-8 items-center">
|
||||||
|
<Grid className=" mx-auto">
|
||||||
|
<Grid className="flex flex-col lg:flex-row items-center gap-8 lg:gap-12">
|
||||||
|
<Grid className="relative group">
|
||||||
|
<Grid className="relative">
|
||||||
|
<Grid className="relative bg-white/80 dark:bg-dark-600 backdrop-blur-xl rounded-full p-4 border border-white/20 shadow-2xl">
|
||||||
|
<UserIcon className="h-24 w-24 md:h-28 md:w-28 text-gray-600 drop-shadow-lg" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid className="flex-1 text-center lg:text-right">
|
||||||
|
<Grid className="space-y-4">
|
||||||
|
<h1 className="text-3xl md:text-4xl font-bold text-gray-600 dark:text-dark-100 bg-clip-text drop-shadow-sm">
|
||||||
|
{profile?.user?.first_name} {profile?.user?.last_name}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<Grid container className="w-auto gap-2 justify-start">
|
||||||
|
<Grid className="inline-flex items-center gap-2 bg-white dark:bg-dark-600 backdrop-blur-sm rounded-xl py-3 px-6 border border-primary-200/30">
|
||||||
|
<span className="text-gray-600 dark:text-dark-100 font-semibold text-sm">
|
||||||
|
{profile?.role?.role_name}
|
||||||
|
</span>
|
||||||
|
</Grid>
|
||||||
|
{profile?.organization?.name && (
|
||||||
|
<Grid className="inline-flex items-center gap-2 bg-white dark:bg-dark-600 backdrop-blur-sm rounded-xl py-2 px-4 border border-emerald-200/30">
|
||||||
|
<span className="text-gray-600 dark:text-dark-100 font-semibold text-sm">
|
||||||
|
سازمان: {profile?.organization?.name}
|
||||||
|
</span>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid className="flex flex-col justify-center items-end gap-4">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsDark(!isDark)}
|
||||||
|
className={clsx(
|
||||||
|
"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"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{isDark ? (
|
||||||
|
<SunIcon className="w-5 h-5 text-gray-600 dark:text-dark-100" />
|
||||||
|
) : (
|
||||||
|
<MoonIcon className="w-5 h-5 text-gray-600 dark:text-dark-100" />
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
<span className="text-gray-700 dark:text-dark-100 font-semibold text-sm">
|
||||||
|
{isDark ? "حالت روشن" : "حالت تاریک"}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
const path = TRAINING;
|
||||||
|
navigate({ to: path });
|
||||||
|
}}
|
||||||
|
className={clsx(
|
||||||
|
"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"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<BookOpenIcon className="h-5 w-5 text-green-500" />
|
||||||
|
</Grid>
|
||||||
|
<span className="text-green-600 font-semibold text-sm">
|
||||||
|
آموزش
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "از سامانه خارج میشوید؟",
|
||||||
|
content: <Logout />,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className={clsx(
|
||||||
|
"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"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ArrowLeftStartOnRectangleIcon className="h-5 w-5 text-red-700" />
|
||||||
|
</Grid>
|
||||||
|
<span className="text-red-700 font-semibold text-sm">
|
||||||
|
خروج از سامانه
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Grid className="pb-8 mt-4">
|
||||||
|
<Grid className="max-full mx-auto bg-white dark:bg-gray-900 p-4 rounded-2xl">
|
||||||
|
<Grid className="flex items-center gap-2">
|
||||||
|
<span className="text-sm font-bold text-gray-600 bg-[#FFF2E5] p-2 rounded-xl">
|
||||||
|
اطلاعات کاربری
|
||||||
|
</span>
|
||||||
|
<div className="flex-1 border-t-2 border-dotted border-primary-800"></div>
|
||||||
|
</Grid>
|
||||||
|
<Grid className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-4 2xl:grid-cols-6 gap-6 mt-2">
|
||||||
|
<ProfileCard
|
||||||
|
icon={UserIcon}
|
||||||
|
label="نام کاربری"
|
||||||
|
value={profile?.user?.username || "بدون نام کاربری"}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ProfileCard
|
||||||
|
icon={PhoneIcon}
|
||||||
|
label="شماره موبایل"
|
||||||
|
value={profile?.user?.mobile || "بدون شماره موبایل"}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ProfileCard
|
||||||
|
icon={MapPinIcon}
|
||||||
|
label="موقعیت"
|
||||||
|
value={`${profile?.organization?.province?.name || ""}، ${
|
||||||
|
profile?.organization?.city?.name || ""
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ProfileCard
|
||||||
|
icon={IdentificationIcon}
|
||||||
|
label="کد ملی"
|
||||||
|
value={profile?.user?.national_code || "بدون کد ملی"}
|
||||||
|
show={!!profile?.user?.national_code}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ProfileCard
|
||||||
|
icon={CalendarIcon}
|
||||||
|
label="تاریخ تولد"
|
||||||
|
value={
|
||||||
|
profile?.user?.birthdate
|
||||||
|
? formatJustDate(profile.user.birthdate)
|
||||||
|
: "بدون تاریخ تولد"
|
||||||
|
}
|
||||||
|
show={!!profile?.user?.birthdate}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ProfileCard
|
||||||
|
icon={HomeIcon}
|
||||||
|
label="آدرس"
|
||||||
|
value={profile?.user?.address || "بدون آدرس"}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ProfileCard
|
||||||
|
icon={BuildingOfficeIcon}
|
||||||
|
label="نوع کاربر"
|
||||||
|
value={profile?.user?.ownership === "L" ? "حقوقی" : "حقیقی"}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ProfileCard
|
||||||
|
icon={BuildingOfficeIcon}
|
||||||
|
label="نام واحد"
|
||||||
|
value={profile?.user?.unit_name || "بدون نام واحد"}
|
||||||
|
show={profile?.user?.ownership === "L"}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ProfileCard
|
||||||
|
icon={IdentificationIcon}
|
||||||
|
label="شناسه ملی واحد"
|
||||||
|
value={profile?.user?.unit_national_id || "بدون شناسه ملی واحد"}
|
||||||
|
show={profile?.user?.ownership === "L"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
{version && (
|
||||||
|
<Grid className="mt-6 flex justify-end">
|
||||||
|
<Grid className="inline-flex items-center gap-1 bg-white dark:bg-dark-600 backdrop-blur-sm rounded-lg py-1 px-3 border border-sky-200/40">
|
||||||
|
<InformationCircleIcon className="h-3 w-3 text-sky-600" />
|
||||||
|
<span className="text-sky-600 dark:text-dark-100 font-semibold text-xs">
|
||||||
|
نسخه {version}
|
||||||
|
</span>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
208
src/Pages/Users.tsx
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
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 {
|
||||||
|
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";
|
||||||
|
|
||||||
|
type PermissionType = {
|
||||||
|
page_name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Users() {
|
||||||
|
const { openModal } = useModalStore();
|
||||||
|
const { openDrawer } = useDrawerStore();
|
||||||
|
const [selectedRolesKeys, setSelectedRolesKeys] = useState<
|
||||||
|
(string | number)[]
|
||||||
|
>([]);
|
||||||
|
const { profile } = useUserProfileStore();
|
||||||
|
const [params, setParams] = useState({ page: 1, page_size: 10 });
|
||||||
|
const [tableData, setTableData] = useState([]);
|
||||||
|
|
||||||
|
const { data: apiData, refetch } = useApiRequest({
|
||||||
|
api: `/auth/api/v1/user-relations/?role=${selectedRolesKeys[0] || ""}`,
|
||||||
|
method: "get",
|
||||||
|
params: params,
|
||||||
|
queryKey: ["users", params, selectedRolesKeys],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: rolesData } = useApiRequest({
|
||||||
|
api: "/auth/api/v1/role/",
|
||||||
|
method: "get",
|
||||||
|
params: { page: 1, page_size: 1000 },
|
||||||
|
queryKey: ["roles"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const formattedRolesData = [
|
||||||
|
{
|
||||||
|
key: "",
|
||||||
|
value: "کل نقش ها",
|
||||||
|
},
|
||||||
|
...(rolesData?.results
|
||||||
|
?.filter((role: any) => {
|
||||||
|
if (profile?.role?.type?.key !== "ADM") {
|
||||||
|
return role.type?.key !== "ADM";
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
?.map((role: any) => ({
|
||||||
|
key: role.id,
|
||||||
|
value: role.role_name,
|
||||||
|
})) || []),
|
||||||
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (apiData?.results) {
|
||||||
|
const formattedData = apiData.results.map((item: any, i: number) => {
|
||||||
|
const permissionLength = item?.permissions?.length;
|
||||||
|
return [
|
||||||
|
params.page === 1
|
||||||
|
? i + 1
|
||||||
|
: i + params.page_size * (params.page - 1) + 1,
|
||||||
|
item?.user?.username,
|
||||||
|
item?.user?.first_name,
|
||||||
|
item?.user?.last_name,
|
||||||
|
item?.organization?.name,
|
||||||
|
item?.role?.role_name,
|
||||||
|
item?.user?.mobile,
|
||||||
|
item?.user?.national_code,
|
||||||
|
item?.user?.province_name
|
||||||
|
? `${item?.user?.province_name} (${item?.user?.city_name})`
|
||||||
|
: "-",
|
||||||
|
item?.user?.address || "-",
|
||||||
|
item?.user?.ownership === "L" ? "حقوقی" : "حقیقی",
|
||||||
|
item?.user?.unit_name || "-",
|
||||||
|
item?.user?.unit_national_id || "-",
|
||||||
|
item?.user?.is_active ? "فعال" : "غیر فعال",
|
||||||
|
<ShowMoreInfo
|
||||||
|
key={i}
|
||||||
|
title="دسترسی ها"
|
||||||
|
disabled={!permissionLength}
|
||||||
|
counter={permissionLength}
|
||||||
|
>
|
||||||
|
<ShowStringList
|
||||||
|
strings={item?.permissions?.map((option: PermissionType) =>
|
||||||
|
getFaPermissions(option?.page_name)
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</ShowMoreInfo>,
|
||||||
|
<Popover key={i}>
|
||||||
|
<Tooltip title="ویرایش پروفایل کاربر" position="right">
|
||||||
|
<Button
|
||||||
|
page="users"
|
||||||
|
access="Update-User-Profile"
|
||||||
|
variant="edit"
|
||||||
|
onClick={() => {
|
||||||
|
openDrawer({
|
||||||
|
title: "ویرایش پروفایل کاربر",
|
||||||
|
content: <AddUser getData={refetch} item={item} />,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="ویرایش دسترسی کاربر" position="right">
|
||||||
|
<Button
|
||||||
|
page="users"
|
||||||
|
access="Update-User-Access"
|
||||||
|
variant="secondary-edit"
|
||||||
|
onClick={() => {
|
||||||
|
openModal({
|
||||||
|
title: "ویرایش دسترسی کاربر",
|
||||||
|
content: <EditAccess getData={refetch} item={item} />,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<DeleteButtonForPopOver
|
||||||
|
page="users"
|
||||||
|
access="Delete-User"
|
||||||
|
title="از حذف کاربر اطمینان دارید؟"
|
||||||
|
api={`/auth/api/v1/user/${item?.user?.id}/`}
|
||||||
|
getData={refetch}
|
||||||
|
/>
|
||||||
|
</Popover>,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
setTableData(formattedData);
|
||||||
|
}
|
||||||
|
}, [apiData, params]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid container className="items-center gap-2">
|
||||||
|
<Grid>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
page="users"
|
||||||
|
access="Create-User"
|
||||||
|
variant="submit"
|
||||||
|
onClick={() =>
|
||||||
|
openDrawer({
|
||||||
|
title: "ایجاد کاربر",
|
||||||
|
content: <AddUser getData={refetch} />,
|
||||||
|
isOpen: true,
|
||||||
|
direction: "left",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
ایجاد کاربر
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<AutoComplete
|
||||||
|
inPage
|
||||||
|
size="small"
|
||||||
|
data={formattedRolesData}
|
||||||
|
selectedKeys={selectedRolesKeys}
|
||||||
|
onChange={setSelectedRolesKeys}
|
||||||
|
title="فیلتر نقش"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid className="w-full">
|
||||||
|
<Table
|
||||||
|
className="mt-2"
|
||||||
|
onChange={setParams}
|
||||||
|
excelInfo={{
|
||||||
|
link: "auth/excel/user_relations_excel/",
|
||||||
|
}}
|
||||||
|
title="کاربران"
|
||||||
|
isPaginated
|
||||||
|
count={apiData?.count || 10}
|
||||||
|
columns={[
|
||||||
|
"ردیف",
|
||||||
|
"نام کاربری",
|
||||||
|
"نام",
|
||||||
|
"نام خانوادگی",
|
||||||
|
"سازمان",
|
||||||
|
"نقش",
|
||||||
|
"موبایل",
|
||||||
|
"کد ملی",
|
||||||
|
"استان/شهر",
|
||||||
|
"آدرس",
|
||||||
|
"مالکیت",
|
||||||
|
"نام واحد",
|
||||||
|
"شناسه ملی واحد",
|
||||||
|
"وضعیت",
|
||||||
|
"دسترسی ها",
|
||||||
|
"عملیات",
|
||||||
|
]}
|
||||||
|
rows={tableData}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
1
src/assets/animations/nodata.json
Normal file
1
src/assets/animations/waiting.json
Normal file
BIN
src/assets/fonts/eot/iranyekanwebblackfanum.eot
Normal file
BIN
src/assets/fonts/eot/iranyekanwebboldfanum.eot
Normal file
BIN
src/assets/fonts/eot/iranyekanwebextrablackfanum.eot
Normal file
BIN
src/assets/fonts/eot/iranyekanwebextraboldfanum.eot
Normal file
BIN
src/assets/fonts/eot/iranyekanweblightfanum.eot
Normal file
BIN
src/assets/fonts/eot/iranyekanwebmediumfanum.eot
Normal file
BIN
src/assets/fonts/eot/iranyekanwebregularfanum.eot
Normal file
BIN
src/assets/fonts/eot/iranyekanwebthinfanum.eot
Normal file
129
src/assets/fonts/fonts.css
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: iranyekan;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: bold;
|
||||||
|
src: url("./eot/iranyekanwebboldfanum.eot");
|
||||||
|
src: url("./eot/iranyekanwebboldfanum.eot?#iefix") format("embedded-opentype"),
|
||||||
|
/* IE6-8 */ url("./woff/iranyekanwebboldfanum.woff") format("woff"),
|
||||||
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("./ttf/iranyekanwebboldfanum.ttf")
|
||||||
|
format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: iranyekan;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 100;
|
||||||
|
src: url("./eot/iranyekanwebthinfanum.eot");
|
||||||
|
src: url("./eot/iranyekanwebthinfanum.eot?#iefix") format("embedded-opentype"),
|
||||||
|
/* IE6-8 */ url("./woff/iranyekanwebthinfanum.woff") format("woff"),
|
||||||
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("./ttf/iranyekanwebthinfanum.ttf")
|
||||||
|
format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: iranyekan;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
src: url("./eot/iranyekanweblightfanum.eot");
|
||||||
|
src: url("./eot/iranyekanweblightfanum.eot?#iefix")
|
||||||
|
format("embedded-opentype"),
|
||||||
|
/* IE6-8 */ url("./woff/iranyekanweblightfanum.woff") format("woff"),
|
||||||
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("./ttf/iranyekanweblightfanum.ttf")
|
||||||
|
format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: iranyekan;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
src: url("./eot/iranyekanwebregularfanum.eot");
|
||||||
|
src: url("./eot/iranyekanwebregularfanum.eot?#iefix")
|
||||||
|
format("embedded-opentype"),
|
||||||
|
/* IE6-8 */ url("./woff/iranyekanwebregularfanum.woff") format("woff"),
|
||||||
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
||||||
|
url("./ttf/iranyekanwebregularfanum.ttf") format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: iranyekan;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
src: url("./eot/iranyekanwebmediumfanum.eot");
|
||||||
|
src: url("./eot/iranyekanwebmediumfanum.eot?#iefix")
|
||||||
|
format("embedded-opentype"),
|
||||||
|
/* IE6-8 */ url("./woff/iranyekanwebmediumfanum.woff") format("woff"),
|
||||||
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("./ttf/iranyekanwebmediumfanum.ttf")
|
||||||
|
format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: iranyekan;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 800;
|
||||||
|
src: url("./eot/iranyekanwebextraboldfanum.eot");
|
||||||
|
src: url("./eot/iranyekanwebextraboldfanum.eot?#iefix")
|
||||||
|
format("embedded-opentype"),
|
||||||
|
/* IE6-8 */ url("./woff/iranyekanwebextraboldfanum.woff") format("woff"),
|
||||||
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
||||||
|
url("./ttf/iranyekanwebextraboldfanum.ttf") format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: iranyekan;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 850;
|
||||||
|
src: url("./eot/iranyekanwebblackfanum.eot");
|
||||||
|
src: url("./eot/iranyekanwebblackfanum.eot?#iefix")
|
||||||
|
format("embedded-opentype"),
|
||||||
|
/* IE6-8 */ url("./woff/iranyekanwebblackfanum.woff") format("woff"),
|
||||||
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("./ttf/iranyekanwebblackfanum.ttf")
|
||||||
|
format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: iranyekan;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 900;
|
||||||
|
src: url("./eot/iranyekanwebextrablackfanum.eot");
|
||||||
|
src: url("./eot/iranyekanwebextrablackfanum.eot?#iefix")
|
||||||
|
format("embedded-opentype"),
|
||||||
|
/* IE6-8 */ url("./woff/iranyekanwebextrablackfanum.woff") format("woff"),
|
||||||
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
||||||
|
url("./ttf/iranyekanwebextrablackfanum.ttf") format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "nazanin";
|
||||||
|
src: local("nazanin"), url("./ttf/B-NAZANIN.TTF") format("truetype");
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "titr";
|
||||||
|
src: local("titr"), url("./ttf/Titr.ttf") format("truetype");
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @font-face {
|
||||||
|
font-family: "vazir";
|
||||||
|
src: local("vazir"), url("./ttf/Vazir-Medium.ttf") format("truetype");
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "nima";
|
||||||
|
src: local("vazir"), url("./ttf/Nima.ttf") format("truetype");
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "sharif";
|
||||||
|
src: local("vazir"), url("./ttf/Sharif.ttf") format("truetype");
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "azar";
|
||||||
|
src: local("vazir"), url("./ttf/AzarMehr.ttf") format("truetype");
|
||||||
|
font-weight: bold;
|
||||||
|
} */
|
||||||
1475
src/assets/fonts/svg/iranyekanwebblackfanum.svg
Normal file
|
After Width: | Height: | Size: 199 KiB |
1578
src/assets/fonts/svg/iranyekanwebboldfanum.svg
Normal file
|
After Width: | Height: | Size: 221 KiB |
1489
src/assets/fonts/svg/iranyekanwebextrablackfanum.svg
Normal file
|
After Width: | Height: | Size: 201 KiB |
1478
src/assets/fonts/svg/iranyekanwebextraboldfanum.svg
Normal file
|
After Width: | Height: | Size: 198 KiB |
1628
src/assets/fonts/svg/iranyekanweblightfanum.svg
Normal file
|
After Width: | Height: | Size: 233 KiB |
1584
src/assets/fonts/svg/iranyekanwebmediumfanum.svg
Normal file
|
After Width: | Height: | Size: 222 KiB |
1560
src/assets/fonts/svg/iranyekanwebregularfanum.svg
Normal file
|
After Width: | Height: | Size: 219 KiB |
1651
src/assets/fonts/svg/iranyekanwebthinfanum.svg
Normal file
|
After Width: | Height: | Size: 241 KiB |
BIN
src/assets/fonts/ttf/AzarMehr.ttf
Normal file
BIN
src/assets/fonts/ttf/B-NAZANIN.TTF
Normal file
BIN
src/assets/fonts/ttf/Nima.ttf
Normal file
BIN
src/assets/fonts/ttf/Sharif.ttf
Normal file
BIN
src/assets/fonts/ttf/Titr.ttf
Normal file
BIN
src/assets/fonts/ttf/Vazir-Medium.ttf
Normal file
BIN
src/assets/fonts/ttf/iranyekanwebblackfanum.ttf
Normal file
BIN
src/assets/fonts/ttf/iranyekanwebboldfanum.ttf
Normal file
BIN
src/assets/fonts/ttf/iranyekanwebextrablackfanum.ttf
Normal file
BIN
src/assets/fonts/ttf/iranyekanwebextraboldfanum.ttf
Normal file
BIN
src/assets/fonts/ttf/iranyekanweblightfanum.ttf
Normal file
BIN
src/assets/fonts/ttf/iranyekanwebmediumfanum.ttf
Normal file
BIN
src/assets/fonts/ttf/iranyekanwebregularfanum.ttf
Normal file
BIN
src/assets/fonts/ttf/iranyekanwebthinfanum.ttf
Normal file
BIN
src/assets/fonts/woff/iranyekanwebblackfanum.woff
Normal file
BIN
src/assets/fonts/woff/iranyekanwebboldfanum.woff
Normal file
BIN
src/assets/fonts/woff/iranyekanwebextrablackfanum.woff
Normal file
BIN
src/assets/fonts/woff/iranyekanwebextraboldfanum.woff
Normal file
BIN
src/assets/fonts/woff/iranyekanweblightfanum.woff
Normal file
BIN
src/assets/fonts/woff/iranyekanwebmediumfanum.woff
Normal file
BIN
src/assets/fonts/woff/iranyekanwebregularfanum.woff
Normal file
BIN
src/assets/fonts/woff/iranyekanwebthinfanum.woff
Normal file
BIN
src/assets/images/active-stepper.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
src/assets/images/auth-bg.png
Normal file
|
After Width: | Height: | Size: 898 KiB |
BIN
src/assets/images/auth.jpg
Normal file
|
After Width: | Height: | Size: 346 KiB |
BIN
src/assets/images/banks/ansar.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
src/assets/images/banks/ayandeh.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
src/assets/images/banks/eghtesad-novin.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
src/assets/images/banks/keshavarzi.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
src/assets/images/banks/maskan.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
src/assets/images/banks/mehriran.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
src/assets/images/banks/meli.png
Normal file
|
After Width: | Height: | Size: 153 KiB |
BIN
src/assets/images/banks/mellat.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
src/assets/images/banks/pasargad.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
src/assets/images/banks/saderat.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
src/assets/images/banks/saman.png
Normal file
|
After Width: | Height: | Size: 119 KiB |
BIN
src/assets/images/banks/sina.png
Normal file
|
After Width: | Height: | Size: 59 KiB |