import axios from 'axios'; import type { InjectionKey, Ref } from 'vue'; import type { ActivityPriority, RoutineContextDto, RoutineDayState, RoutineEntityRefDto, RoutineTaskDto, RoutineTaskStatus, } from '@mento/shared'; import { addJalaliMonths, dateToJalali, formatJalaliLong, formatJalaliShort, jalaliMonthLength, jalaliMonthName, jalaliToDate, toFaDigits, } from '@/lib/jalali'; export { ROUTINE_ENTITY_LINK_LABELS, ROUTINE_RECURRENCE_LABELS, ROUTINE_TASK_SOURCE_LABELS, ROUTINE_TASK_STATUS_LABELS, ROUTINE_WEEKDAY_LABELS, } from '@mento/shared'; type BadgeVariant = 'neutral' | 'accent' | 'success' | 'warning' | 'danger' | 'info'; export const PRIORITY_LABELS: Record = { very_low: 'خیلی کم', low: 'کم', medium: 'متوسط', high: 'زیاد', very_high: 'خیلی زیاد', }; export const PRIORITY_VARIANTS: Record = { very_low: 'neutral', low: 'neutral', medium: 'info', high: 'warning', very_high: 'danger', }; export const STATUS_VARIANTS: Record = { todo: 'neutral', in_progress: 'info', completed: 'success', overdue: 'danger', skipped: 'warning', cancelled: 'neutral', }; export const DAY_STATE_LABELS: Record = { complete: 'روز کامل', partial: 'روز ناقص', missed: 'روز از دست رفته', empty: 'بدون کار', }; export const DAY_STATE_SYMBOLS: Record = { complete: '✓', partial: '△', missed: '×', empty: '·', }; /** زمینه مشترک بخش کارهای روتین (کاربران، دسته‌ها، نقش مدیر، رفرش) */ export type RoutinesShellContext = { context: Ref; refreshTick: Ref; notifyChanged: () => void; openTask: (id: string) => void; }; export const ROUTINES_CTX: InjectionKey = Symbol('routines-ctx'); /* ───────── تاریخ‌ها (کلید روز YYYY-MM-DD به وقت تهران) ───────── */ const TEHRAN_DAY = new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Tehran', year: 'numeric', month: '2-digit', day: '2-digit', }); const TEHRAN_TIME = new Intl.DateTimeFormat('en-GB', { timeZone: 'Asia/Tehran', hour: '2-digit', minute: '2-digit', hourCycle: 'h23', }); export function todayKey(): string { return TEHRAN_DAY.format(new Date()); } export function dayKeyOf(iso: string | Date): string { return TEHRAN_DAY.format(typeof iso === 'string' ? new Date(iso) : iso); } /** Date محلی (نیمه‌شب) برای نمایش شمسی */ export function keyToDate(key: string): Date { const [y, m, d] = key.split('-').map(Number); return new Date(y!, m! - 1, d!); } export function dateToKey(d: Date): string { const pad = (n: number) => String(n).padStart(2, '0'); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; } export function addDaysKey(key: string, n: number): string { const d = keyToDate(key); d.setDate(d.getDate() + n); return dateToKey(d); } /** ۰ = شنبه */ export function weekdayIndex(key: string): number { return (keyToDate(key).getDay() + 1) % 7; } export function weekStart(key: string): string { return addDaysKey(key, -weekdayIndex(key)); } export function jalaliMonthRange(key: string): { from: string; to: string; label: string } { const { jy, jm } = dateToJalali(keyToDate(key)); const from = dateToKey(jalaliToDate(jy, jm, 1)); const to = dateToKey(jalaliToDate(jy, jm, jalaliMonthLength(jy, jm))); return { from, to, label: `${jalaliMonthName(jm)} ${toFaDigits(jy)}` }; } export function shiftJalaliMonth(key: string, delta: number): string { const { jy, jm } = dateToJalali(keyToDate(key)); const next = addJalaliMonths(jy, jm, delta); return dateToKey(jalaliToDate(next.jy, next.jm, 1)); } export function formatDayLong(key: string): string { return formatJalaliLong(keyToDate(key)); } export function formatDayShort(key: string): string { return formatJalaliShort(keyToDate(key)); } export function formatDayNumeric(key: string): string { const { jm, jd } = dateToJalali(keyToDate(key)); return `${toFaDigits(jd)} ${jalaliMonthName(jm)}`; } export function formatTime(iso: string | null | undefined): string { if (!iso) return '—'; return toFaDigits(TEHRAN_TIME.format(new Date(iso))); } export function timeOf(iso: string | null | undefined): string { if (!iso) return ''; return TEHRAN_TIME.format(new Date(iso)); } /** زمان پایان روز بدون ساعت مشخص (۲۳:۵۹) در UI نمایش داده نمی‌شود */ export function hasExplicitTime(task: Pick): boolean { return !!task.dueAt && timeOf(task.dueAt) !== '23:59'; } export function formatDelay(iso: string | null | undefined): string { if (!iso) return '—'; const diff = Date.now() - new Date(iso).getTime(); if (diff <= 0) return '—'; const minutes = Math.floor(diff / 60_000); if (minutes < 60) return `${toFaDigits(minutes)} دقیقه`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${toFaDigits(hours)} ساعت`; return `${toFaDigits(Math.floor(hours / 24))} روز`; } export function formatMinutes(min: number | null | undefined): string { if (min == null) return '—'; if (min < 60) return `${toFaDigits(min)} دقیقه`; const h = Math.floor(min / 60); const m = min % 60; return m ? `${toFaDigits(h)} ساعت و ${toFaDigits(m)} دقیقه` : `${toFaDigits(h)} ساعت`; } export function formatPercent(p: number | null | undefined): string { return p == null ? '—' : `${toFaDigits(p)}٪`; } export function percentTone(p: number | null | undefined): 'good' | 'mid' | 'low' | 'none' { if (p == null) return 'none'; if (p >= 85) return 'good'; if (p >= 60) return 'mid'; return 'low'; } export function isoToLocalInput(iso: string | null | undefined): { date: string; time: string } { if (!iso) return { date: '', time: '' }; return { date: dayKeyOf(iso), time: timeOf(iso) }; } /** ورودی تاریخ/ساعت همیشه به وقت تهران تفسیر می‌شود */ export function localInputToIso(date: string, time: string): string | null { if (!date) return null; const d = new Date(`${date}T${time || '23:59'}:00+03:30`); return Number.isNaN(d.getTime()) ? null : d.toISOString(); } /* ───────── پیوند پرونده‌ها ───────── */ export const ENTITY_TYPE_LABELS: Record = { lead: 'لید', person: 'مشتری', company: 'شرکت', deal: 'معامله', invoice: 'فاکتور', activity: 'پیگیری', }; export function entityRoute(ref: RoutineEntityRefDto): string { switch (ref.type) { case 'lead': return `/leads/${ref.id}`; case 'person': return `/contacts/people/${ref.id}`; case 'company': return `/contacts/companies/${ref.id}`; case 'deal': return `/deals/${ref.id}`; case 'invoice': return `/invoices/${ref.id}`; case 'activity': return `/activities/${ref.id}`; default: return '/'; } } export function apiError(e: unknown, fallback: string): string { if (axios.isAxiosError(e)) { const msg = (e.response?.data as { message?: string | string[] })?.message; if (Array.isArray(msg)) return msg.join('، '); if (msg) return msg; if (!e.response) return 'اتصال به سرور برقرار نشد.'; } return fallback; } export function isOpenStatus(status: RoutineTaskStatus): boolean { return status === 'todo' || status === 'in_progress' || status === 'overdue'; }