feat(frontend): add React UI — procurement office, wizard, suppliers, inventory panel (dash port), role-based login
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
// ============================================================
|
||||
// ابزار تقویم شمسی — پورت jalali.js به TypeScript
|
||||
// تبدیلها با الگوریتم استاندارد Kazimierz M. Borkowski
|
||||
// ============================================================
|
||||
|
||||
const FA_DIGITS = ["۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹"];
|
||||
|
||||
export function div(a: number, b: number): number {
|
||||
return Math.floor(a / b);
|
||||
}
|
||||
|
||||
export function jalaliToGregorian(jy: number, jm: number, jd: number): [number, number, number] {
|
||||
jy = +jy;
|
||||
jm = +jm;
|
||||
jd = +jd;
|
||||
let gy = jy <= 979 ? 621 : 1600;
|
||||
jy -= jy <= 979 ? 0 : 979;
|
||||
let days =
|
||||
365 * jy +
|
||||
div(jy, 33) * 8 +
|
||||
div((jy % 33) + 3, 4) +
|
||||
78 +
|
||||
jd +
|
||||
(jm < 7 ? (jm - 1) * 31 : (jm - 7) * 30 + 186);
|
||||
gy += 400 * div(days, 146097);
|
||||
days %= 146097;
|
||||
if (days > 36524) {
|
||||
gy += 100 * div(--days, 36524);
|
||||
if (days === 36524) days = 0;
|
||||
days++;
|
||||
}
|
||||
gy += 4 * div(days, 1461);
|
||||
days %= 1461;
|
||||
if (days > 365) {
|
||||
gy += div(days - 1, 365);
|
||||
days = (days - 1) % 365;
|
||||
}
|
||||
let gd = days + 1;
|
||||
const sal_a = [0, 31, (gy % 4 === 0 && gy % 100 !== 0) || gy % 400 === 0 ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
let gm = 0;
|
||||
for (; gm < 13 && gd > sal_a[gm]; gm++) gd -= sal_a[gm];
|
||||
return [gy, gm, gd];
|
||||
}
|
||||
|
||||
export function gregorianToJalali(gy: number, gm: number, gd: number): [number, number, number] {
|
||||
gy = +gy;
|
||||
gm = +gm;
|
||||
gd = +gd;
|
||||
const g_d_m = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
|
||||
let jy = gy <= 1600 ? 0 : 979;
|
||||
gy -= gy <= 1600 ? 621 : 1600;
|
||||
let days =
|
||||
365 * gy +
|
||||
div(gy + 3, 4) -
|
||||
div(gy + 99, 100) +
|
||||
div(gy + 399, 400) -
|
||||
80 +
|
||||
gd +
|
||||
g_d_m[gm - 1];
|
||||
jy += 33 * div(days, 12053);
|
||||
days %= 12053;
|
||||
jy += 4 * div(days, 1461);
|
||||
days %= 1461;
|
||||
if (days > 365) {
|
||||
jy += div(days - 1, 365);
|
||||
days = (days - 1) % 365;
|
||||
}
|
||||
const jm = days < 186 ? 1 + div(days, 31) : 7 + div(days - 186, 30);
|
||||
const jd = 1 + (days < 186 ? days % 31 : (days - 186) % 30);
|
||||
return [jy, jm, jd];
|
||||
}
|
||||
|
||||
export function isLeapJalali(jy: number): boolean {
|
||||
const breaks = [-61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181, 1210, 1635, 2060, 2097, 2192, 2262, 2324, 2394, 2456, 3178];
|
||||
const jp = breaks[0];
|
||||
const jm = breaks[1];
|
||||
let jump = 0;
|
||||
let jp2 = jp;
|
||||
for (const jl of breaks) {
|
||||
jump = jl - jp2;
|
||||
if (jy <= jl) break;
|
||||
jp2 = jl;
|
||||
}
|
||||
let n = jy - jm;
|
||||
if (n < jump) n = n + jump + 1;
|
||||
return (n % 33) % 4 === 1;
|
||||
}
|
||||
|
||||
// ---------- formatting (Persian digits) ----------
|
||||
export function toFa(s: string | number): string {
|
||||
return String(s).replace(/[0-9]/g, (d) => FA_DIGITS[Number(d)]);
|
||||
}
|
||||
|
||||
// ماههای شمسی (نسخهی درستشده — «مهر» بهجای «آذر» تکراری)
|
||||
export const MONTHS = [
|
||||
"فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور",
|
||||
"مهر", "آبان", "آذر", "دی", "بهمن", "اسفند",
|
||||
];
|
||||
|
||||
export const WEEKDAYS = ["ش", "ی", "د", "س", "چ", "پ", "ج"];
|
||||
|
||||
function toEnSafe(s: string): string {
|
||||
return String(s).replace(/[۰-۹]/g, (d) => String("۰۱۲۳۴۵۶۷۸۹".indexOf(d)));
|
||||
}
|
||||
|
||||
// format an ISO (gregorian) string to "۱۴۰۳/۰۵/۱۲"
|
||||
export function isoToJalaliStr(iso: string | null | undefined): string {
|
||||
if (!iso) return "";
|
||||
const d = new Date(iso);
|
||||
if (Number.isNaN(d.getTime())) return "";
|
||||
const [jy, jm, jd] = gregorianToJalali(d.getFullYear(), d.getMonth() + 1, d.getDate());
|
||||
return `${toFa(jy)}/${toFa(String(jm).padStart(2, "0"))}/${toFa(String(jd).padStart(2, "0"))}`;
|
||||
}
|
||||
|
||||
// format with month name: "۱۸ مرداد ۱۴۰۴"
|
||||
export function isoToJalaliLong(iso: string | null | undefined): string {
|
||||
if (!iso) return "";
|
||||
const d = new Date(iso);
|
||||
if (Number.isNaN(d.getTime())) return "";
|
||||
const [jy, jm, jd] = gregorianToJalali(d.getFullYear(), d.getMonth() + 1, d.getDate());
|
||||
return `${toFa(jd)} ${MONTHS[jm - 1]} ${toFa(jy)}`;
|
||||
}
|
||||
|
||||
// parse a jalali "yyyy/m/d" string to a JS Date (gregorian) at local midnight
|
||||
export function jalaliStrToIso(str: string | null | undefined): string | null {
|
||||
if (!str) return null;
|
||||
const nums = String(str)
|
||||
.split(/[/\-.]/)
|
||||
.map((x) => parseInt(toEnSafe(x), 10));
|
||||
if (nums.length < 3 || nums.some((n) => Number.isNaN(n))) return null;
|
||||
const [jy, jm, jd] = nums;
|
||||
const [gy, gm, gd] = jalaliToGregorian(jy, jm, jd);
|
||||
const dt = new Date(gy, gm - 1, gd);
|
||||
return Number.isNaN(dt.getTime()) ? null : dt.toISOString();
|
||||
}
|
||||
|
||||
export function daysInMonth(jy: number, jm: number): number {
|
||||
if (jm <= 6) return 31;
|
||||
if (jm <= 11) return 30;
|
||||
return isLeapJalali(jy) ? 30 : 29;
|
||||
}
|
||||
Reference in New Issue
Block a user