Compare commits
No commits in common. "main" and "feat/calendar" have entirely different histories.
main
...
feat/calen
|
|
@ -160,9 +160,9 @@ This document describes the exact implementation of the login page based on the
|
||||||
onChange={(e) => setRememberMe(e.target.checked)}
|
onChange={(e) => setRememberMe(e.target.checked)}
|
||||||
className="w-4 h-4 text-[#4FD1C7] bg-white border-gray-300 rounded focus:ring-[#4FD1C7] focus:ring-2 accent-[#4FD1C7]"
|
className="w-4 h-4 text-[#4FD1C7] bg-white border-gray-300 rounded focus:ring-[#4FD1C7] focus:ring-2 accent-[#4FD1C7]"
|
||||||
/>
|
/>
|
||||||
// <Label htmlFor="remember" className="text-white text-sm font-persian cursor-pointer">
|
<Label htmlFor="remember" className="text-white text-sm font-persian cursor-pointer">
|
||||||
// همیشه متصل بمانم
|
همیشه متصل بمانم
|
||||||
// </Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Submit Button */}
|
{/* Submit Button */}
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ export function LoginForm({ onSuccess }: LoginFormProps) {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Remember Me Checkbox */}
|
{/* Remember Me Checkbox */}
|
||||||
{/* <div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<CheckboxField
|
<CheckboxField
|
||||||
id="remember"
|
id="remember"
|
||||||
label="همیشه متصل بمان"
|
label="همیشه متصل بمان"
|
||||||
|
|
@ -185,7 +185,7 @@ export function LoginForm({ onSuccess }: LoginFormProps) {
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
size="md"
|
size="md"
|
||||||
/>
|
/>
|
||||||
</div> */}
|
</div>
|
||||||
|
|
||||||
{/* Login Button */}
|
{/* Login Button */}
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -212,9 +212,7 @@ export function LoginForm({ onSuccess }: LoginFormProps) {
|
||||||
{/* Right Side - Branding */}
|
{/* Right Side - Branding */}
|
||||||
<LoginSidebar>
|
<LoginSidebar>
|
||||||
<LoginBranding
|
<LoginBranding
|
||||||
// brandName="پتروشیمی آپادانا"
|
brandName="پتروشیمی بندر امام"
|
||||||
brandName="پتروشیمی نوری"
|
|
||||||
// brandName="پتروشیمی بندر امام"
|
|
||||||
engSub="Inception by Fara"
|
engSub="Inception by Fara"
|
||||||
companyName="توسعهیافته توسط شرکت رهپویان دانش و فناوری فرا"
|
companyName="توسعهیافته توسط شرکت رهپویان دانش و فناوری فرا"
|
||||||
logo={<img src="/brand2.svg"/>}
|
logo={<img src="/brand2.svg"/>}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
|
|
||||||
interface LoginLayoutProps {
|
interface LoginLayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|
@ -107,26 +106,14 @@ export function LoginBranding({
|
||||||
}: LoginBrandingProps) {
|
}: LoginBrandingProps) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{/* Top Logo */}
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<div className="text-slate-800 font-persian">
|
<div className="text-slate-800 font-persian">
|
||||||
<div className="text-lg font-bold leading-tight">
|
<div className="text-lg font-bold leading-tight">
|
||||||
<img
|
<img src="/brand.svg" />
|
||||||
src="/brand.svg?v=1"
|
</div>
|
||||||
alt="Brand Logo"
|
|
||||||
className="w-auto h-8"
|
|
||||||
onError={(e) => {
|
|
||||||
e.target.style.display = 'none';
|
|
||||||
console.log('Image failed to load');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* Bottom Section */}
|
{/* Bottom Section */}
|
||||||
<div className="flex flex-col gap-2 mb-4 items-end justify-end">
|
<div className="flex flex-col gap-2 mb-4 items-end justify-end">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
//این فایل مخصوص
|
|
||||||
//شماتیک نوری
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { formatNumber } from "~/lib/utils";
|
import { formatNumber } from "~/lib/utils";
|
||||||
|
|
||||||
|
|
@ -27,12 +25,17 @@ const InfoBox = ({ company, style }: { company: CompanyInfo; style :any }) => {
|
||||||
<div className={`info-box`} style={style}>
|
<div className={`info-box`} style={style}>
|
||||||
<div className="info-box-content">
|
<div className="info-box-content">
|
||||||
<div className="info-row">
|
<div className="info-row">
|
||||||
<div className="info-label">هزینه عملیاتی:</div>
|
<div className="info-label">درآمد:</div>
|
||||||
|
<div className="info-value revenue text-[12px]">{formatNumber(company?.revenue || 0)}</div>
|
||||||
|
<div className="info-unit">میلیون ریال</div>
|
||||||
|
</div>
|
||||||
|
<div className="info-row">
|
||||||
|
<div className="info-label">هزینه:</div>
|
||||||
<div className="info-value cost text-[12px]">{formatNumber(company?.cost || 0)}</div>
|
<div className="info-value cost text-[12px]">{formatNumber(company?.cost || 0)}</div>
|
||||||
<div className="info-unit">میلیون ریال</div>
|
<div className="info-unit">میلیون ریال</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="info-row">
|
<div className="info-row">
|
||||||
<div className="info-label">افزایش ظرفیت:</div>
|
<div className="info-label">ظرفیت:</div>
|
||||||
<div className="info-value capacity text-[12px]">{formatNumber(company?.capacity || 0)}</div>
|
<div className="info-value capacity text-[12px]">{formatNumber(company?.capacity || 0)}</div>
|
||||||
<div className="info-unit">تن در سال</div>
|
<div className="info-unit">تن در سال</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -42,34 +45,18 @@ const InfoBox = ({ company, style }: { company: CompanyInfo; style :any }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function D3ImageInfo({ companies }: D3ImageInfoProps) {
|
export function D3ImageInfo({ companies }: D3ImageInfoProps) {
|
||||||
const sample = [
|
// Ensure we have exactly 6 companies
|
||||||
{ id: "PX", name: "PX", imageUrl: "/abniro.png" },
|
const displayCompanies = companies;
|
||||||
{ id: "LLTE&HLTE", name: "LLTE&HLTE", imageUrl: "/besparan.png" },
|
|
||||||
{ id: "BTX", name: "BTX", imageUrl: "/khwarazmi.png" },
|
|
||||||
{ id: "Utility", name: "Utility", imageUrl: "/faravash1.png" },
|
|
||||||
{ id: "Reforming", name: "Reforming", imageUrl: "/faravash2.png" },
|
|
||||||
{ id: "Storage Tank", name: "Storage Tank", imageUrl: "/kimia.png" }
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
const merged = sample.map(company => {
|
|
||||||
const found = companies.find(item => item.id == company.id);
|
|
||||||
return found
|
|
||||||
? found
|
|
||||||
: { ...company, cost: 0, capacity: 0, revenue: 0 };
|
|
||||||
});
|
|
||||||
|
|
||||||
const displayCompanies = merged;
|
|
||||||
|
|
||||||
// Positions inside a 5x4 grid (col, row)
|
// Positions inside a 5x4 grid (col, row)
|
||||||
// Layout keeps same visual logic: left/middle/right on two bands with spacing grid around
|
// Layout keeps same visual logic: left/middle/right on two bands with spacing grid around
|
||||||
const gridPositions = [
|
const gridPositions = [
|
||||||
{ col: 2, row: 2 , colI : 1 , rowI : 2 , name : "LLTE&HLTE"}, // left - top band
|
{ col: 2, row: 2 , colI : 1 , rowI : 2 , name : "بسپاران"}, // left - top band
|
||||||
{ col: 3, row: 2 , colI : 3 , rowI : 1 , name : "BTX"}, // middle top (image sits in row 2, info box goes to row 1)
|
{ col: 3, row: 2 , colI : 3 , rowI : 1 , name : "خوارزمی"}, // middle top (image sits in row 2, info box goes to row 1)
|
||||||
{ col: 4, row: 2 ,colI : 5 , rowI : 2 , name : "Utility"}, // right - top band
|
{ col: 4, row: 2 ,colI : 5 , rowI : 2 , name : "فراورش 1"}, // right - top band
|
||||||
{ col: 2, row: 3 , colI : 1 , rowI : 3 , name : "Storage Tank"}, // left - bottom band
|
{ col: 2, row: 3 , colI : 1 , rowI : 3 , name : "کیمیا"}, // left - bottom band
|
||||||
{ col: 3, row: 3 , colI : 3, rowI : 4 , name : "PX"}, // middle bottom (image sits in row 3, info box goes to row 4)
|
{ col: 3, row: 3 , colI : 3, rowI : 4 , name : "آب نیرو"}, // middle bottom (image sits in row 3, info box goes to row 4)
|
||||||
{ col: 4, row: 3 , colI : 5 , rowI : 3 , name : "Reforming"}, // right - bottom band
|
{ col: 4, row: 3 , colI : 5 , rowI : 3 , name : "فراورش 2"}, // right - bottom band
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -137,10 +124,10 @@ export function D3ImageInfo({ companies }: D3ImageInfoProps) {
|
||||||
align-self : center;
|
align-self : center;
|
||||||
justify-self : center;
|
justify-self : center;
|
||||||
padding : .2rem 1.2rem;
|
padding : .2rem 1.2rem;
|
||||||
min-width : 8rem;
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.info-box-content {
|
.info-box-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -154,12 +141,11 @@ export function D3ImageInfo({ companies }: D3ImageInfoProps) {
|
||||||
gap : .5rem;
|
gap : .5rem;
|
||||||
justify-content : space-between;
|
justify-content : space-between;
|
||||||
direction: rtl;
|
direction: rtl;
|
||||||
}
|
|
||||||
|
|
||||||
.info-row:has(.info-value.cost) {
|
&:has(.info-value.revenue) {border-bottom: 1px solid #3AEA83;}
|
||||||
border-bottom: 1px solid #F76276;
|
&:has(.info-value.cost) {border-bottom: 1px solid #F76276;}
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
.info-label {
|
.info-label {
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
|
|
@ -177,6 +163,7 @@ export function D3ImageInfo({ companies }: D3ImageInfoProps) {
|
||||||
margin-bottom : .5rem;
|
margin-bottom : .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.info-value.revenue { color: #fff;}
|
||||||
.info-value.cost { color: #fff; }
|
.info-value.cost { color: #fff; }
|
||||||
.info-value.capacity { color: #fff; }
|
.info-value.capacity { color: #fff; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,213 +0,0 @@
|
||||||
//این فایل مخصوص
|
|
||||||
//شماتیک بندر امام
|
|
||||||
import React from "react";
|
|
||||||
import { formatNumber } from "~/lib/utils";
|
|
||||||
|
|
||||||
export type CompanyInfo = {
|
|
||||||
id: string;
|
|
||||||
imageUrl: string;
|
|
||||||
name: string;
|
|
||||||
costReduction: number;
|
|
||||||
revenue?: number;
|
|
||||||
capacity?: number;
|
|
||||||
costI : number,
|
|
||||||
capacityI : number,
|
|
||||||
revenueI : number,
|
|
||||||
cost : number | string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type D3ImageInfoProps = {
|
|
||||||
companies: CompanyInfo[];
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const InfoBox = ({ company, style }: { company: CompanyInfo; style :any }) => {
|
|
||||||
const hideCapacity = company.name === "خوارزمی"; // اگر خوارزمی بود ظرفیت مخفی شود
|
|
||||||
return (
|
|
||||||
<div className={`info-box`} style={style}>
|
|
||||||
<div className="info-box-content">
|
|
||||||
<div className="info-row">
|
|
||||||
<div className="info-label">درآمد:</div>
|
|
||||||
<div className="info-value revenue text-[12px]">{formatNumber(company?.revenue || 0)}</div>
|
|
||||||
<div className="info-unit">میلیون ریال</div>
|
|
||||||
</div>
|
|
||||||
<div className="info-row">
|
|
||||||
<div className="info-label">هزینه:</div>
|
|
||||||
{
|
|
||||||
(hideCapacity ?
|
|
||||||
|
|
||||||
<div className="info-value cost2 text-[12px]">{formatNumber(company?.cost || 0)}</div>
|
|
||||||
:
|
|
||||||
<div className="info-value cost text-[12px]">{formatNumber(company?.cost || 0)}</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
<div className="info-unit">میلیون ریال</div>
|
|
||||||
</div>
|
|
||||||
{!hideCapacity && (
|
|
||||||
<div className="info-row">
|
|
||||||
<div className="info-label">ظرفیت:</div>
|
|
||||||
<div className="info-value capacity text-[12px]">{formatNumber(company?.capacity || 0)}</div>
|
|
||||||
<div className="info-unit">تن در سال</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function D3ImageInfo({ companies }: D3ImageInfoProps) {
|
|
||||||
// Ensure we have exactly 6 companies
|
|
||||||
|
|
||||||
const sample = [
|
|
||||||
{ id: "آب نیرو", name: "آب نیرو", imageUrl: "/abniro.png" },
|
|
||||||
{ id: "بسپاران", name: "بسپاران", imageUrl: "/besparan.png" },
|
|
||||||
{ id: "خوارزمی", name: "خوارزمی", imageUrl: "/khwarazmi.png" },
|
|
||||||
{ id: "فراورش 1", name: "فراورش 1", imageUrl: "/faravash1.png" },
|
|
||||||
{ id: "فراورش 2", name: "فراورش 2", imageUrl: "/faravash2.png" },
|
|
||||||
{ id: "کیمیا", name: "کیمیا", imageUrl: "/kimia.png" }
|
|
||||||
];
|
|
||||||
const merged = sample.map(company => {
|
|
||||||
const found = companies.find(item => item.id == company.id);
|
|
||||||
return found
|
|
||||||
? found
|
|
||||||
: { ...company, cost: 0, capacity: 0, revenue: 0 };
|
|
||||||
});
|
|
||||||
|
|
||||||
const displayCompanies = merged;
|
|
||||||
console.log(displayCompanies)
|
|
||||||
|
|
||||||
// Positions inside a 5x4 grid (col, row)
|
|
||||||
// Layout keeps same visual logic: left/middle/right on two bands with spacing grid around
|
|
||||||
const gridPositions = [
|
|
||||||
{ col: 2, row: 2 , colI : 1 , rowI : 2 , name : "بسپاران"}, // left - top band
|
|
||||||
{ col: 3, row: 2 , colI : 3 , rowI : 1 , name : "خوارزمی"}, // middle top (image sits in row 2, info box goes to row 1)
|
|
||||||
{ col: 4, row: 2 ,colI : 5 , rowI : 2 , name : "فراورش 1"}, // right - top band
|
|
||||||
{ col: 2, row: 3 , colI : 1 , rowI : 3 , name : "کیمیا"}, // left - bottom band
|
|
||||||
{ col: 3, row: 3 , colI : 3, rowI : 4 , name : "آب نیرو"}, // middle bottom (image sits in row 3, info box goes to row 4)
|
|
||||||
{ col: 4, row: 3 , colI : 5 , rowI : 3 , name : "فراورش 2"}, // right - bottom band
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full h-[500px] rounded-xl">
|
|
||||||
<div dir="ltr" className="company-grid-container">
|
|
||||||
{displayCompanies.map((company, index) => {
|
|
||||||
const gp = gridPositions.find(v => v.name === company.name) ;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
key={company.id}
|
|
||||||
className={`company-item`}
|
|
||||||
style={{ gridColumn: gp.col, gridRow: gp.row }}
|
|
||||||
>
|
|
||||||
<div className="company-image-containe">
|
|
||||||
<img
|
|
||||||
src={company.imageUrl}
|
|
||||||
alt={company.name}
|
|
||||||
className="company-image"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{company.name}
|
|
||||||
</div>
|
|
||||||
<InfoBox company={company} key={index +10} style={{ gridColumn: gp?.colI , gridRow: gp?.rowI }} />
|
|
||||||
</>);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style jsx>{`
|
|
||||||
.company-grid-container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(5, 1fr);
|
|
||||||
grid-template-rows: repeat(4, 1fr);
|
|
||||||
gap: 5px;
|
|
||||||
width: 100%;
|
|
||||||
height: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.company-item {
|
|
||||||
border-radius: 8px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.company-image-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.company-image {
|
|
||||||
object-fit: contain;
|
|
||||||
height : 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-box {
|
|
||||||
border: 1px solid #3F415A;
|
|
||||||
border-radius: 10px;
|
|
||||||
height: max-content;
|
|
||||||
align-self : center;
|
|
||||||
justify-self : center;
|
|
||||||
padding : .2rem 1.2rem;
|
|
||||||
min-width : 8rem;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.info-box-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-row {
|
|
||||||
position : relative;
|
|
||||||
margin: .1rem 0;
|
|
||||||
display: flex;
|
|
||||||
gap : .5rem;
|
|
||||||
justify-content : space-between;
|
|
||||||
direction: rtl;
|
|
||||||
|
|
||||||
&:has(.info-value.revenue) {border-bottom: 1px solid #3AEA83;}
|
|
||||||
&:has(.info-value.cost) {border-bottom: 1px solid #F76276;}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-label {
|
|
||||||
color: #FFFFFF;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 300;
|
|
||||||
text-align: right;
|
|
||||||
margin : auto 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-value {
|
|
||||||
color: #34D399;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
text-align: right;
|
|
||||||
margin-bottom : .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-value.revenue { color: #fff;}
|
|
||||||
.info-value.cost { color: #fff; }
|
|
||||||
.info-value.cost2 { color: #fff; }
|
|
||||||
.info-value.capacity { color: #fff; }
|
|
||||||
|
|
||||||
.info-unit {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: 2px;
|
|
||||||
color: #ACACAC;
|
|
||||||
font-size: 6px;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,211 +0,0 @@
|
||||||
//این فایل مخصوص
|
|
||||||
//شماتیک آپادانا
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import { formatNumber } from "~/lib/utils";
|
|
||||||
|
|
||||||
export type CompanyInfo = {
|
|
||||||
id: string;
|
|
||||||
imageUrl: string;
|
|
||||||
name: string;
|
|
||||||
costReduction: number;
|
|
||||||
revenue?: number;
|
|
||||||
capacity?: number;
|
|
||||||
costI: number;
|
|
||||||
capacityI: number;
|
|
||||||
revenueI: number;
|
|
||||||
cost: number | string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type D3ImageInfoProps = {
|
|
||||||
companies: CompanyInfo[];
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const InfoBox = ({ company, style }: { company: CompanyInfo; style: any }) => {
|
|
||||||
// const hideCapacity = company.name === "واحد 300"; // اگر واحد 300 بود ظرفیت مخفی شود
|
|
||||||
const hideCapacity = false;
|
|
||||||
return (
|
|
||||||
<div className={`info-box`} style={style}>
|
|
||||||
<div className="info-box-content">
|
|
||||||
<div className="info-row">
|
|
||||||
<div className="info-label">درآمد:</div>
|
|
||||||
<div className="info-value revenue text-[12px]">{formatNumber(company?.revenue || 0)}</div>
|
|
||||||
<div className="info-unit">میلیون ریال</div>
|
|
||||||
</div>
|
|
||||||
<div className="info-row">
|
|
||||||
<div className="info-label">هزینه:</div>
|
|
||||||
{hideCapacity ? (
|
|
||||||
<div className="info-value cost2 text-[12px]">{formatNumber(company?.cost || 0)}</div>
|
|
||||||
) : (
|
|
||||||
<div className="info-value cost text-[12px]">{formatNumber(company?.cost || 0)}</div>
|
|
||||||
)}
|
|
||||||
<div className="info-unit">میلیون ریال</div>
|
|
||||||
</div>
|
|
||||||
{!hideCapacity && (
|
|
||||||
<div className="info-row">
|
|
||||||
<div className="info-label">ظرفیت:</div>
|
|
||||||
<div className="info-value capacity text-[12px]">{formatNumber(company?.capacity || 0)}</div>
|
|
||||||
<div className="info-unit">تن در سال</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function D3ImageInfo({ companies }: D3ImageInfoProps) {
|
|
||||||
// واحدهای جدید - 4 واحد
|
|
||||||
const sample = [
|
|
||||||
{ id: "واحد 100", name: "واحد 100", imageUrl: "/abniro.png" },
|
|
||||||
{ id: "واحد 200", name: "واحد 200", imageUrl: "/besparan.png" },
|
|
||||||
{ id: "واحد 300", name: "واحد 300", imageUrl: "/khwarazmi.png" },
|
|
||||||
{ id: "واحد 400", name: "واحد 400", imageUrl: "/faravash1.png" }
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const merged = sample.map(company => {
|
|
||||||
const found = companies.find(item => item.id === company.id);
|
|
||||||
return found
|
|
||||||
? found
|
|
||||||
: { ...company, cost: 0, capacity: 0, revenue: 0, costReduction: 0, costI: 0, capacityI: 0, revenueI: 0 };
|
|
||||||
});
|
|
||||||
|
|
||||||
const displayCompanies = merged;
|
|
||||||
console.log(displayCompanies);
|
|
||||||
|
|
||||||
// موقعیتهای جدید برای چیدمان لوزی شکل (3 ردیف - 1-2-1)
|
|
||||||
// گرید 5x4 نگه داشته شده اما موقعیتها تغییر کرده
|
|
||||||
const gridPositions = [
|
|
||||||
{ col: 2, row: 1, colI: 1, rowI: 1, name: "واحد 100" }, // ردیف اول - ستون اول
|
|
||||||
{ col: 4, row: 1, colI: 5, rowI: 1, name: "واحد 200" }, // ردیف اول - ستون دوم
|
|
||||||
{ col: 2, row: 3, colI: 1, rowI: 3, name: "واحد 300" }, // ردیف دوم - ستون اول
|
|
||||||
{ col: 4, row: 3, colI: 5, rowI: 3, name: "واحد 400" }, // ردیف دوم - ستون دوم
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full h-[500px] rounded-xl">
|
|
||||||
<div dir="ltr" className="company-grid-container">
|
|
||||||
{displayCompanies.map((company, index) => {
|
|
||||||
const gp = gridPositions.find(v => v.name === company.name);
|
|
||||||
return (
|
|
||||||
<React.Fragment key={company.id}>
|
|
||||||
<div
|
|
||||||
className={`company-item`}
|
|
||||||
style={{ gridColumn: gp?.col, gridRow: gp?.row }}
|
|
||||||
>
|
|
||||||
<div className="company-image-container">
|
|
||||||
<img
|
|
||||||
src={company.imageUrl}
|
|
||||||
alt={company.name}
|
|
||||||
className="company-image"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{company.name}
|
|
||||||
</div>
|
|
||||||
<InfoBox company={company} style={{ gridColumn: gp?.colI, gridRow: gp?.rowI }} />
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style jsx>{`
|
|
||||||
.company-grid-container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(5, 1fr);
|
|
||||||
grid-template-rows: repeat(4, 1fr);
|
|
||||||
gap: 5px;
|
|
||||||
width: 100%;
|
|
||||||
height: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.company-item {
|
|
||||||
border-radius: 8px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.company-image-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.company-image {
|
|
||||||
object-fit: contain;
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-box {
|
|
||||||
border: 1px solid #3F415A;
|
|
||||||
border-radius: 10px;
|
|
||||||
height: max-content;
|
|
||||||
align-self: center;
|
|
||||||
justify-self: center;
|
|
||||||
padding: .2rem 1.2rem;
|
|
||||||
min-width: 8rem;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-box-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-row {
|
|
||||||
position: relative;
|
|
||||||
margin: .1rem 0;
|
|
||||||
display: flex;
|
|
||||||
gap: .5rem;
|
|
||||||
justify-content: space-between;
|
|
||||||
direction: rtl;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-row:has(.info-value.revenue) {
|
|
||||||
border-bottom: 1px solid #3AEA83;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-row:has(.info-value.cost) {
|
|
||||||
border-bottom: 1px solid #F76276;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-label {
|
|
||||||
color: #FFFFFF;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 300;
|
|
||||||
text-align: right;
|
|
||||||
margin: auto 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-value {
|
|
||||||
color: #34D399;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
text-align: right;
|
|
||||||
margin-bottom: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-value.revenue { color: #fff; }
|
|
||||||
.info-value.cost { color: #fff; }
|
|
||||||
.info-value.cost2 { color: #fff; }
|
|
||||||
.info-value.capacity { color: #fff; }
|
|
||||||
|
|
||||||
.info-unit {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: 2px;
|
|
||||||
color: #ACACAC;
|
|
||||||
font-size: 6px;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +1,5 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { formatNumber } from "~/lib/utils";
|
import { formatNumber } from "~/lib/utils";
|
||||||
import {
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from "~/components/ui/tooltip"
|
|
||||||
|
|
||||||
interface DataItem {
|
interface DataItem {
|
||||||
label: string;
|
label: string;
|
||||||
|
|
@ -59,27 +54,12 @@ export function DashboardCustomBarChart({
|
||||||
<div className="flex-row-reverse items-center gap-2 flex min-h-6 h-10 rounded-lg overflow-hidden">
|
<div className="flex-row-reverse items-center gap-2 flex min-h-6 h-10 rounded-lg overflow-hidden">
|
||||||
{/* Animated bar */}
|
{/* Animated bar */}
|
||||||
<div
|
<div
|
||||||
className={`h-auto gap-2 overflow-hidden ${item.color} rounded-lg transition-all duration-1000 ease-out flex items-center justify-end px-2`}
|
className={`h-auto gap-2 ${item.color} rounded-lg transition-all duration-1000 ease-out flex items-center justify-end px-2`}
|
||||||
style={{ width: `${widthPercentage}%` }}
|
style={{ width: `${widthPercentage}%` }}
|
||||||
>
|
>
|
||||||
{ widthPercentage > 20 ? (
|
<span className="text-[#3F415A] text-left font-persian font-medium text-sm py-1 w-max">
|
||||||
<span className="text-[#3F415A] min-w-max text-left font-persian font-medium text-sm py-1 w-max">
|
|
||||||
{item.label}
|
{item.label}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger className={`${item.color}`} asChild>
|
|
||||||
<span className="text-[#3F415A] text-left font-persian font-medium text-sm py-1">
|
|
||||||
<span className="invisible">""</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent className={`${item.color} ${item.color.replace("bg","fill")}`}>
|
|
||||||
<p className="font-persian text-sm">{item.label}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
|
|
||||||
</Tooltip>
|
|
||||||
) }
|
|
||||||
</div>
|
</div>
|
||||||
<span className="text-white font-bold text-base">
|
<span className="text-white font-bold text-base">
|
||||||
{formatNumber(item.value)}
|
{formatNumber(item.value)}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import jalaali from "jalaali-js";
|
||||||
import { Book, CheckCircle } from "lucide-react";
|
import { Book, CheckCircle } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
|
@ -15,7 +16,6 @@ import { ChartContainer } from "~/components/ui/chart";
|
||||||
import { MetricCard } from "~/components/ui/metric-card";
|
import { MetricCard } from "~/components/ui/metric-card";
|
||||||
import { Progress } from "~/components/ui/progress";
|
import { Progress } from "~/components/ui/progress";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
|
||||||
import { useStoredDate } from "~/hooks/useStoredDate";
|
|
||||||
import apiService from "~/lib/api";
|
import apiService from "~/lib/api";
|
||||||
import { EventBus, formatNumber } from "~/lib/utils";
|
import { EventBus, formatNumber } from "~/lib/utils";
|
||||||
import type { CalendarDate } from "~/types/util.type";
|
import type { CalendarDate } from "~/types/util.type";
|
||||||
|
|
@ -25,6 +25,7 @@ import { InteractiveBarChart } from "./interactive-bar-chart";
|
||||||
import { DashboardLayout } from "./layout";
|
import { DashboardLayout } from "./layout";
|
||||||
|
|
||||||
export function DashboardHome() {
|
export function DashboardHome() {
|
||||||
|
const { jy } = jalaali.toJalaali(new Date());
|
||||||
const [dashboardData, setDashboardData] = useState<any | null>(null);
|
const [dashboardData, setDashboardData] = useState<any | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
@ -41,22 +42,19 @@ export function DashboardHome() {
|
||||||
}[]
|
}[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
const [date, setDate] = useStoredDate();
|
const [date, setDate] = useState<CalendarDate>({
|
||||||
|
start: `${jy}/01/01`,
|
||||||
|
end: `${jy}/12/30`,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (date: CalendarDate) => {
|
EventBus.on("dateSelected", (date: CalendarDate) => {
|
||||||
if (date) setDate(date);
|
if (date) setDate(date);
|
||||||
};
|
});
|
||||||
|
|
||||||
EventBus.on("dateSelected", handler);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
EventBus.off("dateSelected", handler);
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (date?.end && date?.start) fetchDashboardData();
|
fetchDashboardData();
|
||||||
}, [date]);
|
}, [date]);
|
||||||
|
|
||||||
const fetchDashboardData = async () => {
|
const fetchDashboardData = async () => {
|
||||||
|
|
@ -64,6 +62,12 @@ export function DashboardHome() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
|
// First authenticate if needed
|
||||||
|
const token = localStorage.getItem("auth_token");
|
||||||
|
if (!token) {
|
||||||
|
await apiService.login("inogen_admin", "123456");
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch top cards data
|
// Fetch top cards data
|
||||||
const topCardsResponse = await apiService.call({
|
const topCardsResponse = await apiService.call({
|
||||||
main_page_first_function: {
|
main_page_first_function: {
|
||||||
|
|
@ -614,7 +618,7 @@ export function DashboardHome() {
|
||||||
|
|
||||||
{/* Main Content with Tabs */}
|
{/* Main Content with Tabs */}
|
||||||
<Tabs
|
<Tabs
|
||||||
defaultValue="canvas"
|
defaultValue="charts"
|
||||||
className="grid overflow-hidden rounded-lg grid-rows-[max-content] items-center col-span-2 row-start-2 bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)]"
|
className="grid overflow-hidden rounded-lg grid-rows-[max-content] items-center col-span-2 row-start-2 bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)]"
|
||||||
>
|
>
|
||||||
<div className="flex items-center border-b border-gray-600 justify-between gap-2">
|
<div className="flex items-center border-b border-gray-600 justify-between gap-2">
|
||||||
|
|
@ -641,41 +645,16 @@ export function DashboardHome() {
|
||||||
<TabsContent value="canvas" className="w-ful h-full">
|
<TabsContent value="canvas" className="w-ful h-full">
|
||||||
<div className="p-4 h-full w-full">
|
<div className="p-4 h-full w-full">
|
||||||
<D3ImageInfo
|
<D3ImageInfo
|
||||||
|
|
||||||
//پتروشیمی بندر امام
|
|
||||||
// companies={companyChartData.map((item) => {
|
|
||||||
// const imageMap: Record<string, string> = {
|
|
||||||
// بسپاران: "/besparan.png",
|
|
||||||
// خوارزمی: "/khwarazmi.png",
|
|
||||||
// "فراورش 1": "/faravash1.png",
|
|
||||||
// "فراورش 2": "/faravash2.png",
|
|
||||||
// کیمیا: "/kimia.png",
|
|
||||||
// "آب نیرو": "/abniro.png",
|
|
||||||
// };
|
|
||||||
|
|
||||||
|
|
||||||
//پتروشیمی آپادانا
|
|
||||||
// companies={companyChartData.map((item) => {
|
|
||||||
// const imageMap: Record<string, string> = {
|
|
||||||
// "واحد 100": "/abniro.png" ,
|
|
||||||
// "واحد 200": "/besparan.png" ,
|
|
||||||
// "واحد 300": "/khwarazmi.png" ,
|
|
||||||
// "واحد 400": "/faravash1.png"
|
|
||||||
// };
|
|
||||||
|
|
||||||
//پتروشیمی نوری
|
|
||||||
companies={companyChartData.map((item) => {
|
companies={companyChartData.map((item) => {
|
||||||
const imageMap: Record<string, string> = {
|
const imageMap: Record<string, string> = {
|
||||||
"LLTE&HLTE": "/besparan.png",
|
بسپاران: "/besparan.png",
|
||||||
BTX: "/khwarazmi.png",
|
خوارزمی: "/khwarazmi.png",
|
||||||
Utility: "/faravash1.png",
|
"فراورش 1": "/faravash1.png",
|
||||||
Reforming: "/faravash2.png",
|
"فراورش 2": "/faravash2.png",
|
||||||
"Storage Tank": "/kimia.png",
|
کیمیا: "/kimia.png",
|
||||||
PX: "/abniro.png",
|
"آب نیرو": "/abniro.png",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: item.category,
|
id: item.category,
|
||||||
name: item.category,
|
name: item.category,
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,20 @@
|
||||||
import { saveAs } from "file-saver";
|
|
||||||
import jalaali from "jalaali-js";
|
import jalaali from "jalaali-js";
|
||||||
import {
|
import {
|
||||||
Calendar,
|
Calendar,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
FileChartColumnIncreasing,
|
|
||||||
Menu,
|
Menu,
|
||||||
PanelLeft,
|
PanelLeft,
|
||||||
Server,
|
Server,
|
||||||
|
Settings,
|
||||||
User,
|
User,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { useLocation } from "react-router";
|
import { Link } from "react-router";
|
||||||
import XLSX from "xlsx-js-style";
|
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Calendar as CustomCalendar } from "~/components/ui/Calendar";
|
import { Calendar as CustomCalendar } from "~/components/ui/Calendar";
|
||||||
import { useAuth } from "~/contexts/auth-context";
|
import { useAuth } from "~/contexts/auth-context";
|
||||||
import apiService from "~/lib/api";
|
import apiService from "~/lib/api";
|
||||||
import { cn, EventBus, handleDataValue } from "~/lib/utils";
|
import { cn, EventBus } from "~/lib/utils";
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
onToggleSidebar?: () => void;
|
onToggleSidebar?: () => void;
|
||||||
|
|
@ -67,116 +65,7 @@ const monthList: Array<MonthItem> = [
|
||||||
id: "month-4",
|
id: "month-4",
|
||||||
label: "زمستان",
|
label: "زمستان",
|
||||||
start: "10/01",
|
start: "10/01",
|
||||||
end: "12/30",
|
end: "12/29",
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const columns: Array<any> = [
|
|
||||||
{ key: "title", label: "عنوان پروژه", sortable: true, width: "300px" },
|
|
||||||
{
|
|
||||||
key: "importance_project",
|
|
||||||
label: "میزان اهمیت",
|
|
||||||
sortable: true,
|
|
||||||
width: "160px",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "strategic_theme",
|
|
||||||
label: "مضمون راهبردی",
|
|
||||||
sortable: true,
|
|
||||||
width: "200px",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "value_technology_and_innovation",
|
|
||||||
label: "ارزش فناوری و نوآوری",
|
|
||||||
sortable: true,
|
|
||||||
width: "220px",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "type_of_innovation",
|
|
||||||
label: "انواع نوآوری",
|
|
||||||
sortable: true,
|
|
||||||
width: "160px",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "innovation",
|
|
||||||
label: "میزان نوآوری",
|
|
||||||
sortable: true,
|
|
||||||
width: "140px",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "person_executing",
|
|
||||||
label: "مسئول اجرا",
|
|
||||||
sortable: true,
|
|
||||||
width: "180px",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "excellent_observer",
|
|
||||||
label: "ناطر عالی",
|
|
||||||
sortable: true,
|
|
||||||
width: "180px",
|
|
||||||
},
|
|
||||||
{ key: "observer", label: "ناظر پروژه", sortable: true, width: "180px" },
|
|
||||||
{ key: "moderator", label: "مجری", sortable: true, width: "180px" },
|
|
||||||
{
|
|
||||||
key: "executive_phase",
|
|
||||||
label: "فاز اجرایی",
|
|
||||||
sortable: true,
|
|
||||||
width: "160px",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "start_date",
|
|
||||||
label: "تاریخ شروع",
|
|
||||||
sortable: true,
|
|
||||||
width: "120px",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "remaining_time",
|
|
||||||
label: "زمان باقی مانده",
|
|
||||||
sortable: true,
|
|
||||||
width: "140px",
|
|
||||||
computed: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "end_date",
|
|
||||||
label: "تاریخ پایان (برنامهریزی)",
|
|
||||||
sortable: true,
|
|
||||||
width: "160px",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "renewed_duration",
|
|
||||||
label: "مدت زمان تمدید",
|
|
||||||
sortable: true,
|
|
||||||
width: "140px",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "done_date",
|
|
||||||
label: "تاریخ پایان (واقعی)",
|
|
||||||
sortable: true,
|
|
||||||
width: "160px",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "deviation_from_program",
|
|
||||||
label: "متوسط انحراف برنامهای",
|
|
||||||
sortable: true,
|
|
||||||
width: "160px",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "approved_budget",
|
|
||||||
label: "بودجه مصوب",
|
|
||||||
sortable: true,
|
|
||||||
width: "150px",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "budget_spent",
|
|
||||||
label: "بودجه صرف شده",
|
|
||||||
sortable: true,
|
|
||||||
width: "150px",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "cost_deviation",
|
|
||||||
label: "متوسط انحراف هزینهای",
|
|
||||||
sortable: true,
|
|
||||||
width: "160px",
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -193,54 +82,22 @@ export function Header({
|
||||||
const [isProfileMenuOpen, setIsProfileMenuOpen] = useState<boolean>(false);
|
const [isProfileMenuOpen, setIsProfileMenuOpen] = useState<boolean>(false);
|
||||||
const [isNotificationOpen, setIsNotificationOpen] = useState<boolean>(false);
|
const [isNotificationOpen, setIsNotificationOpen] = useState<boolean>(false);
|
||||||
const [openCalendar, setOpenCalendar] = useState<boolean>(false);
|
const [openCalendar, setOpenCalendar] = useState<boolean>(false);
|
||||||
const [excelLoading, setExcelLoading] = useState<boolean>(false);
|
|
||||||
const location = useLocation();
|
|
||||||
const projectManagerRoute = "/dashboard/project-management";
|
|
||||||
const [currentYear, setCurrentYear] = useState<SelectedDate>({
|
const [currentYear, setCurrentYear] = useState<SelectedDate>({
|
||||||
since: jy,
|
since: jy,
|
||||||
until: jy,
|
until: jy,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [selectedDate, setSelectedDate] = useState<CurrentDay>({});
|
const [selectedDate, setSelectedDate] = useState<CurrentDay>({
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const storedDate = localStorage.getItem("dateSelected");
|
|
||||||
if (storedDate) {
|
|
||||||
const parsedDate = JSON.parse(storedDate);
|
|
||||||
setSelectedDate(parsedDate);
|
|
||||||
|
|
||||||
const sinceYear = parsedDate.start
|
|
||||||
? parseInt(parsedDate.start.split("/")[0], 10)
|
|
||||||
: jy;
|
|
||||||
const untilYear = parsedDate.end
|
|
||||||
? parseInt(parsedDate.end.split("/")[0], 10)
|
|
||||||
: jy;
|
|
||||||
|
|
||||||
setCurrentYear({ since: sinceYear, until: untilYear });
|
|
||||||
} else {
|
|
||||||
const defaultDate = {
|
|
||||||
sinceMonth: "بهار",
|
sinceMonth: "بهار",
|
||||||
fromMonth: "زمستان",
|
fromMonth: "زمستان",
|
||||||
start: `${jy}/01/01`,
|
start: `${currentYear.since}/01/01`,
|
||||||
end: `${jy}/12/30`,
|
end: `${currentYear.until}/12/30`,
|
||||||
};
|
});
|
||||||
setSelectedDate(defaultDate);
|
|
||||||
localStorage.setItem("dateSelected", JSON.stringify(defaultDate));
|
|
||||||
setCurrentYear({ since: jy, until: jy });
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const redirectHandler = async () => {
|
const redirectHandler = async () => {
|
||||||
try {
|
try {
|
||||||
const getData = await apiService.post("/GenerateSsoCode");
|
const getData = await apiService.post("/GenerateSsoCode");
|
||||||
|
const url = `https://inogen-bpms.pelekan.org/redirect/${getData.data}`;
|
||||||
//بندر امام
|
|
||||||
// const url = `https://inogen-bpms.pelekan.org/redirect/${getData.data}`;
|
|
||||||
//آپادانا
|
|
||||||
// const url = `https://APADANA-IATM-bpms.pelekan.org/redirect/${getData.data}`;
|
|
||||||
//نوری
|
|
||||||
const url = `https://NOPC-IATM-bpms.pelekan.org/redirect/${getData.data}`;
|
|
||||||
|
|
||||||
window.open(url, "_blank");
|
window.open(url, "_blank");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|
@ -262,7 +119,6 @@ export function Header({
|
||||||
start: `${newSince}/${selectedDate.start?.split("/").slice(1).join("/")}`,
|
start: `${newSince}/${selectedDate.start?.split("/").slice(1).join("/")}`,
|
||||||
};
|
};
|
||||||
setSelectedDate(updatedDate);
|
setSelectedDate(updatedDate);
|
||||||
localStorage.setItem("dateSelected", JSON.stringify(updatedDate));
|
|
||||||
EventBus.emit("dateSelected", updatedDate);
|
EventBus.emit("dateSelected", updatedDate);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -276,7 +132,6 @@ export function Header({
|
||||||
sinceMonth: val.label,
|
sinceMonth: val.label,
|
||||||
};
|
};
|
||||||
setSelectedDate(data);
|
setSelectedDate(data);
|
||||||
localStorage.setItem("dateSelected", JSON.stringify(data));
|
|
||||||
EventBus.emit("dateSelected", data);
|
EventBus.emit("dateSelected", data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -295,7 +150,6 @@ export function Header({
|
||||||
end: `${newUntil}/${selectedDate.end?.split("/").slice(1).join("/")}`,
|
end: `${newUntil}/${selectedDate.end?.split("/").slice(1).join("/")}`,
|
||||||
};
|
};
|
||||||
setSelectedDate(updatedDate);
|
setSelectedDate(updatedDate);
|
||||||
localStorage.setItem("dateSelected", JSON.stringify(updatedDate));
|
|
||||||
EventBus.emit("dateSelected", updatedDate);
|
EventBus.emit("dateSelected", updatedDate);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -309,7 +163,6 @@ export function Header({
|
||||||
fromMonth: val.label,
|
fromMonth: val.label,
|
||||||
};
|
};
|
||||||
setSelectedDate(data);
|
setSelectedDate(data);
|
||||||
localStorage.setItem("dateSelected", JSON.stringify(data));
|
|
||||||
EventBus.emit("dateSelected", data);
|
EventBus.emit("dateSelected", data);
|
||||||
toggleCalendar();
|
toggleCalendar();
|
||||||
};
|
};
|
||||||
|
|
@ -333,66 +186,6 @@ export function Header({
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const exportToExcel = async () => {
|
|
||||||
let arr = [];
|
|
||||||
const data: any = await fetchExcelData();
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
|
||||||
let obj: Record<string, any> = {};
|
|
||||||
const project = data[i];
|
|
||||||
|
|
||||||
Object.entries(project).forEach(([pKey, pValue]: [any, any]) => {
|
|
||||||
Object.values(columns).forEach((col) => {
|
|
||||||
if (pKey === col?.key) {
|
|
||||||
``;
|
|
||||||
obj[col?.label] = handleDataValue(
|
|
||||||
pValue?.includes(",") ? pValue.replaceAll(",", "") : pValue
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
arr.push(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
const worksheet = XLSX.utils.json_to_sheet(arr);
|
|
||||||
|
|
||||||
const workbook = XLSX.utils.book_new();
|
|
||||||
XLSX.utils.book_append_sheet(workbook, worksheet, "People");
|
|
||||||
|
|
||||||
const excelBuffer = XLSX.write(workbook, {
|
|
||||||
bookType: "xlsx",
|
|
||||||
type: "array",
|
|
||||||
});
|
|
||||||
|
|
||||||
const blob = new Blob([excelBuffer], {
|
|
||||||
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
||||||
});
|
|
||||||
saveAs(blob, "reports.xls");
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchExcelData = async () => {
|
|
||||||
setExcelLoading(true);
|
|
||||||
const fetchableColumns = columns.filter((c) => !c.computed);
|
|
||||||
const outputFields = fetchableColumns.map((c) => c.apiField ?? c.key);
|
|
||||||
|
|
||||||
const response = await apiService.select({
|
|
||||||
ProcessName: "project",
|
|
||||||
OutputFields: outputFields,
|
|
||||||
Conditions: [
|
|
||||||
["start_date", ">=", selectedDate?.start || null, "and"],
|
|
||||||
["start_date", "<=", selectedDate?.end || null],
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const parsedData = JSON.parse(response.data);
|
|
||||||
setExcelLoading(false);
|
|
||||||
return parsedData;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDownloadFile = () => {
|
|
||||||
if (excelLoading) return null;
|
|
||||||
else exportToExcel();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -448,16 +241,12 @@ export function Header({
|
||||||
<div className="flex flex-row gap-1.5 w-max">
|
<div className="flex flex-row gap-1.5 w-max">
|
||||||
<span className="text-md">از</span>
|
<span className="text-md">از</span>
|
||||||
<span className="text-md">{selectedDate?.sinceMonth}</span>
|
<span className="text-md">{selectedDate?.sinceMonth}</span>
|
||||||
<span className="text-md">
|
<span className="text-md">{currentYear.since}</span>
|
||||||
{handleDataValue(currentYear.since)}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row gap-1.5 w-max">
|
<div className="flex flex-row gap-1.5 w-max">
|
||||||
<span className="text-md">تا</span>
|
<span className="text-md">تا</span>
|
||||||
<span className="text-md">{selectedDate?.fromMonth}</span>
|
<span className="text-md">{selectedDate?.fromMonth}</span>
|
||||||
<span className="text-md">
|
<span className="text-md">{currentYear.until}</span>
|
||||||
{handleDataValue(currentYear.until)}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -469,9 +258,9 @@ export function Header({
|
||||||
<div className="flex flex-row gap-2.5 absolute top-14 right-[-40px] p-2.5 !pt-3.5 w-80 rounded-3xl overflow-hidden bg-pr-gray border-2 border-[#5F6284]">
|
<div className="flex flex-row gap-2.5 absolute top-14 right-[-40px] p-2.5 !pt-3.5 w-80 rounded-3xl overflow-hidden bg-pr-gray border-2 border-[#5F6284]">
|
||||||
<CustomCalendar
|
<CustomCalendar
|
||||||
title="از"
|
title="از"
|
||||||
nextYearHandler={prevFromYearHandler}
|
nextYearHandler={nextFromYearHandler}
|
||||||
prevYearHandler={nextFromYearHandler}
|
prevYearHandler={prevFromYearHandler}
|
||||||
currentYear={handleDataValue(currentYear?.since)}
|
currentYear={currentYear?.since}
|
||||||
monthList={monthList}
|
monthList={monthList}
|
||||||
selectedDate={selectedDate?.sinceMonth}
|
selectedDate={selectedDate?.sinceMonth}
|
||||||
selectDateHandler={selectFromDateHandler}
|
selectDateHandler={selectFromDateHandler}
|
||||||
|
|
@ -479,9 +268,9 @@ export function Header({
|
||||||
<span className="w-0.5 h-[12.5rem] border border-[#5F6284] block "></span>
|
<span className="w-0.5 h-[12.5rem] border border-[#5F6284] block "></span>
|
||||||
<CustomCalendar
|
<CustomCalendar
|
||||||
title="تا"
|
title="تا"
|
||||||
nextYearHandler={prevUntilYearHandler}
|
nextYearHandler={nextUntilYearHandler}
|
||||||
prevYearHandler={nextUntilYearHandler}
|
prevYearHandler={prevUntilYearHandler}
|
||||||
currentYear={handleDataValue(currentYear?.until)}
|
currentYear={currentYear?.until}
|
||||||
monthList={monthList}
|
monthList={monthList}
|
||||||
selectedDate={selectedDate?.fromMonth}
|
selectedDate={selectedDate?.fromMonth}
|
||||||
selectDateHandler={selectUntilDateHandler}
|
selectDateHandler={selectUntilDateHandler}
|
||||||
|
|
@ -496,20 +285,6 @@ export function Header({
|
||||||
{/* User Menu */}
|
{/* User Menu */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{location.pathname === projectManagerRoute ? (
|
|
||||||
<div className="flex justify-end w-full mb-0 pl-2">
|
|
||||||
<span
|
|
||||||
className={`flex w-full cursor-pointer items-center gap-2 px-3 py-2 text-sm text-gray-300 hover:bg-gradient-to-r hover:from-emerald-500/10 hover:to-teal-500/10 hover:text-emerald-300 font-persian ${excelLoading ? "!cursor-not-allowed !opacity-10" : ""}`}
|
|
||||||
onClick={handleDownloadFile}
|
|
||||||
>
|
|
||||||
<FileChartColumnIncreasing className="h-4 w-4" />
|
|
||||||
دانلود فایل اکسل
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
|
|
||||||
{user?.id === 2041 && (
|
{user?.id === 2041 && (
|
||||||
<button
|
<button
|
||||||
className="flex w-full cursor-pointer items-center gap-2 px-3 py-2 text-sm text-gray-300 hover:bg-gradient-to-r hover:from-emerald-500/10 hover:to-teal-500/10 hover:text-emerald-300 font-persian"
|
className="flex w-full cursor-pointer items-center gap-2 px-3 py-2 text-sm text-gray-300 hover:bg-gradient-to-r hover:from-emerald-500/10 hover:to-teal-500/10 hover:text-emerald-300 font-persian"
|
||||||
|
|
@ -539,7 +314,6 @@ export function Header({
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Profile Dropdown */}
|
{/* Profile Dropdown */}
|
||||||
{isProfileMenuOpen && (
|
{isProfileMenuOpen && (
|
||||||
<div className="absolute left-0 top-full mt-2 w-48 bg-gray-800 border border-emerald-500/30 rounded-lg shadow-lg z-50">
|
<div className="absolute left-0 top-full mt-2 w-48 bg-gray-800 border border-emerald-500/30 rounded-lg shadow-lg z-50">
|
||||||
|
|
@ -551,7 +325,7 @@ export function Header({
|
||||||
{user?.email}
|
{user?.email}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="py-1">
|
<div className="py-1">
|
||||||
<Link
|
<Link
|
||||||
to="/dashboard/profile"
|
to="/dashboard/profile"
|
||||||
className="flex items-center gap-2 px-3 py-2 text-sm text-gray-300 hover:bg-gradient-to-r hover:from-emerald-500/10 hover:to-teal-500/10 hover:text-emerald-300 font-persian"
|
className="flex items-center gap-2 px-3 py-2 text-sm text-gray-300 hover:bg-gradient-to-r hover:from-emerald-500/10 hover:to-teal-500/10 hover:text-emerald-300 font-persian"
|
||||||
|
|
@ -568,7 +342,7 @@ export function Header({
|
||||||
<Settings className="h-4 w-4" />
|
<Settings className="h-4 w-4" />
|
||||||
تنظیمات
|
تنظیمات
|
||||||
</Link>
|
</Link>
|
||||||
</div> */}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -27,22 +27,19 @@ const chartConfig = {
|
||||||
},
|
},
|
||||||
revenue: {
|
revenue: {
|
||||||
label: "افزایش درآمد",
|
label: "افزایش درآمد",
|
||||||
color: "#4ADE80",
|
color: "#4ADE80", // Green-400
|
||||||
},
|
},
|
||||||
cost: {
|
cost: {
|
||||||
// آپادانا بندرامام
|
label: "کاهش هزینه",
|
||||||
//label: "کاهش هزینه",
|
color: "#F87171", // Red-400
|
||||||
label: "هزینه عملیاتی", // نوری
|
|
||||||
color: "#F87171",
|
|
||||||
},
|
},
|
||||||
} satisfies ChartConfig;
|
} satisfies ChartConfig;
|
||||||
|
|
||||||
|
|
||||||
export function InteractiveBarChart({
|
export function InteractiveBarChart({
|
||||||
data,
|
data,
|
||||||
showRevenue = "نوری"!="نوری", // آپادانا بندرامام
|
|
||||||
}: {
|
}: {
|
||||||
data: CompanyChartDatum[];
|
data: CompanyChartDatum[];
|
||||||
showRevenue?: boolean;
|
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Card className="py-0 bg-transparent mt-8 border-none h-full">
|
<Card className="py-0 bg-transparent mt-8 border-none h-full">
|
||||||
|
|
@ -69,67 +66,31 @@ export function InteractiveBarChart({
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
tickMargin={25}
|
tickMargin={25}
|
||||||
style={{ fill: "#ACACAC", fontSize: 11 }}
|
style={{ fill: "#ACACAC", fontSize: 11 }}
|
||||||
tickFormatter={(value) =>
|
tickFormatter={(value) => `${formatNumber(Math.round(value))}%`}
|
||||||
`${formatNumber(Math.round(value))}%`
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<Bar
|
<Bar dataKey="capacity" fill={chartConfig.capacity.color} radius={[8, 8, 0, 0]}>
|
||||||
dataKey="capacity"
|
|
||||||
fill={chartConfig.capacity.color}
|
|
||||||
radius={[8, 8, 0, 0]}
|
|
||||||
>
|
|
||||||
<LabelList
|
<LabelList
|
||||||
dataKey="capacity"
|
dataKey="capacity"
|
||||||
position="top"
|
position="top"
|
||||||
offset={15}
|
offset={15}
|
||||||
style={{
|
style={{ fill: "#ffffff", fontSize: "16px", fontWeight: "bold" }}
|
||||||
fill: "#ffffff",
|
formatter={(v: number) => `${formatNumber(Math.round(v))}%`}
|
||||||
fontSize: "16px",
|
|
||||||
fontWeight: "bold",
|
|
||||||
}}
|
|
||||||
formatter={(v: number) =>
|
|
||||||
`${formatNumber(Math.round(v))}%`
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Bar>
|
</Bar>
|
||||||
|
<Bar dataKey="revenue" fill={chartConfig.revenue.color} radius={[8, 8, 0, 0]}>
|
||||||
{showRevenue && (
|
|
||||||
<Bar
|
|
||||||
dataKey="revenue"
|
|
||||||
fill={chartConfig.revenue.color}
|
|
||||||
radius={[8, 8, 0, 0]}
|
|
||||||
>
|
|
||||||
<LabelList
|
<LabelList
|
||||||
dataKey="revenue"
|
dataKey="revenue"
|
||||||
position="top"
|
position="top"
|
||||||
style={{
|
style={{ fill: "#ffffff", fontSize: "16px", fontWeight: "bold" }}
|
||||||
fill: "#ffffff",
|
formatter={(v: number) => `${formatNumber(Math.round(v))}%`}
|
||||||
fontSize: "16px",
|
|
||||||
fontWeight: "bold",
|
|
||||||
}}
|
|
||||||
formatter={(v: number) =>
|
|
||||||
`${formatNumber(Math.round(v))}%`
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Bar>
|
</Bar>
|
||||||
)}
|
<Bar dataKey="cost" fill={chartConfig.cost.color} radius={[8, 8, 0, 0]}>
|
||||||
|
|
||||||
<Bar
|
|
||||||
dataKey="cost"
|
|
||||||
fill={chartConfig.cost.color}
|
|
||||||
radius={[8, 8, 0, 0]}
|
|
||||||
>
|
|
||||||
<LabelList
|
<LabelList
|
||||||
dataKey="cost"
|
dataKey="cost"
|
||||||
position="top"
|
position="top"
|
||||||
style={{
|
style={{ fill: "#ffffff", fontSize: "16px", fontWeight: "bold" }}
|
||||||
fill: "#ffffff",
|
formatter={(v: number) => `${formatNumber(Math.round(v))}%`}
|
||||||
fontSize: "16px",
|
|
||||||
fontWeight: "bold",
|
|
||||||
}}
|
|
||||||
formatter={(v: number) =>
|
|
||||||
`${formatNumber(Math.round(v))}%`
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Bar>
|
</Bar>
|
||||||
</BarChart>
|
</BarChart>
|
||||||
|
|
@ -142,32 +103,23 @@ export function InteractiveBarChart({
|
||||||
className="w-6 h-2 rounded"
|
className="w-6 h-2 rounded"
|
||||||
style={{ backgroundColor: chartConfig.capacity.color }}
|
style={{ backgroundColor: chartConfig.capacity.color }}
|
||||||
></div>
|
></div>
|
||||||
<span className="text-xs text-white">
|
<span className="text-xs text-white">{chartConfig.capacity.label}</span>
|
||||||
{chartConfig.capacity.label}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div
|
<div
|
||||||
className="w-6 h-2 rounded"
|
className="w-6 h-2 rounded"
|
||||||
style={{ backgroundColor: chartConfig.cost.color }}
|
style={{ backgroundColor: chartConfig.cost.color }}
|
||||||
></div>
|
></div>
|
||||||
<span className="text-xs text-white">
|
<span className="text-xs text-white">{chartConfig.cost.label}</span>
|
||||||
{chartConfig.cost.label}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showRevenue && (
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div
|
<div
|
||||||
className="w-6 h-2 rounded"
|
className="w-6 h-2 rounded"
|
||||||
style={{ backgroundColor: chartConfig.revenue.color }}
|
style={{ backgroundColor: chartConfig.revenue.color }}
|
||||||
></div>
|
></div>
|
||||||
<span className="text-xs text-white">
|
<span className="text-xs text-white">{chartConfig.revenue.label}</span>
|
||||||
{chartConfig.revenue.label}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import jalaali from "jalaali-js";
|
||||||
import {
|
import {
|
||||||
BrainCircuit,
|
BrainCircuit,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
|
|
@ -15,7 +16,6 @@ import moment from "moment-jalaali";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { Badge } from "~/components/ui/badge";
|
import { Badge } from "~/components/ui/badge";
|
||||||
import { BaseCard } from "~/components/ui/base-card";
|
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Card, CardContent } from "~/components/ui/card";
|
import { Card, CardContent } from "~/components/ui/card";
|
||||||
import { Checkbox } from "~/components/ui/checkbox";
|
import { Checkbox } from "~/components/ui/checkbox";
|
||||||
|
|
@ -34,7 +34,6 @@ import {
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "~/components/ui/table";
|
} from "~/components/ui/table";
|
||||||
import { useStoredDate } from "~/hooks/useStoredDate";
|
|
||||||
import apiService from "~/lib/api";
|
import apiService from "~/lib/api";
|
||||||
import { EventBus, formatCurrency, formatNumber } from "~/lib/utils";
|
import { EventBus, formatCurrency, formatNumber } from "~/lib/utils";
|
||||||
import type { CalendarDate } from "~/types/util.type";
|
import type { CalendarDate } from "~/types/util.type";
|
||||||
|
|
@ -149,13 +148,18 @@ const columns = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export function DigitalInnovationPage() {
|
export function DigitalInnovationPage() {
|
||||||
|
const { jy } = jalaali.toJalaali(new Date());
|
||||||
const [projects, setProjects] = useState<DigitalInnovationMetrics[]>([]);
|
const [projects, setProjects] = useState<DigitalInnovationMetrics[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [loadingMore, setLoadingMore] = useState(false);
|
const [loadingMore, setLoadingMore] = useState(false);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [pageSize] = useState(20);
|
const [pageSize] = useState(20);
|
||||||
const [hasMore, setHasMore] = useState(true);
|
const [hasMore, setHasMore] = useState(true);
|
||||||
const [date, setDate] = useStoredDate();
|
// const [totalCount, setTotalCount] = useState(0);
|
||||||
|
const [date, setDate] = useState<CalendarDate>({
|
||||||
|
start: `${jy}/01/01`,
|
||||||
|
end: `${jy}/12/30`,
|
||||||
|
});
|
||||||
const [actualTotalCount, setActualTotalCount] = useState(0);
|
const [actualTotalCount, setActualTotalCount] = useState(0);
|
||||||
const [statsLoading, setStatsLoading] = useState(false);
|
const [statsLoading, setStatsLoading] = useState(false);
|
||||||
const [rating, setRating] = useState<ListItem[]>([]);
|
const [rating, setRating] = useState<ListItem[]>([]);
|
||||||
|
|
@ -360,27 +364,21 @@ export function DigitalInnovationPage() {
|
||||||
}, [hasMore, loading, loadingMore]);
|
}, [hasMore, loading, loadingMore]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (date?.start && date?.end) {
|
|
||||||
fetchTable(true);
|
fetchTable(true);
|
||||||
fetchTotalCount();
|
fetchTotalCount();
|
||||||
fetchStats();
|
fetchStats();
|
||||||
}
|
|
||||||
}, [sortConfig, date]);
|
}, [sortConfig, date]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (date: CalendarDate) => {
|
EventBus.on("dateSelected", (date: CalendarDate) => {
|
||||||
if (date) setDate(date);
|
if (date) {
|
||||||
};
|
setDate(date);
|
||||||
|
}
|
||||||
EventBus.on("dateSelected", handler);
|
});
|
||||||
|
|
||||||
return () => {
|
|
||||||
EventBus.off("dateSelected", handler);
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentPage > 1 && date?.start && date?.end) {
|
if (currentPage > 1) {
|
||||||
fetchTable(false);
|
fetchTable(false);
|
||||||
}
|
}
|
||||||
}, [currentPage]);
|
}, [currentPage]);
|
||||||
|
|
@ -434,7 +432,7 @@ export function DigitalInnovationPage() {
|
||||||
prev.field === field && prev.direction === "asc" ? "desc" : "asc",
|
prev.field === field && prev.direction === "asc" ? "desc" : "asc",
|
||||||
}));
|
}));
|
||||||
fetchTotalCount(date?.start, date?.end);
|
fetchTotalCount(date?.start, date?.end);
|
||||||
fetchStats();
|
fetchStats(date?.start, date?.end);
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
setProjects([]);
|
setProjects([]);
|
||||||
setHasMore(true);
|
setHasMore(true);
|
||||||
|
|
@ -755,12 +753,12 @@ export function DigitalInnovationPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Process Impacts Chart */}
|
{/* Process Impacts Chart */}
|
||||||
<BaseCard className="rounded-xl w-full overflow-hidden">
|
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm rounded-lg w-full overflow-hidden h-[18rem]">
|
||||||
{/* <CardContent > */}
|
{/* <CardContent > */}
|
||||||
<CustomBarChart
|
<CustomBarChart
|
||||||
title="تاثیرات نوآوری دیجیتال به صورت درصد مقایسه ای"
|
title="تاثیرات نوآوری دیجیتال به صورت درصد مقایسه ای"
|
||||||
loading={statsLoading}
|
loading={statsLoading}
|
||||||
// height="100%"
|
height="100%"
|
||||||
data={[
|
data={[
|
||||||
{
|
{
|
||||||
label: DigitalCardLabel.decreasCost,
|
label: DigitalCardLabel.decreasCost,
|
||||||
|
|
@ -790,7 +788,8 @@ export function DigitalInnovationPage() {
|
||||||
barHeight="h-5"
|
barHeight="h-5"
|
||||||
showAxisLabels={true}
|
showAxisLabels={true}
|
||||||
/>
|
/>
|
||||||
</BaseCard>
|
{/* </CardContent> */}
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Data Table */}
|
{/* Data Table */}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
// import moment from "moment-jalaali";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Bar,
|
Bar,
|
||||||
|
|
@ -27,6 +28,7 @@ import {
|
||||||
} from "~/components/ui/table";
|
} from "~/components/ui/table";
|
||||||
import { EventBus, formatNumber } from "~/lib/utils";
|
import { EventBus, formatNumber } from "~/lib/utils";
|
||||||
|
|
||||||
|
import jalaali from "jalaali-js";
|
||||||
import {
|
import {
|
||||||
Building2,
|
Building2,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
|
|
@ -42,17 +44,13 @@ import {
|
||||||
UsersIcon,
|
UsersIcon,
|
||||||
Zap,
|
Zap,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import moment from "moment-jalaali";
|
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { MetricCard } from "~/components/ui/metric-card";
|
|
||||||
import { useStoredDate } from "~/hooks/useStoredDate";
|
|
||||||
import apiService from "~/lib/api";
|
import apiService from "~/lib/api";
|
||||||
import { formatCurrency } from "~/lib/utils";
|
import { formatCurrency } from "~/lib/utils";
|
||||||
import type { CalendarDate } from "~/types/util.type";
|
import type { CalendarDate } from "~/types/util.type";
|
||||||
import DashboardLayout from "../layout";
|
import DashboardLayout from "../layout";
|
||||||
|
|
||||||
moment.loadPersian({ usePersianDigits: true });
|
// moment.loadPersian({ usePersianDigits: true });
|
||||||
|
|
||||||
interface GreenInnovationData {
|
interface GreenInnovationData {
|
||||||
WorkflowID: string;
|
WorkflowID: string;
|
||||||
approved_budget: string;
|
approved_budget: string;
|
||||||
|
|
@ -161,6 +159,7 @@ const columns = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export function GreenInnovationPage() {
|
export function GreenInnovationPage() {
|
||||||
|
const { jy } = jalaali.toJalaali(new Date());
|
||||||
const [projects, setProjects] = useState<GreenInnovationData[]>([]);
|
const [projects, setProjects] = useState<GreenInnovationData[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [loadingMore, setLoadingMore] = useState(false);
|
const [loadingMore, setLoadingMore] = useState(false);
|
||||||
|
|
@ -170,8 +169,10 @@ export function GreenInnovationPage() {
|
||||||
const [totalCount, setTotalCount] = useState(0);
|
const [totalCount, setTotalCount] = useState(0);
|
||||||
const [actualTotalCount, setActualTotalCount] = useState(0);
|
const [actualTotalCount, setActualTotalCount] = useState(0);
|
||||||
const [statsLoading, setStatsLoading] = useState(false);
|
const [statsLoading, setStatsLoading] = useState(false);
|
||||||
const [date, setDate] = useStoredDate();
|
const [date, setDate] = useState<CalendarDate>({
|
||||||
|
start: `${jy}/01/01`,
|
||||||
|
end: `${jy}/12/30`,
|
||||||
|
});
|
||||||
const [stats, setStats] = useState<stateCounter>();
|
const [stats, setStats] = useState<stateCounter>();
|
||||||
const [sortConfig, setSortConfig] = useState<SortConfig>({
|
const [sortConfig, setSortConfig] = useState<SortConfig>({
|
||||||
field: "start_date",
|
field: "start_date",
|
||||||
|
|
@ -361,15 +362,11 @@ export function GreenInnovationPage() {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (date: CalendarDate) => {
|
EventBus.on("dateSelected", (date: CalendarDate) => {
|
||||||
if (date) setDate(date);
|
if (date) {
|
||||||
};
|
setDate(date);
|
||||||
|
}
|
||||||
EventBus.on("dateSelected", handler);
|
});
|
||||||
|
|
||||||
return () => {
|
|
||||||
EventBus.off("dateSelected", handler);
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadMore = useCallback(() => {
|
const loadMore = useCallback(() => {
|
||||||
|
|
@ -379,14 +376,12 @@ export function GreenInnovationPage() {
|
||||||
}, [hasMore, loading]);
|
}, [hasMore, loading]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (date.end && date.start) {
|
|
||||||
fetchProjects(true);
|
fetchProjects(true);
|
||||||
fetchTotalCount();
|
fetchTotalCount();
|
||||||
}
|
|
||||||
}, [sortConfig, date]);
|
}, [sortConfig, date]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (date.end && date.start) fetchStats();
|
fetchStats();
|
||||||
}, [selectedProjects, date]);
|
}, [selectedProjects, date]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -524,13 +519,13 @@ export function GreenInnovationPage() {
|
||||||
},
|
},
|
||||||
|
|
||||||
pollution: {
|
pollution: {
|
||||||
value: parseNum(stats.pollution_reduction),
|
value: formatNumber(parseNum(stats.pollution_reduction)),
|
||||||
percent: parseNum(stats.pollution_reduction_percent),
|
percent: formatNumber(parseNum(stats.pollution_reduction_percent)),
|
||||||
},
|
},
|
||||||
|
|
||||||
waste: {
|
waste: {
|
||||||
value: parseNum(stats.waste_reduction),
|
value: formatNumber(parseNum(stats.waste_reduction)),
|
||||||
percent: parseNum(stats.waste_reductionn_percent),
|
percent: formatNumber(parseNum(stats.waste_reductionn_percent)),
|
||||||
},
|
},
|
||||||
avarage: stats.average_project_score,
|
avarage: stats.average_project_score,
|
||||||
countInnovationGreenProjects: stats.count_innovation_green_projects,
|
countInnovationGreenProjects: stats.count_innovation_green_projects,
|
||||||
|
|
@ -548,6 +543,7 @@ export function GreenInnovationPage() {
|
||||||
setStatsLoading(false);
|
setStatsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setPageData = (normalized: any) => {
|
const setPageData = (normalized: any) => {
|
||||||
setSustainabilityStats((prev) => ({
|
setSustainabilityStats((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
|
|
@ -751,14 +747,39 @@ export function GreenInnovationPage() {
|
||||||
</Card>
|
</Card>
|
||||||
))
|
))
|
||||||
: Object.entries(sustainabilityStats).map(([key, value]) => (
|
: Object.entries(sustainabilityStats).map(([key, value]) => (
|
||||||
<MetricCard
|
<Card
|
||||||
key={key}
|
key={key}
|
||||||
title={value.title}
|
className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] rounded-lg backdrop-blur-sm border-gray-700/50"
|
||||||
value={Math.round(value.total.value || 0)}
|
>
|
||||||
valueLabel={value.total?.description}
|
<CardContent className="p-0 h-full">
|
||||||
percentValue={value.percent?.value || 0}
|
<div className="flex flex-col justify-between gap-2 h-full">
|
||||||
percentLabel={value.percent?.description}
|
<div className="flex justify-between items-center border-b-2 border-gray-500/20 ">
|
||||||
/>
|
<h3 className="text-lg font-bold text-white font-persian p-4">
|
||||||
|
{value.title}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between p-6 flex-row-reverse">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-3xl font-bold text-pr-green mb-1 font-persian">
|
||||||
|
% {value.percent?.value}
|
||||||
|
</span>
|
||||||
|
<span className="text-sm text-gray-400 font-persian">
|
||||||
|
{value.percent?.description}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<b className="block w-0.5 h-8 bg-gray-600 rotate-45" />
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-3xl font-bold text-pr-green mb-1 font-persian">
|
||||||
|
{value.total?.value}
|
||||||
|
</span>
|
||||||
|
<span className="text-sm text-gray-400 font-persian">
|
||||||
|
{value.total?.description}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -1165,7 +1186,7 @@ export function GreenInnovationPage() {
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h4 className="font-medium text-gray-300 font-persian mb-2 flex items-center gap-1">
|
<h4 className="font-medium text-gray-300 font-persian mb-2 flex items-center gap-1">
|
||||||
<UsersIcon className="h-4 text-green-500" />
|
<UsersIcon className="h-4 text-green-500" />
|
||||||
هزینه برآورد شده(میلیون ریال):
|
هزینه برآورد شده:
|
||||||
</h4>
|
</h4>
|
||||||
<span className="text-white font-bold font-persian">
|
<span className="text-white font-bold font-persian">
|
||||||
{formatNumber(
|
{formatNumber(
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import {
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "~/components/ui/table";
|
} from "~/components/ui/table";
|
||||||
|
|
||||||
|
import jalaali from "jalaali-js";
|
||||||
import {
|
import {
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
ChevronUp,
|
ChevronUp,
|
||||||
|
|
@ -39,8 +40,6 @@ import {
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
XAxis,
|
XAxis,
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
import { MetricCard } from "~/components/ui/metric-card";
|
|
||||||
import { useStoredDate } from "~/hooks/useStoredDate";
|
|
||||||
import apiService from "~/lib/api";
|
import apiService from "~/lib/api";
|
||||||
import { EventBus, formatCurrency, formatNumber } from "~/lib/utils";
|
import { EventBus, formatCurrency, formatNumber } from "~/lib/utils";
|
||||||
import type { CalendarDate } from "~/types/util.type";
|
import type { CalendarDate } from "~/types/util.type";
|
||||||
|
|
@ -180,6 +179,7 @@ const dialogChartData = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export function InnovationBuiltInsidePage() {
|
export function InnovationBuiltInsidePage() {
|
||||||
|
const { jy } = jalaali.toJalaali(new Date());
|
||||||
const [projects, setProjects] = useState<innovationBuiltInDate[]>([]);
|
const [projects, setProjects] = useState<innovationBuiltInDate[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [loadingMore, setLoadingMore] = useState(false);
|
const [loadingMore, setLoadingMore] = useState(false);
|
||||||
|
|
@ -194,8 +194,10 @@ export function InnovationBuiltInsidePage() {
|
||||||
field: "start_date",
|
field: "start_date",
|
||||||
direction: "asc",
|
direction: "asc",
|
||||||
});
|
});
|
||||||
|
const [date, setDate] = useState<CalendarDate>({
|
||||||
const [date, setDate] = useStoredDate();
|
start: `${jy}/01/01`,
|
||||||
|
end: `${jy}/12/30`,
|
||||||
|
});
|
||||||
const [tblAvarage, setTblAvarage] = useState<number>(0);
|
const [tblAvarage, setTblAvarage] = useState<number>(0);
|
||||||
const [selectedProjects, setSelectedProjects] =
|
const [selectedProjects, setSelectedProjects] =
|
||||||
useState<Set<string | number>>();
|
useState<Set<string | number>>();
|
||||||
|
|
@ -426,23 +428,19 @@ export function InnovationBuiltInsidePage() {
|
||||||
}, [hasMore, loading]);
|
}, [hasMore, loading]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (date: CalendarDate) => {
|
EventBus.on("dateSelected", (date: CalendarDate) => {
|
||||||
if (date) setDate(date);
|
if (date) {
|
||||||
};
|
setDate(date);
|
||||||
|
}
|
||||||
EventBus.on("dateSelected", handler);
|
});
|
||||||
|
|
||||||
return () => {
|
|
||||||
EventBus.off("dateSelected", handler);
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (date.start && date.end) fetchProjects(true);
|
fetchProjects(true);
|
||||||
}, [sortConfig, date]);
|
}, [sortConfig, date]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (date.end && date.start) fetchStats();
|
fetchStats();
|
||||||
}, [selectedProjects, date]);
|
}, [selectedProjects, date]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -528,13 +526,15 @@ export function InnovationBuiltInsidePage() {
|
||||||
const stats = data[0];
|
const stats = data[0];
|
||||||
const normalized: any = {
|
const normalized: any = {
|
||||||
currencySaving: {
|
currencySaving: {
|
||||||
value: parseNum(stats?.foreign_currency_saving),
|
value: formatNumber(parseNum(stats?.foreign_currency_saving)),
|
||||||
percent: parseNum(stats?.foreign_currency_saving_percent),
|
percent: formatNumber(
|
||||||
|
parseNum(stats?.foreign_currency_saving_percent)
|
||||||
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
investmentAmount: {
|
investmentAmount: {
|
||||||
value: parseNum(stats?.investment_amount),
|
value: formatNumber(parseNum(stats?.investment_amount)),
|
||||||
percent: parseNum(stats?.investment_amount_percent),
|
percent: formatNumber(parseNum(stats?.investment_amount_percent)),
|
||||||
},
|
},
|
||||||
|
|
||||||
technology: {
|
technology: {
|
||||||
|
|
@ -724,7 +724,7 @@ export function InnovationBuiltInsidePage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardLayout title="نوآوری ساخت داخل">
|
<DashboardLayout title="نوآوری ساخت داخل">
|
||||||
<div className="space-y-4 justify-between gap-8 grid pl-6 sm:grid-cols-1 xl:grid-cols-[35%_65%]">
|
<div className="space-y-4 justify-between gap-8 grid pl-3.5 sm:grid-cols-1 xl:grid-cols-[35%_65%]">
|
||||||
{/* Stats Cards */}
|
{/* Stats Cards */}
|
||||||
<div className="flex w-full mb-0">
|
<div className="flex w-full mb-0">
|
||||||
<div className="flex flex-col w-full justify-between gap-2">
|
<div className="flex flex-col w-full justify-between gap-2">
|
||||||
|
|
@ -758,47 +758,39 @@ export function InnovationBuiltInsidePage() {
|
||||||
</Card>
|
</Card>
|
||||||
))
|
))
|
||||||
: Object.entries(sustainabilityStats).map(([key, value]) => (
|
: Object.entries(sustainabilityStats).map(([key, value]) => (
|
||||||
<MetricCard
|
<Card
|
||||||
key={key}
|
key={key}
|
||||||
title={value.title}
|
className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] rounded-lg backdrop-blur-sm border-gray-700/50"
|
||||||
value={Math.round(value.total.value || 0)}
|
>
|
||||||
valueLabel={value.total?.description}
|
<CardContent className="p-0 h-full">
|
||||||
percentValue={value.percent?.value || 0}
|
<div className="flex flex-col justify-between gap-2 h-full">
|
||||||
percentLabel={value.percent?.description}
|
<div className="flex justify-between items-center border-b-2 border-gray-500/20 ">
|
||||||
/>
|
<h3 className="text-lg font-semibold text-white p-4">
|
||||||
// <Card
|
{value.title}
|
||||||
// key={key}
|
</h3>
|
||||||
// className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] rounded-lg backdrop-blur-sm border-gray-700/50"
|
</div>
|
||||||
// >
|
<div className="flex items-center justify-between p-6 flex-row-reverse">
|
||||||
// <CardContent className="p-0 h-full">
|
<div className="flex flex-col">
|
||||||
// <div className="flex flex-col justify-between gap-2 h-full">
|
<span className="text-3xl font-bold text-pr-green mb-1 font-persian">
|
||||||
// <div className="flex justify-between items-center border-b-2 border-gray-500/20 ">
|
% {value.percent?.value}
|
||||||
// <h3 className="text-lg font-semibold text-white p-4">
|
</span>
|
||||||
// {value.title}
|
<span className="text-sm text-gray-400 font-persian">
|
||||||
// </h3>
|
{value.percent?.description}
|
||||||
// </div>
|
</span>
|
||||||
// <div className="flex items-center justify-between p-6 flex-row-reverse">
|
</div>
|
||||||
// <div className="flex flex-col">
|
<b className="block w-0.5 h-8 bg-gray-600 rotate-45" />
|
||||||
// <span className="text-3xl font-bold text-pr-green mb-1 font-persian">
|
<div className="flex flex-col">
|
||||||
// % {value.percent?.value}
|
<span className="text-3xl font-bold text-pr-green mb-1 font-persian">
|
||||||
// </span>
|
{value.total?.value}
|
||||||
// <span className="text-sm text-gray-400 font-persian">
|
</span>
|
||||||
// {value.percent?.description}
|
<span className="text-sm text-gray-400 font-persian">
|
||||||
// </span>
|
{value.total?.description}
|
||||||
// </div>
|
</span>
|
||||||
// <b className="block w-0.5 h-8 bg-gray-600 rotate-45" />
|
</div>
|
||||||
// <div className="flex flex-col">
|
</div>
|
||||||
// <span className="text-3xl font-bold text-pr-green mb-1 font-persian">
|
</div>
|
||||||
// {value.total?.value}
|
</CardContent>
|
||||||
// </span>
|
</Card>
|
||||||
// <span className="text-sm text-gray-400 font-persian">
|
|
||||||
// {value.total?.description}
|
|
||||||
// </span>
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// </CardContent>
|
|
||||||
// </Card>
|
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{statsLoading ? (
|
{statsLoading ? (
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import jalaali from "jalaali-js";
|
||||||
import {
|
import {
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
ChevronUp,
|
ChevronUp,
|
||||||
|
|
@ -42,7 +43,6 @@ import {
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "~/components/ui/table";
|
} from "~/components/ui/table";
|
||||||
import { useStoredDate } from "~/hooks/useStoredDate";
|
|
||||||
import apiService from "~/lib/api";
|
import apiService from "~/lib/api";
|
||||||
import { EventBus, formatCurrency, formatNumber } from "~/lib/utils";
|
import { EventBus, formatCurrency, formatNumber } from "~/lib/utils";
|
||||||
import type { CalendarDate } from "~/types/util.type";
|
import type { CalendarDate } from "~/types/util.type";
|
||||||
|
|
@ -261,6 +261,7 @@ const VerticalBarChart = memo<{
|
||||||
const MemoizedVerticalBarChart = VerticalBarChart;
|
const MemoizedVerticalBarChart = VerticalBarChart;
|
||||||
|
|
||||||
export function ManageIdeasTechPage() {
|
export function ManageIdeasTechPage() {
|
||||||
|
const { jy } = jalaali.toJalaali(new Date());
|
||||||
const [ideas, setIdeas] = useState<IdeaData[]>([]);
|
const [ideas, setIdeas] = useState<IdeaData[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [loadingMore, setLoadingMore] = useState(false);
|
const [loadingMore, setLoadingMore] = useState(false);
|
||||||
|
|
@ -275,7 +276,10 @@ export function ManageIdeasTechPage() {
|
||||||
field: "idea_title",
|
field: "idea_title",
|
||||||
direction: "asc",
|
direction: "asc",
|
||||||
});
|
});
|
||||||
const [date, setDate] = useStoredDate();
|
const [date, setDate] = useState<CalendarDate>({
|
||||||
|
start: `${jy}/01/01`,
|
||||||
|
end: `${jy}/12/30`,
|
||||||
|
});
|
||||||
|
|
||||||
// People ranking state
|
// People ranking state
|
||||||
const [peopleRanking, setPeopleRanking] = useState<PersonRanking[]>([]);
|
const [peopleRanking, setPeopleRanking] = useState<PersonRanking[]>([]);
|
||||||
|
|
@ -405,25 +409,19 @@ export function ManageIdeasTechPage() {
|
||||||
}, [hasMore, loading, loadingMore]);
|
}, [hasMore, loading, loadingMore]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (date: CalendarDate) => {
|
EventBus.on("dateSelected", (date: CalendarDate) => {
|
||||||
if (date) setDate(date);
|
if (date) {
|
||||||
};
|
setDate(date);
|
||||||
|
}
|
||||||
EventBus.on("dateSelected", handler);
|
});
|
||||||
|
|
||||||
return () => {
|
|
||||||
EventBus.off("dateSelected", handler);
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (date.end && date.start) {
|
|
||||||
fetchIdeas(true);
|
fetchIdeas(true);
|
||||||
fetchTotalCount();
|
fetchTotalCount();
|
||||||
fetchPeopleRanking();
|
fetchPeopleRanking();
|
||||||
fetchChartData();
|
fetchChartData();
|
||||||
fetchStatsData();
|
fetchStatsData();
|
||||||
}
|
|
||||||
}, [sortConfig, date]);
|
}, [sortConfig, date]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import jalaali from "jalaali-js";
|
||||||
import {
|
import {
|
||||||
Building2,
|
Building2,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
|
|
@ -34,7 +35,6 @@ import {
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "~/components/ui/table";
|
} from "~/components/ui/table";
|
||||||
import { useStoredDate } from "~/hooks/useStoredDate";
|
|
||||||
import apiService from "~/lib/api";
|
import apiService from "~/lib/api";
|
||||||
import { EventBus, formatNumber } from "~/lib/utils";
|
import { EventBus, formatNumber } from "~/lib/utils";
|
||||||
import type { CalendarDate } from "~/types/util.type";
|
import type { CalendarDate } from "~/types/util.type";
|
||||||
|
|
@ -67,11 +67,9 @@ interface ProjectStats {
|
||||||
percent_reduction_value_currency: string;
|
percent_reduction_value_currency: string;
|
||||||
percent_sum_stopping_production: string;
|
percent_sum_stopping_production: string;
|
||||||
percent_throat_removal: string;
|
percent_throat_removal: string;
|
||||||
percent_operating_cost_before_innovation: string;
|
|
||||||
sum_reducing_breakdowns: number;
|
sum_reducing_breakdowns: number;
|
||||||
sum_reduction_value_currency: number;
|
sum_reduction_value_currency: number;
|
||||||
sum_stopping_production: number;
|
sum_stopping_production: number;
|
||||||
sum_operating_cost_reduction: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SortConfig {
|
interface SortConfig {
|
||||||
|
|
@ -96,11 +94,9 @@ interface InnovationStats {
|
||||||
currencyReductionSum: number; // مجموع کاهش ارز بری (میلیون ریال)
|
currencyReductionSum: number; // مجموع کاهش ارز بری (میلیون ریال)
|
||||||
frequentFailuresReductionSum: number; // مجموع کاهش خرابی های پرتکرار
|
frequentFailuresReductionSum: number; // مجموع کاهش خرابی های پرتکرار
|
||||||
percentProductionStops: number | string; // درصد مقایسهای جلوگیری از توقفات تولید
|
percentProductionStops: number | string; // درصد مقایسهای جلوگیری از توقفات تولید
|
||||||
reductionCostOprationSum: number; // مجموع کاهش هزینه عملیاتی
|
|
||||||
percentBottleneckRemoval: number | string; // درصد مقایسهای رفع گلوگاه
|
percentBottleneckRemoval: number | string; // درصد مقایسهای رفع گلوگاه
|
||||||
percentCurrencyReduction: number | string; // درصد مقایسهای کاهش ارز بری
|
percentCurrencyReduction: number | string; // درصد مقایسهای کاهش ارز بری
|
||||||
percentFailuresReduction: number | string; // درصد مقایسهای کاهش خرابیهای پرتکرار
|
percentFailuresReduction: number | string; // درصد مقایسهای کاهش خرابیهای پرتکرار
|
||||||
percentOperatingCostBeforeInnovation: number | string; // درصد مقایسهای کاهش هزینه عملیاتی
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
|
|
@ -123,20 +119,24 @@ const columns = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export function ProcessInnovationPage() {
|
export function ProcessInnovationPage() {
|
||||||
|
const { jy } = jalaali.toJalaali(new Date());
|
||||||
const [projects, setProjects] = useState<ProcessInnovationData[]>([]);
|
const [projects, setProjects] = useState<ProcessInnovationData[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [loadingMore, setLoadingMore] = useState(false);
|
const [loadingMore, setLoadingMore] = useState(false);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [pageSize] = useState(20);
|
const [pageSize] = useState(20);
|
||||||
const [hasMore, setHasMore] = useState(true);
|
const [hasMore, setHasMore] = useState(true);
|
||||||
const [date, setDate] = useStoredDate();
|
// const [totalCount, setTotalCount] = useState(0);
|
||||||
|
const [date, setDate] = useState<CalendarDate>({
|
||||||
|
start: `${jy}/01/01`,
|
||||||
|
end: `${jy}/12/30`,
|
||||||
|
});
|
||||||
const [actualTotalCount, setActualTotalCount] = useState(0);
|
const [actualTotalCount, setActualTotalCount] = useState(0);
|
||||||
const [statsLoading, setStatsLoading] = useState(false);
|
const [statsLoading, setStatsLoading] = useState(false);
|
||||||
const [stats, setStats] = useState<InnovationStats>({
|
const [stats, setStats] = useState<InnovationStats>({
|
||||||
totalProjects: 0,
|
totalProjects: 0,
|
||||||
averageScore: 0,
|
averageScore: 0,
|
||||||
productionStopsPreventionSum: 0,
|
productionStopsPreventionSum: 0,
|
||||||
reductionCostOprationSum: 0,
|
|
||||||
bottleneckRemovalCount: 0,
|
bottleneckRemovalCount: 0,
|
||||||
currencyReductionSum: 0,
|
currencyReductionSum: 0,
|
||||||
frequentFailuresReductionSum: 0,
|
frequentFailuresReductionSum: 0,
|
||||||
|
|
@ -144,7 +144,6 @@ export function ProcessInnovationPage() {
|
||||||
percentBottleneckRemoval: 0,
|
percentBottleneckRemoval: 0,
|
||||||
percentCurrencyReduction: 0,
|
percentCurrencyReduction: 0,
|
||||||
percentFailuresReduction: 0,
|
percentFailuresReduction: 0,
|
||||||
percentOperatingCostBeforeInnovation: 0,
|
|
||||||
});
|
});
|
||||||
const [sortConfig, setSortConfig] = useState<SortConfig>({
|
const [sortConfig, setSortConfig] = useState<SortConfig>({
|
||||||
field: "start_date",
|
field: "start_date",
|
||||||
|
|
@ -160,7 +159,7 @@ export function ProcessInnovationPage() {
|
||||||
const [stateCard, setStateCard] = useState({
|
const [stateCard, setStateCard] = useState({
|
||||||
productionstopsprevention: {
|
productionstopsprevention: {
|
||||||
id: "productionstopsprevention",
|
id: "productionstopsprevention",
|
||||||
title: "توقفات تولید",
|
title: "جلوگیری از توقفات تولید",
|
||||||
value: formatNumber(
|
value: formatNumber(
|
||||||
stats.productionStopsPreventionSum.toFixed?.(1) ??
|
stats.productionStopsPreventionSum.toFixed?.(1) ??
|
||||||
stats.productionStopsPreventionSum
|
stats.productionStopsPreventionSum
|
||||||
|
|
@ -171,7 +170,7 @@ export function ProcessInnovationPage() {
|
||||||
},
|
},
|
||||||
bottleneckremoval: {
|
bottleneckremoval: {
|
||||||
id: "bottleneckremoval",
|
id: "bottleneckremoval",
|
||||||
title: "گلوگاه ها",
|
title: "رفع گلوگاه",
|
||||||
value: formatNumber(stats.bottleneckRemovalCount),
|
value: formatNumber(stats.bottleneckRemovalCount),
|
||||||
description: "تعداد رفع گلوگاه",
|
description: "تعداد رفع گلوگاه",
|
||||||
icon: Funnel,
|
icon: Funnel,
|
||||||
|
|
@ -179,7 +178,7 @@ export function ProcessInnovationPage() {
|
||||||
},
|
},
|
||||||
currencyreduction: {
|
currencyreduction: {
|
||||||
id: "currencyreduction",
|
id: "currencyreduction",
|
||||||
title: "ارز بری",
|
title: "کاهش ارز بری",
|
||||||
value: formatNumber(
|
value: formatNumber(
|
||||||
stats.currencyReductionSum.toFixed?.(0) ?? stats.currencyReductionSum
|
stats.currencyReductionSum.toFixed?.(0) ?? stats.currencyReductionSum
|
||||||
),
|
),
|
||||||
|
|
@ -187,25 +186,14 @@ export function ProcessInnovationPage() {
|
||||||
icon: DollarSign,
|
icon: DollarSign,
|
||||||
color: "text-pr-green",
|
color: "text-pr-green",
|
||||||
},
|
},
|
||||||
decreaseCurrencyOperation: {
|
|
||||||
id: "decreaseCurrencyOperation",
|
|
||||||
title: "هزینه های عملیاتی",
|
|
||||||
value: formatNumber(
|
|
||||||
stats.reductionCostOprationSum.toFixed?.(0) ??
|
|
||||||
stats.reductionCostOprationSum
|
|
||||||
),
|
|
||||||
description: "میلیون ریال کاهش یافته",
|
|
||||||
icon: DollarSign,
|
|
||||||
color: "text-pr-green",
|
|
||||||
},
|
|
||||||
frequentfailuresreduction: {
|
frequentfailuresreduction: {
|
||||||
id: "frequentfailuresreduction",
|
id: "frequentfailuresreduction",
|
||||||
title: "خرابی های پرتکرار",
|
title: "کاهش خرابی های پرتکرار",
|
||||||
value: formatNumber(
|
value: formatNumber(
|
||||||
stats.frequentFailuresReductionSum.toFixed?.(1) ??
|
stats.frequentFailuresReductionSum.toFixed?.(1) ??
|
||||||
stats.frequentFailuresReductionSum
|
stats.frequentFailuresReductionSum
|
||||||
),
|
),
|
||||||
description: "خرابی پر تکرار کاهش یافته",
|
description: "مجموع درصد کاهش خرابی",
|
||||||
icon: Wrench,
|
icon: Wrench,
|
||||||
color: "text-pr-green",
|
color: "text-pr-green",
|
||||||
},
|
},
|
||||||
|
|
@ -214,6 +202,15 @@ export function ProcessInnovationPage() {
|
||||||
const observerRef = useRef<HTMLDivElement>(null);
|
const observerRef = useRef<HTMLDivElement>(null);
|
||||||
const fetchingRef = useRef(false);
|
const fetchingRef = useRef(false);
|
||||||
|
|
||||||
|
// Selection handlers
|
||||||
|
// const handleSelectAll = () => {
|
||||||
|
// if (selectedProjects.size === projects.length) {
|
||||||
|
// setSelectedProjects(new Set());
|
||||||
|
// } else {
|
||||||
|
// setSelectedProjects(new Set(projects.map((p) => p.project_no)));
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
const handleSelectProject = (projectNo: string) => {
|
const handleSelectProject = (projectNo: string) => {
|
||||||
const newSelected = new Set(selectedProjects);
|
const newSelected = new Set(selectedProjects);
|
||||||
if (newSelected.has(projectNo)) {
|
if (newSelected.has(projectNo)) {
|
||||||
|
|
@ -340,26 +337,20 @@ export function ProcessInnovationPage() {
|
||||||
}, [hasMore, loading]);
|
}, [hasMore, loading]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (date: CalendarDate) => {
|
EventBus.on("dateSelected", (date: CalendarDate) => {
|
||||||
if (date) setDate(date);
|
if (date) {
|
||||||
};
|
setDate(date);
|
||||||
|
}
|
||||||
EventBus.on("dateSelected", handler);
|
});
|
||||||
|
|
||||||
return () => {
|
|
||||||
EventBus.off("dateSelected", handler);
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (date?.start && date?.end) {
|
|
||||||
fetchProjects(true);
|
fetchProjects(true);
|
||||||
fetchTotalCount();
|
fetchTotalCount();
|
||||||
}
|
|
||||||
}, [sortConfig, date]);
|
}, [sortConfig, date]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (date?.start && date?.end) fetchStats();
|
fetchStats();
|
||||||
}, [selectedProjects, date]);
|
}, [selectedProjects, date]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -480,13 +471,10 @@ export function ProcessInnovationPage() {
|
||||||
totalProjects: parseNum(stats?.count_innovation_process_projects),
|
totalProjects: parseNum(stats?.count_innovation_process_projects),
|
||||||
averageScore: parseFloat(data[0].average_project_score),
|
averageScore: parseFloat(data[0].average_project_score),
|
||||||
productionStopsPreventionSum: parseNum(stats?.sum_stopping_production),
|
productionStopsPreventionSum: parseNum(stats?.sum_stopping_production),
|
||||||
reductionCostOprationSum: parseNum(stats?.sum_operating_cost_reduction),
|
|
||||||
bottleneckRemovalCount: parseNum(stats?.count_throat_removal),
|
bottleneckRemovalCount: parseNum(stats?.count_throat_removal),
|
||||||
currencyReductionSum: parseNum(stats?.sum_reduction_value_currency),
|
currencyReductionSum: parseNum(stats?.sum_reduction_value_currency),
|
||||||
frequentFailuresReductionSum: parseNum(stats?.sum_reducing_breakdowns),
|
frequentFailuresReductionSum: parseNum(stats?.sum_reducing_breakdowns),
|
||||||
percentProductionStops: stats?.percent_sum_stopping_production,
|
percentProductionStops: stats?.percent_sum_stopping_production,
|
||||||
percentOperatingCostBeforeInnovation:
|
|
||||||
stats?.percent_operating_cost_before_innovation,
|
|
||||||
percentBottleneckRemoval: stats?.percent_throat_removal,
|
percentBottleneckRemoval: stats?.percent_throat_removal,
|
||||||
percentCurrencyReduction: stats?.percent_reduction_value_currency,
|
percentCurrencyReduction: stats?.percent_reduction_value_currency,
|
||||||
percentFailuresReduction: stats?.percent_reducing_breakdowns,
|
percentFailuresReduction: stats?.percent_reducing_breakdowns,
|
||||||
|
|
@ -509,10 +497,6 @@ export function ProcessInnovationPage() {
|
||||||
...prev.currencyreduction,
|
...prev.currencyreduction,
|
||||||
value: formatNumber(normalized.currencyReductionSum),
|
value: formatNumber(normalized.currencyReductionSum),
|
||||||
},
|
},
|
||||||
decreaseCurrencyOperation: {
|
|
||||||
...prev.decreaseCurrencyOperation,
|
|
||||||
value: formatNumber(normalized.reductionCostOprationSum),
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
setStats(normalized);
|
setStats(normalized);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -639,14 +623,13 @@ export function ProcessInnovationPage() {
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<div className="space-y-4 w-full">
|
<div className="space-y-4 w-full">
|
||||||
{/* Stats Grid */}
|
{/* Stats Grid */}
|
||||||
<div className="h-full">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
{loading || statsLoading ? (
|
{loading || statsLoading
|
||||||
// Skeleton cards
|
? // Loading skeleton for stats cards - matching new design
|
||||||
<div className="flex flex-wrap justify-between gap-3">
|
Array.from({ length: 4 }).map((_, index) => (
|
||||||
{Array.from({ length: 6 }).map((_, index) => (
|
|
||||||
<BaseCard
|
<BaseCard
|
||||||
key={`skeleton-${index}`}
|
key={`skeleton-${index}`}
|
||||||
className="rounded-2xl overflow-hidden w-full sm:w-[48%] md:w-[30%]"
|
className="rounded-2xl overflow-hidden"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col justify-between gap-2">
|
<div className="flex flex-col justify-between gap-2">
|
||||||
<div className="flex justify-between items-center border-b-2 mx-4 border-gray-500/20">
|
<div className="flex justify-between items-center border-b-2 mx-4 border-gray-500/20">
|
||||||
|
|
@ -670,112 +653,42 @@ export function ProcessInnovationPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</BaseCard>
|
</BaseCard>
|
||||||
))}
|
))
|
||||||
</div>
|
: Object.entries(stateCard).map(([key, card]) => {
|
||||||
) : (
|
// map percent values for each card key
|
||||||
<div className="flex flex-col h-full gap-5">
|
const percentMap: Record<
|
||||||
<div className="flex flex-row gap-4 h-full">
|
string,
|
||||||
<BaseCard
|
number | string | undefined
|
||||||
key={stateCard.productionstopsprevention.id}
|
> = {
|
||||||
title={stateCard.productionstopsprevention.title}
|
productionstopsprevention: stats.percentProductionStops,
|
||||||
className="border-gray-700/50 w-full"
|
bottleneckremoval: stats.percentBottleneckRemoval,
|
||||||
icon={stateCard.productionstopsprevention.icon}
|
currencyreduction: stats.percentCurrencyReduction,
|
||||||
>
|
frequentfailuresreduction: stats.percentFailuresReduction,
|
||||||
<div className="flex items-center justify-center flex-col">
|
};
|
||||||
<div className="flex items-center gap-4">
|
const percentValue = percentMap[key];
|
||||||
<div className="text-center">
|
|
||||||
<p className="text-3xl text-pr-green font-bold mb-1">
|
|
||||||
{stateCard.productionstopsprevention.value}
|
|
||||||
</p>
|
|
||||||
<div className="text-[11px] text-[#ACACAC] font-light font-persian">
|
|
||||||
{stateCard.productionstopsprevention.description}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BaseCard>
|
|
||||||
|
|
||||||
|
return (
|
||||||
<BaseCard
|
<BaseCard
|
||||||
key={stateCard.frequentfailuresreduction.id}
|
key={card.id}
|
||||||
title={stateCard.frequentfailuresreduction.title}
|
title={card.title}
|
||||||
className="border-gray-700/50 w-full"
|
className="border-gray-700/50"
|
||||||
icon={stateCard.frequentfailuresreduction.icon}
|
icon={card.icon}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-center flex-col">
|
<div className="flex items-center justify-center flex-col">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-3xl text-pr-green font-bold mb-1">
|
<p className="text-3xl text-pr-green font-bold mb-1">
|
||||||
{stateCard.frequentfailuresreduction.value}
|
{card.value}
|
||||||
</p>
|
</p>
|
||||||
<div className="text-[11px] text-[#ACACAC] font-light font-persian">
|
<div className="text-[11px] text-[#ACACAC] font-light font-persian">
|
||||||
{stateCard.frequentfailuresreduction.description}
|
{card.description}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</BaseCard>
|
</BaseCard>
|
||||||
</div>
|
);
|
||||||
<div className="flex flex-row gap-4 h-full">
|
})}
|
||||||
<BaseCard
|
|
||||||
key={stateCard.currencyreduction.id}
|
|
||||||
title={stateCard.currencyreduction.title}
|
|
||||||
className="border-gray-700/50 w-full"
|
|
||||||
icon={stateCard.currencyreduction.icon}
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-center flex-col">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div className="text-center">
|
|
||||||
<p className="text-3xl text-pr-green font-bold mb-1">
|
|
||||||
{stateCard.currencyreduction.value}
|
|
||||||
</p>
|
|
||||||
<div className="text-[11px] text-[#ACACAC] font-light font-persian">
|
|
||||||
{stateCard.currencyreduction.description}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BaseCard>
|
|
||||||
<BaseCard
|
|
||||||
key={stateCard.decreaseCurrencyOperation.id}
|
|
||||||
title={stateCard.decreaseCurrencyOperation.title}
|
|
||||||
className="border-gray-700/50 w-full"
|
|
||||||
icon={stateCard.decreaseCurrencyOperation.icon}
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-center flex-col">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div className="text-center">
|
|
||||||
<p className="text-3xl text-pr-green font-bold mb-1">
|
|
||||||
{stateCard.decreaseCurrencyOperation.value}
|
|
||||||
</p>
|
|
||||||
<div className="text-[11px] text-[#ACACAC] font-light font-persian">
|
|
||||||
{stateCard.decreaseCurrencyOperation.description}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BaseCard>
|
|
||||||
<BaseCard
|
|
||||||
key={stateCard.bottleneckremoval.id}
|
|
||||||
title={stateCard.bottleneckremoval.title}
|
|
||||||
className="border-gray-700/50 w-full"
|
|
||||||
icon={stateCard.bottleneckremoval.icon}
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-center flex-col">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div className="text-center">
|
|
||||||
<p className="text-3xl text-pr-green font-bold mb-1">
|
|
||||||
{stateCard.bottleneckremoval.value}
|
|
||||||
</p>
|
|
||||||
<div className="text-[11px] text-[#ACACAC] font-light font-persian">
|
|
||||||
{stateCard.bottleneckremoval.description}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BaseCard>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -792,7 +705,7 @@ export function ProcessInnovationPage() {
|
||||||
loading={statsLoading}
|
loading={statsLoading}
|
||||||
data={[
|
data={[
|
||||||
{
|
{
|
||||||
label: "توقفات تولید",
|
label: "کاهش توقفات تولید",
|
||||||
value: Number(stats.percentProductionStops) || 0,
|
value: Number(stats.percentProductionStops) || 0,
|
||||||
labelColor: "text-white",
|
labelColor: "text-white",
|
||||||
},
|
},
|
||||||
|
|
@ -802,23 +715,17 @@ export function ProcessInnovationPage() {
|
||||||
labelColor: "text-white",
|
labelColor: "text-white",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "ارز بری",
|
label: "کاهش ارز بری",
|
||||||
value: Number(stats.percentCurrencyReduction) || 0,
|
value: Number(stats.percentCurrencyReduction) || 0,
|
||||||
labelColor: "text-white",
|
labelColor: "text-white",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "خرابی پر تکرار",
|
label: "کاهش خرابی پر تکرار",
|
||||||
value: Number(stats.percentFailuresReduction) || 0,
|
value: Number(stats.percentFailuresReduction) || 0,
|
||||||
labelColor: "text-white",
|
labelColor: "text-white",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: "هزینه های عملیاتی",
|
|
||||||
value:
|
|
||||||
Number(stats.percentOperatingCostBeforeInnovation) || 0,
|
|
||||||
labelColor: "text-white",
|
|
||||||
},
|
|
||||||
]}
|
]}
|
||||||
barHeight="h-5"
|
barHeight="h-6"
|
||||||
showAxisLabels={true}
|
showAxisLabels={true}
|
||||||
/>
|
/>
|
||||||
</BaseCard>
|
</BaseCard>
|
||||||
|
|
@ -1018,7 +925,7 @@ export function ProcessInnovationPage() {
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h4 className="font-light text-sm text-white font-persian mb-2 flex items-center gap-1">
|
<h4 className="font-light text-sm text-white font-persian mb-2 flex items-center gap-1">
|
||||||
<UsersIcon className="h-4 text-pr-green text-sm font-light" />
|
<UsersIcon className="h-4 text-pr-green text-sm font-light" />
|
||||||
هزینه برآورد شده(میلیون ریال):
|
هزینه برآورد شده:
|
||||||
</h4>
|
</h4>
|
||||||
<span className="text-white font-normal text-base font-persian">
|
<span className="text-white font-normal text-base font-persian">
|
||||||
{selectedProjectDetails?.approved_budget
|
{selectedProjectDetails?.approved_budget
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import {
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "~/components/ui/popover";
|
} from "~/components/ui/popover";
|
||||||
|
|
||||||
|
import jalaali from "jalaali-js";
|
||||||
import {
|
import {
|
||||||
CartesianGrid,
|
CartesianGrid,
|
||||||
Legend,
|
Legend,
|
||||||
|
|
@ -41,7 +42,6 @@ import {
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "~/components/ui/table";
|
} from "~/components/ui/table";
|
||||||
import { Tooltip as TooltipSh, TooltipTrigger } from "~/components/ui/tooltip";
|
import { Tooltip as TooltipSh, TooltipTrigger } from "~/components/ui/tooltip";
|
||||||
import { useStoredDate } from "~/hooks/useStoredDate";
|
|
||||||
import apiService from "~/lib/api";
|
import apiService from "~/lib/api";
|
||||||
import { EventBus, formatNumber, handleDataValue } from "~/lib/utils";
|
import { EventBus, formatNumber, handleDataValue } from "~/lib/utils";
|
||||||
import type { CalendarDate } from "~/types/util.type";
|
import type { CalendarDate } from "~/types/util.type";
|
||||||
|
|
@ -199,6 +199,7 @@ export default function Timeline(valueTimeLine: string) {
|
||||||
|
|
||||||
export function ProductInnovationPage() {
|
export function ProductInnovationPage() {
|
||||||
// const [showPopup, setShowPopup] = useState(false);
|
// const [showPopup, setShowPopup] = useState(false);
|
||||||
|
const { jy } = jalaali.toJalaali(new Date());
|
||||||
const [projects, setProjects] = useState<ProductInnovationData[]>([]);
|
const [projects, setProjects] = useState<ProductInnovationData[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [loadingMore, setLoadingMore] = useState(false);
|
const [loadingMore, setLoadingMore] = useState(false);
|
||||||
|
|
@ -263,8 +264,10 @@ export function ProductInnovationPage() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const [date, setDate] = useStoredDate();
|
const [date, setDate] = useState<CalendarDate>({
|
||||||
|
start: `${jy}/01/01`,
|
||||||
|
end: `${jy}/12/30`,
|
||||||
|
});
|
||||||
const observerRef = useRef<HTMLDivElement>(null);
|
const observerRef = useRef<HTMLDivElement>(null);
|
||||||
const fetchingRef = useRef(false);
|
const fetchingRef = useRef(false);
|
||||||
|
|
||||||
|
|
@ -285,7 +288,9 @@ export function ProductInnovationPage() {
|
||||||
// Fetch popup stats
|
// Fetch popup stats
|
||||||
const statsResponse = await apiService.call({
|
const statsResponse = await apiService.call({
|
||||||
innovation_product_popup_function1: {
|
innovation_product_popup_function1: {
|
||||||
project_id: project.project_id
|
project_id: project.project_id,
|
||||||
|
start_date: startDate || null,
|
||||||
|
end_date: endDate || null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -515,11 +520,11 @@ export function ProductInnovationPage() {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (date.end && date.start) fetchProjects(true);
|
fetchProjects(true);
|
||||||
}, [sortConfig, date]);
|
}, [sortConfig, date]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (date.end && date.start) fetchStats();
|
fetchStats();
|
||||||
}, [date]);
|
}, [date]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import { saveAs } from "file-saver";
|
import jalaali from "jalaali-js";
|
||||||
import { ChevronDown, ChevronUp, RefreshCw } from "lucide-react";
|
import { ChevronDown, ChevronUp, RefreshCw } from "lucide-react";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import XLSX from "xlsx-js-style";
|
|
||||||
import { Badge } from "~/components/ui/badge";
|
import { Badge } from "~/components/ui/badge";
|
||||||
import { Card, CardContent } from "~/components/ui/card";
|
import { Card, CardContent } from "~/components/ui/card";
|
||||||
import {
|
import {
|
||||||
|
|
@ -14,14 +13,8 @@ import {
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "~/components/ui/table";
|
} from "~/components/ui/table";
|
||||||
import { useStoredDate } from "~/hooks/useStoredDate";
|
|
||||||
import apiService from "~/lib/api";
|
import apiService from "~/lib/api";
|
||||||
import {
|
import { EventBus, formatCurrency, formatNumber } from "~/lib/utils";
|
||||||
EventBus,
|
|
||||||
formatCurrency,
|
|
||||||
formatNumber,
|
|
||||||
handleDataValue,
|
|
||||||
} from "~/lib/utils";
|
|
||||||
import type { CalendarDate } from "~/types/util.type";
|
import type { CalendarDate } from "~/types/util.type";
|
||||||
import { DashboardLayout } from "../layout";
|
import { DashboardLayout } from "../layout";
|
||||||
|
|
||||||
|
|
@ -161,6 +154,7 @@ const columns: ColumnDef[] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export function ProjectManagementPage() {
|
export function ProjectManagementPage() {
|
||||||
|
const { jy } = jalaali.toJalaali(new Date());
|
||||||
const [projects, setProjects] = useState<ProjectData[]>([]);
|
const [projects, setProjects] = useState<ProjectData[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [loadingMore, setLoadingMore] = useState(false);
|
const [loadingMore, setLoadingMore] = useState(false);
|
||||||
|
|
@ -177,12 +171,10 @@ export function ProjectManagementPage() {
|
||||||
const fetchingRef = useRef(false);
|
const fetchingRef = useRef(false);
|
||||||
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||||
// const [date, setDate] = useState<CalendarDate>({
|
const [date, setDate] = useState<CalendarDate>({
|
||||||
// start: `${jy}/01/01`,
|
start: `${jy}/01/01`,
|
||||||
// end: `${jy}/12/30`,
|
end: `${jy}/12/30`,
|
||||||
// });
|
});
|
||||||
|
|
||||||
const [date, setDate] = useStoredDate();
|
|
||||||
|
|
||||||
const fetchProjects = async (reset = false) => {
|
const fetchProjects = async (reset = false) => {
|
||||||
// Prevent concurrent API calls
|
// Prevent concurrent API calls
|
||||||
|
|
@ -283,15 +275,11 @@ export function ProjectManagementPage() {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (date: CalendarDate) => {
|
EventBus.on("dateSelected", (date: CalendarDate) => {
|
||||||
if (date) setDate(date);
|
if (date) {
|
||||||
};
|
setDate(date);
|
||||||
|
}
|
||||||
EventBus.on("dateSelected", handler);
|
});
|
||||||
|
|
||||||
return () => {
|
|
||||||
EventBus.off("dateSelected", handler);
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
const loadMore = useCallback(() => {
|
const loadMore = useCallback(() => {
|
||||||
if (hasMore && !loading && !loadingMore && !fetchingRef.current) {
|
if (hasMore && !loading && !loadingMore && !fetchingRef.current) {
|
||||||
|
|
@ -300,10 +288,8 @@ export function ProjectManagementPage() {
|
||||||
}, [hasMore, loading, loadingMore]);
|
}, [hasMore, loading, loadingMore]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (date.end && date.start) {
|
|
||||||
fetchProjects(true);
|
fetchProjects(true);
|
||||||
fetchTotalCount();
|
fetchTotalCount();
|
||||||
}
|
|
||||||
}, [sortConfig, date]);
|
}, [sortConfig, date]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -797,88 +783,13 @@ export function ProjectManagementPage() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// const totalPages = Math.ceil(totalCount / pageSize);
|
const totalPages = Math.ceil(totalCount / pageSize);
|
||||||
|
|
||||||
const exportToExcel = async () => {
|
|
||||||
let arr = [];
|
|
||||||
const data = await fetchExcelData();
|
|
||||||
debugger;
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
|
||||||
let obj: Record<string, any> = {};
|
|
||||||
const project = data[i];
|
|
||||||
|
|
||||||
Object.entries(project).forEach(([pKey, pValue]) => {
|
|
||||||
Object.values(columns).forEach((col) => {
|
|
||||||
if (pKey === col.key) {
|
|
||||||
``;
|
|
||||||
obj[col.label] = handleDataValue(
|
|
||||||
pValue?.includes(",") ? pValue.replaceAll(",", "") : pValue
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
arr.push(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
// تبدیل دادهها به worksheet
|
|
||||||
const worksheet = XLSX.utils.json_to_sheet(arr);
|
|
||||||
|
|
||||||
// ساخت workbook
|
|
||||||
const workbook = XLSX.utils.book_new();
|
|
||||||
XLSX.utils.book_append_sheet(workbook, worksheet, "People");
|
|
||||||
|
|
||||||
// تبدیل به فایل باینری
|
|
||||||
const excelBuffer = XLSX.write(workbook, {
|
|
||||||
bookType: "xlsx",
|
|
||||||
type: "array",
|
|
||||||
});
|
|
||||||
|
|
||||||
const blob = new Blob([excelBuffer], {
|
|
||||||
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
||||||
});
|
|
||||||
saveAs(blob, "people.xls");
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchExcelData = async () => {
|
|
||||||
const fetchableColumns = columns.filter((c) => !c.computed);
|
|
||||||
const outputFields = fetchableColumns.map((c) => c.apiField ?? c.key);
|
|
||||||
const sortCol = columns.find((c) => c.key === sortConfig.field);
|
|
||||||
const sortField = sortCol?.computed
|
|
||||||
? undefined
|
|
||||||
: (sortCol?.apiField ?? sortCol?.key);
|
|
||||||
|
|
||||||
const response = await apiService.select({
|
|
||||||
ProcessName: "project",
|
|
||||||
OutputFields: outputFields,
|
|
||||||
Sorts: sortField ? [[sortField, sortConfig.direction]] : [],
|
|
||||||
Conditions: [
|
|
||||||
["start_date", ">=", date?.start || null, "and"],
|
|
||||||
["start_date", "<=", date?.end || null],
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const parsedData = JSON.parse(response.data);
|
|
||||||
return parsedData;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardLayout title="مدیریت پروژهها">
|
<DashboardLayout title="مدیریت پروژهها">
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* <div className="flex justify-end w-full mb-0 pl-2">
|
|
||||||
<Button
|
|
||||||
className="flex w-max justify-center rounded-xl mb-4 border-gray-500/20 border-2 cursor-pointer transition-all hover:bg-[#3F415A]/50 bg-[#3F415A] py-3 text-center items-center gap-3 "
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={exportToExcel}
|
|
||||||
>
|
|
||||||
<FileChartColumnIncreasing />
|
|
||||||
دانلود فایل اکسل
|
|
||||||
</Button>
|
|
||||||
</div> */}
|
|
||||||
|
|
||||||
{/* Data Table */}
|
{/* Data Table */}
|
||||||
<Card className="bg-transparent backdrop-blur-sm rounded-2xl overflow-hidden">
|
<Card className="bg-transparent backdrop-blur-sm rounded-2xl overflow-hidden">
|
||||||
{/* <div onClick={exportToExcel}>DownloadExcle</div> */}
|
|
||||||
<CardContent className="p-0">
|
<CardContent className="p-0">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -110,12 +110,12 @@ const menuItems: MenuItem[] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const bottomMenuItems: MenuItem[] = [
|
const bottomMenuItems: MenuItem[] = [
|
||||||
// {
|
{
|
||||||
// id: "settings",
|
id: "settings",
|
||||||
// label: "تنظیمات",
|
label: "تنظیمات",
|
||||||
// icon: Settings,
|
icon: Settings,
|
||||||
// href: "/dashboard/settings",
|
href: "/dashboard/settings",
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
id: "logout",
|
id: "logout",
|
||||||
label: "خروج",
|
label: "خروج",
|
||||||
|
|
@ -415,41 +415,17 @@ export function Sidebar({
|
||||||
<div className="flex items-center justify-start">
|
<div className="flex items-center justify-start">
|
||||||
{!isCollapsed ? (
|
{!isCollapsed ? (
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
<GalleryVerticalEnd
|
||||||
{/* آپادانا بندرامام */}
|
|
||||||
{"نوری" == "نوری" ? (
|
|
||||||
<img
|
|
||||||
src="/brand.svg"
|
|
||||||
alt="logo"
|
|
||||||
className="bg-green-400 p-1.5 rounded-lg w-auto max-h-[30px] object-contain"
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
) : (<GalleryVerticalEnd
|
|
||||||
color="black"
|
color="black"
|
||||||
size={32}
|
size={32}
|
||||||
strokeWidth={1}
|
strokeWidth={1}
|
||||||
className="bg-green-400 p-1.5 rounded-lg"
|
className="bg-green-400 p-1.5 rounded-lg"
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="font-persian">
|
<div className="font-persian">
|
||||||
|
|
||||||
{/* آپادانا بندرامام */}
|
|
||||||
{"نوری" == "نوری" ? (
|
|
||||||
<div>
|
|
||||||
<div className="text-sm font-semibold text-white">
|
|
||||||
پتروشیمی نوری
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-gray-400">نسخه ۰.۱</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
):(
|
|
||||||
|
|
||||||
<div className="text-sm font-semibold text-white">
|
<div className="text-sm font-semibold text-white">
|
||||||
داشبورد مدیریت فناوری و نوآوری
|
داشبورد مدیریت فناوری و نوآوری
|
||||||
</div>
|
</div>
|
||||||
)}
|
{/* <div className="text-xs text-gray-400">نسخه ۰.۱</div> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import jalaali from "jalaali-js";
|
||||||
import { useEffect, useReducer, useRef, useState } from "react";
|
import { useEffect, useReducer, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Bar,
|
Bar,
|
||||||
|
|
@ -11,7 +12,6 @@ import {
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
import { Dialog, DialogContent, DialogHeader } from "~/components/ui/dialog";
|
import { Dialog, DialogContent, DialogHeader } from "~/components/ui/dialog";
|
||||||
import { Skeleton } from "~/components/ui/skeleton";
|
import { Skeleton } from "~/components/ui/skeleton";
|
||||||
import { useStoredDate } from "~/hooks/useStoredDate";
|
|
||||||
import apiService from "~/lib/api";
|
import apiService from "~/lib/api";
|
||||||
import { EventBus, formatNumber } from "~/lib/utils";
|
import { EventBus, formatNumber } from "~/lib/utils";
|
||||||
import type { CalendarDate } from "~/types/util.type";
|
import type { CalendarDate } from "~/types/util.type";
|
||||||
|
|
@ -118,6 +118,7 @@ export function StrategicAlignmentPopup({
|
||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
}: StrategicAlignmentPopupProps) {
|
}: StrategicAlignmentPopupProps) {
|
||||||
|
const { jy } = jalaali.toJalaali(new Date());
|
||||||
const [data, setData] = useState<StrategicAlignmentData[]>([]);
|
const [data, setData] = useState<StrategicAlignmentData[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const contentRef = useRef<HTMLDivElement | null>(null);
|
const contentRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
@ -127,8 +128,10 @@ export function StrategicAlignmentPopup({
|
||||||
dropDownItems: [],
|
dropDownItems: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const [date, setDate] = useStoredDate();
|
const [date, setDate] = useState<CalendarDate>({
|
||||||
|
start: `${jy}/01/01`,
|
||||||
|
end: `${jy}/12/30`,
|
||||||
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
fetchData();
|
fetchData();
|
||||||
|
|
@ -136,15 +139,11 @@ export function StrategicAlignmentPopup({
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (date: CalendarDate) => {
|
EventBus.on("dateSelected", (date: CalendarDate) => {
|
||||||
if (date) setDate(date);
|
if (date) {
|
||||||
};
|
setDate(date);
|
||||||
|
}
|
||||||
EventBus.on("dateSelected", handler);
|
});
|
||||||
|
|
||||||
return () => {
|
|
||||||
EventBus.off("dateSelected", handler);
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import {
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
||||||
import { CustomBarChart } from "~/components/ui/custom-bar-chart";
|
import { CustomBarChart } from "~/components/ui/custom-bar-chart";
|
||||||
import { useStoredDate } from "~/hooks/useStoredDate";
|
|
||||||
import apiService from "~/lib/api";
|
import apiService from "~/lib/api";
|
||||||
import { EventBus, formatNumber } from "~/lib/utils";
|
import { EventBus, formatNumber } from "~/lib/utils";
|
||||||
import type { CalendarDate } from "~/types/util.type";
|
import type { CalendarDate } from "~/types/util.type";
|
||||||
|
|
@ -64,23 +63,17 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) {
|
||||||
const [counts, setCounts] = useState<EcosystemCounts | null>(null);
|
const [counts, setCounts] = useState<EcosystemCounts | null>(null);
|
||||||
const [processData, setProcessData] = useState<ProcessActorsData[]>([]);
|
const [processData, setProcessData] = useState<ProcessActorsData[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
// const [date, setDate] = useState<CalendarDate>();
|
const [date, setDate] = useState<CalendarDate>();
|
||||||
const [date, setDate] = useStoredDate();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (date: CalendarDate) => {
|
EventBus.on("dateSelected", (date: CalendarDate) => {
|
||||||
if (date) setDate(date);
|
if (date) {
|
||||||
};
|
setDate(date);
|
||||||
|
}
|
||||||
EventBus.on("dateSelected", handler);
|
});
|
||||||
|
|
||||||
return () => {
|
|
||||||
EventBus.off("dateSelected", handler);
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (date.end && date.start) fetchCounts();
|
fetchCounts();
|
||||||
}, [date]);
|
}, [date]);
|
||||||
|
|
||||||
const fetchCounts = async () => {
|
const fetchCounts = async () => {
|
||||||
|
|
@ -190,7 +183,7 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) {
|
||||||
{ label: "شتابدهنده", value: parseNumber(counts.accelerator_count) },
|
{ label: "شتابدهنده", value: parseNumber(counts.accelerator_count) },
|
||||||
{ label: "دانشگاه", value: parseNumber(counts.university_count) },
|
{ label: "دانشگاه", value: parseNumber(counts.university_count) },
|
||||||
{ label: "صندوق های مالی", value: parseNumber(counts.fund_count) },
|
{ label: "صندوق های مالی", value: parseNumber(counts.fund_count) },
|
||||||
{ label: "تامین کننده", value: parseNumber(counts.company_count) },
|
{ label: "شرکت", value: parseNumber(counts.company_count) },
|
||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,12 @@
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useStoredDate } from "~/hooks/useStoredDate";
|
|
||||||
import { EventBus } from "~/lib/utils";
|
import { EventBus } from "~/lib/utils";
|
||||||
import type { CalendarDate } from "~/types/util.type";
|
import type { CalendarDate } from "~/types/util.type";
|
||||||
import { useAuth } from "../../contexts/auth-context";
|
import { useAuth } from "../../contexts/auth-context";
|
||||||
import apiService from "../../lib/api";
|
import apiService from "../../lib/api";
|
||||||
|
|
||||||
const API_BASE_URL =
|
const API_BASE_URL =
|
||||||
//بندر امام
|
import.meta.env.VITE_API_URL || "https://inogen-back.pelekan.org/api";
|
||||||
// import.meta.env.VITE_API_URL || "https://inogen-back.pelekan.org/api";
|
|
||||||
//آپادانا
|
|
||||||
// import.meta.env.VITE_API_URL || "https://APADANA-IATM-back.pelekan.org/api";
|
|
||||||
//نوری
|
|
||||||
import.meta.env.VITE_API_URL || "https://NOPC-IATM-back.pelekan.org/api";
|
|
||||||
|
|
||||||
|
|
||||||
export interface Node {
|
export interface Node {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -80,19 +73,13 @@ export function NetworkGraph({
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const { token } = useAuth();
|
const { token } = useAuth();
|
||||||
|
|
||||||
// const [date, setDate] = useState<CalendarDate>();
|
const [date, setDate] = useState<CalendarDate>();
|
||||||
|
|
||||||
const [date, setDate] = useStoredDate();
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (date: CalendarDate) => {
|
EventBus.on("dateSelected", (date: CalendarDate) => {
|
||||||
if (date) setDate(date);
|
if (date) {
|
||||||
};
|
setDate(date);
|
||||||
|
}
|
||||||
EventBus.on("dateSelected", handler);
|
});
|
||||||
|
|
||||||
return () => {
|
|
||||||
EventBus.off("dateSelected", handler);
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -115,8 +102,8 @@ export function NetworkGraph({
|
||||||
return await apiService.call<any>({
|
return await apiService.call<any>({
|
||||||
get_values_workflow_function: {
|
get_values_workflow_function: {
|
||||||
stage_id: stage_id,
|
stage_id: stage_id,
|
||||||
// start_date: date?.start || null,
|
start_date: date?.start || null,
|
||||||
// end_date: date?.end || null,
|
end_date: date?.end || null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -133,10 +120,7 @@ export function NetworkGraph({
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await apiService.call<any[]>({
|
const res = await apiService.call<any[]>({
|
||||||
graph_production_function: {
|
graph_production_function: {},
|
||||||
start_date: date.start || null,
|
|
||||||
end_date: date.end || null,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
if (aborted) return;
|
if (aborted) return;
|
||||||
|
|
||||||
|
|
@ -149,9 +133,7 @@ export function NetworkGraph({
|
||||||
// نود مرکزی
|
// نود مرکزی
|
||||||
const centerNode: Node = {
|
const centerNode: Node = {
|
||||||
id: "center",
|
id: "center",
|
||||||
// label: "پتروشیمی بندر امام",
|
label: "پتروشیمی بندر امام",
|
||||||
label: "پتروشیمی نوری",
|
|
||||||
// label: "پتروشیمی آپادانا",
|
|
||||||
category: "center",
|
category: "center",
|
||||||
stageid: 0,
|
stageid: 0,
|
||||||
isCenter: true,
|
isCenter: true,
|
||||||
|
|
@ -206,7 +188,7 @@ export function NetworkGraph({
|
||||||
aborted = true;
|
aborted = true;
|
||||||
controller.abort();
|
controller.abort();
|
||||||
};
|
};
|
||||||
}, [isMounted, token, getImageUrl, date]);
|
}, [isMounted, token, getImageUrl]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isMounted || !svgRef.current || isLoading || nodes.length === 0)
|
if (!isMounted || !svgRef.current || isLoading || nodes.length === 0)
|
||||||
|
|
@ -250,7 +232,7 @@ export function NetworkGraph({
|
||||||
مشاور: "#10B981",
|
مشاور: "#10B981",
|
||||||
"دانش بنیان": "#F59E0B",
|
"دانش بنیان": "#F59E0B",
|
||||||
استارتاپ: "#EF4444",
|
استارتاپ: "#EF4444",
|
||||||
"تامین کننده": "#8B5CF6",
|
شرکت: "#8B5CF6",
|
||||||
صندوق: "#06B6D4",
|
صندوق: "#06B6D4",
|
||||||
شتابدهنده: "#9333EA",
|
شتابدهنده: "#9333EA",
|
||||||
"مرکز نوآوری": "#F472B6",
|
"مرکز نوآوری": "#F472B6",
|
||||||
|
|
@ -375,62 +357,13 @@ export function NetworkGraph({
|
||||||
nodeGroup.each(function (d) {
|
nodeGroup.each(function (d) {
|
||||||
const group = d3.select(this);
|
const group = d3.select(this);
|
||||||
|
|
||||||
// if (d.isCenter) {
|
|
||||||
// const rect = group
|
|
||||||
// .append("rect")
|
|
||||||
// .attr("width", 200)
|
|
||||||
// .attr("height", 80)
|
|
||||||
// .attr("x", -100) // نصف عرض جدید منفی
|
|
||||||
// .attr("y", -40) // نصف ارتفاع جدید منفی
|
|
||||||
// .attr("rx", 8)
|
|
||||||
// .attr("ry", 8)
|
|
||||||
// .attr("fill", categoryToColor[d.category] || "#94A3B8")
|
|
||||||
// .attr("stroke", "#FFFFFF")
|
|
||||||
// .attr("stroke-width", 3)
|
|
||||||
// .style("pointer-events", "none");
|
|
||||||
|
|
||||||
// if (d.imageUrl || d.isCenter) {
|
|
||||||
// const pattern = defs
|
|
||||||
// .append("pattern")
|
|
||||||
// .attr("id", `image-${d.id}`)
|
|
||||||
// .attr("x", 0)
|
|
||||||
// .attr("y", 0)
|
|
||||||
// .attr("width", 1)
|
|
||||||
// .attr("height", 1);
|
|
||||||
|
|
||||||
// pattern
|
|
||||||
// .append("image")
|
|
||||||
// .attr("x", 0)
|
|
||||||
// .attr("y", 0)
|
|
||||||
// .attr("width", 200) // ← هماندازه با مستطیل
|
|
||||||
// .attr("height", 80)
|
|
||||||
// .attr("href", d.isCenter ? "/main-circle.png" : d.imageUrl)
|
|
||||||
// .attr("preserveAspectRatio", "xMidYMid slice");
|
|
||||||
|
|
||||||
// rect.attr("fill", `url(#image-${d.id})`);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// راه حل سادهتر - ابعاد ثابت با حفظ نسبت
|
|
||||||
if (d.isCenter) {
|
if (d.isCenter) {
|
||||||
|
|
||||||
//آپادانا
|
|
||||||
// const fixedWidth = 196;
|
|
||||||
// const fixedHeight = 200; // یا میتوانید براساس نسبت تصویر محاسبه کنید
|
|
||||||
|
|
||||||
//بندر امام
|
|
||||||
// const fixedWidth = 100;
|
|
||||||
// const fixedHeight = 80; // یا میتوانید براساس نسبت تصویر محاسبه کنید
|
|
||||||
|
|
||||||
//نوری
|
|
||||||
const fixedWidth = 186;
|
|
||||||
const fixedHeight = 70; // یا میتوانید براساس نسبت تصویر محاسبه کنید
|
|
||||||
|
|
||||||
const rect = group
|
const rect = group
|
||||||
.append("rect")
|
.append("rect")
|
||||||
.attr("width", fixedWidth)
|
.attr("width", 200)
|
||||||
.attr("height", fixedHeight)
|
.attr("height", 80)
|
||||||
.attr("x", -fixedWidth / 2)
|
.attr("x", -100) // نصف عرض جدید منفی
|
||||||
.attr("y", -fixedHeight / 2)
|
.attr("y", -40) // نصف ارتفاع جدید منفی
|
||||||
.attr("rx", 8)
|
.attr("rx", 8)
|
||||||
.attr("ry", 8)
|
.attr("ry", 8)
|
||||||
.attr("fill", categoryToColor[d.category] || "#94A3B8")
|
.attr("fill", categoryToColor[d.category] || "#94A3B8")
|
||||||
|
|
@ -438,6 +371,7 @@ const fixedHeight = 70; // یا میتوانید براساس نسبت تصو
|
||||||
.attr("stroke-width", 3)
|
.attr("stroke-width", 3)
|
||||||
.style("pointer-events", "none");
|
.style("pointer-events", "none");
|
||||||
|
|
||||||
|
if (d.imageUrl || d.isCenter) {
|
||||||
const pattern = defs
|
const pattern = defs
|
||||||
.append("pattern")
|
.append("pattern")
|
||||||
.attr("id", `image-${d.id}`)
|
.attr("id", `image-${d.id}`)
|
||||||
|
|
@ -450,15 +384,14 @@ const fixedHeight = 70; // یا میتوانید براساس نسبت تصو
|
||||||
.append("image")
|
.append("image")
|
||||||
.attr("x", 0)
|
.attr("x", 0)
|
||||||
.attr("y", 0)
|
.attr("y", 0)
|
||||||
.attr("width", fixedWidth)
|
.attr("width", 200) // ← هماندازه با مستطیل
|
||||||
.attr("height", fixedHeight)
|
.attr("height", 80)
|
||||||
.attr("href", d.isCenter ? "/main-circle.png" : d.imageUrl)
|
.attr("href", d.isCenter ? "/main-circle.png" : d.imageUrl)
|
||||||
.attr("preserveAspectRatio", "xMidYMid meet"); // حفظ نسبت تصویر
|
.attr("preserveAspectRatio", "xMidYMid slice");
|
||||||
|
|
||||||
rect.attr("fill", `url(#image-${d.id})`);
|
rect.attr("fill", `url(#image-${d.id})`);
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
} else {
|
||||||
const circle = group
|
const circle = group
|
||||||
.append("circle")
|
.append("circle")
|
||||||
.attr("r", 25)
|
.attr("r", 25)
|
||||||
|
|
@ -501,22 +434,7 @@ const fixedHeight = 70; // یا میتوانید براساس نسبت تصو
|
||||||
.append("text")
|
.append("text")
|
||||||
.text((d) => d.label)
|
.text((d) => d.label)
|
||||||
.attr("text-anchor", "middle")
|
.attr("text-anchor", "middle")
|
||||||
.attr("dy", (d) => {
|
.attr("dy", (d) => (d.isCenter ? 50 : 45))
|
||||||
if (d.isCenter) {
|
|
||||||
|
|
||||||
//آپادانا
|
|
||||||
// const centerNodeHeight = 200; // ارتفاع نود مرکزی
|
|
||||||
|
|
||||||
//بندر امام
|
|
||||||
// const centerNodeHeight = 80; // ارتفاع نود مرکزی
|
|
||||||
|
|
||||||
//نوری
|
|
||||||
const centerNodeHeight = 80; // ارتفاع نود مرکزی
|
|
||||||
|
|
||||||
return centerNodeHeight / 2 + 20; // نصف ارتفاع + فاصله 20px
|
|
||||||
}
|
|
||||||
return 45; // برای نودهای دیگر
|
|
||||||
})
|
|
||||||
.attr("font-size", (d) => (d.isCenter ? "14px" : "12px"))
|
.attr("font-size", (d) => (d.isCenter ? "14px" : "12px"))
|
||||||
.attr("font-weight", "bold")
|
.attr("font-weight", "bold")
|
||||||
.attr("fill", "#F9FAFB")
|
.attr("fill", "#F9FAFB")
|
||||||
|
|
@ -566,7 +484,6 @@ const fixedHeight = 70; // یا میتوانید براساس نسبت تصو
|
||||||
onLoadingChange?.(true);
|
onLoadingChange?.(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (date.start && date.end) {
|
|
||||||
const res = await callAPI(d.stageid);
|
const res = await callAPI(d.stageid);
|
||||||
const responseData = JSON.parse(res.data);
|
const responseData = JSON.parse(res.data);
|
||||||
const fieldValues =
|
const fieldValues =
|
||||||
|
|
@ -596,7 +513,6 @@ const fixedHeight = 70; // یا میتوانید براساس نسبت تصو
|
||||||
};
|
};
|
||||||
|
|
||||||
onNodeClick(companyDetails);
|
onNodeClick(companyDetails);
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch company details:", error);
|
console.error("Failed to fetch company details:", error);
|
||||||
// Keep the basic details already shown
|
// Keep the basic details already shown
|
||||||
|
|
@ -625,7 +541,7 @@ const fixedHeight = 70; // یا میتوانید براساس نسبت تصو
|
||||||
return () => {
|
return () => {
|
||||||
simulation.stop();
|
simulation.stop();
|
||||||
};
|
};
|
||||||
}, [nodes, links, isLoading, isMounted, onNodeClick, callAPI, date]);
|
}, [nodes, links, isLoading, isMounted, onNodeClick, callAPI]);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -23,34 +23,22 @@ export function BaseCard({
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm py-2 pb-0 grid items-center",
|
"bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm py-4 grid items-center",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{Icon && title ? (
|
{Icon && title ? (
|
||||||
<CardHeader
|
<CardHeader className={cn("border-b-2 border-gray-500/20 py-2 px-0 pb-4", headerClassName)}>
|
||||||
className={cn(
|
<CardTitle className="text-white text-sm text-right font-persian px-4 my-auto items-center flex w-full justify-between">{title} {<Icon />} </CardTitle>
|
||||||
"border-b-2 border-gray-500/20 py-2 px-0 pb-4",
|
|
||||||
headerClassName
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<CardTitle className="text-white text-sm text-right font-persian px-4 my-auto items-center flex w-full justify-between">
|
|
||||||
{title} {<Icon />}{" "}
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
) : withHeader && title ? (
|
) :
|
||||||
<CardHeader
|
withHeader && title ? (
|
||||||
className={cn("pb-2 border-b-2 border-gray-500/20", headerClassName)}
|
<CardHeader className={cn("pb-2 border-b-2 border-gray-500/20", headerClassName)}>
|
||||||
>
|
<CardTitle className="text-white text-sm text-right font-persian px-4">{title}</CardTitle>
|
||||||
<CardTitle className="text-white text-sm text-right font-persian px-4">
|
|
||||||
{title}
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
) : title ? (
|
) : title ? (
|
||||||
<div className="border-b-2 border-gray-500/20 pb-2">
|
<div className="border-b-2 border-gray-500/20 pb-2">
|
||||||
<h3 className="text-sm font-bold text-white text-right font-persian px-4">
|
<h3 className="text-sm font-bold text-white text-right font-persian px-4">{title}</h3>
|
||||||
{title}
|
|
||||||
</h3>
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<CardContent className={cn("py-2 px-4", contentClassName)}>
|
<CardContent className={cn("py-2 px-4", contentClassName)}>
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export function CustomBarChart({
|
||||||
// Loading skeleton
|
// Loading skeleton
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className={`space-y-6 p-4 pt-0 ${className}`} style={{ height }}>
|
<div className={`space-y-6 p-4 ${className}`} style={{ height }}>
|
||||||
{title && (
|
{title && (
|
||||||
<div className="h-7 bg-gray-600 rounded animate-pulse mb-4 w-1/2"></div>
|
<div className="h-7 bg-gray-600 rounded animate-pulse mb-4 w-1/2"></div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -71,7 +71,7 @@ export function CustomBarChart({
|
||||||
<div className={`space-y-6 ${className}`} style={{ height }}>
|
<div className={`space-y-6 ${className}`} style={{ height }}>
|
||||||
{title && (
|
{title && (
|
||||||
<div className="border-b-[#3F415A] border-b-2">
|
<div className="border-b-[#3F415A] border-b-2">
|
||||||
<h3 className="text-sm font-semibold text-white font-persian text-right px-4 pb-3">
|
<h3 className="text-sm font-semibold text-white font-persian text-right p-4">
|
||||||
{title}
|
{title}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@ export function MetricCard({
|
||||||
percentLabel = "درصد به کل",
|
percentLabel = "درصد به کل",
|
||||||
}: MetricCardProps) {
|
}: MetricCardProps) {
|
||||||
return (
|
return (
|
||||||
<BaseCard title={title} className="h-full">
|
<BaseCard title={title}>
|
||||||
<div className="flex items-center justify-center flex-col">
|
<div className="flex items-center justify-center flex-col">
|
||||||
<div className="flex items-center gap-4 h-full">
|
<div className="flex items-center gap-4">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-3xl font-bold text-green-400">
|
<p className="text-3xl font-bold text-green-400">
|
||||||
{formatNumber(value)}
|
{formatNumber(value)}
|
||||||
|
|
|
||||||
|
|
@ -6,27 +6,7 @@ import { cn, formatNumber } from "~/lib/utils"
|
||||||
const Progress = React.forwardRef<
|
const Progress = React.forwardRef<
|
||||||
React.ElementRef<typeof ProgressPrimitive.Root>,
|
React.ElementRef<typeof ProgressPrimitive.Root>,
|
||||||
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
|
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
|
||||||
>(({ className, value, ...props }, ref) => {
|
>(({ className, value, ...props }, ref) => (
|
||||||
// Dynamic scaling logic based on value ranges
|
|
||||||
const getScaledValue = (inputValue: number) => {
|
|
||||||
const numValue = Number(inputValue);
|
|
||||||
if (numValue <= 1) {
|
|
||||||
return numValue * 100;
|
|
||||||
}
|
|
||||||
else if (numValue <= 10) {
|
|
||||||
return (numValue / 10) * 100;
|
|
||||||
} else if (numValue <= 50) {
|
|
||||||
return (numValue / 50) * 100;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return numValue
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const scaledValue = getScaledValue(Number(value) || 0);
|
|
||||||
const displayValue = Number(value) || 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ProgressPrimitive.Root
|
<ProgressPrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -36,16 +16,14 @@ const Progress = React.forwardRef<
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="left-0 text-sm absolute z-10 px-2 text-[#5F6284]">۰%</span>
|
<span className="left-0 text-sm absolute z-10 px-2 text-[#5F6284]">۰%</span>
|
||||||
<span className="w-full right-0 text-sm absolute z-10 px-2 text-[#5F6284]">
|
<span className="w-full right-0 text-sm absolute z-10 px-2 text-[#5F6284]"
|
||||||
{formatNumber(displayValue.toFixed(2))}%
|
>{formatNumber(Math.ceil(value || 0 * 10) / 10)}%</span>
|
||||||
</span>
|
|
||||||
<ProgressPrimitive.Indicator
|
<ProgressPrimitive.Indicator
|
||||||
className="h-full w-full flex-1 bg-pr-green transition-all z-20"
|
className="h-full w-full flex-1 bg-pr-green transition-all"
|
||||||
style={{ transform: `translateX(-${100-scaledValue}%)` }}
|
style={{ transform: `translateX(-${15 - (value || 0)}%)` }}
|
||||||
/>
|
/>
|
||||||
</ProgressPrimitive.Root>
|
</ProgressPrimitive.Root>
|
||||||
)
|
))
|
||||||
})
|
|
||||||
Progress.displayName = ProgressPrimitive.Root.displayName
|
Progress.displayName = ProgressPrimitive.Root.displayName
|
||||||
|
|
||||||
export { Progress }
|
export { Progress }
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ function TooltipContent({
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<TooltipPrimitive.Arrow className={cn("bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]",className)} />
|
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
||||||
</TooltipPrimitive.Content>
|
</TooltipPrimitive.Content>
|
||||||
</TooltipPrimitive.Portal>
|
</TooltipPrimitive.Portal>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import jalaali from "jalaali-js";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import type { CalendarDate } from "~/types/util.type";
|
|
||||||
|
|
||||||
const { jy } = jalaali.toJalaali(new Date());
|
|
||||||
|
|
||||||
export function useStoredDate(): [
|
|
||||||
CalendarDate,
|
|
||||||
React.Dispatch<React.SetStateAction<CalendarDate>>,
|
|
||||||
] {
|
|
||||||
const [date, setDate] = useState<CalendarDate>({});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const storedDate = localStorage.getItem("dateSelected");
|
|
||||||
|
|
||||||
if (storedDate) {
|
|
||||||
setDate(JSON.parse(storedDate));
|
|
||||||
} else {
|
|
||||||
setDate({
|
|
||||||
start: `${jy}/01/01`,
|
|
||||||
end: `${jy}/12/30`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [jy]);
|
|
||||||
|
|
||||||
return [date, setDate];
|
|
||||||
}
|
|
||||||
|
|
@ -162,17 +162,10 @@ class ApiService {
|
||||||
|
|
||||||
// Innovation process function call wrapper
|
// Innovation process function call wrapper
|
||||||
public async call<T = any>(payload: any) {
|
public async call<T = any>(payload: any) {
|
||||||
//بندر امام
|
const url = "https://inogen-back.pelekan.org/api/call";
|
||||||
// const url = "https://inogen-back.pelekan.org/api/call";
|
|
||||||
//آپادانا
|
|
||||||
// const url = "https://APADANA-IATM-back.pelekan.org/api/call";
|
|
||||||
//نوری
|
|
||||||
const url = "https://NOPC-IATM-back.pelekan.org/api/call";
|
|
||||||
return this.postAbsolute<T>(url, payload);
|
return this.postAbsolute<T>(url, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// GET request
|
// GET request
|
||||||
public async get<T = any>(endpoint: string): Promise<ApiResponse<T>> {
|
public async get<T = any>(endpoint: string): Promise<ApiResponse<T>> {
|
||||||
return this.request<T>(endpoint, {
|
return this.request<T>(endpoint, {
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,28 @@
|
||||||
import moment from "moment-jalaali";
|
import type { Route } from "./+types/ecosystem";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { ProtectedRoute } from "~/components/auth/protected-route";
|
import { ProtectedRoute } from "~/components/auth/protected-route";
|
||||||
import { DashboardLayout } from "~/components/dashboard/layout";
|
import { DashboardLayout } from "~/components/dashboard/layout";
|
||||||
import { InfoPanel } from "~/components/ecosystem/info-panel";
|
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
||||||
import { NetworkGraph } from "~/components/ecosystem/network-graph";
|
|
||||||
import { Card, CardContent } from "~/components/ui/card";
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "~/components/ui/dialog";
|
} from "~/components/ui/dialog";
|
||||||
|
import { NetworkGraph } from "~/components/ecosystem/network-graph";
|
||||||
|
import { InfoPanel } from "~/components/ecosystem/info-panel";
|
||||||
import { useAuth } from "~/contexts/auth-context";
|
import { useAuth } from "~/contexts/auth-context";
|
||||||
import type { Route } from "./+types/ecosystem";
|
import moment from "moment-jalaali";
|
||||||
|
|
||||||
// Get API base URL at module level to avoid process.env access in browser
|
// Get API base URL at module level to avoid process.env access in browser
|
||||||
const API_BASE_URL =
|
const API_BASE_URL =
|
||||||
//بندر امام
|
import.meta.env.VITE_API_URL || "https://inogen-back.pelekan.org/api";
|
||||||
// import.meta.env.VITE_API_URL || "https://inogen-back.pelekan.org/api";
|
|
||||||
//آپادانا
|
|
||||||
// import.meta.env.VITE_API_URL || "https://APADANA-IATM-back.pelekan.org/api";
|
|
||||||
//نوری
|
|
||||||
import.meta.env.VITE_API_URL || "https://NOPC-IATM-back.pelekan.org/api";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Import the CompanyDetails type
|
// Import the CompanyDetails type
|
||||||
import { Hexagon } from "lucide-react";
|
|
||||||
import type { CompanyDetails } from "~/components/ecosystem/network-graph";
|
import type { CompanyDetails } from "~/components/ecosystem/network-graph";
|
||||||
|
import { formatNumber } from "~/lib/utils";
|
||||||
|
import { Hexagon } from "lucide-react";
|
||||||
|
|
||||||
export function meta({}: Route.MetaArgs) {
|
export function meta({}: Route.MetaArgs) {
|
||||||
return [
|
return [
|
||||||
|
|
@ -94,10 +89,7 @@ export default function EcosystemPage() {
|
||||||
<div className="lg:col-span-8 h-full">
|
<div className="lg:col-span-8 h-full">
|
||||||
<Card className="h-full overflow-hidden bg-transparent border-[#3F415A]">
|
<Card className="h-full overflow-hidden bg-transparent border-[#3F415A]">
|
||||||
<CardContent className="p-0 h-full bg-transparent">
|
<CardContent className="p-0 h-full bg-transparent">
|
||||||
<NetworkGraph
|
<NetworkGraph onNodeClick={handleNodeClick} onLoadingChange={handleLoadingChange} />
|
||||||
onNodeClick={handleNodeClick}
|
|
||||||
onLoadingChange={handleLoadingChange}
|
|
||||||
/>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -233,9 +225,7 @@ export default function EcosystemPage() {
|
||||||
<span className="text-right min-w-1/3">
|
<span className="text-right min-w-1/3">
|
||||||
<span className="font-persian text-sm font-normal text-right">
|
<span className="font-persian text-sm font-normal text-right">
|
||||||
{handleValue(field.V)}
|
{handleValue(field.V)}
|
||||||
{field.U && (
|
{field.U && <span className="mr-1">({field.U})</span>}
|
||||||
<span className="mr-1">({field.U})</span>
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export interface CalendarDate {
|
export interface CalendarDate {
|
||||||
start?: string;
|
start: string;
|
||||||
end?: string;
|
end: string;
|
||||||
sinceMonth?: string;
|
sinceMonth?: string;
|
||||||
untilMonth?: string;
|
untilMonth?: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
5128
package-lock.json
generated
|
|
@ -26,7 +26,6 @@
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"d3": "^7.9.0",
|
"d3": "^7.9.0",
|
||||||
"file-saver": "^2.0.5",
|
|
||||||
"graphology": "^0.26.0",
|
"graphology": "^0.26.0",
|
||||||
"isbot": "^5.1.27",
|
"isbot": "^5.1.27",
|
||||||
"lucide-react": "^0.525.0",
|
"lucide-react": "^0.525.0",
|
||||||
|
|
@ -36,13 +35,11 @@
|
||||||
"react-hot-toast": "^2.5.2",
|
"react-hot-toast": "^2.5.2",
|
||||||
"react-router": "^7.7.0",
|
"react-router": "^7.7.0",
|
||||||
"recharts": "^2.15.4",
|
"recharts": "^2.15.4",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1"
|
||||||
"xlsx-js-style": "^1.2.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-router/dev": "^7.7.0",
|
"@react-router/dev": "^7.7.0",
|
||||||
"@tailwindcss/vite": "^4.1.4",
|
"@tailwindcss/vite": "^4.1.4",
|
||||||
"@types/file-saver": "^2.0.7",
|
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19.1.2",
|
"@types/react": "^19.1.2",
|
||||||
"@types/react-dom": "^19.1.2",
|
"@types/react-dom": "^19.1.2",
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
|
@ -1,13 +1,23 @@
|
||||||
<svg width="60" height="16" viewBox="0 0 60 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="76" height="38" viewBox="0 0 76 38" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<g clip-path="url(#clip0_2855_3816)">
|
<g clip-path="url(#clip0_1007_3463)">
|
||||||
<path d="M34.9888 10.8078C35.4446 11.1829 35.9075 11.4573 36.4274 11.6275C38.8345 12.4228 41.3485 11.103 41.993 8.71017C42.6304 6.33465 40.9497 3.89315 38.45 3.56322C36.1746 3.26107 34.1164 4.67457 33.6428 6.86602C33.5538 7.26888 33.5538 7.68217 33.5538 8.09545C33.5538 10.6446 33.5538 13.1938 33.5538 15.7429C33.5538 15.7985 33.5538 15.8506 33.5538 15.9062C33.5574 15.9999 33.5253 16.0312 33.4363 15.9791C32.8345 15.6249 32.19 15.3331 31.6239 14.9233C31.0577 14.5135 30.7408 13.9405 30.7194 13.2459C30.698 12.4506 30.7087 11.6553 30.7087 10.8599C30.7087 8.96369 30.7194 7.07092 30.7052 5.17468C30.698 4.16057 31.136 3.4243 32.0369 2.92419C33.5431 2.0872 35.0458 1.24327 36.5414 0.388917C37.4173 -0.107719 38.2755 -0.111192 39.1479 0.388917C40.6434 1.2398 42.1461 2.0872 43.6523 2.92419C44.5639 3.42777 44.9948 4.17793 44.9912 5.19552C44.9805 6.88685 44.9912 8.58167 44.9912 10.273C44.9912 11.0197 44.7348 11.6587 44.1402 12.1449C44.0084 12.2526 43.8624 12.3464 43.7129 12.4297C42.1817 13.2945 40.6506 14.1523 39.1194 15.0171C38.2648 15.4998 37.4173 15.4998 36.5663 15.0136C36.0891 14.7427 35.6084 14.4753 35.1313 14.2044C35.0601 14.1662 34.9888 14.1384 34.9888 14.0308C34.9924 12.9715 34.9888 11.9157 34.9888 10.8113V10.8078Z" fill="#2E3144"/>
|
<path d="M21.8536 12.3231C22.9134 12.3231 23.8351 12.2642 24.7464 12.3364C26.3737 12.4645 27.2042 11.1349 27.26 9.99516C27.2997 9.18825 27.3276 8.3725 27.26 7.56853C27.1792 6.60407 27.8745 6.18736 28.4771 5.76623C28.9931 5.40548 29.4811 5.73237 29.4664 6.36847C29.4223 8.29888 29.7472 10.2632 28.9137 12.117C31.0069 12.6471 32.1872 11.7842 32.2505 9.72128C32.2696 9.09696 32.3225 8.46085 32.2343 7.8483C32.0946 6.87206 32.6958 6.33461 33.3294 5.84722C33.9203 5.39223 34.4789 5.68966 34.4818 6.42001C34.4877 7.59651 34.4656 8.77301 34.4377 9.94952C34.4186 10.7211 34.2187 11.4617 33.8821 12.1744C35.355 12.435 36.378 11.5972 36.5089 10.0688C36.5647 9.41059 36.6132 8.73031 36.5059 8.08684C36.3295 7.02225 36.9778 6.46271 37.648 5.88403C37.9229 5.64696 38.2581 5.60132 38.5741 5.74856C38.8622 5.88403 38.7608 6.19619 38.752 6.43621C38.7005 7.86597 38.8872 9.30163 38.5991 10.727C38.4977 11.2306 38.2728 11.6914 38.1596 12.1921C39.4355 12.3923 40.4012 11.7003 40.5526 10.403C40.6541 9.53134 40.6746 8.6346 40.6026 7.76143C40.5203 6.77193 41.1494 6.26834 41.7962 5.81188C42.3739 5.40401 42.8663 5.70292 42.8619 6.40528C42.8546 7.76437 42.9619 9.12641 42.7664 10.4811C42.4209 12.8827 40.757 14.4067 38.361 14.414C31.6037 14.4332 24.8479 14.4258 18.0906 14.414C16.2781 14.4111 14.967 13.5335 14.0277 12.011C13.4809 11.1231 13.644 10.1616 13.6778 9.21917C13.6925 8.80688 14.3481 8.72442 14.7185 9.11315C15.1492 9.56373 15.4579 10.085 15.6696 10.699C16.1091 11.9742 17.2586 12.5322 18.5712 12.2171C19.4988 11.9933 20.3646 10.8801 20.3293 9.95688C20.2925 9.00861 19.2636 7.78351 18.3287 7.75554C16.459 7.69958 14.5833 7.70547 12.7136 7.75848C11.8728 7.78204 11.2392 8.55362 11.1878 9.49747C11.1422 10.3412 11.1672 11.1893 11.1687 12.0345C11.1687 12.5146 11.1363 12.9887 11.0261 13.4569C10.6557 15.034 9.33859 16.1368 7.71725 16.1795C6.68977 16.206 5.66082 16.2075 4.63333 16.1781C2.38874 16.1118 0.483709 14.3021 0.376404 12.061C0.299968 10.484 0.339656 8.89965 0.348475 7.31969C0.351415 6.74984 1.17311 5.74709 1.69934 5.60426C2.1903 5.47027 2.41814 5.64107 2.42255 6.21092C2.43284 7.90279 2.43872 9.59465 2.41226 11.2865C2.40197 11.9418 2.58571 12.5204 2.92527 13.0564C3.36478 13.75 3.99391 14.1667 4.82589 14.1961C5.55939 14.2211 6.29436 14.2197 7.02933 14.2005C8.33757 14.1667 9.02991 13.4496 9.05196 12.1111C9.06518 11.3763 9.02991 10.6401 9.05048 9.90387C9.06371 9.42826 9.1034 8.94824 9.19454 8.48146C9.49734 6.93096 10.763 5.77801 12.3417 5.72942C14.2849 5.66905 16.2341 5.64696 18.1758 5.72206C20.2822 5.80452 21.9859 7.35944 22.4034 9.42973C22.5151 9.98044 22.4548 10.5282 22.2814 11.0612C22.1579 11.4382 22.0271 11.8137 21.8521 12.3276L21.8536 12.3231Z" fill="#3F415A"/>
|
||||||
<path d="M10.7145 11.4608V0.0486193C10.743 0.0486193 10.768 0.0416734 10.7822 0.0486193C11.3448 0.36466 11.9145 0.670282 12.4629 1.00369C13.1929 1.44823 13.5632 2.11851 13.5668 2.94855C13.5774 5.91448 13.5774 8.88387 13.5668 11.8498C13.5632 12.7493 13.1252 13.43 12.324 13.8815C11.505 14.3434 10.6825 14.7983 9.86706 15.2672C9.71039 15.3575 9.66053 15.3262 9.57507 15.1838C7.39585 11.4955 5.21306 7.81072 3.03027 4.12937C2.98754 4.05643 2.94125 3.9835 2.86647 3.85847V15.3123C2.79881 15.3506 2.76677 15.3123 2.73472 15.295C2.17211 14.9755 1.60237 14.6733 1.05757 14.333C0.409496 13.9301 0.0818991 13.3258 0.0142433 12.5826C0.00356083 12.4819 0.00712166 12.3812 0.00712166 12.2804C0 9.41176 0.00712166 6.53961 0 3.66746C0 2.67072 0.4273 1.93792 1.31751 1.44823C2.11869 1.00716 2.91276 0.559147 3.70326 0.100714C3.84926 0.0173625 3.90623 0.0277815 3.99169 0.173646C6.17448 3.86542 8.36083 7.55719 10.5472 11.249C10.5899 11.3219 10.6362 11.3948 10.6825 11.4678C10.6932 11.4643 10.7074 11.4608 10.7181 11.4573L10.7145 11.4608Z" fill="#2E3144"/>
|
<path d="M54.2581 32.2824C57.7801 32.2824 61.1565 32.3074 64.5315 32.2706C65.9 32.2559 66.8084 31.2958 66.8951 29.8587C66.9466 29.0179 66.8834 28.1698 66.8775 27.3261C66.8731 26.6208 67.5904 25.7211 68.2739 25.5665C68.787 25.4501 69.006 25.6077 69.0045 26.1775C68.9986 27.6853 68.956 29.1917 68.9472 30.6995C68.9442 31.2296 68.5371 31.6404 68.5503 32.2397C69.6454 32.3354 70.7405 32.3722 71.8194 32.2485C72.9395 32.1204 73.7715 31.1368 73.8332 29.9588C73.8803 29.0783 73.8421 28.1948 73.8523 27.3113C73.8597 26.6001 74.4668 25.7932 75.1327 25.5885C75.6795 25.4207 75.9779 25.6077 75.9837 26.182C75.997 27.5425 76.0249 28.9045 75.9632 30.2636C75.9117 31.3989 75.3958 32.3972 74.6696 33.2439C74.1096 33.8962 73.4011 34.2614 72.4559 34.2584C63.9744 34.2305 55.4943 34.2437 47.0128 34.2364C46.7115 34.2364 46.2984 34.4042 46.1323 34.0258C45.9368 33.5811 46.2617 33.2896 46.566 32.9965C47.0613 32.518 47.5729 32.2058 48.3167 32.2809C49.0428 32.3545 49.7836 32.3045 50.5171 32.2839C51.258 32.2633 51.8607 31.9349 52.3134 31.3533C52.8411 30.6774 53.1277 29.9456 52.7029 29.1063C52.2781 28.2684 51.6372 27.6912 50.6612 27.6456C49.8557 27.6088 49.0457 27.6485 48.2387 27.6397C48.0021 27.6368 47.6875 27.7457 47.5832 27.4174C47.4935 27.1361 47.467 26.7989 47.6934 26.581C48.2123 26.0848 48.665 25.4501 49.5396 25.5724C49.8645 25.618 50.1996 25.5782 50.5304 25.5812C53.8495 25.6047 55.803 28.6586 54.4683 31.7361C54.411 31.8672 54.3654 32.0012 54.2581 32.2824Z" fill="#3F415A"/>
|
||||||
<path d="M29.2664 7.69613C29.2664 8.5609 29.2664 9.4222 29.2664 10.287C29.27 11.2351 28.8498 11.9436 28.013 12.4159C26.4534 13.298 24.8937 14.1802 23.327 15.0519C22.5365 15.4895 21.7282 15.493 20.9377 15.0519C19.3709 14.1802 17.8148 13.2946 16.2516 12.4159C15.4112 11.9436 14.9946 11.2281 14.9946 10.287C14.9946 8.5609 14.9946 6.83483 14.9946 5.10877C14.9946 4.16064 15.4148 3.44174 16.2623 2.96594C17.8077 2.0977 19.3495 1.22598 20.8949 0.357734C21.7068 -0.0972257 22.5258 -0.111118 23.3377 0.340369C24.908 1.21903 26.4783 2.10117 28.0415 2.98678C28.8569 3.44868 29.2664 4.14675 29.2664 5.07056C29.2664 5.94575 29.2664 6.82442 29.2664 7.69961V7.69613ZM17.8504 7.68919C17.8326 9.96399 19.759 11.8706 22.1234 11.8672C24.4771 11.8672 26.4035 10.0022 26.4107 7.69961C26.4178 5.35187 24.4593 3.52162 22.1377 3.51814C19.7946 3.51814 17.8504 5.36924 17.8504 7.68919Z" fill="#2E3144"/>
|
<path d="M21.6786 35.94C19.0724 35.94 16.4677 35.9445 13.8615 35.94C11.6581 35.9356 10.0235 34.5633 9.64575 32.387C9.56491 31.9202 9.50317 31.4328 9.54139 30.9631C9.68103 29.202 9.67662 27.4336 9.59871 25.6784C9.55462 24.6712 10.0897 24.1927 10.7585 23.7392C11.2891 23.3784 11.6522 23.5875 11.6566 24.2354C11.6684 26.073 11.6522 27.9107 11.6566 29.7483C11.6581 30.3726 11.6713 30.997 11.711 31.6198C11.7463 32.1808 11.9638 32.6829 12.2946 33.1409C12.7532 33.777 13.3044 34.098 14.1423 34.0906C19.1327 34.0523 24.1231 34.0568 29.1136 34.0685C30.2983 34.0715 31.1612 33.6283 31.661 32.5224C32.1063 31.5359 31.9138 30.6539 31.245 29.8602C30.6291 29.1284 29.5678 28.8251 28.7681 29.1093C27.6951 29.4892 27.0836 30.2975 27.0307 31.4093C26.991 32.2353 26.1796 33.1394 25.4284 33.1747C25.1844 33.1865 24.8508 33.2336 24.8405 32.8611C24.8037 31.5035 24.7229 30.1518 25.6019 28.9488C27.0733 26.9344 29.7471 26.3896 31.8594 27.7281C33.9467 29.0504 34.6332 31.7877 33.3631 33.9242C32.5782 35.245 31.392 35.9769 29.825 35.9827C27.11 35.9931 24.3936 35.9857 21.6786 35.9857C21.6786 35.971 21.6786 35.9562 21.6786 35.9415V35.94Z" fill="#3F415A"/>
|
||||||
<path d="M46.4083 7.6892C46.4083 6.8279 46.4083 5.96313 46.4083 5.10183C46.4083 4.15718 46.8214 3.44522 47.6618 2.96942C49.2214 2.09076 50.781 1.20862 52.3442 0.336907C53.1419 -0.107634 53.9537 -0.111107 54.7514 0.336907C56.3395 1.22252 57.9205 2.11854 59.5051 3.0111C59.6618 3.09792 59.8042 3.20558 59.9395 3.32019C60.0178 3.38618 60.0392 3.42785 59.9324 3.49731C59.0279 4.10508 58.127 4.7198 57.2297 5.33799C57.1193 5.4144 57.0908 5.35188 57.041 5.2859C56.2932 4.28221 55.2891 3.70917 54.0285 3.54594C52.1698 3.30977 50.222 4.44891 49.5632 6.16109C48.7442 8.28655 49.6701 10.5162 51.7674 11.4956C53.5585 12.3291 55.7983 11.763 56.9911 10.1689C57.0908 10.0369 57.1478 10.0196 57.2867 10.1168C58.1342 10.7107 58.9888 11.2941 59.8469 11.8741C60.0072 11.9818 59.9929 12.0408 59.854 12.1416C59.7259 12.2318 59.5977 12.3256 59.4624 12.402C57.9063 13.2772 56.3537 14.1524 54.7977 15.0276C53.9644 15.4964 53.1348 15.4999 52.3015 15.0276C50.7597 14.1594 49.2143 13.2911 47.6724 12.4229C46.825 11.9471 46.4048 11.2316 46.4048 10.28C46.4048 9.41874 46.4048 8.55397 46.4048 7.69267L46.4083 7.6892Z" fill="#2E3144"/>
|
<path d="M54.8078 8.86716C54.949 8.48873 55.0621 8.09705 55.2371 7.7363C55.8265 6.52151 56.7467 5.76614 58.149 5.72785C59.5807 5.68957 60.5876 6.40372 61.3152 7.56991C61.6519 8.10736 61.8077 8.71844 61.8459 9.35012C61.8959 10.1938 61.962 11.039 61.9503 11.8828C61.9429 12.4349 62.159 12.5837 62.6661 12.5586C63.3261 12.5262 63.9905 12.5896 64.6476 12.5424C66.0455 12.4423 66.9319 11.497 66.9686 10.0952C66.9907 9.25 66.9657 8.40333 66.9774 7.55813C66.9877 6.90583 67.7227 5.92075 68.3254 5.71607C68.8134 5.55116 69.0956 5.72932 69.1029 6.26825C69.1279 7.88502 69.125 9.50179 68.9912 11.1171C68.9515 11.5956 68.5899 11.9785 68.6046 12.5513C69.613 12.5513 70.5993 12.6116 71.5768 12.5365C72.9468 12.432 73.8156 11.4425 73.8626 10.0319C73.8905 9.18668 73.8655 8.34001 73.8817 7.49481C73.8935 6.87491 74.5961 5.92664 75.1605 5.73227C75.7044 5.54527 75.9719 5.72638 75.9749 6.34187C75.9793 7.88502 76.0631 9.437 75.8455 10.9684C75.6574 12.2906 75.0209 13.3803 73.8979 14.1607C73.5701 14.3889 73.2246 14.4198 72.8542 14.4198C68.7046 14.4257 64.5564 14.4228 60.4083 14.4493C59.8247 14.4537 59.6689 14.2741 59.691 13.6954C59.7409 12.3363 59.7189 10.9743 59.7115 9.6137C59.7101 9.28534 59.7013 8.94667 59.6219 8.63156C59.4705 8.03816 58.9839 7.63912 58.4562 7.60819C57.9329 7.57727 57.2715 8.01165 57.1009 8.56383C56.9378 9.09097 56.8981 9.6402 56.9025 10.1983C56.9157 11.9638 56.8922 13.7278 56.9143 15.4933C56.9201 15.9968 56.7937 16.2604 56.2425 16.2059C55.9515 16.1765 55.6516 16.175 55.3635 16.2148C54.7843 16.2928 54.6888 15.9321 54.6535 15.4933C54.48 13.2801 54.3757 11.067 54.8078 8.86863V8.86716Z" fill="#3F415A"/>
|
||||||
|
<path d="M0.0367746 31.7038C0.0367746 30.5641 0.0191354 29.4244 0.0397144 28.2862C0.111741 24.2369 4.32604 22.6864 7.06894 24.3738C8.68734 25.3692 9.5546 27.3467 9.11362 29.2109C8.63442 31.2385 7.10863 32.5887 5.11099 32.7271C4.5627 32.7654 4.01001 32.7463 3.46025 32.7419C3.25005 32.7404 2.97811 32.8081 2.87081 32.5755C2.74439 32.3031 2.73116 31.9673 2.96929 31.7524C3.47054 31.2988 3.86007 30.6848 4.69647 30.7305C5.43731 30.7717 6.03557 30.3844 6.4839 29.8028C7.60105 28.3539 7.18065 26.6635 5.51669 25.8802C4.04969 25.1896 2.26813 26.3675 2.17259 28.1271C2.11967 29.1166 2.15054 30.112 2.14907 31.103C2.1476 32.5357 2.13878 33.9699 2.15789 35.4026C2.16377 35.8576 2.03148 36.1079 1.53905 36.0579C1.46702 36.0505 1.39205 36.0579 1.31856 36.0579C-0.29984 36.0829 0.0353046 36.0814 0.0147256 34.7886C-0.00144371 33.7593 0.0117857 32.7301 0.0117857 31.7023H0.0367746V31.7038Z" fill="#3F415A"/>
|
||||||
|
<path d="M53.5321 11.8151C53.5321 12.9916 53.538 14.1681 53.5306 15.3446C53.5247 16.156 53.5159 16.1692 52.6663 16.1707C51.3272 16.1722 51.4683 16.3032 51.4609 15.0207C51.4477 12.8517 51.4271 10.6828 51.4418 8.51532C51.4477 7.60239 51.1243 6.87058 50.454 6.27717C49.6809 5.59542 48.5284 5.51885 47.7038 6.08575C46.7116 6.7675 46.253 7.98082 46.6601 8.96001C47.1026 10.0217 47.8655 10.6916 49.0576 10.7947C49.7749 10.8565 50.4599 11.4382 50.5893 12.0978C50.6569 12.438 50.6613 12.787 50.1645 12.8032C48.9841 12.84 47.817 12.8105 46.7336 12.2377C45.005 11.3248 44.1598 9.49304 44.5082 7.38299C44.8066 5.57333 46.3823 4.03755 48.2021 3.78134C51.0846 3.37493 53.4307 5.35541 53.5071 8.28415C53.538 9.45918 53.5115 10.6372 53.5115 11.8137C53.5189 11.8137 53.5262 11.8137 53.5336 11.8137L53.5321 11.8151Z" fill="#3F415A"/>
|
||||||
|
<path d="M36.9746 29.9427C36.9746 31.7435 36.9614 33.5443 36.9834 35.3452C36.9893 35.8473 36.8585 36.0711 36.3102 36.0696C34.2479 36.0637 34.6506 36.2316 34.6359 34.4882C34.6109 31.5845 34.6256 28.6808 34.6242 25.7771C34.6242 24.8686 35.2166 23.963 36.0191 23.642C36.6218 23.4005 36.9761 23.6361 36.9805 24.3178C36.9908 26.1923 36.9849 28.0668 36.9849 29.9412H36.9775L36.9746 29.9427Z" fill="#3F415A"/>
|
||||||
|
<path d="M46.0442 29.1549C46.0442 30.0722 46.0706 30.9925 46.0383 31.9084C45.9854 33.3588 45.3371 34.5132 44.1774 35.3687C43.6717 35.7412 43.1161 36.0372 42.4664 36.046C41.4389 36.0607 40.4129 36.0578 39.3854 36.0534C39.1796 36.0534 38.9238 36.1079 38.84 35.8207C38.7724 35.5896 38.8474 35.3834 39.0252 35.2259C39.6191 34.6972 40.1321 34.1421 41.0817 34.1451C43.0191 34.1509 43.8569 33.2174 43.8672 31.2723C43.8731 30.0604 43.9495 28.8398 43.8422 27.6367C43.7467 26.5663 44.4346 26.1054 45.1299 25.6666C45.6958 25.3088 46.078 25.593 46.0883 26.288C46.103 27.2436 46.0927 28.1978 46.0927 29.1534C46.078 29.1534 46.0633 29.1534 46.0471 29.1534L46.0442 29.1549Z" fill="#3F415A"/>
|
||||||
|
<path d="M36.6439 3.77984C35.6885 3.77984 34.733 3.79603 33.779 3.77542C32.9191 3.75628 32.8706 3.6856 32.8677 2.79476C32.8657 2.14981 33.1847 1.82096 33.8246 1.8082C34.0084 1.80526 34.195 1.78317 34.3758 1.80673C35.7693 1.98048 36.9747 1.73163 37.8375 0.4712C38.1198 0.0589084 39.6911 -0.126623 40.1086 0.0942477C40.2967 0.194376 40.3276 0.371072 40.3291 0.559548C40.3364 1.44303 40.3394 2.32504 40.3511 3.20852C40.357 3.64142 40.1262 3.77984 39.7293 3.77689C38.7004 3.76806 37.6729 3.77395 36.6439 3.77395V3.77984Z" fill="#3F415A"/>
|
||||||
|
<path d="M67.739 17.8287C67.9345 17.2868 67.4729 16.6478 67.8668 16.2532C68.2564 15.86 68.9164 16.1044 69.4558 16.0971C71.0301 16.0779 72.6059 16.0838 74.1802 16.0941C75.1886 16.1 75.193 16.1103 75.2003 17.1528C75.2048 17.8125 75.2003 18.4736 75.1886 19.1333C75.1856 19.3512 75.1739 19.6472 74.9078 19.6516C74.1008 19.6678 73.1939 20.0551 72.5677 19.204C71.7725 18.1217 70.7376 17.7492 69.4235 17.9288C68.8987 17.9995 68.3358 18.0319 67.739 17.8302V17.8287Z" fill="#3F415A"/>
|
||||||
|
<path d="M64.0935 3.77691C63.4335 3.77691 62.772 3.76365 62.112 3.78132C61.6534 3.7931 61.4167 3.6223 61.455 3.1408C61.4638 3.03184 61.4491 2.91993 61.4476 2.80949C61.4285 1.84944 61.4461 1.82 62.4104 1.80969C63.5849 1.79644 64.7594 1.79791 65.9338 1.80969C66.7114 1.81852 66.7247 1.84503 66.7555 2.61366C66.7614 2.7609 66.7482 2.90815 66.7614 3.05392C66.807 3.55898 66.6144 3.80341 66.0764 3.78132C65.4164 3.75482 64.755 3.77543 64.095 3.77396L64.0935 3.77691Z" fill="#3F415A"/>
|
||||||
|
<path d="M23.9761 17.9435C23.3161 17.9435 22.6561 17.9243 21.9975 17.9494C21.5213 17.967 21.3228 17.7697 21.3522 17.3029C21.364 17.1204 21.361 16.9348 21.3463 16.7522C21.3067 16.2811 21.5272 16.0837 21.9858 16.0852C23.341 16.0911 24.6963 16.0896 26.0531 16.0852C26.4647 16.0837 26.6587 16.2752 26.6469 16.686C26.641 16.8686 26.6337 17.0541 26.6469 17.2367C26.6851 17.7417 26.4867 17.9773 25.9546 17.9538C25.2961 17.9258 24.6361 17.9464 23.9761 17.9464V17.9435Z" fill="#3F415A"/>
|
||||||
|
<path d="M64.7551 23.6361C64.0622 23.6371 63.7134 23.3352 63.7085 22.7306C63.7011 21.7263 63.7702 21.663 64.8682 21.6689C65.7987 21.6733 65.8457 21.7234 65.8531 22.6761C65.858 23.3141 65.492 23.6342 64.7551 23.6361Z" fill="#3F415A"/>
|
||||||
|
<path d="M72.0825 37.0355C72.0825 37.6746 71.7552 37.9961 71.1006 38C70.0481 38.0059 70.0467 38.0044 70.0496 37.0031C70.0525 36.0446 70.0628 36.0343 71.0256 36.0328C72.0678 36.0313 72.084 36.046 72.084 37.037L72.0825 37.0355Z" fill="#3F415A"/>
|
||||||
</g>
|
</g>
|
||||||
<defs>
|
<defs>
|
||||||
<clipPath id="clip0_2855_3816">
|
<clipPath id="clip0_1007_3463">
|
||||||
<rect width="60" height="16" fill="white"/>
|
<rect width="76" height="38" fill="white"/>
|
||||||
</clipPath>
|
</clipPath>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 12 KiB |
|
|
@ -1,53 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 157.92 68.92">
|
|
||||||
<defs>
|
|
||||||
<style>
|
|
||||||
.cls-1 {
|
|
||||||
fill: #40415a;
|
|
||||||
stroke-width: 0px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</defs>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path class="cls-1" d="M42.01,27.03h-13.48v19.64h4.26v-4.96h9.22c2.63,0,4.76-2.14,4.76-4.76v-5.16c0-2.62-2.14-4.76-4.76-4.76ZM42.51,36.95c0,.28-.23.51-.51.51h-9.22v-6.17h9.22c.28,0,.51.23.51.51v5.16Z"/>
|
|
||||||
<path class="cls-1" d="M21.74,27.03h-8.72c-2.62,0-4.76,2.14-4.76,4.76v14.88h4.26v-4.96h9.73v4.96h4.26v-14.88c0-2.62-2.14-4.76-4.76-4.76ZM22.25,37.46h-9.73v-5.67c0-.28.23-.51.51-.51h8.72c.28,0,.51.23.51.51v5.67Z"/>
|
|
||||||
<path class="cls-1" d="M62.28,27.03h-8.72c-2.62,0-4.76,2.14-4.76,4.76v14.88h4.26v-4.96h9.73v4.96h4.26v-14.88c0-2.62-2.14-4.76-4.76-4.76ZM62.78,37.46h-9.73v-5.67c0-.28.23-.51.51-.51h8.72c.28,0,.5.23.5.51v5.67Z"/>
|
|
||||||
<path class="cls-1" d="M102.81,27.03h-8.72c-2.63,0-4.76,2.14-4.76,4.76v14.88h4.26v-4.96h9.73v4.96h4.26v-14.88c0-2.62-2.14-4.76-4.76-4.76ZM103.32,37.46h-9.73v-5.67c0-.28.23-.51.51-.51h8.72c.28,0,.5.23.5.51v5.67Z"/>
|
|
||||||
<path class="cls-1" d="M143.35,27.03h-8.72c-2.63,0-4.76,2.14-4.76,4.76v14.88h4.26v-4.96h9.73v4.96h4.26v-14.88c0-2.62-2.14-4.76-4.76-4.76ZM143.86,37.46h-9.73v-5.67c0-.28.23-.51.51-.51h8.72c.28,0,.51.23.51.51v5.67Z"/>
|
|
||||||
<path class="cls-1" d="M82.03,27.03h-12.46v19.64h12.46c2.63,0,4.76-2.14,4.76-4.76v-10.11c0-2.63-2.14-4.76-4.76-4.76ZM82.53,41.91c0,.28-.22.5-.5.5h-8.19v-11.12h8.19c.28,0,.5.22.5.5v10.11Z"/>
|
|
||||||
<polygon class="cls-1" points="123.14 39.14 114.33 27.03 114.31 27.05 114.31 27.03 110.04 27.03 110.04 46.67 114.31 46.67 114.31 34.53 123.14 46.67 127.41 46.67 127.41 27.03 123.14 27.03 123.14 39.14"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="cls-1" d="M74.98,7.74h-5.59c-1.68,0-3.05,1.37-3.05,3.05v9.55h2.73v-3.18h6.24v3.18h2.73v-9.55c0-1.68-1.37-3.05-3.05-3.05ZM75.3,14.43h-6.24v-3.64c0-.18.15-.32.32-.32h5.59c.18,0,.32.15.32.32v3.64Z"/>
|
|
||||||
<polygon class="cls-1" points="88.55 15.51 82.9 7.74 82.88 7.75 82.88 7.74 80.14 7.74 80.14 20.34 82.88 20.34 82.88 12.55 88.55 20.34 91.29 20.34 91.29 7.74 88.55 7.74 88.55 15.51"/>
|
|
||||||
<path class="cls-1" d="M16.91,7.74h-8.65v12.6h2.73v-3.18h5.92c1.68,0,3.05-1.37,3.05-3.05v-3.31c0-1.68-1.37-3.05-3.05-3.05ZM17.24,14.1c0,.18-.15.32-.32.32h-5.92v-3.96h5.92c.18,0,.32.15.32.32v3.31Z"/>
|
|
||||||
<path class="cls-1" d="M43.58,7.74h-8.65v12.6h2.73v-3.65h5.91c.18,0,.32.15.32.32v3.33h2.73v-3.33c0-.62-.18-1.19-.52-1.68.32-.48.52-1.08.52-1.7v-2.84c0-1.69-1.37-3.05-3.05-3.05ZM43.9,13.64c0,.18-.14.32-.32.32h-5.91v-3.49h5.91c.18,0,.32.14.32.32v2.84Z"/>
|
|
||||||
<polygon class="cls-1" points="140.08 10.47 140.08 12.68 146.75 12.68 146.75 15.41 140.08 15.41 140.08 20.34 137.34 20.34 137.34 7.75 148.11 7.75 148.11 10.47 140.08 10.47"/>
|
|
||||||
<polygon class="cls-1" points="21.74 7.74 21.74 20.34 32.5 20.34 32.5 17.61 24.48 17.61 24.48 15.41 31.14 15.41 31.14 12.67 24.48 12.67 24.48 10.47 32.5 10.47 32.5 7.74 24.48 7.74 21.74 7.74"/>
|
|
||||||
<polygon class="cls-1" points="127.54 7.74 124.79 7.74 124.79 20.34 135.56 20.34 135.56 17.61 127.54 17.61 127.54 7.74"/>
|
|
||||||
<rect class="cls-1" x="61.62" y="7.65" width="2.75" height="12.69"/>
|
|
||||||
<path class="cls-1" d="M56.4,20.34h-7.45v-2.7h7.45c.08,0,.14-.06.14-.14v-1.99s-.13-.14-.13-.14h-5.16c-1.56,0-2.84-1.27-2.84-2.84v-1.99c0-.76.29-1.47.83-2.01.54-.54,1.25-.84,2.01-.84h7.42v2.7h-7.42l-.14.14v1.99c0,.08.07.14.14.14h5.16c.76,0,1.47.3,2.01.84.54.54.83,1.25.83,2.01v1.99c0,1.56-1.28,2.84-2.85,2.84Z"/>
|
|
||||||
<path class="cls-1" d="M106.1,20.34h-5c-1.76,0-3.19-1.43-3.19-3.19v-6.26c0-1.76,1.43-3.19,3.19-3.19h7.16v2.7h-7.16c-.27,0-.49.22-.49.49v6.26c0,.27.22.49.49.49h5c.12,0,.22-.1.22-.22v-1.83h-2.56v-2.7h5.27v4.54c0,1.61-1.31,2.92-2.92,2.92Z"/>
|
|
||||||
<path class="cls-1" d="M119.03,20.34h-4.97c-1.76,0-3.19-1.43-3.19-3.19V7.72h2.7v9.43c0,.27.22.49.49.49h4.97c.27,0,.49-.22.49-.49V7.72h2.7v9.43c0,1.76-1.43,3.19-3.19,3.19Z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path class="cls-1" d="M14.9,53.53h-6.22v9.18h1.92v-2.33h4.3c1.2,0,2.18-.99,2.18-2.21v-2.42c0-1.22-.98-2.21-2.18-2.21ZM15.16,58.16c0,.14-.12.26-.26.26h-4.3v-2.95h4.3c.14,0,.26.12.26.26v2.42Z"/>
|
|
||||||
<path class="cls-1" d="M43.32,53.53h-6.22v9.18h1.93v-2.67h4.29c.14,0,.26.12.26.26v2.41h1.93v-2.41c0-.44-.12-.85-.37-1.21h0s0-.03,0-.03c.24-.36.37-.8.37-1.23v-2.08c0-1.22-.98-2.21-2.18-2.21ZM43.58,57.82c0,.15-.11.26-.26.26h-4.29v-2.6h4.29c.15,0,.26.11.26.26v2.08Z"/>
|
|
||||||
<path class="cls-1" d="M111.47,53.53h-4.04c-1.2,0-2.18.99-2.18,2.21v6.97h1.92v-2.33h4.55v2.33h1.93v-6.97c0-1.22-.98-2.21-2.18-2.21ZM111.73,58.43h-4.55v-2.69c0-.14.12-.26.26-.26h4.04c.14,0,.26.12.26.26v2.69Z"/>
|
|
||||||
<path class="cls-1" d="M52.18,53.53h-3.56c-1.2,0-2.18,1-2.18,2.22v4.75c0,1.22.98,2.21,2.18,2.21h3.56c1.2,0,2.18-.99,2.18-2.21v-4.75c0-1.22-.98-2.22-2.18-2.22ZM52.44,60.5c0,.15-.11.26-.26.26h-3.56c-.14,0-.26-.12-.26-.26v-4.75c0-.14.11-.26.26-.26h3.56c.15,0,.26.11.26.26v4.75Z"/>
|
|
||||||
<path class="cls-1" d="M55.57,55.75v4.75c0,1.22.98,2.21,2.18,2.21h5.75v-1.95h-5.75c-.14,0-.26-.12-.26-.26v-4.75c0-.14.11-.26.26-.26h5.75v-1.96h-5.75c-1.2,0-2.18,1-2.18,2.22Z"/>
|
|
||||||
<path class="cls-1" d="M95.94,55.75v4.75c0,1.22.98,2.21,2.18,2.21h5.75v-1.95h-5.75c-.14,0-.26-.12-.26-.26v-4.75c0-.14.12-.26.26-.26h5.75v-1.96h-5.75c-1.2,0-2.18,1-2.18,2.22Z"/>
|
|
||||||
<polygon class="cls-1" points="18.38 62.71 26.1 62.71 26.1 60.76 20.31 60.76 20.31 59.09 25.12 59.09 25.12 57.14 20.31 57.14 20.31 55.48 26.1 55.48 26.1 53.53 18.38 53.53 18.38 62.71"/>
|
|
||||||
<polygon class="cls-1" points="73.97 62.71 81.69 62.71 81.69 60.76 75.9 60.76 75.9 59.09 80.71 59.09 80.71 57.14 75.9 57.14 75.9 55.48 81.69 55.48 81.69 53.53 73.97 53.53 73.97 62.71"/>
|
|
||||||
<polygon class="cls-1" points="117.26 53.53 115.33 53.53 115.33 62.71 123.06 62.71 123.06 60.76 117.26 60.76 117.26 53.53"/>
|
|
||||||
<polygon class="cls-1" points="27.31 55.49 30.54 55.49 30.54 62.71 32.48 62.71 32.48 55.49 35.71 55.49 35.71 53.53 27.31 53.53 27.31 55.49"/>
|
|
||||||
<polygon class="cls-1" points="70.72 57.1 66.65 57.1 66.65 53.46 64.72 53.46 64.72 62.71 66.65 62.71 66.65 59.07 70.72 59.07 70.72 62.71 72.65 62.71 72.65 53.46 70.72 53.46 70.72 57.1"/>
|
|
||||||
<rect class="cls-1" x="92.42" y="53.46" width="1.94" height="9.24"/>
|
|
||||||
<polygon class="cls-1" points="89.01 53.86 87.01 57.37 84.98 53.46 83.04 53.46 83.04 62.71 84.97 62.71 84.97 57.63 86.5 60.59 87.47 60.59 89.05 57.7 89.03 62.71 90.97 62.71 90.97 53.46 89.03 53.46 89.01 53.86"/>
|
|
||||||
<path class="cls-1" d="M142.67,53.53h-3.56c-1.2,0-2.18,1-2.18,2.22v4.75c0,1.22.98,2.21,2.18,2.21h3.56c1.2,0,2.18-.99,2.18-2.21v-4.75c0-1.22-.98-2.22-2.18-2.22ZM142.92,60.5c0,.15-.11.26-.26.26h-3.56c-.14,0-.26-.12-.26-.26v-4.75c0-.14.11-.26.26-.26h3.56c.15,0,.26.11.26.26v4.75Z"/>
|
|
||||||
<path class="cls-1" d="M127.59,55.75v4.75c0,1.22.98,2.21,2.18,2.21h5.75v-1.95h-5.75c-.14,0-.26-.12-.26-.26v-4.75c0-.14.12-.26.26-.26h5.75v-1.96h-5.75c-1.2,0-2.18,1-2.18,2.22Z"/>
|
|
||||||
<rect class="cls-1" x="146.18" y="60.75" width="1.93" height="1.96"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |