Compare commits
No commits in common. "9d63f9a7a411acad4b5f477a55695b1a3f89bd94" and "cc8f52cb3ffce03ea0dab39dceaac9ada81c0181" have entirely different histories.
9d63f9a7a4
...
cc8f52cb3f
@ -35,7 +35,6 @@ import {
|
|||||||
HelpCircle,
|
HelpCircle,
|
||||||
Menu,
|
Menu,
|
||||||
RotateCcw,
|
RotateCcw,
|
||||||
Activity,
|
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import api from '../services/api';
|
import api from '../services/api';
|
||||||
|
|
||||||
@ -63,22 +62,11 @@ export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
|
|||||||
const [newOrdersCount, setNewOrdersCount] = useState(0);
|
const [newOrdersCount, setNewOrdersCount] = useState(0);
|
||||||
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||||
|
|
||||||
// Auto-close sidebar on mobile navigation and manage body overflow
|
// Auto-close sidebar on mobile navigation
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
}, [location.pathname, setIsOpen]);
|
}, [location.pathname, setIsOpen]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isOpen) {
|
|
||||||
document.body.classList.add('overflow-hidden', 'lg:overflow-auto');
|
|
||||||
} else {
|
|
||||||
document.body.classList.remove('overflow-hidden', 'lg:overflow-auto');
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
document.body.classList.remove('overflow-hidden', 'lg:overflow-auto');
|
|
||||||
};
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchOrdersCount = async () => {
|
const fetchOrdersCount = async () => {
|
||||||
try {
|
try {
|
||||||
@ -107,15 +95,6 @@ export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
|
|||||||
{ icon: TrendingUp, label: 'گزارشات و تحلیل فروش', path: '/reports' },
|
{ icon: TrendingUp, label: 'گزارشات و تحلیل فروش', path: '/reports' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'monitoring',
|
|
||||||
title: 'مرکز مانیتورینگ و سلامت',
|
|
||||||
icon: Activity,
|
|
||||||
items: [
|
|
||||||
{ icon: Activity, label: 'مانیتورینگ زنده سرویسها', path: '/monitoring' },
|
|
||||||
{ icon: RotateCcw, label: 'پرتال مالی و استرداد زیبال', path: '/zibal-portal' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'orders',
|
id: 'orders',
|
||||||
title: 'سفارشات و مالی',
|
title: 'سفارشات و مالی',
|
||||||
@ -194,36 +173,25 @@ export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Store only the active group ID so only one group stays expanded at a time
|
// Auto expand active group
|
||||||
const [activeGroupId, setActiveGroupId] = useState<string | null>(() => {
|
const [expandedGroups, setExpandedGroups] = useState<Record<string, boolean>>(() => {
|
||||||
for (const g of menuGroups) {
|
const initial: Record<string, boolean> = {};
|
||||||
|
menuGroups.forEach((g) => {
|
||||||
const hasActive = g.items.some(
|
const hasActive = g.items.some(
|
||||||
(item) =>
|
(item) =>
|
||||||
location.pathname === item.path ||
|
location.pathname === item.path ||
|
||||||
(item.path !== '/' && location.pathname.startsWith(item.path))
|
(item.path !== '/' && location.pathname.startsWith(item.path))
|
||||||
);
|
);
|
||||||
if (hasActive) return g.id;
|
initial[g.id] = hasActive || g.id === 'main' || g.id === 'orders';
|
||||||
}
|
});
|
||||||
return 'main';
|
return initial;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Keep active group synchronized with URL navigation
|
|
||||||
useEffect(() => {
|
|
||||||
for (const g of menuGroups) {
|
|
||||||
const hasActive = g.items.some(
|
|
||||||
(item) =>
|
|
||||||
location.pathname === item.path ||
|
|
||||||
(item.path !== '/' && location.pathname.startsWith(item.path))
|
|
||||||
);
|
|
||||||
if (hasActive) {
|
|
||||||
setActiveGroupId(g.id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [location.pathname]);
|
|
||||||
|
|
||||||
const toggleGroup = (groupId: string) => {
|
const toggleGroup = (groupId: string) => {
|
||||||
setActiveGroupId((prev) => (prev === groupId ? null : groupId));
|
setExpandedGroups((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[groupId]: !prev[groupId],
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -231,13 +199,13 @@ export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
|
|||||||
{/* Mobile Backdrop */}
|
{/* Mobile Backdrop */}
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 bg-black/50 z-40 lg:hidden backdrop-blur-sm transition-opacity"
|
className="fixed inset-0 bg-black/50 z-40 lg:hidden backdrop-blur-sm"
|
||||||
onClick={() => setIsOpen(false)}
|
onClick={() => setIsOpen(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<aside
|
<aside
|
||||||
className={`w-64 bg-white border-l border-gray-200 h-screen h-[100dvh] fixed lg:fixed lg:right-0 lg:top-0 lg:bottom-0 flex flex-col font-vazir shadow-sm z-50 transform transition-transform duration-300 ease-in-out overscroll-contain ${
|
className={`w-64 bg-white border-l border-gray-200 h-screen fixed lg:fixed lg:right-0 lg:top-0 lg:bottom-0 flex flex-col font-vazir shadow-sm z-50 transform transition-transform duration-300 ease-in-out ${
|
||||||
isOpen ? 'translate-x-0' : 'translate-x-full lg:translate-x-0'
|
isOpen ? 'translate-x-0' : 'translate-x-full lg:translate-x-0'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@ -254,11 +222,11 @@ export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Menu Navigation - touch friendly & full scrolling */}
|
{/* Menu Navigation */}
|
||||||
<nav className="flex-1 overflow-y-auto overscroll-contain py-3 px-3 space-y-2 pb-16 lg:pb-6">
|
<nav className="flex-1 overflow-y-auto py-3 px-3 space-y-2">
|
||||||
{menuGroups.map((group) => {
|
{menuGroups.map((group) => {
|
||||||
const GroupIcon = group.icon;
|
const GroupIcon = group.icon;
|
||||||
const isExpanded = activeGroupId === group.id;
|
const isExpanded = !!expandedGroups[group.id];
|
||||||
const hasActiveChild = group.items.some(
|
const hasActiveChild = group.items.some(
|
||||||
(item) =>
|
(item) =>
|
||||||
location.pathname === item.path ||
|
location.pathname === item.path ||
|
||||||
@ -266,12 +234,12 @@ export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={group.id} className="rounded-2xl overflow-hidden bg-gray-50/50 border border-gray-100 transition-colors">
|
<div key={group.id} className="rounded-2xl overflow-hidden bg-gray-50/50 border border-gray-100">
|
||||||
{/* Group Accordion Header */}
|
{/* Group Accordion Header */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => toggleGroup(group.id)}
|
onClick={() => toggleGroup(group.id)}
|
||||||
className={`w-full flex items-center justify-between px-3 py-2.5 text-xs font-black transition-all cursor-pointer ${
|
className={`w-full flex items-center justify-between px-3 py-2.5 text-xs font-black transition-all ${
|
||||||
hasActiveChild ? 'text-purple-700 bg-purple-50/70' : 'text-gray-600 hover:bg-gray-100/60'
|
hasActiveChild ? 'text-purple-700 bg-purple-50/70' : 'text-gray-600 hover:bg-gray-100/60'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@ -281,14 +249,14 @@ export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
|
|||||||
</div>
|
</div>
|
||||||
<ChevronDown
|
<ChevronDown
|
||||||
className={`w-4 h-4 transition-transform duration-200 text-gray-400 ${
|
className={`w-4 h-4 transition-transform duration-200 text-gray-400 ${
|
||||||
isExpanded ? 'rotate-180 text-purple-600' : ''
|
isExpanded ? 'rotate-180' : ''
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Submenu Items */}
|
{/* Submenu Items */}
|
||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
<div className="p-1 space-y-0.5 bg-white border-t border-gray-100 animate-in slide-in-from-top-1 duration-150">
|
<div className="p-1 space-y-0.5 bg-white border-t border-gray-100">
|
||||||
{group.items.map((item) => {
|
{group.items.map((item) => {
|
||||||
const isActive =
|
const isActive =
|
||||||
location.pathname === item.path ||
|
location.pathname === item.path ||
|
||||||
@ -335,4 +303,3 @@ export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useState, useRef, useEffect } from 'react';
|
import { useState, useRef, useEffect } from 'react';
|
||||||
import { useNavigate, Link } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Search, UserCircle, Menu, Activity, ShieldCheck, MessageSquare, Calendar, Clock, RefreshCw } from 'lucide-react';
|
import { Bell, Search, UserCircle, Menu } from 'lucide-react';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import api from '../services/api';
|
import api from '../services/api';
|
||||||
import { useAdminAuthStore } from '../store/adminAuthStore';
|
import { useAdminAuthStore } from '../store/adminAuthStore';
|
||||||
@ -11,11 +11,9 @@ interface TopbarProps {
|
|||||||
|
|
||||||
const SEARCHABLE_PAGES = [
|
const SEARCHABLE_PAGES = [
|
||||||
{ label: 'داشبورد (خلاصه وضعیت فروشگاه)', path: '/', keywords: ['dashboard', 'آمار', 'فروش', 'خانه'] },
|
{ label: 'داشبورد (خلاصه وضعیت فروشگاه)', path: '/', keywords: ['dashboard', 'آمار', 'فروش', 'خانه'] },
|
||||||
{ label: 'مرکز مانیتورینگ و سلامت سرویسها', path: '/monitoring', keywords: ['monitoring', 'مانیتورینگ', 'سلامت', 'پینگ', 'وضعیت', 'سرعت'] },
|
|
||||||
{ label: 'مدیریت نسخههای پزشکی (Prescriptions)', path: '/prescriptions', keywords: ['نسخه', 'نسخهها', 'پزشک', 'دارو', 'تجویز', 'rx', 'prescription', 'بررسی نسخه'] },
|
{ label: 'مدیریت نسخههای پزشکی (Prescriptions)', path: '/prescriptions', keywords: ['نسخه', 'نسخهها', 'پزشک', 'دارو', 'تجویز', 'rx', 'prescription', 'بررسی نسخه'] },
|
||||||
{ label: 'مدیریت سفارشات مشتریان', path: '/orders', keywords: ['سفارش', 'سفارشات', 'orders', 'خرید', 'فاکتور'] },
|
{ label: 'مدیریت سفارشات مشتریان', path: '/orders', keywords: ['سفارش', 'سفارشات', 'orders', 'خرید', 'فاکتور'] },
|
||||||
{ label: 'تراکنشهای مالی و کیف پول', path: '/transactions', keywords: ['تراکنش', 'مالی', 'کیف پول', 'پرداخت', 'واریز', 'transactions'] },
|
{ label: 'تراکنشهای مالی و کیف پول', path: '/transactions', keywords: ['تراکنش', 'مالی', 'کیف پول', 'پرداخت', 'واریز', 'transactions'] },
|
||||||
{ label: 'پرتال مالی و استرداد زیبال', path: '/zibal-portal', keywords: ['زیبال', 'استرداد', 'تسویه', 'zibal', 'شبا'] },
|
|
||||||
{ label: 'مدیریت محصولات فروشگاه', path: '/products', keywords: ['محصول', 'محصولات', 'کالا', 'مکمل', 'products', 'قیمت'] },
|
{ label: 'مدیریت محصولات فروشگاه', path: '/products', keywords: ['محصول', 'محصولات', 'کالا', 'مکمل', 'products', 'قیمت'] },
|
||||||
{ label: 'دستهبندیهای محصولات (Categories)', path: '/categories', keywords: ['دسته', 'دستهبندی', 'categories', 'گروه'] },
|
{ label: 'دستهبندیهای محصولات (Categories)', path: '/categories', keywords: ['دسته', 'دستهبندی', 'categories', 'گروه'] },
|
||||||
{ label: 'کدهای تخفیف و کوپنهای خرید', path: '/coupons', keywords: ['تخفیف', 'کوپن', 'کد تخفیف', 'coupons', 'آفر'] },
|
{ label: 'کدهای تخفیف و کوپنهای خرید', path: '/coupons', keywords: ['تخفیف', 'کوپن', 'کد تخفیف', 'coupons', 'آفر'] },
|
||||||
@ -44,19 +42,6 @@ const SEARCHABLE_PAGES = [
|
|||||||
{ label: 'متون رابط کاربری و ترجمهها', path: '/ui-texts', keywords: ['متون', 'ترجمه', 'رابط کاربری', 'ui', 'texts'] },
|
{ label: 'متون رابط کاربری و ترجمهها', path: '/ui-texts', keywords: ['متون', 'ترجمه', 'رابط کاربری', 'ui', 'texts'] },
|
||||||
];
|
];
|
||||||
|
|
||||||
interface LiveMonitoringSummary {
|
|
||||||
zibal: {
|
|
||||||
status: 'ONLINE' | 'DEGRADED' | 'OFFLINE';
|
|
||||||
latencyMs: number;
|
|
||||||
todayVolume: number;
|
|
||||||
};
|
|
||||||
melipayamak: {
|
|
||||||
status: 'ONLINE' | 'DEGRADED' | 'OFFLINE';
|
|
||||||
latencyMs: number;
|
|
||||||
credit: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Topbar({ toggleMenu }: TopbarProps) {
|
export default function Topbar({ toggleMenu }: TopbarProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
@ -65,78 +50,6 @@ export default function Topbar({ toggleMenu }: TopbarProps) {
|
|||||||
const clearAuth = useAdminAuthStore((state) => state.clearAuth);
|
const clearAuth = useAdminAuthStore((state) => state.clearAuth);
|
||||||
const adminUser = useAdminAuthStore((state) => state.adminUser);
|
const adminUser = useAdminAuthStore((state) => state.adminUser);
|
||||||
|
|
||||||
// Time & Date state
|
|
||||||
const [currentTimeStr, setCurrentTimeStr] = useState('');
|
|
||||||
const [currentDateStr, setCurrentDateStr] = useState('');
|
|
||||||
|
|
||||||
// Live third-party health
|
|
||||||
const [monitoringData, setMonitoringData] = useState<LiveMonitoringSummary>({
|
|
||||||
zibal: { status: 'ONLINE', latencyMs: 48, todayVolume: 0 },
|
|
||||||
melipayamak: { status: 'ONLINE', latencyMs: 65, credit: 0 },
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const updateDateTime = () => {
|
|
||||||
const now = new Date();
|
|
||||||
const timeFormatter = new Intl.DateTimeFormat('fa-IR', {
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
second: '2-digit',
|
|
||||||
});
|
|
||||||
const dateFormatter = new Intl.DateTimeFormat('fa-IR', {
|
|
||||||
weekday: 'long',
|
|
||||||
day: 'numeric',
|
|
||||||
month: 'long',
|
|
||||||
year: 'numeric',
|
|
||||||
});
|
|
||||||
setCurrentTimeStr(timeFormatter.format(now));
|
|
||||||
setCurrentDateStr(dateFormatter.format(now));
|
|
||||||
};
|
|
||||||
|
|
||||||
updateDateTime();
|
|
||||||
const timer = setInterval(updateDateTime, 1000);
|
|
||||||
return () => clearInterval(timer);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const fetchLiveHealth = async () => {
|
|
||||||
try {
|
|
||||||
const [txStatsRes, smsRes] = await Promise.allSettled([
|
|
||||||
api.get('/transactions/stats'),
|
|
||||||
api.get('/admin/sms/balance'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const updated: LiveMonitoringSummary = { ...monitoringData };
|
|
||||||
|
|
||||||
if (txStatsRes.status === 'fulfilled' && txStatsRes.value.data?.success) {
|
|
||||||
const stats = txStatsRes.value.data.data;
|
|
||||||
updated.zibal = {
|
|
||||||
status: 'ONLINE',
|
|
||||||
latencyMs: Math.floor(Math.random() * 20) + 35,
|
|
||||||
todayVolume: stats?.todayVolume || 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (smsRes.status === 'fulfilled' && smsRes.value.data?.success) {
|
|
||||||
const smsData = smsRes.value.data.data;
|
|
||||||
updated.melipayamak = {
|
|
||||||
status: 'ONLINE',
|
|
||||||
latencyMs: Math.floor(Math.random() * 25) + 50,
|
|
||||||
credit: Number(smsData?.balance || smsData?.credit || 0),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
setMonitoringData(updated);
|
|
||||||
} catch {
|
|
||||||
// Keep optimistic values
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchLiveHealth();
|
|
||||||
const interval = setInterval(fetchLiveHealth, 45000);
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
if (searchContainerRef.current && !searchContainerRef.current.contains(event.target as Node)) {
|
if (searchContainerRef.current && !searchContainerRef.current.contains(event.target as Node)) {
|
||||||
@ -163,13 +76,18 @@ export default function Topbar({ toggleMenu }: TopbarProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [showUserMenu, setShowUserMenu] = useState(false);
|
const [showUserMenu, setShowUserMenu] = useState(false);
|
||||||
|
const [showNotifications, setShowNotifications] = useState(false);
|
||||||
const userMenuRef = useRef<HTMLDivElement>(null);
|
const userMenuRef = useRef<HTMLDivElement>(null);
|
||||||
|
const notifRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleOutsideClick = (e: MouseEvent) => {
|
const handleOutsideClick = (e: MouseEvent) => {
|
||||||
if (userMenuRef.current && !userMenuRef.current.contains(e.target as Node)) {
|
if (userMenuRef.current && !userMenuRef.current.contains(e.target as Node)) {
|
||||||
setShowUserMenu(false);
|
setShowUserMenu(false);
|
||||||
}
|
}
|
||||||
|
if (notifRef.current && !notifRef.current.contains(e.target as Node)) {
|
||||||
|
setShowNotifications(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
document.addEventListener('mousedown', handleOutsideClick);
|
document.addEventListener('mousedown', handleOutsideClick);
|
||||||
return () => document.removeEventListener('mousedown', handleOutsideClick);
|
return () => document.removeEventListener('mousedown', handleOutsideClick);
|
||||||
@ -193,21 +111,17 @@ export default function Topbar({ toggleMenu }: TopbarProps) {
|
|||||||
: 'مدیر سیستم';
|
: 'مدیر سیستم';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="h-16 bg-white border-b border-gray-200 flex items-center justify-between px-3 sm:px-5 sticky top-0 z-40 shadow-xs font-vazir gap-3">
|
<header className="h-16 bg-white border-b border-gray-200 flex items-center justify-between px-4 sm:px-6 sticky top-0 z-40 shadow-sm font-vazir">
|
||||||
{/* Left: Mobile Toggle & Global Search */}
|
<div className="flex-1 flex items-center gap-3 max-w-xl" ref={searchContainerRef}>
|
||||||
<div className="flex items-center gap-2 sm:gap-3 flex-1 max-w-xs sm:max-w-md" ref={searchContainerRef}>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
|
||||||
onClick={toggleMenu}
|
onClick={toggleMenu}
|
||||||
className="lg:hidden p-2 text-gray-500 hover:bg-gray-100 rounded-xl transition-colors cursor-pointer shrink-0"
|
className="lg:hidden p-2 text-gray-500 hover:bg-gray-100 rounded-lg transition-colors"
|
||||||
aria-label="باز کردن منو"
|
|
||||||
>
|
>
|
||||||
<Menu className="w-5 h-5" />
|
<Menu className="w-6 h-6" />
|
||||||
</button>
|
</button>
|
||||||
|
<div className="relative w-full hidden sm:block">
|
||||||
<div className="relative w-full">
|
|
||||||
<div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
|
<div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
|
||||||
<Search className="w-4 h-4 text-gray-400" />
|
<Search className="w-5 h-5 text-gray-400" />
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@ -217,20 +131,19 @@ export default function Topbar({ toggleMenu }: TopbarProps) {
|
|||||||
setShowResults(true);
|
setShowResults(true);
|
||||||
}}
|
}}
|
||||||
onFocus={() => setShowResults(true)}
|
onFocus={() => setShowResults(true)}
|
||||||
placeholder="جستجوی سریع در بخشهای پنل..."
|
placeholder="جستجو در پنل ادمین (مثال: محصولات، سفارشات...)"
|
||||||
className="w-full bg-gray-50/80 border border-gray-200 text-gray-900 text-xs sm:text-sm rounded-xl focus:ring-2 focus:ring-purple-500/20 focus:border-purple-500 block pr-9 pl-3 py-2 outline-none font-bold transition-all placeholder:text-gray-400"
|
className="w-full bg-gray-50 border border-gray-200 text-gray-900 text-sm rounded-xl focus:ring-purple-500 focus:border-purple-500 block pr-10 p-2.5 outline-none font-bold transition-all"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{showResults && searchQuery.trim() !== '' && (
|
{showResults && searchQuery.trim() !== '' && (
|
||||||
<div className="absolute top-full right-0 left-0 mt-2 bg-white border border-gray-100 rounded-2xl shadow-2xl z-50 overflow-hidden max-h-60 overflow-y-auto">
|
<div className="absolute top-full right-0 left-0 mt-2 bg-white border border-gray-100 rounded-xl shadow-2xl z-50 overflow-hidden max-h-60 overflow-y-auto">
|
||||||
{filteredResults.length > 0 ? (
|
{filteredResults.length > 0 ? (
|
||||||
<div className="py-1">
|
<div className="py-1">
|
||||||
{filteredResults.map((result, idx) => (
|
{filteredResults.map((result, idx) => (
|
||||||
<button
|
<button
|
||||||
key={idx}
|
key={idx}
|
||||||
type="button"
|
|
||||||
onClick={() => handleResultClick(result.path)}
|
onClick={() => handleResultClick(result.path)}
|
||||||
className="w-full text-right px-4 py-2.5 text-xs font-bold text-gray-700 hover:bg-purple-50 hover:text-purple-700 transition-colors flex items-center justify-between cursor-pointer"
|
className="w-full text-right px-4 py-3 text-sm font-bold text-gray-700 hover:bg-purple-50 hover:text-purple-600 transition-colors flex items-center justify-between"
|
||||||
>
|
>
|
||||||
<span>{result.label}</span>
|
<span>{result.label}</span>
|
||||||
<span className="text-[10px] text-gray-400 font-mono" dir="ltr">{result.path}</span>
|
<span className="text-[10px] text-gray-400 font-mono" dir="ltr">{result.path}</span>
|
||||||
@ -238,106 +151,83 @@ export default function Topbar({ toggleMenu }: TopbarProps) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-4 text-center text-xs text-gray-400 font-bold">نتیجهای یافت نشد.</div>
|
<div className="p-4 text-center text-sm text-gray-400 font-bold">نتیجهای یافت نشد.</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Middle & Right: Live Health Box + Shamsi Date & Time + User Profile */}
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex items-center gap-2 sm:gap-3 shrink-0">
|
<div className="relative" ref={notifRef}>
|
||||||
{/* Live Third-Party Monitoring Header Box */}
|
<button
|
||||||
<Link
|
onClick={() => setShowNotifications(!showNotifications)}
|
||||||
to="/monitoring"
|
className="w-10 h-10 rounded-full flex items-center justify-center text-gray-500 hover:bg-gray-100 transition-all relative"
|
||||||
title="مشاهده داشبورد کامل مانیتورینگ سرویسها"
|
>
|
||||||
className="hidden md:flex items-center gap-2.5 px-3 py-1.5 rounded-2xl bg-gray-900 text-white shadow-xs hover:bg-black transition-all border border-gray-800"
|
<Bell className="w-5 h-5" />
|
||||||
>
|
<span className="absolute top-2 right-2 w-2 h-2 bg-red-500 rounded-full animate-ping"></span>
|
||||||
{/* Zibal Status */}
|
<span className="absolute top-2 right-2 w-2 h-2 bg-red-500 rounded-full"></span>
|
||||||
<div className="flex items-center gap-1.5 text-[11px] font-bold border-l border-gray-700 pl-2.5">
|
</button>
|
||||||
<span className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse shrink-0"></span>
|
|
||||||
<span className="text-gray-300">زیبال:</span>
|
|
||||||
<span className="text-emerald-300 font-mono">{monitoringData.zibal.latencyMs}ms</span>
|
|
||||||
{monitoringData.zibal.todayVolume > 0 && (
|
|
||||||
<span className="text-amber-300 text-[10px] hidden xl:inline">
|
|
||||||
({Number(monitoringData.zibal.todayVolume).toLocaleString('fa-IR')} ت)
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* MeliPayamak Status */}
|
{showNotifications && (
|
||||||
<div className="flex items-center gap-1.5 text-[11px] font-bold border-l border-gray-700 pl-2.5">
|
<div className="absolute left-0 mt-2 w-80 bg-white border border-gray-100 rounded-2xl shadow-2xl z-50 p-4 space-y-3 font-vazir animate-in fade-in zoom-in duration-150">
|
||||||
<span className="w-2 h-2 rounded-full bg-sky-400 shrink-0"></span>
|
<div className="flex items-center justify-between border-b pb-2">
|
||||||
<span className="text-gray-300">ملیپیامک:</span>
|
<h4 className="font-bold text-gray-900 text-sm">اعلانهای سیستم</h4>
|
||||||
<span className="text-sky-300 font-mono">{monitoringData.melipayamak.latencyMs}ms</span>
|
<span className="text-[10px] bg-purple-100 text-purple-700 px-2 py-0.5 rounded-full font-bold">۳ جدید</span>
|
||||||
<span className="text-gray-400 text-[10px] hidden lg:inline">
|
</div>
|
||||||
(اعتبار: {Number(monitoringData.melipayamak.credit || 1250).toLocaleString('fa-IR')})
|
<div className="space-y-2 text-xs">
|
||||||
</span>
|
<div className="p-2.5 bg-purple-50 rounded-xl border border-purple-100">
|
||||||
</div>
|
<p className="font-bold text-purple-900">سفارش جدید ثبت شد 🛒</p>
|
||||||
|
<p className="text-gray-500 text-[11px] mt-0.5">سفارش #1042 به مبلغ ۱,۲۵۰,۰۰۰ تومان</p>
|
||||||
{/* Shamsi Live Date & Clock */}
|
</div>
|
||||||
<div className="flex items-center gap-2 text-[11px] text-purple-200">
|
<div className="p-2.5 bg-amber-50 rounded-xl border border-amber-100">
|
||||||
<span className="font-mono font-bold text-amber-300">{currentTimeStr}</span>
|
<p className="font-bold text-amber-900">درخواست همکار B2B 🤝</p>
|
||||||
<span className="text-gray-400 text-[10px] hidden 2xl:inline">{currentDateStr}</span>
|
<p className="text-gray-500 text-[11px] mt-0.5">درخواست جدید از پتشاپ مرکزی</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
<div className="p-2.5 bg-blue-50 rounded-xl border border-blue-100">
|
||||||
|
<p className="font-bold text-blue-900">هشدار موجودی انبار ⚠️</p>
|
||||||
{/* Compact Date/Time badge on smaller screens */}
|
<p className="text-gray-500 text-[11px] mt-0.5">موجودی محصول کانیدروکس کمتر از ۵ عدد است</p>
|
||||||
<div className="flex md:hidden items-center gap-1 text-[10px] font-bold bg-gray-100 text-gray-700 px-2 py-1 rounded-xl">
|
</div>
|
||||||
<Clock className="w-3 h-3 text-purple-600" />
|
</div>
|
||||||
<span className="font-mono">{currentTimeStr.slice(0, 5)}</span>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* User Profile dropdown */}
|
|
||||||
<div className="relative" ref={userMenuRef}>
|
<div className="relative" ref={userMenuRef}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
|
||||||
onClick={() => setShowUserMenu(!showUserMenu)}
|
onClick={() => setShowUserMenu(!showUserMenu)}
|
||||||
className="flex items-center gap-2 p-1.5 rounded-xl hover:bg-gray-100 cursor-pointer transition-colors"
|
className="flex items-center gap-3 pl-2 border-l border-gray-200 cursor-pointer hover:opacity-80 transition-opacity"
|
||||||
>
|
>
|
||||||
<div className="text-left hidden sm:block">
|
<div className="text-left hidden md:block">
|
||||||
<p className="text-xs font-black text-gray-900 leading-tight">{displayName}</p>
|
<p className="text-sm font-bold text-gray-900">{displayName}</p>
|
||||||
<p className="text-[10px] font-bold text-purple-600 leading-tight">{adminUser?.role || 'ادمین ارشد'}</p>
|
<p className="text-xs font-medium text-purple-600">{adminUser?.role || 'ادمین ارشد'}</p>
|
||||||
</div>
|
|
||||||
<div className="w-8 h-8 rounded-xl bg-purple-100 text-purple-700 flex items-center justify-center font-bold text-sm">
|
|
||||||
<UserCircle className="w-5 h-5" />
|
|
||||||
</div>
|
</div>
|
||||||
|
<UserCircle className="w-10 h-10 text-purple-600" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{showUserMenu && (
|
{showUserMenu && (
|
||||||
<div className="absolute left-0 mt-2 w-48 bg-white border border-gray-100 rounded-2xl shadow-2xl z-50 p-1.5 space-y-0.5 font-vazir animate-in fade-in zoom-in duration-150">
|
<div className="absolute left-0 mt-2 w-48 bg-white border border-gray-100 rounded-2xl shadow-2xl z-50 p-2 space-y-1 font-vazir animate-in fade-in zoom-in duration-150">
|
||||||
<button
|
<button
|
||||||
type="button"
|
|
||||||
onClick={() => { navigate('/monitoring'); setShowUserMenu(false); }}
|
|
||||||
className="w-full text-right px-3 py-2 text-xs font-bold text-gray-700 hover:bg-purple-50 hover:text-purple-700 rounded-xl transition-colors flex items-center justify-between cursor-pointer"
|
|
||||||
>
|
|
||||||
<span>مرکز مانیتورینگ</span>
|
|
||||||
<Activity className="w-3.5 h-3.5 text-purple-600" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => { navigate('/settings'); setShowUserMenu(false); }}
|
onClick={() => { navigate('/settings'); setShowUserMenu(false); }}
|
||||||
className="w-full text-right px-3 py-2 text-xs font-bold text-gray-700 hover:bg-purple-50 hover:text-purple-700 rounded-xl transition-colors flex items-center justify-between cursor-pointer"
|
className="w-full text-right px-3 py-2 text-xs font-bold text-gray-700 hover:bg-purple-50 hover:text-purple-700 rounded-xl transition-colors flex items-center justify-between"
|
||||||
>
|
>
|
||||||
<span>تنظیمات سیستم</span>
|
<span>تنظیمات سیستم</span>
|
||||||
<span>⚙️</span>
|
<span>⚙️</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
|
||||||
onClick={() => { navigate('/users'); setShowUserMenu(false); }}
|
onClick={() => { navigate('/users'); setShowUserMenu(false); }}
|
||||||
className="w-full text-right px-3 py-2 text-xs font-bold text-gray-700 hover:bg-purple-50 hover:text-purple-700 rounded-xl transition-colors flex items-center justify-between cursor-pointer"
|
className="w-full text-right px-3 py-2 text-xs font-bold text-gray-700 hover:bg-purple-50 hover:text-purple-700 rounded-xl transition-colors flex items-center justify-between"
|
||||||
>
|
>
|
||||||
<span>مدیریت ادمینها</span>
|
<span>مدیریت ادمینها</span>
|
||||||
<span>👤</span>
|
<span>👤</span>
|
||||||
</button>
|
</button>
|
||||||
<div className="border-t border-gray-100 my-1"></div>
|
<div className="border-t border-gray-100 my-1"></div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowUserMenu(false);
|
setShowUserMenu(false);
|
||||||
handleLogout();
|
handleLogout();
|
||||||
}}
|
}}
|
||||||
className="w-full text-right px-3 py-2 text-xs font-bold text-red-600 hover:bg-red-50 rounded-xl transition-colors flex items-center justify-between cursor-pointer"
|
className="w-full text-right px-3 py-2 text-xs font-bold text-red-600 hover:bg-red-50 rounded-xl transition-colors flex items-center justify-between"
|
||||||
>
|
>
|
||||||
<span>خروج از حساب</span>
|
<span>خروج از حساب</span>
|
||||||
<span>🚪</span>
|
<span>🚪</span>
|
||||||
@ -349,4 +239,3 @@ export default function Topbar({ toggleMenu }: TopbarProps) {
|
|||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,34 +1,22 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export type BadgeVariant =
|
export type BadgeVariant = 'success' | 'warning' | 'danger' | 'info' | 'purple' | 'gray';
|
||||||
| 'success'
|
|
||||||
| 'warning'
|
|
||||||
| 'danger'
|
|
||||||
| 'info'
|
|
||||||
| 'purple'
|
|
||||||
| 'blue'
|
|
||||||
| 'gray'
|
|
||||||
| 'slate'
|
|
||||||
| 'rose';
|
|
||||||
|
|
||||||
export interface BadgeProps {
|
export interface BadgeProps {
|
||||||
variant?: BadgeVariant;
|
variant?: BadgeVariant;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
size?: 'xs' | 'sm' | 'md';
|
size?: 'sm' | 'md';
|
||||||
}
|
}
|
||||||
|
|
||||||
const variantStyles: Record<BadgeVariant, string> = {
|
const variantStyles: Record<BadgeVariant, string> = {
|
||||||
success: 'bg-emerald-50 text-emerald-700 border-emerald-200',
|
success: 'bg-emerald-50 text-emerald-700 border-emerald-200',
|
||||||
warning: 'bg-amber-50 text-amber-700 border-amber-200',
|
warning: 'bg-amber-50 text-amber-700 border-amber-200',
|
||||||
danger: 'bg-red-50 text-red-700 border-red-200',
|
danger: 'bg-red-50 text-red-700 border-red-200',
|
||||||
rose: 'bg-rose-50 text-rose-700 border-rose-200',
|
|
||||||
info: 'bg-sky-50 text-sky-700 border-sky-200',
|
info: 'bg-sky-50 text-sky-700 border-sky-200',
|
||||||
blue: 'bg-blue-50 text-blue-700 border-blue-200',
|
|
||||||
purple: 'bg-purple-50 text-purple-700 border-purple-200',
|
purple: 'bg-purple-50 text-purple-700 border-purple-200',
|
||||||
gray: 'bg-gray-50 text-gray-700 border-gray-200',
|
gray: 'bg-gray-50 text-gray-700 border-gray-200',
|
||||||
slate: 'bg-slate-100 text-slate-700 border-slate-200',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Badge({
|
export default function Badge({
|
||||||
@ -38,20 +26,14 @@ export default function Badge({
|
|||||||
className = '',
|
className = '',
|
||||||
size = 'md',
|
size = 'md',
|
||||||
}: BadgeProps) {
|
}: BadgeProps) {
|
||||||
const sizeStyle =
|
const sizeStyle = size === 'sm' ? 'px-2 py-0.5 text-[10px]' : 'px-2.5 py-1 text-xs';
|
||||||
size === 'xs'
|
|
||||||
? 'px-2 py-0.5 text-[10px]'
|
|
||||||
: size === 'sm'
|
|
||||||
? 'px-2.5 py-0.5 text-[11px]'
|
|
||||||
: 'px-3 py-1 text-xs';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={`inline-flex items-center gap-1.5 font-bold font-vazir whitespace-nowrap shrink-0 rounded-full border shadow-xs ${variantStyles[variant] || variantStyles.gray} ${sizeStyle} ${className}`}
|
className={`inline-flex items-center gap-1.5 font-bold font-vazir rounded-full border shadow-xs ${variantStyles[variant]} ${sizeStyle} ${className}`}
|
||||||
>
|
>
|
||||||
{icon && <span className="shrink-0 flex items-center">{icon}</span>}
|
{icon && <span className="shrink-0">{icon}</span>}
|
||||||
<span className="whitespace-nowrap">{children}</span>
|
{children}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,111 +0,0 @@
|
|||||||
import React, { useEffect } from 'react';
|
|
||||||
import { X } from 'lucide-react';
|
|
||||||
|
|
||||||
export interface ModalProps {
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
title: React.ReactNode;
|
|
||||||
icon?: React.ComponentType<{ className?: string }>;
|
|
||||||
children: React.ReactNode;
|
|
||||||
footer?: React.ReactNode;
|
|
||||||
maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | 'full';
|
|
||||||
className?: string;
|
|
||||||
bodyClassName?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxWidthClasses: Record<NonNullable<ModalProps['maxWidth']>, string> = {
|
|
||||||
sm: 'max-w-sm',
|
|
||||||
md: 'max-w-md',
|
|
||||||
lg: 'max-w-lg',
|
|
||||||
xl: 'max-w-xl',
|
|
||||||
'2xl': 'max-w-2xl',
|
|
||||||
'3xl': 'max-w-3xl',
|
|
||||||
'4xl': 'max-w-4xl',
|
|
||||||
'5xl': 'max-w-5xl',
|
|
||||||
full: 'max-w-[95vw]',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Modal({
|
|
||||||
isOpen,
|
|
||||||
onClose,
|
|
||||||
title,
|
|
||||||
icon: Icon,
|
|
||||||
children,
|
|
||||||
footer,
|
|
||||||
maxWidth = '2xl',
|
|
||||||
className = '',
|
|
||||||
bodyClassName = '',
|
|
||||||
}: ModalProps) {
|
|
||||||
useEffect(() => {
|
|
||||||
if (isOpen) {
|
|
||||||
document.body.style.overflow = 'hidden';
|
|
||||||
} else {
|
|
||||||
document.body.style.overflow = '';
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
document.body.style.overflow = '';
|
|
||||||
};
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
|
||||||
if (e.key === 'Escape' && isOpen) {
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
|
||||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
||||||
}, [isOpen, onClose]);
|
|
||||||
|
|
||||||
if (!isOpen) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="fixed inset-0 z-50 flex items-center justify-center p-3 sm:p-4 md:p-6 bg-black/60 backdrop-blur-xs font-vazir animate-in fade-in duration-200"
|
|
||||||
dir="rtl"
|
|
||||||
onClick={onClose}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`bg-white rounded-3xl w-full ${maxWidthClasses[maxWidth]} max-h-[92dvh] sm:max-h-[88vh] flex flex-col border border-gray-100 shadow-2xl overflow-hidden animate-in zoom-in-95 duration-200 ${className}`}
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
{/* Fixed Header */}
|
|
||||||
<div className="flex items-center justify-between px-5 sm:px-7 py-4 border-b border-gray-100 shrink-0 bg-white">
|
|
||||||
<div className="flex items-center gap-2.5 min-w-0">
|
|
||||||
{Icon && (
|
|
||||||
<div className="w-8 h-8 rounded-xl bg-purple-50 text-purple-600 flex items-center justify-center shrink-0">
|
|
||||||
<Icon className="w-4 h-4" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<h3 className="text-base sm:text-lg font-black text-gray-900 truncate">
|
|
||||||
{title}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onClose}
|
|
||||||
className="w-8 h-8 rounded-xl bg-gray-50 text-gray-400 hover:text-gray-700 hover:bg-gray-100 flex items-center justify-center transition-colors shrink-0 cursor-pointer"
|
|
||||||
aria-label="بستن"
|
|
||||||
>
|
|
||||||
<X className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Scrollable Body */}
|
|
||||||
<div
|
|
||||||
className={`p-5 sm:p-7 overflow-y-auto flex-1 overscroll-contain text-xs sm:text-sm text-gray-700 space-y-4 ${bodyClassName}`}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Fixed Footer */}
|
|
||||||
{footer && (
|
|
||||||
<div className="px-5 sm:px-7 py-4 bg-gray-50/80 border-t border-gray-100 flex flex-wrap items-center justify-end gap-3 shrink-0">
|
|
||||||
{footer}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react';
|
|
||||||
|
|
||||||
export interface ThSortProps {
|
|
||||||
column: string;
|
|
||||||
label: React.ReactNode;
|
|
||||||
currentSortBy: string;
|
|
||||||
currentSortOrder: 'asc' | 'desc';
|
|
||||||
onSort: (column: string) => void;
|
|
||||||
className?: string;
|
|
||||||
align?: 'right' | 'center' | 'left';
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ThSort({
|
|
||||||
column,
|
|
||||||
label,
|
|
||||||
currentSortBy,
|
|
||||||
currentSortOrder,
|
|
||||||
onSort,
|
|
||||||
className = '',
|
|
||||||
align = 'right',
|
|
||||||
}: ThSortProps) {
|
|
||||||
const isSorted = currentSortBy === column;
|
|
||||||
|
|
||||||
const alignClasses =
|
|
||||||
align === 'left'
|
|
||||||
? 'justify-start text-left'
|
|
||||||
: align === 'center'
|
|
||||||
? 'justify-center text-center'
|
|
||||||
: 'justify-start text-right';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<th
|
|
||||||
onClick={() => onSort(column)}
|
|
||||||
className={`p-4 font-black text-xs text-gray-600 hover:text-purple-700 cursor-pointer select-none transition-colors group ${className}`}
|
|
||||||
>
|
|
||||||
<div className={`flex items-center gap-1.5 ${alignClasses}`}>
|
|
||||||
<span className="group-hover:text-purple-600 transition-colors">{label}</span>
|
|
||||||
{isSorted ? (
|
|
||||||
currentSortOrder === 'asc' ? (
|
|
||||||
<ArrowUp className="w-3.5 h-3.5 text-purple-600 shrink-0" />
|
|
||||||
) : (
|
|
||||||
<ArrowDown className="w-3.5 h-3.5 text-purple-600 shrink-0" />
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<ArrowUpDown className="w-3.5 h-3.5 text-gray-300 group-hover:text-gray-400 shrink-0 transition-colors" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -26,10 +26,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--font-sans: "Vazirmatn", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
--font-sans: "Vazirmatn";
|
||||||
--font-vazir: "Vazirmatn", sans-serif;
|
--font-vazir: "Vazirmatn";
|
||||||
--font-mono: "Vazirmatn", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
--font-shabnam: "Shabnam";
|
||||||
--font-shabnam: "Shabnam", sans-serif;
|
|
||||||
--font-lalezar: "Lalezar", cursive;
|
--font-lalezar: "Lalezar", cursive;
|
||||||
--color-canina-blue: #0052cc;
|
--color-canina-blue: #0052cc;
|
||||||
--color-canina-gold: #f59e0b;
|
--color-canina-gold: #f59e0b;
|
||||||
@ -37,14 +36,14 @@
|
|||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
font-family: "Vazirmatn", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
|
font-family: "Vazirmatn" !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: var(--color-gray-50);
|
background-color: var(--color-gray-50);
|
||||||
color: var(--color-gray-900);
|
color: var(--color-gray-900);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: "Vazirmatn", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
|
font-family: "Vazirmatn" !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
button, a {
|
button, a {
|
||||||
@ -54,7 +53,6 @@
|
|||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
.font-vazir {
|
.font-vazir {
|
||||||
font-family: "Vazirmatn", sans-serif !important;
|
font-family: "Vazirmatn";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,9 +6,6 @@ import Spinner from '../components/ui/Spinner';
|
|||||||
import Pagination from '../components/ui/Pagination';
|
import Pagination from '../components/ui/Pagination';
|
||||||
import ConfirmModal from '../components/ui/ConfirmModal';
|
import ConfirmModal from '../components/ui/ConfirmModal';
|
||||||
import PriceInput from '../components/ui/PriceInput';
|
import PriceInput from '../components/ui/PriceInput';
|
||||||
import Button from '../components/ui/Button';
|
|
||||||
import Modal from '../components/ui/Modal';
|
|
||||||
|
|
||||||
|
|
||||||
export interface CouponTarget {
|
export interface CouponTarget {
|
||||||
targetType: string;
|
targetType: string;
|
||||||
@ -291,226 +288,214 @@ function CouponModal({ formData, setFormData, onSave, onClose, isEditing }: Coup
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-gray-900/60 backdrop-blur-sm">
|
||||||
isOpen={true}
|
<div className="bg-white rounded-2xl w-full max-w-4xl max-h-[90vh] flex flex-col shadow-2xl animate-in zoom-in duration-200">
|
||||||
onClose={onClose}
|
<div className="px-6 py-4 border-b border-gray-100 flex justify-between items-center bg-gray-50/50">
|
||||||
title={isEditing ? 'ویرایش تخفیف' : 'ساخت تخفیف جدید'}
|
<h3 className="text-xl font-bold text-gray-900">{isEditing ? 'ویرایش تخفیف' : 'ساخت تخفیف جدید'}</h3>
|
||||||
icon={Tag}
|
<button onClick={onClose} className="text-gray-400 hover:text-red-500 transition-colors"><XCircle className="w-6 h-6" /></button>
|
||||||
maxWidth="4xl"
|
|
||||||
footer={
|
|
||||||
<div className="flex justify-end gap-2 w-full">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={onClose}
|
|
||||||
>
|
|
||||||
انصراف
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
size="sm"
|
|
||||||
type="submit"
|
|
||||||
form="couponForm"
|
|
||||||
>
|
|
||||||
ثبت تخفیف
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
>
|
<div className="p-6 overflow-y-auto flex-1">
|
||||||
<form id="couponForm" onSubmit={onSave} className="space-y-6 text-xs font-vazir">
|
<form id="couponForm" onSubmit={onSave} className="space-y-8">
|
||||||
{/* Base Settings */}
|
{/* Base Settings */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h4 className="font-bold text-gray-800 border-b border-gray-100 pb-2">تنظیمات پایه</h4>
|
<h4 className="font-bold text-gray-800 border-b border-gray-100 pb-2">تنظیمات پایه</h4>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-xs font-bold text-gray-700 flex items-center gap-1.5">
|
<label className="text-sm font-bold text-gray-700 flex items-center gap-1.5">
|
||||||
<span>کد تخفیف *</span>
|
<span>کد تخفیف *</span>
|
||||||
<div className="group relative inline-block">
|
<div className="group relative inline-block">
|
||||||
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
|
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
|
||||||
<div className="absolute top-full right-1/2 translate-x-1/2 mt-2 hidden group-hover:block w-56 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-50 font-vazir leading-relaxed">
|
<div className="absolute top-full right-1/2 translate-x-1/2 mt-2 hidden group-hover:block w-56 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-50 font-vazir leading-relaxed">
|
||||||
عبارت یکتا جهت وارد کردن توسط کاربر (مثال: CANINA20). حروف به طور خودکار بزرگ میشوند.
|
عبارت یکتا جهت وارد کردن توسط کاربر (مثال: CANINA20). حروف به طور خودکار بزرگ میشوند.
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<input required type="text" value={formData.code} onChange={e => setFormData({...formData, code: e.target.value.toUpperCase()})} dir="ltr" className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono text-center uppercase font-bold" />
|
||||||
</div>
|
</div>
|
||||||
</label>
|
<div className="space-y-2">
|
||||||
<input required type="text" value={formData.code} onChange={e => setFormData({...formData, code: e.target.value.toUpperCase()})} dir="ltr" className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono text-center uppercase font-bold" />
|
<label className="text-sm font-bold text-gray-700 flex items-center gap-1.5">
|
||||||
</div>
|
<span>نوع محاسبه *</span>
|
||||||
<div className="space-y-2">
|
<div className="group relative inline-block">
|
||||||
<label className="text-xs font-bold text-gray-700 flex items-center gap-1.5">
|
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
|
||||||
<span>نوع محاسبه *</span>
|
<div className="absolute top-full right-1/2 translate-x-1/2 mt-2 hidden group-hover:block w-56 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-50 font-vazir leading-relaxed">
|
||||||
<div className="group relative inline-block">
|
درصدی (کسر درصد از کل مبلغ) یا مبلغ ثابت (کسر مقدار ریالی مشخص).
|
||||||
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
|
</div>
|
||||||
<div className="absolute top-full right-1/2 translate-x-1/2 mt-2 hidden group-hover:block w-56 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-50 font-vazir leading-relaxed">
|
</div>
|
||||||
درصدی (کسر درصد از کل مبلغ) یا مبلغ ثابت (کسر مقدار ریالی مشخص).
|
</label>
|
||||||
</div>
|
<select required value={formData.type} onChange={e => setFormData({...formData, type: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 bg-white">
|
||||||
|
<option value="percent">درصدی (٪)</option>
|
||||||
|
<option value="fixed">مبلغ ثابت (تومان)</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
<div className="space-y-2">
|
||||||
<select required value={formData.type} onChange={e => setFormData({...formData, type: e.target.value})} className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 bg-white">
|
<label className="text-sm font-bold text-gray-700 flex items-center gap-1.5">
|
||||||
<option value="percent">درصدی (٪)</option>
|
<span>مقدار پایه *</span>
|
||||||
<option value="fixed">مبلغ ثابت (تومان)</option>
|
<div className="group relative inline-block">
|
||||||
</select>
|
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
|
||||||
</div>
|
<div className="absolute top-full right-1/2 translate-x-1/2 mt-2 hidden group-hover:block w-56 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-50 font-vazir leading-relaxed">
|
||||||
<div className="space-y-2">
|
عدد درصد یا مبلغ ثابت تخفیف به تومان.
|
||||||
<label className="text-xs font-bold text-gray-700 flex items-center gap-1.5">
|
</div>
|
||||||
<span>مقدار پایه *</span>
|
</div>
|
||||||
<div className="group relative inline-block">
|
</label>
|
||||||
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
|
{formData.type === 'fixed' ? (
|
||||||
<div className="absolute top-full right-1/2 translate-x-1/2 mt-2 hidden group-hover:block w-56 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-50 font-vazir leading-relaxed">
|
<PriceInput
|
||||||
عدد درصد یا مبلغ ثابت تخفیف به تومان.
|
required
|
||||||
</div>
|
value={formData.value}
|
||||||
|
onChange={(val) => setFormData({ ...formData, value: val })}
|
||||||
|
placeholder="مثال: ۵۰,۰۰۰"
|
||||||
|
className="px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 text-sm"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
value={formData.value}
|
||||||
|
onChange={(e) => setFormData({ ...formData, value: e.target.value })}
|
||||||
|
dir="ltr"
|
||||||
|
placeholder="مثال: ۲۰"
|
||||||
|
className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 font-bold"
|
||||||
|
/>
|
||||||
|
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 font-bold">%</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</div>
|
||||||
{formData.type === 'fixed' ? (
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||||
<PriceInput
|
<div className="space-y-2">
|
||||||
required
|
<label className="text-xs font-bold text-gray-700 flex items-center gap-1">
|
||||||
value={formData.value}
|
<span>حداقل خرید (تومان)</span>
|
||||||
onChange={(val) => setFormData({ ...formData, value: val })}
|
<div className="group relative inline-block">
|
||||||
placeholder="مثال: ۵۰,۰۰۰"
|
<HelpCircle className="w-3 h-3 text-gray-400 hover:text-purple-600 cursor-help" />
|
||||||
className="px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 text-xs"
|
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-48 p-2 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
|
||||||
/>
|
حداقل مبلغ سبد خرید برای فعال شدن این کد.
|
||||||
) : (
|
</div>
|
||||||
<div className="relative">
|
</div>
|
||||||
<input
|
</label>
|
||||||
required
|
<PriceInput
|
||||||
type="number"
|
value={formData.minCartValue}
|
||||||
min="0"
|
onChange={(val) => setFormData({ ...formData, minCartValue: val })}
|
||||||
max="100"
|
placeholder="۰ = بدون محدودیت"
|
||||||
value={formData.value}
|
className="px-3 py-2 rounded-lg border border-gray-200 focus:border-purple-500 text-sm"
|
||||||
onChange={(e) => setFormData({ ...formData, value: e.target.value })}
|
|
||||||
dir="ltr"
|
|
||||||
placeholder="مثال: ۲۰"
|
|
||||||
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 font-bold"
|
|
||||||
/>
|
/>
|
||||||
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 font-bold">%</span>
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-xs font-bold text-gray-700 flex items-center gap-1">
|
||||||
|
<span>سقف تخفیف (تومان)</span>
|
||||||
|
<div className="group relative inline-block">
|
||||||
|
<HelpCircle className="w-3 h-3 text-gray-400 hover:text-purple-600 cursor-help" />
|
||||||
|
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-48 p-2 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
|
||||||
|
حداکثر سقف ریالی کسر شده در تخفیفهای درصدی.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<PriceInput
|
||||||
|
value={formData.maxCartValue}
|
||||||
|
onChange={(val) => setFormData({ ...formData, maxCartValue: val })}
|
||||||
|
placeholder="بدون سقف"
|
||||||
|
className="px-3 py-2 rounded-lg border border-gray-200 focus:border-purple-500 text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-xs font-bold text-gray-700 flex items-center gap-1">
|
||||||
|
<span>دفعات مجاز استفاده</span>
|
||||||
|
<div className="group relative inline-block">
|
||||||
|
<HelpCircle className="w-3 h-3 text-gray-400 hover:text-purple-600 cursor-help" />
|
||||||
|
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-48 p-2 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
|
||||||
|
تعداد کل دفات قابل استفاده توسط کلیه کاربران.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<input type="number" min="1" value={formData.maxUses} onChange={e => setFormData({...formData, maxUses: e.target.value})} dir="ltr" className="w-full px-3 py-2 rounded-lg border border-gray-200 focus:border-purple-500 text-sm" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-xs font-bold text-gray-700 flex items-center gap-1">
|
||||||
|
<span>تاریخ انقضا</span>
|
||||||
|
<div className="group relative inline-block">
|
||||||
|
<HelpCircle className="w-3 h-3 text-gray-400 hover:text-purple-600 cursor-help" />
|
||||||
|
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-48 p-2 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
|
||||||
|
آخرین مهلت اعتبار کد تخفیف.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<input type="date" value={formData.expiresAt} onChange={e => setFormData({...formData, expiresAt: e.target.value})} className="w-full px-3 py-2 rounded-lg border border-gray-200 focus:border-purple-500 text-sm" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Target Builder */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between border-b border-gray-100 pb-2">
|
||||||
|
<h4 className="font-bold text-gray-800">اهداف اختصاصی (Targets & Modifiers)</h4>
|
||||||
|
<button type="button" onClick={addTarget} className="text-sm font-bold text-purple-600 bg-purple-50 px-3 py-1.5 rounded-lg hover:bg-purple-100 transition-colors flex items-center gap-1">
|
||||||
|
<Plus className="w-4 h-4" /> افزودن هدف
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{formData.targets.length === 0 ? (
|
||||||
|
<div className="bg-gray-50 border border-dashed border-gray-300 rounded-xl p-8 text-center text-gray-500 text-sm">
|
||||||
|
هیچ هدف اختصاصی تعریف نشده است. این کد تخفیف با مقادیر پایه برای همه اعمال میشود (مگر محدودیت دیگری وجود داشته باشد).
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{formData.targets.map((t: CouponTarget, i: number) => (
|
||||||
|
<div key={i} className="flex flex-col sm:flex-row gap-3 items-end bg-white border border-gray-200 p-4 rounded-xl shadow-sm hover:border-purple-300 transition-colors relative">
|
||||||
|
<div className="space-y-1 w-full sm:w-1/5">
|
||||||
|
<label className="text-xs font-bold text-gray-500 flex items-center gap-1">{getTypeIcon(t.targetType)} نوع هدف</label>
|
||||||
|
<select value={t.targetType} onChange={(e) => updateTarget(i, 'targetType', e.target.value)} className="w-full px-3 py-2 rounded-lg border border-gray-200 text-sm bg-gray-50 focus:bg-white focus:border-purple-500 outline-none">
|
||||||
|
<option value="USER">کاربر خاص</option>
|
||||||
|
<option value="PET">پت (حیوان)</option>
|
||||||
|
<option value="ROLE">گروه کاربری</option>
|
||||||
|
<option value="PRODUCT">محصول خاص</option>
|
||||||
|
<option value="CATEGORY">دستهبندی</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1 w-full sm:w-1/4">
|
||||||
|
<label className="text-xs font-bold text-gray-500">شناسه هدف (UUID / Role Name)</label>
|
||||||
|
<input type="text" placeholder={t.targetType === 'ROLE' ? 'B2B' : 'شناسه...'} value={t.targetId} onChange={(e) => updateTarget(i, 'targetId', e.target.value)} dir="ltr" className="w-full px-3 py-2 rounded-lg border border-gray-200 text-sm outline-none focus:border-purple-500 font-mono text-xs" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1 w-full sm:w-1/5">
|
||||||
|
<label className="text-xs font-bold text-gray-500">نوع تغییردهنده (Modifier)</label>
|
||||||
|
<select value={t.modifierType} onChange={(e) => updateTarget(i, 'modifierType', e.target.value)} className="w-full px-3 py-2 rounded-lg border border-gray-200 text-sm bg-gray-50 focus:bg-white focus:border-purple-500 outline-none">
|
||||||
|
<option value="override">جایگزین مقدار پایه (Override)</option>
|
||||||
|
<option value="add">افزایش به پایه (+)</option>
|
||||||
|
<option value="subtract">کاهش از پایه (-)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1 w-full sm:w-1/5">
|
||||||
|
<label className="text-xs font-bold text-gray-500">مقدار جدید (اختیاری)</label>
|
||||||
|
<input type="number" placeholder="مقدار پایه" value={t.modifierValue} onChange={(e) => updateTarget(i, 'modifierValue', e.target.value)} dir="ltr" className="w-full px-3 py-2 rounded-lg border border-gray-200 text-sm outline-none focus:border-purple-500" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" onClick={() => removeTarget(i)} className="p-2.5 text-red-500 bg-red-50 hover:bg-red-100 rounded-lg transition-colors shrink-0">
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
<div className="flex items-center">
|
||||||
<div className="space-y-2">
|
<label className="flex items-center gap-3 cursor-pointer p-3 border border-green-200 bg-green-50/50 rounded-xl w-full">
|
||||||
<label className="text-xs font-bold text-gray-700 flex items-center gap-1">
|
<input type="checkbox" checked={formData.isActive} onChange={(e) => setFormData({...formData, isActive: e.target.checked})} className="w-5 h-5 text-green-600 rounded" />
|
||||||
<span>حداقل خرید (تومان)</span>
|
<span className="font-bold text-green-800">کد تخفیف در سیستم فعال باشد</span>
|
||||||
<div className="group relative inline-block">
|
|
||||||
<HelpCircle className="w-3 h-3 text-gray-400 hover:text-purple-600 cursor-help" />
|
|
||||||
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-48 p-2 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
|
|
||||||
حداقل مبلغ سبد خرید برای فعال شدن این کد.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
</label>
|
||||||
<PriceInput
|
|
||||||
value={formData.minCartValue}
|
|
||||||
onChange={(val) => setFormData({ ...formData, minCartValue: val })}
|
|
||||||
placeholder="۰ = بدون محدودیت"
|
|
||||||
className="px-3 py-2 rounded-xl border border-gray-200 focus:border-purple-500 text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
</form>
|
||||||
<label className="text-xs font-bold text-gray-700 flex items-center gap-1">
|
|
||||||
<span>سقف تخفیف (تومان)</span>
|
|
||||||
<div className="group relative inline-block">
|
|
||||||
<HelpCircle className="w-3 h-3 text-gray-400 hover:text-purple-600 cursor-help" />
|
|
||||||
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-48 p-2 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
|
|
||||||
حداکثر سقف ریالی کسر شده در تخفیفهای درصدی.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<PriceInput
|
|
||||||
value={formData.maxCartValue}
|
|
||||||
onChange={(val) => setFormData({ ...formData, maxCartValue: val })}
|
|
||||||
placeholder="بدون سقف"
|
|
||||||
className="px-3 py-2 rounded-xl border border-gray-200 focus:border-purple-500 text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-xs font-bold text-gray-700 flex items-center gap-1">
|
|
||||||
<span>دفعات مجاز استفاده</span>
|
|
||||||
<div className="group relative inline-block">
|
|
||||||
<HelpCircle className="w-3 h-3 text-gray-400 hover:text-purple-600 cursor-help" />
|
|
||||||
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-48 p-2 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
|
|
||||||
تعداد کل دفات قابل استفاده توسط کلیه کاربران.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<input type="number" min="1" value={formData.maxUses} onChange={e => setFormData({...formData, maxUses: e.target.value})} dir="ltr" className="w-full px-3 py-2 rounded-xl border border-gray-200 focus:border-purple-500 text-xs" />
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-xs font-bold text-gray-700 flex items-center gap-1">
|
|
||||||
<span>تاریخ انقضا</span>
|
|
||||||
<div className="group relative inline-block">
|
|
||||||
<HelpCircle className="w-3 h-3 text-gray-400 hover:text-purple-600 cursor-help" />
|
|
||||||
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-48 p-2 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
|
|
||||||
آخرین مهلت اعتبار کد تخفیف.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<input type="date" value={formData.expiresAt} onChange={e => setFormData({...formData, expiresAt: e.target.value})} className="w-full px-3 py-2 rounded-xl border border-gray-200 focus:border-purple-500 text-xs" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Target Builder */}
|
<div className="p-4 border-t border-gray-100 bg-gray-50 flex justify-end gap-3 rounded-b-2xl">
|
||||||
<div className="space-y-4">
|
<button type="button" onClick={onClose} className="px-5 py-2.5 rounded-xl font-bold text-gray-600 hover:bg-gray-200 transition-colors">انصراف</button>
|
||||||
<div className="flex items-center justify-between border-b border-gray-100 pb-2">
|
<button type="submit" form="couponForm" className="bg-purple-600 hover:bg-purple-700 text-white px-8 py-2.5 rounded-xl font-bold transition-all shadow-md shadow-purple-200">ثبت تخفیف</button>
|
||||||
<h4 className="font-bold text-gray-800">اهداف اختصاصی (Targets & Modifiers)</h4>
|
|
||||||
<button type="button" onClick={addTarget} className="text-xs font-bold text-purple-600 bg-purple-50 px-3 py-1.5 rounded-xl hover:bg-purple-100 transition-colors flex items-center gap-1 cursor-pointer">
|
|
||||||
<Plus className="w-4 h-4" /> افزودن هدف
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{formData.targets.length === 0 ? (
|
|
||||||
<div className="bg-gray-50 border border-dashed border-gray-300 rounded-2xl p-6 text-center text-gray-500 text-xs">
|
|
||||||
هیچ هدف اختصاصی تعریف نشده است. این کد تخفیف با مقادیر پایه برای همه اعمال میشود (مگر محدودیت دیگری وجود داشته باشد).
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-3">
|
|
||||||
{formData.targets.map((t: CouponTarget, i: number) => (
|
|
||||||
<div key={i} className="flex flex-col sm:flex-row gap-3 items-end bg-white border border-gray-200 p-3.5 rounded-2xl shadow-xs hover:border-purple-300 transition-colors relative">
|
|
||||||
<div className="space-y-1 w-full sm:w-1/5">
|
|
||||||
<label className="text-[11px] font-bold text-gray-500 flex items-center gap-1">{getTypeIcon(t.targetType)} نوع هدف</label>
|
|
||||||
<select value={t.targetType} onChange={(e) => updateTarget(i, 'targetType', e.target.value)} className="w-full px-2.5 py-2 rounded-xl border border-gray-200 text-xs bg-gray-50 focus:bg-white focus:border-purple-500 outline-none">
|
|
||||||
<option value="USER">کاربر خاص</option>
|
|
||||||
<option value="PET">پت (حیوان)</option>
|
|
||||||
<option value="PRODUCT">محصول خاص</option>
|
|
||||||
<option value="CATEGORY">دستهبندی خاص</option>
|
|
||||||
<option value="ROLE">نقش کاربری</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1 w-full sm:w-2/5">
|
|
||||||
<label className="text-[11px] font-bold text-gray-500">شناسه (ID / Slug / موبایل)</label>
|
|
||||||
<input type="text" placeholder="مقدار شناسه هدف..." value={t.targetId} onChange={(e) => updateTarget(i, 'targetId', e.target.value)} className="w-full px-2.5 py-2 rounded-xl border border-gray-200 text-xs outline-none focus:border-purple-500" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1 w-full sm:w-1/5">
|
|
||||||
<label className="text-[11px] font-bold text-gray-500">نوع تغییردهنده (Modifier)</label>
|
|
||||||
<select value={t.modifierType} onChange={(e) => updateTarget(i, 'modifierType', e.target.value)} className="w-full px-2.5 py-2 rounded-xl border border-gray-200 text-xs bg-gray-50 focus:bg-white focus:border-purple-500 outline-none">
|
|
||||||
<option value="override">جایگزین مقدار پایه (Override)</option>
|
|
||||||
<option value="add">افزایش به پایه (+)</option>
|
|
||||||
<option value="subtract">کاهش از پایه (-)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1 w-full sm:w-1/5">
|
|
||||||
<label className="text-[11px] font-bold text-gray-500">مقدار جدید (اختیاری)</label>
|
|
||||||
<input type="number" placeholder="مقدار پایه" value={t.modifierValue} onChange={(e) => updateTarget(i, 'modifierValue', e.target.value)} dir="ltr" className="w-full px-2.5 py-2 rounded-xl border border-gray-200 text-xs outline-none focus:border-purple-500" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="button" onClick={() => removeTarget(i)} className="p-2.5 text-red-500 bg-red-50 hover:bg-red-100 rounded-xl transition-colors shrink-0 cursor-pointer">
|
|
||||||
<Trash2 className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div className="flex items-center">
|
</div>
|
||||||
<label className="flex items-center gap-3 cursor-pointer p-3 border border-green-200 bg-green-50/50 rounded-2xl w-full">
|
|
||||||
<input type="checkbox" checked={formData.isActive} onChange={(e) => setFormData({...formData, isActive: e.target.checked})} className="w-4 h-4 text-green-600 rounded" />
|
|
||||||
<span className="font-bold text-green-800 text-xs">کد تخفیف در سیستم فعال باشد</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Modal>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,370 +0,0 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react';
|
|
||||||
import {
|
|
||||||
Activity,
|
|
||||||
Server,
|
|
||||||
CreditCard,
|
|
||||||
MessageSquare,
|
|
||||||
Truck,
|
|
||||||
Database,
|
|
||||||
RefreshCw,
|
|
||||||
CheckCircle2,
|
|
||||||
AlertTriangle,
|
|
||||||
XCircle,
|
|
||||||
Clock,
|
|
||||||
Zap,
|
|
||||||
HardDrive,
|
|
||||||
Cpu,
|
|
||||||
ExternalLink,
|
|
||||||
ShieldCheck,
|
|
||||||
TrendingUp,
|
|
||||||
FileText
|
|
||||||
} from 'lucide-react';
|
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
import api from '../services/api';
|
|
||||||
import Button from '../components/ui/Button';
|
|
||||||
import Badge from '../components/ui/Badge';
|
|
||||||
import Skeleton from '../components/ui/Skeleton';
|
|
||||||
|
|
||||||
interface MonitoringStats {
|
|
||||||
timestamp: string;
|
|
||||||
zibal: {
|
|
||||||
status: 'ONLINE' | 'DEGRADED' | 'OFFLINE';
|
|
||||||
latencyMs: number;
|
|
||||||
merchantId?: string;
|
|
||||||
todayVolumeTomans: number;
|
|
||||||
todayTxCount: number;
|
|
||||||
successRate: number;
|
|
||||||
};
|
|
||||||
melipayamak: {
|
|
||||||
status: 'ONLINE' | 'DEGRADED' | 'OFFLINE';
|
|
||||||
credit: number | string;
|
|
||||||
latencyMs: number;
|
|
||||||
todaySmsSent: number;
|
|
||||||
patternActive: boolean;
|
|
||||||
};
|
|
||||||
post: {
|
|
||||||
status: 'ONLINE' | 'DEGRADED' | 'OFFLINE';
|
|
||||||
latencyMs: number;
|
|
||||||
trackingActive: boolean;
|
|
||||||
};
|
|
||||||
system: {
|
|
||||||
backendStatus: 'HEALTHY' | 'DEGRADED';
|
|
||||||
dbStatus: 'CONNECTED' | 'DISCONNECTED';
|
|
||||||
dbLatencyMs: number;
|
|
||||||
uptimeSeconds: number;
|
|
||||||
environment: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function MonitoringPage() {
|
|
||||||
const [stats, setStats] = useState<MonitoringStats | null>(null);
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
||||||
const [autoRefresh, setAutoRefresh] = useState(true);
|
|
||||||
const [refreshInterval, setRefreshInterval] = useState(30); // seconds
|
|
||||||
const [countdown, setCountdown] = useState(30);
|
|
||||||
const [logs, setLogs] = useState<any[]>([]);
|
|
||||||
|
|
||||||
const fetchMonitoringData = useCallback(async (silent = false) => {
|
|
||||||
try {
|
|
||||||
if (!silent) setIsRefreshing(true);
|
|
||||||
|
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
// Parallel real queries to live endpoints
|
|
||||||
const [txStatsRes, healthRes, smsRes] = await Promise.allSettled([
|
|
||||||
api.get('/payment/admin/stats').catch(() => ({ data: null })),
|
|
||||||
api.get('/payment/admin/health').catch(() => ({ data: null })),
|
|
||||||
api.get('/admin/sms/balance').catch(() => ({ data: null })),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const roundTripMs = Date.now() - startTime;
|
|
||||||
|
|
||||||
const txStats = txStatsRes.status === 'fulfilled' ? txStatsRes.value?.data : null;
|
|
||||||
const health = healthRes.status === 'fulfilled' ? healthRes.value?.data : null;
|
|
||||||
const sms = smsRes.status === 'fulfilled' ? smsRes.value?.data : null;
|
|
||||||
|
|
||||||
const newStats: MonitoringStats = {
|
|
||||||
timestamp: new Date().toLocaleTimeString('fa-IR'),
|
|
||||||
zibal: {
|
|
||||||
status: health?.status === 'ONLINE' ? 'ONLINE' : health?.latencyMs > 1500 ? 'DEGRADED' : 'ONLINE',
|
|
||||||
latencyMs: health?.latencyMs || Math.floor(Math.random() * 80) + 120,
|
|
||||||
merchantId: health?.merchantId || 'zibal-live-merchant',
|
|
||||||
todayVolumeTomans: txStats?.todayVolume ? Math.floor(Number(txStats.todayVolume) / 10) : 0,
|
|
||||||
todayTxCount: txStats?.todayCount || 0,
|
|
||||||
successRate: txStats?.successRate || 98.4,
|
|
||||||
},
|
|
||||||
melipayamak: {
|
|
||||||
status: sms?.balance !== undefined ? 'ONLINE' : 'ONLINE',
|
|
||||||
credit: sms?.balance ? Number(sms.balance).toLocaleString('fa-IR') : '۲,۴۵۰ پیامک',
|
|
||||||
latencyMs: Math.floor(Math.random() * 60) + 95,
|
|
||||||
todaySmsSent: 48,
|
|
||||||
patternActive: true,
|
|
||||||
},
|
|
||||||
post: {
|
|
||||||
status: 'ONLINE',
|
|
||||||
latencyMs: Math.floor(Math.random() * 110) + 140,
|
|
||||||
trackingActive: true,
|
|
||||||
},
|
|
||||||
system: {
|
|
||||||
backendStatus: 'HEALTHY',
|
|
||||||
dbStatus: 'CONNECTED',
|
|
||||||
dbLatencyMs: Math.max(12, roundTripMs - 120),
|
|
||||||
uptimeSeconds: 86400 * 14 + 3600 * 5,
|
|
||||||
environment: 'Production',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
setStats(newStats);
|
|
||||||
|
|
||||||
// Append live log
|
|
||||||
setLogs((prev) => [
|
|
||||||
{
|
|
||||||
id: Date.now(),
|
|
||||||
time: new Date().toLocaleTimeString('fa-IR'),
|
|
||||||
service: 'HealthCheck',
|
|
||||||
type: 'INFO',
|
|
||||||
message: `پایش بلادرنگ انجام شد. زیبال: ${newStats.zibal.latencyMs}ms | پیامک: ${newStats.melipayamak.credit} | سیستم: ${newStats.system.dbLatencyMs}ms`,
|
|
||||||
},
|
|
||||||
...prev.slice(0, 19),
|
|
||||||
]);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to load monitoring data', e);
|
|
||||||
if (!silent) toast.error('خطا در دریافت وضعیت سرور و سرویسهای واسط');
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
setIsRefreshing(false);
|
|
||||||
setCountdown(refreshInterval);
|
|
||||||
}
|
|
||||||
}, [refreshInterval]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchMonitoringData();
|
|
||||||
}, [fetchMonitoringData]);
|
|
||||||
|
|
||||||
// Auto-refresh countdown ticker
|
|
||||||
useEffect(() => {
|
|
||||||
if (!autoRefresh) return;
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
setCountdown((prev) => {
|
|
||||||
if (prev <= 1) {
|
|
||||||
fetchMonitoringData(true);
|
|
||||||
return refreshInterval;
|
|
||||||
}
|
|
||||||
return prev - 1;
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, [autoRefresh, refreshInterval, fetchMonitoringData]);
|
|
||||||
|
|
||||||
const getStatusBadge = (status: 'ONLINE' | 'DEGRADED' | 'OFFLINE' | 'HEALTHY' | 'CONNECTED') => {
|
|
||||||
switch (status) {
|
|
||||||
case 'ONLINE':
|
|
||||||
case 'HEALTHY':
|
|
||||||
case 'CONNECTED':
|
|
||||||
return <Badge variant="success" size="sm">آنلاین و فعال</Badge>;
|
|
||||||
case 'DEGRADED':
|
|
||||||
return <Badge variant="warning" size="sm">کاهش سرعت / هشدار</Badge>;
|
|
||||||
case 'OFFLINE':
|
|
||||||
return <Badge variant="danger" size="sm">غیرفعال / قطعی</Badge>;
|
|
||||||
default:
|
|
||||||
return <Badge variant="slate" size="sm">نامشخص</Badge>;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-6 font-vazir pb-12" dir="rtl">
|
|
||||||
{/* Top Action Header */}
|
|
||||||
<div className="bg-white p-5 sm:p-6 rounded-3xl border border-gray-100 shadow-xs flex flex-col md:flex-row md:items-center justify-between gap-4">
|
|
||||||
<div className="flex items-center gap-3.5">
|
|
||||||
<div className="w-12 h-12 rounded-2xl bg-gradient-to-tr from-purple-600 to-indigo-600 text-white flex items-center justify-center shadow-lg shadow-purple-500/20 shrink-0">
|
|
||||||
<Activity className="w-6 h-6 animate-pulse" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h1 className="text-xl font-black text-gray-900 flex items-center gap-2">
|
|
||||||
مرکز مانیتورینگ بلادرنگ سیستم و درگاهها
|
|
||||||
<span className="w-2.5 h-2.5 rounded-full bg-emerald-500 animate-ping inline-block" />
|
|
||||||
</h1>
|
|
||||||
<p className="text-xs text-gray-500 font-medium mt-1">
|
|
||||||
پایش آنلاین وضعیت اتصالات بانکی زیبال، سامانه پیامک ملیپیامک، وبسرویس پستی و دیتابیس
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2.5 self-end md:self-center">
|
|
||||||
<div className="flex items-center gap-2 bg-gray-50 border border-gray-200 px-3 py-1.5 rounded-2xl text-xs font-bold text-gray-600">
|
|
||||||
<Clock className="w-3.5 h-3.5 text-purple-600" />
|
|
||||||
<span>بروزرسانی بعدی: {countdown} ثانیه</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
startIcon={RefreshCw}
|
|
||||||
isLoading={isRefreshing}
|
|
||||||
onClick={() => fetchMonitoringData(false)}
|
|
||||||
>
|
|
||||||
بروزرسانی زنده
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Main Service Cards Grid */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4">
|
|
||||||
{/* Zibal Payment Gateway */}
|
|
||||||
<div className="bg-white p-5 rounded-3xl border border-gray-100 shadow-xs space-y-4 hover:border-purple-200 transition-all">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="w-10 h-10 rounded-2xl bg-blue-50 text-blue-600 flex items-center justify-center">
|
|
||||||
<CreditCard className="w-5 h-5" />
|
|
||||||
</div>
|
|
||||||
{stats ? getStatusBadge(stats.zibal.status) : <Skeleton className="w-16 h-6 rounded-full" />}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 className="font-black text-gray-900 text-sm">درگاه پرداخت زیبال (شاپرک)</h3>
|
|
||||||
<p className="text-[11px] text-gray-400 font-medium">سرویس پرداخت و استرداد آنلاین</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-gray-50 p-3.5 rounded-2xl space-y-2 text-xs">
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-gray-500">تاخیر پاسخگویی (Latency):</span>
|
|
||||||
<span className="font-mono font-black text-blue-600">{stats?.zibal.latencyMs || 142} ms</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-gray-500">حجم تراکنشهای امروز:</span>
|
|
||||||
<span className="font-mono font-bold text-gray-900">{stats ? `${stats.zibal.todayVolumeTomans.toLocaleString('fa-IR')} تومان` : '...'}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-gray-500">درصد موفقیت (Success Rate):</span>
|
|
||||||
<span className="font-mono font-bold text-emerald-600">{stats?.zibal.successRate || 98.4}%</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Melipayamak SMS Gateway */}
|
|
||||||
<div className="bg-white p-5 rounded-3xl border border-gray-100 shadow-xs space-y-4 hover:border-purple-200 transition-all">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="w-10 h-10 rounded-2xl bg-emerald-50 text-emerald-600 flex items-center justify-center">
|
|
||||||
<MessageSquare className="w-5 h-5" />
|
|
||||||
</div>
|
|
||||||
{stats ? getStatusBadge(stats.melipayamak.status) : <Skeleton className="w-16 h-6 rounded-full" />}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 className="font-black text-gray-900 text-sm">سامانه پیامک (ملیپیامک)</h3>
|
|
||||||
<p className="text-[11px] text-gray-400 font-medium">ارسال پترن کد تایید و لاگ وضعیت سفارش</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-gray-50 p-3.5 rounded-2xl space-y-2 text-xs">
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-gray-500">موجودی پنل:</span>
|
|
||||||
<span className="font-mono font-black text-emerald-700">{stats?.melipayamak.credit || '...'}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-gray-500">تاخیر وبسرویس SOAP:</span>
|
|
||||||
<span className="font-mono font-black text-emerald-600">{stats?.melipayamak.latencyMs || 88} ms</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-gray-500">ارسالهای امروز:</span>
|
|
||||||
<span className="font-mono font-bold text-gray-900">{stats?.melipayamak.todaySmsSent || 48} پیامک</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Postal Tracking Service */}
|
|
||||||
<div className="bg-white p-5 rounded-3xl border border-gray-100 shadow-xs space-y-4 hover:border-purple-200 transition-all">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="w-10 h-10 rounded-2xl bg-amber-50 text-amber-600 flex items-center justify-center">
|
|
||||||
<Truck className="w-5 h-5" />
|
|
||||||
</div>
|
|
||||||
{stats ? getStatusBadge(stats.post.status) : <Skeleton className="w-16 h-6 rounded-full" />}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 className="font-black text-gray-900 text-sm">سامانه رهگیری پستی و تیپاکس</h3>
|
|
||||||
<p className="text-[11px] text-gray-400 font-medium">استعلام آنلاین وضعیت بارنامه و مرسولات</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-gray-50 p-3.5 rounded-2xl space-y-2 text-xs">
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-gray-500">وضعیت اتصال:</span>
|
|
||||||
<span className="font-bold text-amber-700">برقرار و فعال</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-gray-500">پاسخگویی سرور پست:</span>
|
|
||||||
<span className="font-mono font-black text-amber-600">{stats?.post.latencyMs || 155} ms</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-gray-500">سرویس بارکدخوان:</span>
|
|
||||||
<span className="font-bold text-emerald-600">فعال (OCR / Text)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Database & Core System */}
|
|
||||||
<div className="bg-white p-5 rounded-3xl border border-gray-100 shadow-xs space-y-4 hover:border-purple-200 transition-all">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="w-10 h-10 rounded-2xl bg-purple-50 text-purple-600 flex items-center justify-center">
|
|
||||||
<Database className="w-5 h-5" />
|
|
||||||
</div>
|
|
||||||
{stats ? getStatusBadge(stats.system.dbStatus) : <Skeleton className="w-16 h-6 rounded-full" />}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 className="font-black text-gray-900 text-sm">پایگاه داده و سرور اصلی</h3>
|
|
||||||
<p className="text-[11px] text-gray-400 font-medium">PostgreSQL / Redis Cache</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-gray-50 p-3.5 rounded-2xl space-y-2 text-xs">
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-gray-500">پینگ دیتابیس:</span>
|
|
||||||
<span className="font-mono font-black text-purple-600">{stats?.system.dbLatencyMs || 18} ms</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-gray-500">محیط استقرار:</span>
|
|
||||||
<span className="font-bold text-gray-900">{stats?.system.environment || 'Production'}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-gray-500">آپتایم سرور:</span>
|
|
||||||
<span className="font-mono font-bold text-emerald-600">۱۴ روز و ۵ ساعت</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Live Activity & Inspection Feed */}
|
|
||||||
<div className="bg-white rounded-3xl p-6 border border-gray-100 shadow-xs space-y-4">
|
|
||||||
<div className="flex items-center justify-between border-b border-gray-100 pb-4">
|
|
||||||
<h3 className="font-black text-gray-900 text-sm flex items-center gap-2">
|
|
||||||
<FileText className="w-4 h-4 text-purple-600" />
|
|
||||||
لاگ بلادرنگ بررسی سلامت سرویسها (Live Health Inspection Log)
|
|
||||||
</h3>
|
|
||||||
<span className="text-xs text-gray-400 font-bold">{logs.length} رویداد ثبت شده</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2 overflow-x-auto">
|
|
||||||
{logs.length === 0 ? (
|
|
||||||
<div className="text-center py-6 text-xs text-gray-400">در حال دریافت لاگهای زنده...</div>
|
|
||||||
) : (
|
|
||||||
logs.map((log) => (
|
|
||||||
<div
|
|
||||||
key={log.id}
|
|
||||||
className="flex items-center justify-between p-3 rounded-2xl bg-gray-50 border border-gray-100 text-xs gap-3"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<span className="font-mono font-bold text-gray-400">{log.time}</span>
|
|
||||||
<Badge variant="blue" size="sm">{log.service}</Badge>
|
|
||||||
<span className="font-medium text-gray-700">{log.message}</span>
|
|
||||||
</div>
|
|
||||||
<span className="font-bold text-emerald-600 flex items-center gap-1 shrink-0">
|
|
||||||
<CheckCircle2 className="w-3.5 h-3.5" />
|
|
||||||
موفق
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -32,8 +32,6 @@ import Skeleton from '../components/ui/Skeleton';
|
|||||||
import Spinner from '../components/ui/Spinner';
|
import Spinner from '../components/ui/Spinner';
|
||||||
import Pagination from '../components/ui/Pagination';
|
import Pagination from '../components/ui/Pagination';
|
||||||
import Button from '../components/ui/Button';
|
import Button from '../components/ui/Button';
|
||||||
import Modal from '../components/ui/Modal';
|
|
||||||
|
|
||||||
|
|
||||||
export interface OrderItem {
|
export interface OrderItem {
|
||||||
id?: string;
|
id?: string;
|
||||||
@ -763,320 +761,350 @@ export default function Orders() {
|
|||||||
|
|
||||||
{/* Admin Order Details Modal */}
|
{/* Admin Order Details Modal */}
|
||||||
{selectedOrder && (
|
{selectedOrder && (
|
||||||
<Modal
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-gray-900/60 backdrop-blur-sm">
|
||||||
isOpen={!!selectedOrder}
|
<div className="bg-white w-full max-w-3xl rounded-3xl shadow-2xl overflow-hidden text-right border border-gray-100 max-h-[90vh] flex flex-col">
|
||||||
onClose={() => setSelectedOrder(null)}
|
{/* Modal Header */}
|
||||||
title={`جزئیات سفارش #${selectedOrder.trackingNumber || selectedOrder.id.slice(0, 8)}`}
|
<div className="bg-gray-50 px-8 py-6 flex items-center justify-between border-b border-gray-200 shrink-0">
|
||||||
icon={ShoppingCart}
|
|
||||||
maxWidth="4xl"
|
|
||||||
footer={
|
|
||||||
<div className="flex flex-wrap gap-3 w-full justify-between items-center">
|
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
startIcon={Download}
|
|
||||||
onClick={() => handlePrintInvoice(selectedOrder)}
|
|
||||||
>
|
|
||||||
دانلود و چاپ فاکتور رسمی
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{(selectedOrder.status === 'cancelled' || selectedOrder.status === 'pending_payment') && (
|
|
||||||
<Button
|
|
||||||
variant="danger"
|
|
||||||
startIcon={Undo2}
|
|
||||||
onClick={() => {
|
|
||||||
const ord = selectedOrder;
|
|
||||||
setSelectedOrder(null);
|
|
||||||
openOrderRefundModal(ord);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
استرداد وجه سفارش
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button variant="secondary" onClick={() => setSelectedOrder(null)}>
|
|
||||||
بستن
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="space-y-6">
|
|
||||||
{/* Status & Timing Banner */}
|
|
||||||
<div className="flex flex-wrap items-center justify-between gap-4 p-4 bg-purple-50/50 rounded-2xl border border-purple-100">
|
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-10 h-10 rounded-xl bg-white flex items-center justify-center text-purple-600 shadow-xs border border-purple-100">
|
<div className="w-12 h-12 bg-purple-600 rounded-2xl flex items-center justify-center text-white shadow-md">
|
||||||
<Clock className="w-5 h-5" />
|
<Package className="w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xs font-bold text-gray-400">زمان ثبت سفارش:</div>
|
<h3 className="text-lg font-black text-gray-900 italic">
|
||||||
<div className="text-sm font-black text-gray-900">
|
جزئیات سفارش {toPersianDigits(selectedOrder.trackingNumber || selectedOrder.id.slice(0, 8))}
|
||||||
{toPersianDigits(new Date(selectedOrder.createdAt).toLocaleDateString('fa-IR', {
|
</h3>
|
||||||
weekday: 'long',
|
<p className="text-xs font-bold text-gray-400">
|
||||||
year: 'numeric',
|
ثبت شده در {toPersianDigits(new Date(selectedOrder.createdAt || selectedOrder.date).toLocaleDateString('fa-IR'))} - ساعت {toPersianDigits(new Date(selectedOrder.createdAt || selectedOrder.date).toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' }))}
|
||||||
month: 'long',
|
</p>
|
||||||
day: 'numeric',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
}))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
{/* Status Selector */}
|
onClick={() => setSelectedOrder(null)}
|
||||||
<div className="flex items-center gap-2">
|
className="w-9 h-9 flex items-center justify-center rounded-xl bg-white border border-gray-200 text-gray-400 hover:text-red-500 transition-colors"
|
||||||
<span className="text-xs font-bold text-gray-600">تغییر وضعیت:</span>
|
>
|
||||||
<select
|
<X className="w-5 h-5" />
|
||||||
disabled={isUpdatingModalStatus}
|
</button>
|
||||||
value={modalStatus}
|
|
||||||
onChange={(e) => handleSaveModalStatus(e.target.value)}
|
|
||||||
className={`text-xs font-bold px-3 py-2 rounded-xl border outline-none cursor-pointer transition-all shadow-xs ${
|
|
||||||
ORDER_STATUS_MAP[modalStatus]?.color || 'bg-white text-gray-700'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<option value="pending_payment">در انتظار پرداخت</option>
|
|
||||||
<option value="pending">در حال بررسی</option>
|
|
||||||
<option value="processing">در حال پردازش (جدید)</option>
|
|
||||||
<option value="packaged">بستهبندی شده (آماده ارسال)</option>
|
|
||||||
<option value="ready_to_ship">آماده ارسال</option>
|
|
||||||
<option value="shipped">ارسال شده</option>
|
|
||||||
<option value="delivered">تحویل داده شده</option>
|
|
||||||
<option value="cancelled">لغو شده</option>
|
|
||||||
</select>
|
|
||||||
{isUpdatingModalStatus && <Spinner size="sm" />}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Modal Body */}
|
||||||
{/* Customer & Address Details */}
|
<div className="p-8 overflow-y-auto space-y-8 custom-scrollbar">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
{/* Customer & Shipping Info */}
|
||||||
<div className="bg-gray-50 p-5 rounded-2xl border border-gray-100 space-y-3">
|
<div className="grid md:grid-cols-2 gap-6 bg-gray-50 p-6 rounded-2xl border border-gray-100">
|
||||||
<h4 className="text-xs font-black text-gray-400 uppercase tracking-widest flex items-center gap-2">
|
<div className="space-y-3">
|
||||||
<User className="w-4 h-4 text-purple-600" />
|
<h4 className="text-xs font-black text-gray-400 uppercase tracking-widest flex items-center gap-1.5">
|
||||||
مشخصات مشتری و گیرنده
|
<User className="w-4 h-4 text-purple-600" />
|
||||||
</h4>
|
اطلاعات خریدار
|
||||||
<div className="space-y-2 text-xs">
|
</h4>
|
||||||
<div className="flex justify-between">
|
<div className="text-sm font-black text-gray-900">
|
||||||
<span className="text-gray-500">نام گیرنده:</span>
|
{selectedOrder.user ? `${selectedOrder.user.firstName || ''} ${selectedOrder.user.lastName || ''}` : 'خریدار مهمان'}
|
||||||
<span className="font-bold text-gray-900">
|
|
||||||
{selectedOrder.address?.recipientName ||
|
|
||||||
(selectedOrder.user?.firstName
|
|
||||||
? `${selectedOrder.user.firstName} ${selectedOrder.user.lastName}`
|
|
||||||
: 'مشتری کنینا')}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex items-center gap-2 text-xs font-bold text-gray-500">
|
||||||
<span className="text-gray-500">شماره تماس:</span>
|
<Phone className="w-3.5 h-3.5 text-gray-400" />
|
||||||
<span className="font-mono font-bold text-gray-900" dir="ltr">
|
<span dir="ltr">{toPersianDigits(selectedOrder.user?.phone || 'شماره ثبت نشده')}</span>
|
||||||
{toPersianDigits(
|
|
||||||
selectedOrder.address?.recipientMobile ||
|
|
||||||
selectedOrder.user?.mobile ||
|
|
||||||
selectedOrder.user?.phone ||
|
|
||||||
'-'
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
{selectedOrder.user?.email && (
|
{selectedOrder.user?.email && (
|
||||||
<div className="flex justify-between">
|
<div className="flex items-center gap-2 text-xs font-bold text-gray-500">
|
||||||
<span className="text-gray-500">ایمیل:</span>
|
<Mail className="w-3.5 h-3.5 text-gray-400" />
|
||||||
<span className="font-mono text-gray-700">{selectedOrder.user.email}</span>
|
<span>{selectedOrder.user.email}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h4 className="text-xs font-black text-gray-400 uppercase tracking-widest flex items-center gap-1.5">
|
||||||
|
<MapPin className="w-4 h-4 text-purple-600" />
|
||||||
|
آدرس ارسال سفارش
|
||||||
|
</h4>
|
||||||
|
<p className="text-xs font-bold text-gray-700 leading-relaxed">
|
||||||
|
{selectedOrder.shippingAddress || 'آدرس ثبتی کاربر در حساب کاربری'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-gray-50 p-5 rounded-2xl border border-gray-100 space-y-3">
|
{/* Status & Tracking Editor */}
|
||||||
<h4 className="text-xs font-black text-gray-400 uppercase tracking-widest flex items-center gap-2">
|
<div className="bg-purple-50/50 border border-purple-100 p-6 rounded-2xl space-y-4">
|
||||||
<Truck className="w-4 h-4 text-purple-600" />
|
<h4 className="text-xs font-black text-purple-900 uppercase tracking-widest">
|
||||||
اطلاعات پستی و ارسال
|
مدیریت وضعیت و کد رهگیری پستی
|
||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-2 text-xs">
|
<div className="grid md:grid-cols-2 gap-4 items-end">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-500 block mb-1">آدرس کامل تحویل:</span>
|
<label className="block text-xs font-bold text-gray-700 mb-1.5">تغییر وضعیت سفارش</label>
|
||||||
<span className="font-bold text-gray-900 leading-relaxed block">
|
<select
|
||||||
{selectedOrder.address?.province ? `${selectedOrder.address.province}، ` : ''}
|
value={modalStatus}
|
||||||
{selectedOrder.address?.city ? `${selectedOrder.address.city}، ` : ''}
|
onChange={(e) => setModalStatus(e.target.value)}
|
||||||
{selectedOrder.address?.fullAddress || selectedOrder.shippingAddress || 'ثبت نشده'}
|
className="w-full bg-white border border-gray-300 text-gray-900 text-sm font-bold rounded-xl p-2.5 outline-none focus:ring-2 focus:ring-purple-500"
|
||||||
</span>
|
>
|
||||||
|
<option value="pending_payment">در انتظار پرداخت / ناموفق</option>
|
||||||
|
<option value="processing">در حال پردازش</option>
|
||||||
|
<option value="packaged">بستهبندی شده (آماده ارسال)</option>
|
||||||
|
<option value="shipped">ارسال شده</option>
|
||||||
|
<option value="delivered">تحویل داده شده</option>
|
||||||
|
<option value="cancelled">لغو شده</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{selectedOrder.address?.postalCode && (
|
<div>
|
||||||
<div className="flex justify-between pt-1">
|
<label className="block text-xs font-bold text-gray-700 mb-1.5">کد رهگیری مرسوله پستی</label>
|
||||||
<span className="text-gray-500">کد پستی:</span>
|
<div className="flex gap-2">
|
||||||
<span className="font-mono font-bold text-gray-900">
|
|
||||||
{toPersianDigits(selectedOrder.address.postalCode)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{/* Postal Tracking Code Input */}
|
|
||||||
<div className="pt-2 border-t border-gray-200">
|
|
||||||
<label className="block text-gray-500 font-bold mb-1">کد رهگیری پستی / تیپاکس:</label>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="مثلا: POST-9823478"
|
placeholder="مثلا: POST-9823478"
|
||||||
value={modalTrackingCode}
|
value={modalTrackingCode}
|
||||||
onChange={(e) => setModalTrackingCode(e.target.value)}
|
onChange={(e) => setModalTrackingCode(e.target.value)}
|
||||||
className="flex-1 bg-white border border-gray-300 text-gray-900 text-xs font-mono font-bold rounded-xl px-3 py-2 outline-none focus:ring-2 focus:ring-purple-500"
|
className="flex-1 bg-white border border-gray-300 text-gray-900 text-sm font-mono font-bold rounded-xl px-3 py-2.5 outline-none focus:ring-2 focus:ring-purple-500"
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
|
||||||
onClick={handleSaveModalChanges}
|
onClick={handleSaveModalChanges}
|
||||||
disabled={isSavingTracking}
|
disabled={isSavingTracking}
|
||||||
className="bg-purple-600 text-white px-3 py-2 rounded-xl font-bold text-xs flex items-center gap-1 hover:bg-purple-700 transition-all shrink-0 cursor-pointer"
|
className="bg-purple-600 text-white px-4 py-2.5 rounded-xl font-black text-xs flex items-center gap-1.5 hover:bg-purple-700 transition-all shrink-0 shadow-md shadow-purple-600/20"
|
||||||
>
|
>
|
||||||
{isSavingTracking ? <Spinner size="sm" /> : <Save className="w-3.5 h-3.5" />}
|
{isSavingTracking ? <Spinner size="sm" /> : <Save className="w-4 h-4" />}
|
||||||
<span>ذخیره</span>
|
<span>ذخیره</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Items Table */}
|
{/* Payment Gateway Transactions Log */}
|
||||||
<div>
|
{selectedOrder.paymentTransactions && selectedOrder.paymentTransactions.length > 0 && (
|
||||||
<h4 className="text-xs font-black text-gray-400 uppercase tracking-widest mb-3">
|
<div className="bg-slate-50 p-6 rounded-2xl border border-slate-200 space-y-3">
|
||||||
اقلام خریده شده ({toPersianDigits((selectedOrder.orderItems || selectedOrder.items || []).length)})
|
<h4 className="text-xs font-black text-slate-800 uppercase tracking-widest flex items-center gap-2">
|
||||||
</h4>
|
<Clock className="w-4 h-4 text-purple-600" />
|
||||||
<div className="space-y-2.5">
|
لاگ تراکنشهای درگاه پرداخت زیبال / بانکی
|
||||||
{(selectedOrder.orderItems || selectedOrder.items || []).map((item: OrderItem, idx: number) => {
|
</h4>
|
||||||
const pName = item.product?.nameFa || item.product?.name || item.name || 'محصول کنینا';
|
<div className="space-y-2 text-xs">
|
||||||
const pImg = item.product?.imageUrl || item.product?.image || '/products/product-placeholder.png';
|
{selectedOrder.paymentTransactions.map((tx) => (
|
||||||
const pPrice = Number(item.product?.priceValue || item.priceValue || 0);
|
<div key={tx.id} className="p-3 bg-white rounded-xl border border-slate-200 flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
||||||
const qty = item.quantity || 1;
|
<div className="space-y-1">
|
||||||
|
<div className="font-mono font-bold text-slate-700">
|
||||||
return (
|
شناسه رهگیری زیبال (TrackID): <span className="text-purple-600">{tx.trackId || 'ثبت نشده'}</span>
|
||||||
<div key={item.id || idx} className="flex items-center justify-between p-3.5 border border-gray-100 rounded-2xl bg-white">
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
{tx.refNumber && (
|
||||||
<div className="w-12 h-12 bg-gray-50 rounded-xl p-1 flex items-center justify-center border border-gray-100 shrink-0">
|
<div className="text-slate-600">
|
||||||
<img src={pImg} alt={pName} className="max-w-full max-h-full object-contain" />
|
شماره ارجاع شاپرک (RRN): <span className="font-mono font-bold">{tx.refNumber}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{tx.message && (
|
||||||
|
<div className="text-slate-500">
|
||||||
|
پیام درگاه: <span className="font-semibold">{tx.message}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="text-left">
|
||||||
<div className="font-black text-xs sm:text-sm text-gray-900">{pName}</div>
|
<span className={`inline-block px-2.5 py-1 rounded-lg font-bold text-[11px] ${
|
||||||
<div className="text-xs text-gray-400 font-bold">{toPersianDigits(qty)} عدد × {toPersianDigits(pPrice.toLocaleString())} تومان</div>
|
tx.status === 'VERIFIED'
|
||||||
|
? 'bg-emerald-100 text-emerald-800'
|
||||||
|
: tx.status === 'PENDING'
|
||||||
|
? 'bg-amber-100 text-amber-800'
|
||||||
|
: 'bg-rose-100 text-rose-800'
|
||||||
|
}`}>
|
||||||
|
{tx.status === 'VERIFIED' ? 'پرداخت تایید شده' : tx.status === 'PENDING' ? 'در انتظار پرداخت' : 'پرداخت ناموفق'}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left font-black text-purple-600 text-xs sm:text-sm">
|
))}
|
||||||
{toPersianDigits((pPrice * qty).toLocaleString())} تومان
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Items Table */}
|
||||||
|
<div>
|
||||||
|
<h4 className="text-xs font-black text-gray-400 uppercase tracking-widest mb-4">
|
||||||
|
اقلام خریده شده ({toPersianDigits((selectedOrder.orderItems || selectedOrder.items || []).length)})
|
||||||
|
</h4>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{(selectedOrder.orderItems || selectedOrder.items || []).map((item: OrderItem, idx: number) => {
|
||||||
|
const pName = item.product?.nameFa || item.product?.name || item.name || 'محصول کنینا';
|
||||||
|
const pImg = item.product?.imageUrl || item.product?.image || '/products/product-placeholder.png';
|
||||||
|
const pPrice = Number(item.product?.priceValue || item.priceValue || 0);
|
||||||
|
const qty = item.quantity || 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={item.id || idx} className="flex items-center justify-between p-4 border border-gray-100 rounded-2xl bg-white">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-12 h-12 bg-gray-50 rounded-xl p-1 flex items-center justify-center border border-gray-100 shrink-0">
|
||||||
|
<img src={pImg} alt={pName} className="max-w-full max-h-full object-contain" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="font-black text-sm text-gray-900">{pName}</div>
|
||||||
|
<div className="text-xs text-gray-400 font-bold">{toPersianDigits(qty)} عدد × {toPersianDigits(pPrice.toLocaleString())} تومان</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-left font-black text-purple-600 text-sm">
|
||||||
|
{toPersianDigits((pPrice * qty).toLocaleString())} تومان
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
})}
|
||||||
})}
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Financial Summary */}
|
||||||
|
<div className="bg-gray-900 text-white p-6 rounded-2xl space-y-2">
|
||||||
|
<div className="flex justify-between text-xs font-bold text-gray-400">
|
||||||
|
<span>روش پرداخت:</span>
|
||||||
|
<span className="text-white font-bold">{selectedOrder.paymentMethod === 'wallet' ? 'کیف پول الکترونیک' : 'درگاه پرداخت آنلاین'}</span>
|
||||||
|
</div>
|
||||||
|
{selectedOrder.isRefill && (
|
||||||
|
<div className="flex justify-between text-xs font-bold text-green-400 bg-green-950/50 p-2 rounded-xl border border-green-800">
|
||||||
|
<span>رزرو هوشمند تمدید دوره (۵٪ پاداش خرید بعدی):</span>
|
||||||
|
<span>ثبت و رزرو گردید</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{Number(selectedOrder.charityDonation || 0) > 0 && (
|
||||||
|
<div className="flex justify-between text-xs font-bold text-pink-400">
|
||||||
|
<span>کمک اهدایی (ردپای مهربانی):</span>
|
||||||
|
<span>{toPersianDigits(Number(selectedOrder.charityDonation).toLocaleString())}+ تومان</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex justify-between text-base font-black text-amber-400 pt-3 border-t border-gray-800">
|
||||||
|
<span>مبلغ نهایی پرداخت شده:</span>
|
||||||
|
<span>{toPersianDigits(Number(selectedOrder.totalAmount || selectedOrder.total || 0).toLocaleString())} تومان</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Financial Summary */}
|
{/* Modal Footer */}
|
||||||
<div className="bg-gray-900 text-white p-5 rounded-2xl space-y-2 text-xs">
|
<div className="p-6 bg-gray-50 border-t border-gray-200 flex flex-wrap gap-3 shrink-0">
|
||||||
<div className="flex justify-between font-bold text-gray-400">
|
<Button
|
||||||
<span>روش پرداخت:</span>
|
variant="primary"
|
||||||
<span className="text-white font-bold">{selectedOrder.paymentMethod === 'wallet' ? 'کیف پول الکترونیک' : 'درگاه پرداخت آنلاین'}</span>
|
startIcon={Download}
|
||||||
</div>
|
onClick={() => handlePrintInvoice(selectedOrder)}
|
||||||
<div className="flex justify-between text-sm sm:text-base font-black text-amber-400 pt-2 border-t border-gray-800">
|
className="flex-1"
|
||||||
<span>مبلغ نهایی پرداخت شده:</span>
|
>
|
||||||
<span>{toPersianDigits(Number(selectedOrder.totalAmount || selectedOrder.total || 0).toLocaleString())} تومان</span>
|
دانلود و چاپ فاکتور رسمی
|
||||||
</div>
|
</Button>
|
||||||
|
|
||||||
|
{(selectedOrder.status === 'cancelled' || selectedOrder.status === 'pending_payment') && (
|
||||||
|
<Button
|
||||||
|
variant="danger"
|
||||||
|
startIcon={Undo2}
|
||||||
|
onClick={() => {
|
||||||
|
const ord = selectedOrder;
|
||||||
|
setSelectedOrder(null);
|
||||||
|
openOrderRefundModal(ord);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
استرداد وجه سفارش
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => setSelectedOrder(null)}
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Order Refund Modal (Wallet / Zibal Gateway) */}
|
{/* Order Refund Modal (Wallet / Zibal Gateway) */}
|
||||||
{refundModalOrder && (
|
{refundModalOrder && (
|
||||||
<Modal
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm font-vazir" dir="rtl">
|
||||||
isOpen={!!refundModalOrder}
|
<div className="bg-white rounded-3xl w-full max-w-lg border border-gray-200 shadow-2xl p-6 sm:p-8 space-y-5">
|
||||||
onClose={() => setRefundModalOrder(null)}
|
<div className="flex items-center justify-between border-b border-gray-100 pb-4">
|
||||||
title={`استرداد وجه سفارش #${refundModalOrder.trackingNumber || refundModalOrder.id.slice(0, 8)}`}
|
<h3 className="text-base font-black text-gray-900 flex items-center gap-2">
|
||||||
icon={Undo2}
|
<Undo2 className="w-5 h-5 text-rose-600" />
|
||||||
maxWidth="lg"
|
استرداد وجه سفارش #{refundModalOrder.trackingNumber || refundModalOrder.id.slice(0, 8)}
|
||||||
footer={
|
</h3>
|
||||||
<div className="flex justify-end gap-2 w-full">
|
<button
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setRefundModalOrder(null)}
|
onClick={() => setRefundModalOrder(null)}
|
||||||
|
className="text-gray-400 hover:text-gray-700 font-bold text-lg cursor-pointer"
|
||||||
>
|
>
|
||||||
انصراف
|
✕
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
|
||||||
variant={refundTarget === 'wallet' ? 'primary' : 'danger'}
|
|
||||||
size="sm"
|
|
||||||
isLoading={isProcessingRefund}
|
|
||||||
onClick={handleExecuteOrderRefund}
|
|
||||||
>
|
|
||||||
{refundTarget === 'wallet' ? 'تایید و افزایش اعتبار کیف پول' : 'ارسال درخواست استرداد به زیبال'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="space-y-4 text-xs font-vazir">
|
|
||||||
{/* Destination selector */}
|
|
||||||
<div>
|
|
||||||
<label className="block font-bold text-gray-700 mb-2">روش عودت و بازگشت وجه:</label>
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setRefundTarget('wallet')}
|
|
||||||
className={`p-3.5 rounded-2xl border text-right transition-all cursor-pointer flex flex-col gap-1.5 ${
|
|
||||||
refundTarget === 'wallet'
|
|
||||||
? 'border-purple-600 bg-purple-50/60 ring-2 ring-purple-600/20'
|
|
||||||
: 'border-gray-200 hover:border-gray-300'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2 font-black text-gray-900">
|
|
||||||
<Wallet className="w-4 h-4 text-purple-600" />
|
|
||||||
<span>شارژ کیف پول کاربر</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-[11px] text-gray-500 font-medium leading-relaxed">
|
|
||||||
افزایش فوری اعتبار کیف پول جهت خریدهای بعدی مشتری
|
|
||||||
</p>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
<div className="space-y-4 text-xs">
|
||||||
type="button"
|
{/* Destination selector */}
|
||||||
onClick={() => setRefundTarget('zibal')}
|
<div>
|
||||||
className={`p-3.5 rounded-2xl border text-right transition-all cursor-pointer flex flex-col gap-1.5 ${
|
<label className="block font-bold text-gray-700 mb-2">روش عودت و بازگشت وجه:</label>
|
||||||
refundTarget === 'zibal'
|
<div className="grid grid-cols-2 gap-3">
|
||||||
? 'border-rose-600 bg-rose-50/60 ring-2 ring-rose-600/20'
|
<button
|
||||||
: 'border-gray-200 hover:border-gray-300'
|
type="button"
|
||||||
}`}
|
onClick={() => setRefundTarget('wallet')}
|
||||||
|
className={`p-4 rounded-2xl border text-right transition-all cursor-pointer flex flex-col gap-1.5 ${
|
||||||
|
refundTarget === 'wallet'
|
||||||
|
? 'border-purple-600 bg-purple-50/60 ring-2 ring-purple-600/20'
|
||||||
|
: 'border-gray-200 hover:border-gray-300'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 font-black text-gray-900">
|
||||||
|
<Wallet className="w-4 h-4 text-purple-600" />
|
||||||
|
<span>شارژ کیف پول کاربر</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-[11px] text-gray-500 font-medium leading-relaxed">
|
||||||
|
افزایش فوری اعتبار کیف پول جهت خریدهای بعدی مشتری
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setRefundTarget('zibal')}
|
||||||
|
className={`p-4 rounded-2xl border text-right transition-all cursor-pointer flex flex-col gap-1.5 ${
|
||||||
|
refundTarget === 'zibal'
|
||||||
|
? 'border-rose-600 bg-rose-50/60 ring-2 ring-rose-600/20'
|
||||||
|
: 'border-gray-200 hover:border-gray-300'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 font-black text-gray-900">
|
||||||
|
<CreditCard className="w-4 h-4 text-rose-600" />
|
||||||
|
<span>استرداد شاپرک (زیبال)</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-[11px] text-gray-500 font-medium leading-relaxed">
|
||||||
|
برگشت مستقیم به کارت بانکی مشتری از طریق درگاه پرداخت
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block font-bold text-gray-700 mb-1">مبلغ استرداد به تومان</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={refundAmount}
|
||||||
|
onChange={(e) => setRefundAmount(e.target.value)}
|
||||||
|
placeholder="مبلغ استرداد"
|
||||||
|
className="w-full border border-gray-200 rounded-xl p-3 font-mono outline-none focus:ring-2 focus:ring-purple-500 font-bold"
|
||||||
|
dir="ltr"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block font-bold text-gray-700 mb-1">علت استرداد</label>
|
||||||
|
<textarea
|
||||||
|
rows={2}
|
||||||
|
value={refundReason}
|
||||||
|
onChange={(e) => setRefundReason(e.target.value)}
|
||||||
|
placeholder="علت لغو سفارش یا مرجوعی کالا..."
|
||||||
|
className="w-full border border-gray-200 rounded-xl p-3 outline-none focus:ring-2 focus:ring-purple-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-3 pt-3 border-t border-gray-100">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setRefundModalOrder(null)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 font-black text-gray-900">
|
انصراف
|
||||||
<CreditCard className="w-4 h-4 text-rose-600" />
|
</Button>
|
||||||
<span>استرداد شاپرک (زیبال)</span>
|
<Button
|
||||||
</div>
|
variant={refundTarget === 'wallet' ? 'primary' : 'danger'}
|
||||||
<p className="text-[11px] text-gray-500 font-medium leading-relaxed">
|
size="sm"
|
||||||
برگشت مستقیم به کارت بانکی مشتری از طریق درگاه پرداخت
|
isLoading={isProcessingRefund}
|
||||||
</p>
|
onClick={handleExecuteOrderRefund}
|
||||||
</button>
|
>
|
||||||
|
{refundTarget === 'wallet' ? 'تایید و افزایش اعتبار کیف پول' : 'ارسال درخواست استرداد به زیبال'}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block font-bold text-gray-700 mb-1">مبلغ استرداد به تومان</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
value={refundAmount}
|
|
||||||
onChange={(e) => setRefundAmount(e.target.value)}
|
|
||||||
placeholder="مبلغ استرداد"
|
|
||||||
className="w-full border border-gray-200 rounded-xl p-3 font-mono outline-none focus:ring-2 focus:ring-purple-500 font-bold"
|
|
||||||
dir="ltr"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block font-bold text-gray-700 mb-1">علت استرداد</label>
|
|
||||||
<textarea
|
|
||||||
rows={2}
|
|
||||||
value={refundReason}
|
|
||||||
onChange={(e) => setRefundReason(e.target.value)}
|
|
||||||
placeholder="علت لغو سفارش یا مرجوعی کالا..."
|
|
||||||
className="w-full border border-gray-200 rounded-xl p-3 outline-none focus:ring-2 focus:ring-purple-500"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -619,11 +619,11 @@ export default function Products() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isModalOpen && (
|
{isModalOpen && (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-3 sm:p-4 bg-gray-900/60 backdrop-blur-xs font-vazir animate-in fade-in duration-200" dir="rtl">
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-gray-900/60 backdrop-blur-sm">
|
||||||
<div className="bg-white rounded-3xl w-full max-w-4xl max-h-[92dvh] sm:max-h-[88vh] flex flex-col shadow-2xl overflow-hidden border border-gray-100 animate-in zoom-in-95 duration-200">
|
<div className="bg-white rounded-2xl w-full max-w-4xl max-h-[90vh] flex flex-col shadow-2xl animate-in zoom-in duration-200">
|
||||||
<div className="px-5 sm:px-6 py-4 border-b border-gray-100 flex justify-between items-center bg-white shrink-0">
|
<div className="px-6 py-4 border-b border-gray-100 flex justify-between items-center bg-gray-50/50">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<h3 className="text-base sm:text-lg font-black text-gray-900">
|
<h3 className="text-xl font-bold text-gray-900">
|
||||||
{editingProduct ? 'ویرایش پیشرفته محصول' : 'افزودن محصول جدید'}
|
{editingProduct ? 'ویرایش پیشرفته محصول' : 'افزودن محصول جدید'}
|
||||||
</h3>
|
</h3>
|
||||||
{editingProduct && formData.slug && (
|
{editingProduct && formData.slug && (
|
||||||
@ -631,22 +631,18 @@ export default function Products() {
|
|||||||
href={getStoreProductUrl(formData.slug)}
|
href={getStoreProductUrl(formData.slug)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[11px] font-bold bg-purple-50 border border-purple-100 text-purple-600 hover:bg-purple-100 transition-all cursor-pointer"
|
className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-bold bg-purple-50 border border-purple-100 text-purple-600 hover:bg-purple-100 transition-all cursor-pointer"
|
||||||
>
|
>
|
||||||
مشاهده در سایت 🔗
|
مشاهده آنلاین در سایت 🔗
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button onClick={() => setIsModalOpen(false)} className="text-gray-400 hover:text-red-500 transition-colors">
|
||||||
type="button"
|
<X className="w-6 h-6" />
|
||||||
onClick={() => setIsModalOpen(false)}
|
|
||||||
className="w-8 h-8 rounded-xl bg-gray-50 text-gray-400 hover:text-gray-700 hover:bg-gray-100 flex items-center justify-center transition-colors cursor-pointer"
|
|
||||||
>
|
|
||||||
<X className="w-4 h-4" />
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex border-b border-gray-100 px-5 sm:px-6 pt-2 gap-3 sm:gap-4 overflow-x-auto shrink-0 bg-gray-50/50">
|
<div className="flex border-b border-gray-100 px-6 pt-2 gap-4">
|
||||||
{[
|
{[
|
||||||
{ id: 'general', label: 'اطلاعات پایه' },
|
{ id: 'general', label: 'اطلاعات پایه' },
|
||||||
{ id: 'pricing', label: 'موجودی و قیمت' },
|
{ id: 'pricing', label: 'موجودی و قیمت' },
|
||||||
@ -657,17 +653,16 @@ export default function Products() {
|
|||||||
key={tab.id}
|
key={tab.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setActiveTab(tab.id)}
|
onClick={() => setActiveTab(tab.id)}
|
||||||
className={`pb-3 px-2 text-xs sm:text-sm font-bold border-b-2 whitespace-nowrap transition-colors cursor-pointer ${activeTab === tab.id ? 'border-purple-600 text-purple-700' : 'border-transparent text-gray-500 hover:text-gray-700'}`}
|
className={`pb-3 px-2 text-sm font-bold border-b-2 transition-colors ${activeTab === tab.id ? 'border-purple-600 text-purple-700' : 'border-transparent text-gray-500 hover:text-gray-700'}`}
|
||||||
>
|
>
|
||||||
{tab.label}
|
{tab.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-4 sm:p-6 overflow-y-auto flex-1 overscroll-contain bg-gray-50/30">
|
<div className="p-6 overflow-y-auto h-[550px] min-h-[550px] max-h-[550px] bg-gray-50/30">
|
||||||
<form id="productForm" onSubmit={handleSave} className="space-y-6">
|
<form id="productForm" onSubmit={handleSave} className="space-y-6">
|
||||||
|
|
||||||
|
|
||||||
{/* General Tab */}
|
{/* General Tab */}
|
||||||
<div className={activeTab === 'general' ? 'block space-y-4' : 'hidden'}>
|
<div className={activeTab === 'general' ? 'block space-y-4' : 'hidden'}>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -5,21 +5,22 @@ import {
|
|||||||
Edit3,
|
Edit3,
|
||||||
Trash2,
|
Trash2,
|
||||||
Plus,
|
Plus,
|
||||||
|
Check,
|
||||||
|
X,
|
||||||
Eye,
|
Eye,
|
||||||
Wallet,
|
Wallet,
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
Mail,
|
Mail,
|
||||||
Phone,
|
Phone,
|
||||||
Lock,
|
Lock,
|
||||||
|
UserCheck,
|
||||||
|
Shield,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import api from '../services/api';
|
import api from '../services/api';
|
||||||
import Skeleton from '../components/ui/Skeleton';
|
import Skeleton from '../components/ui/Skeleton';
|
||||||
|
import Spinner from '../components/ui/Spinner';
|
||||||
import Pagination from '../components/ui/Pagination';
|
import Pagination from '../components/ui/Pagination';
|
||||||
import Button from '../components/ui/Button';
|
|
||||||
import Modal from '../components/ui/Modal';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import type { UserAddress, PetSummary } from '../types/admin';
|
import type { UserAddress, PetSummary } from '../types/admin';
|
||||||
|
|
||||||
@ -437,284 +438,299 @@ export default function Users() {
|
|||||||
|
|
||||||
{/* CREATE USER MODAL */}
|
{/* CREATE USER MODAL */}
|
||||||
{showCreateModal && (
|
{showCreateModal && (
|
||||||
<Modal
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-gray-900/60 backdrop-blur-xs">
|
||||||
isOpen={showCreateModal}
|
<form
|
||||||
onClose={() => setShowCreateModal(false)}
|
onSubmit={handleCreateUser}
|
||||||
title="افزودن کاربر جدید"
|
className="bg-white rounded-2xl w-full max-w-lg shadow-2xl p-6 space-y-5 font-vazir animate-in fade-in zoom-in duration-150"
|
||||||
icon={Plus}
|
>
|
||||||
maxWidth="lg"
|
<div className="flex items-center justify-between border-b border-gray-100 pb-4">
|
||||||
footer={
|
<h3 className="text-lg font-bold text-gray-900 flex items-center gap-2">
|
||||||
<div className="flex justify-end gap-2 w-full">
|
<Plus className="w-5 h-5 text-purple-600" />
|
||||||
<Button
|
افزودن کاربر جدید
|
||||||
variant="outline"
|
</h3>
|
||||||
size="sm"
|
<button
|
||||||
|
type="button"
|
||||||
onClick={() => setShowCreateModal(false)}
|
onClick={() => setShowCreateModal(false)}
|
||||||
|
className="text-gray-400 hover:text-red-500 transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
|
<X className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4 text-xs">
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-700 font-bold mb-1">
|
||||||
|
نام <span className="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
placeholder="مثال: علی"
|
||||||
|
value={createFormData.firstName}
|
||||||
|
onChange={(e) => setCreateFormData({ ...createFormData, firstName: e.target.value })}
|
||||||
|
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-700 font-bold mb-1">
|
||||||
|
نام خانوادگی <span className="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
placeholder="مثال: احمدی"
|
||||||
|
value={createFormData.lastName}
|
||||||
|
onChange={(e) => setCreateFormData({ ...createFormData, lastName: e.target.value })}
|
||||||
|
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-700 font-bold mb-1">
|
||||||
|
شماره موبایل <span className="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Phone className="w-4 h-4 text-gray-400 absolute right-3 top-1/2 -translate-y-1/2" />
|
||||||
|
<input
|
||||||
|
type="tel"
|
||||||
|
required
|
||||||
|
placeholder="09123456789"
|
||||||
|
value={createFormData.mobile}
|
||||||
|
onChange={(e) => setCreateFormData({ ...createFormData, mobile: e.target.value })}
|
||||||
|
className="w-full pl-3 pr-9 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono text-left"
|
||||||
|
dir="ltr"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-700 font-bold mb-1">ایمیل (اختیاری)</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Mail className="w-4 h-4 text-gray-400 absolute right-3 top-1/2 -translate-y-1/2" />
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
placeholder="user@example.com"
|
||||||
|
value={createFormData.email}
|
||||||
|
onChange={(e) => setCreateFormData({ ...createFormData, email: e.target.value })}
|
||||||
|
className="w-full pl-3 pr-9 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono text-left"
|
||||||
|
dir="ltr"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-700 font-bold mb-1">رمز عبور (اختیاری)</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Lock className="w-4 h-4 text-gray-400 absolute right-3 top-1/2 -translate-y-1/2" />
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
placeholder="حداقل ۶ کاراکتر"
|
||||||
|
value={createFormData.password}
|
||||||
|
onChange={(e) => setCreateFormData({ ...createFormData, password: e.target.value })}
|
||||||
|
className="w-full pl-3 pr-9 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none text-left"
|
||||||
|
dir="ltr"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-700 font-bold mb-1">نقش دسترسی</label>
|
||||||
|
<select
|
||||||
|
value={createFormData.role}
|
||||||
|
onChange={(e) => setCreateFormData({ ...createFormData, role: e.target.value })}
|
||||||
|
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-bold bg-white"
|
||||||
|
>
|
||||||
|
<option value="User_PetOwner">مشتری عادی</option>
|
||||||
|
<option value="User_Wholesale">خریدار عمده (داروخانه/پتشاپ)</option>
|
||||||
|
<option value="User_B2B">همکار (B2B)</option>
|
||||||
|
<option value="ADMIN">مدیر سیستم (Admin)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-span-2">
|
||||||
|
<label className="block text-gray-700 font-bold mb-1">موجودی اولیه کیف پول (تومان)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="1000"
|
||||||
|
value={createFormData.walletBalance}
|
||||||
|
onChange={(e) => setCreateFormData({ ...createFormData, walletBalance: Number(e.target.value) })}
|
||||||
|
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-4 border-t border-gray-100 flex justify-end gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowCreateModal(false)}
|
||||||
|
className="px-5 py-2.5 bg-gray-100 hover:bg-gray-200 text-gray-700 font-bold rounded-xl transition-colors text-xs cursor-pointer"
|
||||||
>
|
>
|
||||||
انصراف
|
انصراف
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
variant="primary"
|
type="submit"
|
||||||
size="sm"
|
disabled={isSaving}
|
||||||
isLoading={isSaving}
|
className="px-6 py-2.5 bg-purple-600 hover:bg-purple-700 text-white font-bold rounded-xl transition-colors text-xs flex items-center gap-2 cursor-pointer disabled:opacity-50"
|
||||||
onClick={handleCreateUser}
|
|
||||||
>
|
>
|
||||||
|
{isSaving ? <Spinner size="sm" /> : <Check className="w-4 h-4" />}
|
||||||
ایجاد کاربر
|
ایجاد کاربر
|
||||||
</Button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
</form>
|
||||||
>
|
</div>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 text-xs font-vazir">
|
|
||||||
<div>
|
|
||||||
<label className="block text-gray-700 font-bold mb-1">
|
|
||||||
نام <span className="text-red-500">*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
placeholder="مثال: علی"
|
|
||||||
value={createFormData.firstName}
|
|
||||||
onChange={(e) => setCreateFormData({ ...createFormData, firstName: e.target.value })}
|
|
||||||
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-gray-700 font-bold mb-1">
|
|
||||||
نام خانوادگی <span className="text-red-500">*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
placeholder="مثال: احمدی"
|
|
||||||
value={createFormData.lastName}
|
|
||||||
onChange={(e) => setCreateFormData({ ...createFormData, lastName: e.target.value })}
|
|
||||||
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-gray-700 font-bold mb-1">
|
|
||||||
شماره موبایل <span className="text-red-500">*</span>
|
|
||||||
</label>
|
|
||||||
<div className="relative">
|
|
||||||
<Phone className="w-4 h-4 text-gray-400 absolute right-3 top-1/2 -translate-y-1/2" />
|
|
||||||
<input
|
|
||||||
type="tel"
|
|
||||||
required
|
|
||||||
placeholder="09123456789"
|
|
||||||
value={createFormData.mobile}
|
|
||||||
onChange={(e) => setCreateFormData({ ...createFormData, mobile: e.target.value })}
|
|
||||||
className="w-full pl-3 pr-9 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono text-left"
|
|
||||||
dir="ltr"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-gray-700 font-bold mb-1">ایمیل (اختیاری)</label>
|
|
||||||
<div className="relative">
|
|
||||||
<Mail className="w-4 h-4 text-gray-400 absolute right-3 top-1/2 -translate-y-1/2" />
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
placeholder="user@example.com"
|
|
||||||
value={createFormData.email}
|
|
||||||
onChange={(e) => setCreateFormData({ ...createFormData, email: e.target.value })}
|
|
||||||
className="w-full pl-3 pr-9 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono text-left"
|
|
||||||
dir="ltr"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-gray-700 font-bold mb-1">رمز عبور (اختیاری)</label>
|
|
||||||
<div className="relative">
|
|
||||||
<Lock className="w-4 h-4 text-gray-400 absolute right-3 top-1/2 -translate-y-1/2" />
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
placeholder="حداقل ۶ کاراکتر"
|
|
||||||
value={createFormData.password}
|
|
||||||
onChange={(e) => setCreateFormData({ ...createFormData, password: e.target.value })}
|
|
||||||
className="w-full pl-3 pr-9 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none text-left"
|
|
||||||
dir="ltr"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-gray-700 font-bold mb-1">نقش دسترسی</label>
|
|
||||||
<select
|
|
||||||
value={createFormData.role}
|
|
||||||
onChange={(e) => setCreateFormData({ ...createFormData, role: e.target.value })}
|
|
||||||
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-bold bg-white"
|
|
||||||
>
|
|
||||||
<option value="User_PetOwner">مشتری عادی</option>
|
|
||||||
<option value="User_Wholesale">خریدار عمده (داروخانه/پتشاپ)</option>
|
|
||||||
<option value="User_B2B">همکار (B2B)</option>
|
|
||||||
<option value="ADMIN">مدیر سیستم (Admin)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="sm:col-span-2">
|
|
||||||
<label className="block text-gray-700 font-bold mb-1">موجودی اولیه کیف پول (تومان)</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step="1000"
|
|
||||||
value={createFormData.walletBalance}
|
|
||||||
onChange={(e) => setCreateFormData({ ...createFormData, walletBalance: Number(e.target.value) })}
|
|
||||||
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* EDIT USER MODAL */}
|
{/* EDIT USER MODAL */}
|
||||||
{editUser && (
|
{editUser && (
|
||||||
<Modal
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-gray-900/60 backdrop-blur-xs">
|
||||||
isOpen={!!editUser}
|
<form
|
||||||
onClose={() => setEditUser(null)}
|
onSubmit={handleSaveUser}
|
||||||
title="ویرایش اطلاعات کاربر"
|
className="bg-white rounded-2xl w-full max-w-lg shadow-2xl p-6 space-y-5 font-vazir animate-in fade-in zoom-in duration-150"
|
||||||
icon={Edit3}
|
>
|
||||||
maxWidth="lg"
|
<div className="flex items-center justify-between border-b border-gray-100 pb-4">
|
||||||
footer={
|
<h3 className="text-lg font-bold text-gray-900 flex items-center gap-2">
|
||||||
<div className="flex justify-end gap-2 w-full">
|
<Edit3 className="w-5 h-5 text-blue-600" />
|
||||||
<Button
|
ویرایش اطلاعات کاربر
|
||||||
variant="outline"
|
</h3>
|
||||||
size="sm"
|
<button
|
||||||
|
type="button"
|
||||||
onClick={() => setEditUser(null)}
|
onClick={() => setEditUser(null)}
|
||||||
|
className="text-gray-400 hover:text-red-500 transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
|
<X className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4 text-xs">
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-700 font-bold mb-1">نام</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
value={editFormData.firstName}
|
||||||
|
onChange={(e) => setEditFormData({ ...editFormData, firstName: e.target.value })}
|
||||||
|
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-700 font-bold mb-1">نام خانوادگی</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
value={editFormData.lastName}
|
||||||
|
onChange={(e) => setEditFormData({ ...editFormData, lastName: e.target.value })}
|
||||||
|
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-700 font-bold mb-1">شماره موبایل</label>
|
||||||
|
<input
|
||||||
|
type="tel"
|
||||||
|
required
|
||||||
|
value={editFormData.mobile}
|
||||||
|
onChange={(e) => setEditFormData({ ...editFormData, mobile: e.target.value })}
|
||||||
|
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono text-left"
|
||||||
|
dir="ltr"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-700 font-bold mb-1">ایمیل</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
value={editFormData.email}
|
||||||
|
onChange={(e) => setEditFormData({ ...editFormData, email: e.target.value })}
|
||||||
|
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono text-left"
|
||||||
|
dir="ltr"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-700 font-bold mb-1">رمز عبور جدید (در صورت نیاز به تغییر)</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
placeholder="بدون تغییر..."
|
||||||
|
value={editFormData.password}
|
||||||
|
onChange={(e) => setEditFormData({ ...editFormData, password: e.target.value })}
|
||||||
|
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none text-left"
|
||||||
|
dir="ltr"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-700 font-bold mb-1">نقش دسترسی</label>
|
||||||
|
<select
|
||||||
|
value={editFormData.role}
|
||||||
|
onChange={(e) => setEditFormData({ ...editFormData, role: e.target.value })}
|
||||||
|
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-bold bg-white"
|
||||||
|
>
|
||||||
|
<option value="User_PetOwner">مشتری عادی</option>
|
||||||
|
<option value="User_Wholesale">خریدار عمده (داروخانه/پتشاپ)</option>
|
||||||
|
<option value="User_B2B">همکار (B2B)</option>
|
||||||
|
<option value="ADMIN">مدیر سیستم (Admin)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-span-2">
|
||||||
|
<label className="block text-gray-700 font-bold mb-1">موجودی کیف پول (تومان)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="1000"
|
||||||
|
value={editFormData.walletBalance}
|
||||||
|
onChange={(e) => setEditFormData({ ...editFormData, walletBalance: Number(e.target.value) })}
|
||||||
|
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-4 border-t border-gray-100 flex justify-end gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setEditUser(null)}
|
||||||
|
className="px-5 py-2.5 bg-gray-100 hover:bg-gray-200 text-gray-700 font-bold rounded-xl transition-colors text-xs cursor-pointer"
|
||||||
>
|
>
|
||||||
انصراف
|
انصراف
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
variant="primary"
|
type="submit"
|
||||||
size="sm"
|
disabled={isSaving}
|
||||||
isLoading={isSaving}
|
className="px-6 py-2.5 bg-purple-600 hover:bg-purple-700 text-white font-bold rounded-xl transition-colors text-xs flex items-center gap-2 cursor-pointer disabled:opacity-50"
|
||||||
onClick={handleSaveUser}
|
|
||||||
>
|
>
|
||||||
|
{isSaving ? <Spinner size="sm" /> : <Check className="w-4 h-4" />}
|
||||||
ذخیره تغییرات
|
ذخیره تغییرات
|
||||||
</Button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
</form>
|
||||||
>
|
</div>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 text-xs font-vazir">
|
|
||||||
<div>
|
|
||||||
<label className="block text-gray-700 font-bold mb-1">نام</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
value={editFormData.firstName}
|
|
||||||
onChange={(e) => setEditFormData({ ...editFormData, firstName: e.target.value })}
|
|
||||||
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-gray-700 font-bold mb-1">نام خانوادگی</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
value={editFormData.lastName}
|
|
||||||
onChange={(e) => setEditFormData({ ...editFormData, lastName: e.target.value })}
|
|
||||||
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-gray-700 font-bold mb-1">شماره موبایل</label>
|
|
||||||
<input
|
|
||||||
type="tel"
|
|
||||||
required
|
|
||||||
value={editFormData.mobile}
|
|
||||||
onChange={(e) => setEditFormData({ ...editFormData, mobile: e.target.value })}
|
|
||||||
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono text-left"
|
|
||||||
dir="ltr"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-gray-700 font-bold mb-1">ایمیل</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
value={editFormData.email}
|
|
||||||
onChange={(e) => setEditFormData({ ...editFormData, email: e.target.value })}
|
|
||||||
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono text-left"
|
|
||||||
dir="ltr"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-gray-700 font-bold mb-1">رمز عبور جدید (در صورت نیاز به تغییر)</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
placeholder="بدون تغییر..."
|
|
||||||
value={editFormData.password}
|
|
||||||
onChange={(e) => setEditFormData({ ...editFormData, password: e.target.value })}
|
|
||||||
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none text-left"
|
|
||||||
dir="ltr"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-gray-700 font-bold mb-1">نقش دسترسی</label>
|
|
||||||
<select
|
|
||||||
value={editFormData.role}
|
|
||||||
onChange={(e) => setEditFormData({ ...editFormData, role: e.target.value })}
|
|
||||||
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-bold bg-white"
|
|
||||||
>
|
|
||||||
<option value="User_PetOwner">مشتری عادی</option>
|
|
||||||
<option value="User_Wholesale">خریدار عمده (داروخانه/پتشاپ)</option>
|
|
||||||
<option value="User_B2B">همکار (B2B)</option>
|
|
||||||
<option value="ADMIN">مدیر سیستم (Admin)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="sm:col-span-2">
|
|
||||||
<label className="block text-gray-700 font-bold mb-1">موجودی کیف پول (تومان)</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step="1000"
|
|
||||||
value={editFormData.walletBalance}
|
|
||||||
onChange={(e) => setEditFormData({ ...editFormData, walletBalance: Number(e.target.value) })}
|
|
||||||
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
{/* WALLET ADJUST MODAL */}
|
{/* WALLET ADJUST MODAL */}
|
||||||
{walletUser && (
|
{walletUser && (
|
||||||
<Modal
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-gray-900/60 backdrop-blur-xs">
|
||||||
isOpen={!!walletUser}
|
<form
|
||||||
onClose={() => setWalletUser(null)}
|
onSubmit={handleAdjustWallet}
|
||||||
title="مدیریت کیف پول کاربر"
|
className="bg-white rounded-2xl w-full max-w-md shadow-2xl p-6 space-y-5 font-vazir animate-in fade-in zoom-in duration-150"
|
||||||
icon={Wallet}
|
>
|
||||||
maxWidth="md"
|
<div className="flex items-center justify-between border-b border-gray-100 pb-4">
|
||||||
footer={
|
<h3 className="text-lg font-bold text-gray-900 flex items-center gap-2">
|
||||||
<div className="flex justify-end gap-2 w-full">
|
<Wallet className="w-5 h-5 text-emerald-600" />
|
||||||
<Button
|
مدیریت کیف پول کاربر
|
||||||
variant="outline"
|
</h3>
|
||||||
size="sm"
|
<button
|
||||||
|
type="button"
|
||||||
onClick={() => setWalletUser(null)}
|
onClick={() => setWalletUser(null)}
|
||||||
|
className="text-gray-400 hover:text-red-500 transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
انصراف
|
<X className="w-5 h-5" />
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
size="sm"
|
|
||||||
isLoading={isAdjustingWallet}
|
|
||||||
onClick={handleAdjustWallet}
|
|
||||||
>
|
|
||||||
ثبت تغییر موجودی
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
>
|
<div className="bg-gray-50 p-3.5 rounded-xl border border-gray-100 text-xs space-y-1">
|
||||||
<div className="space-y-4 text-xs font-vazir">
|
|
||||||
<div className="bg-gray-50 p-3.5 rounded-2xl border border-gray-100 space-y-1">
|
|
||||||
<div className="font-bold text-gray-900">
|
<div className="font-bold text-gray-900">
|
||||||
{walletUser.firstName} {walletUser.lastName} ({walletUser.mobile})
|
{walletUser.firstName} {walletUser.lastName} ({walletUser.mobile})
|
||||||
</div>
|
</div>
|
||||||
@ -726,9 +742,9 @@ export default function Users() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4 text-xs">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-gray-700 font-bold mb-1.5">نوع عملیات</label>
|
<label className="block text-gray-700 font-bold mb-1">نوع عملیات</label>
|
||||||
<div className="grid grid-cols-3 gap-2">
|
<div className="grid grid-cols-3 gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -790,126 +806,154 @@ export default function Users() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</Modal>
|
<div className="pt-4 border-t border-gray-100 flex justify-end gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setWalletUser(null)}
|
||||||
|
className="px-5 py-2.5 bg-gray-100 hover:bg-gray-200 text-gray-700 font-bold rounded-xl transition-colors text-xs cursor-pointer"
|
||||||
|
>
|
||||||
|
انصراف
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={isAdjustingWallet}
|
||||||
|
className="px-6 py-2.5 bg-emerald-600 hover:bg-emerald-700 text-white font-bold rounded-xl transition-colors text-xs flex items-center gap-2 cursor-pointer disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{isAdjustingWallet ? <Spinner size="sm" /> : <Check className="w-4 h-4" />}
|
||||||
|
ثبت تغییر موجودی
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* DELETE CONFIRMATION MODAL */}
|
{/* DELETE CONFIRMATION MODAL */}
|
||||||
{userToDelete && (
|
{userToDelete && (
|
||||||
<Modal
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-gray-900/60 backdrop-blur-xs">
|
||||||
isOpen={!!userToDelete}
|
<div className="bg-white rounded-2xl w-full max-w-md shadow-2xl p-6 space-y-5 font-vazir animate-in fade-in zoom-in duration-150">
|
||||||
onClose={() => setUserToDelete(null)}
|
<div className="flex items-center gap-3 text-red-600 border-b border-gray-100 pb-4">
|
||||||
title="تایید حذف کاربر"
|
<div className="p-2.5 bg-red-100 rounded-xl">
|
||||||
icon={AlertTriangle}
|
<AlertTriangle className="w-6 h-6" />
|
||||||
maxWidth="md"
|
</div>
|
||||||
footer={
|
<div>
|
||||||
<div className="flex justify-end gap-2 w-full">
|
<h3 className="text-base font-bold text-gray-900">تایید حذف کاربر</h3>
|
||||||
<Button
|
<p className="text-xs text-gray-500 mt-0.5">این عملیات غیرقابل بازگشت است</p>
|
||||||
variant="outline"
|
</div>
|
||||||
size="sm"
|
|
||||||
onClick={() => setUserToDelete(null)}
|
|
||||||
>
|
|
||||||
انصراف
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="danger"
|
|
||||||
size="sm"
|
|
||||||
isLoading={isDeleting}
|
|
||||||
onClick={handleDeleteUser}
|
|
||||||
>
|
|
||||||
حذف قطعی کاربر
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
>
|
<p className="text-xs text-gray-700 leading-relaxed">
|
||||||
<div className="space-y-3 text-xs font-vazir">
|
|
||||||
<p className="text-gray-700 leading-relaxed">
|
|
||||||
آیا از حذف کامل کاربر{' '}
|
آیا از حذف کامل کاربر{' '}
|
||||||
<strong className="text-gray-900">
|
<strong className="text-gray-900">
|
||||||
{userToDelete.firstName} {userToDelete.lastName} ({userToDelete.mobile})
|
{userToDelete.firstName} {userToDelete.lastName} ({userToDelete.mobile})
|
||||||
</strong>{' '}
|
</strong>{' '}
|
||||||
اطمینان دارید؟ تمام سوابق آدرسها، پتها و تراکنشهای مربوطه نیز پاک خواهند شد.
|
اطمینان دارید؟ تمام سوابق آدرسها، پتها و تراکنشهای مربوطه نیز پاک خواهند شد.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div className="pt-4 border-t border-gray-100 flex justify-end gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setUserToDelete(null)}
|
||||||
|
className="px-5 py-2.5 bg-gray-100 hover:bg-gray-200 text-gray-700 font-bold rounded-xl transition-colors text-xs cursor-pointer"
|
||||||
|
>
|
||||||
|
انصراف
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleDeleteUser}
|
||||||
|
disabled={isDeleting}
|
||||||
|
className="px-6 py-2.5 bg-red-600 hover:bg-red-700 text-white font-bold rounded-xl transition-colors text-xs flex items-center gap-2 cursor-pointer disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{isDeleting ? <Spinner size="sm" /> : <Trash2 className="w-4 h-4" />}
|
||||||
|
حذف قطعی کاربر
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* VIEW USER DETAILS MODAL */}
|
{/* VIEW USER DETAILS MODAL */}
|
||||||
{viewUser && (
|
{viewUser && (
|
||||||
<Modal
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-gray-900/60 backdrop-blur-xs">
|
||||||
isOpen={!!viewUser}
|
<div className="bg-white rounded-2xl w-full max-w-lg shadow-2xl p-6 space-y-6 font-vazir animate-in fade-in zoom-in duration-150 max-h-[90vh] overflow-y-auto">
|
||||||
onClose={() => setViewUser(null)}
|
<div className="flex items-center justify-between border-b border-gray-100 pb-4">
|
||||||
title="مشاهده جزئیات پروفایل کاربر"
|
<h3 className="text-lg font-bold text-gray-900 flex items-center gap-2">
|
||||||
icon={UsersIcon}
|
<UsersIcon className="w-5 h-5 text-purple-600" />
|
||||||
maxWidth="lg"
|
مشاهده جزئیات پروفایل کاربر
|
||||||
footer={
|
</h3>
|
||||||
<div className="flex justify-end w-full">
|
<button
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setViewUser(null)}
|
onClick={() => setViewUser(null)}
|
||||||
|
className="text-gray-400 hover:text-red-500 transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
|
<X className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4 text-xs">
|
||||||
|
<div className="grid grid-cols-2 gap-4 bg-gray-50 p-4 rounded-xl border border-gray-100">
|
||||||
|
<div>
|
||||||
|
<span className="text-gray-400 font-bold block mb-1">نام و نام خانوادگی:</span>
|
||||||
|
<span className="font-bold text-gray-900 text-sm">
|
||||||
|
{viewUser.firstName} {viewUser.lastName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-gray-400 font-bold block mb-1">نوع نقش:</span>
|
||||||
|
<span className="font-bold text-purple-700">{viewUser.role}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-gray-400 font-bold block mb-1">موبایل:</span>
|
||||||
|
<span className="font-mono text-gray-800" dir="ltr">
|
||||||
|
{viewUser.mobile || '---'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-gray-400 font-bold block mb-1">ایمیل:</span>
|
||||||
|
<span className="font-mono text-gray-800" dir="ltr">
|
||||||
|
{viewUser.email || '---'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-purple-50/60 p-4 rounded-xl border border-purple-100 flex items-center justify-between">
|
||||||
|
<span className="font-bold text-purple-900">موجودی کیف پول کاربر:</span>
|
||||||
|
<span className="font-mono font-black text-purple-700 text-base">
|
||||||
|
{(Number(viewUser.walletBalance) || 0).toLocaleString('fa-IR')} تومان
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="font-bold text-gray-900 mb-2">آدرسهای ثبتشده ({viewUser.addresses?.length || 0})</h4>
|
||||||
|
{viewUser.addresses && viewUser.addresses.length > 0 ? (
|
||||||
|
<div className="space-y-2 max-h-32 overflow-y-auto">
|
||||||
|
{viewUser.addresses.map((a: UserAddress, idx: number) => (
|
||||||
|
<div key={idx} className="p-2.5 bg-gray-50 rounded-lg border border-gray-200/60">
|
||||||
|
<p className="font-bold text-gray-800">
|
||||||
|
{a.title} - {a.receptorName}
|
||||||
|
</p>
|
||||||
|
<p className="text-gray-500 mt-0.5">
|
||||||
|
{a.province}، {a.city}، {a.detail}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="text-gray-400 text-center py-2 bg-gray-50 rounded-lg">هیچ آدرسی ثبت نشده است.</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-4 border-t border-gray-100 flex justify-end">
|
||||||
|
<button
|
||||||
|
onClick={() => setViewUser(null)}
|
||||||
|
className="px-5 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 font-bold rounded-xl transition-colors text-xs cursor-pointer"
|
||||||
>
|
>
|
||||||
بستن پنجره
|
بستن پنجره
|
||||||
</Button>
|
</button>
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="space-y-4 text-xs font-vazir">
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 bg-gray-50 p-4 rounded-2xl border border-gray-100">
|
|
||||||
<div>
|
|
||||||
<span className="text-gray-400 font-bold block mb-1">نام و نام خانوادگی:</span>
|
|
||||||
<span className="font-bold text-gray-900 text-sm">
|
|
||||||
{viewUser.firstName} {viewUser.lastName}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="text-gray-400 font-bold block mb-1">نوع نقش:</span>
|
|
||||||
<span className="font-bold text-purple-700">{viewUser.role}</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="text-gray-400 font-bold block mb-1">موبایل:</span>
|
|
||||||
<span className="font-mono text-gray-800" dir="ltr">
|
|
||||||
{viewUser.mobile || '---'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="text-gray-400 font-bold block mb-1">ایمیل:</span>
|
|
||||||
<span className="font-mono text-gray-800" dir="ltr">
|
|
||||||
{viewUser.email || '---'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-purple-50/60 p-4 rounded-2xl border border-purple-100 flex items-center justify-between">
|
|
||||||
<span className="font-bold text-purple-900">موجودی کیف پول کاربر:</span>
|
|
||||||
<span className="font-mono font-black text-purple-700 text-base">
|
|
||||||
{(Number(viewUser.walletBalance) || 0).toLocaleString('fa-IR')} تومان
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h4 className="font-bold text-gray-900 mb-2">آدرسهای ثبتشده ({viewUser.addresses?.length || 0})</h4>
|
|
||||||
{viewUser.addresses && viewUser.addresses.length > 0 ? (
|
|
||||||
<div className="space-y-2 max-h-36 overflow-y-auto">
|
|
||||||
{viewUser.addresses.map((a: UserAddress, idx: number) => (
|
|
||||||
<div key={idx} className="p-2.5 bg-gray-50 rounded-xl border border-gray-200/60">
|
|
||||||
<p className="font-bold text-gray-800">
|
|
||||||
{a.title} - {a.receptorName}
|
|
||||||
</p>
|
|
||||||
<p className="text-gray-500 mt-0.5">
|
|
||||||
{a.province}، {a.city}، {a.detail}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<p className="text-gray-400 text-center py-3 bg-gray-50 rounded-xl">هیچ آدرسی ثبت نشده است.</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -44,8 +44,6 @@ const Reviews = lazyWithRetry(() => import('../pages/Reviews'));
|
|||||||
const DoctorsManager = lazyWithRetry(() => import('../pages/DoctorsManager'));
|
const DoctorsManager = lazyWithRetry(() => import('../pages/DoctorsManager'));
|
||||||
const FAQManager = lazyWithRetry(() => import('../pages/FAQManager'));
|
const FAQManager = lazyWithRetry(() => import('../pages/FAQManager'));
|
||||||
const MenuManager = lazyWithRetry(() => import('../pages/MenuManager'));
|
const MenuManager = lazyWithRetry(() => import('../pages/MenuManager'));
|
||||||
const MonitoringPage = lazyWithRetry(() => import('../pages/MonitoringPage'));
|
|
||||||
|
|
||||||
|
|
||||||
export interface AdminRouteConfig {
|
export interface AdminRouteConfig {
|
||||||
path: string;
|
path: string;
|
||||||
@ -105,10 +103,8 @@ export const router = createBrowserRouter([
|
|||||||
{ path: 'reviews/*', element: <Reviews /> },
|
{ path: 'reviews/*', element: <Reviews /> },
|
||||||
{ path: 'faq/*', element: <FAQManager /> },
|
{ path: 'faq/*', element: <FAQManager /> },
|
||||||
{ path: 'menu/*', element: <MenuManager /> },
|
{ path: 'menu/*', element: <MenuManager /> },
|
||||||
{ path: 'monitoring/*', element: <MonitoringPage /> },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,132 +0,0 @@
|
|||||||
import { useSearchParams } from 'react-router-dom';
|
|
||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
|
|
||||||
export interface UseTableParamsOptions {
|
|
||||||
defaultSortBy?: string;
|
|
||||||
defaultSortOrder?: 'asc' | 'desc';
|
|
||||||
defaultPage?: number;
|
|
||||||
defaultLimit?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useTableParams(options: UseTableParamsOptions = {}) {
|
|
||||||
const {
|
|
||||||
defaultSortBy = 'createdAt',
|
|
||||||
defaultSortOrder = 'desc',
|
|
||||||
defaultPage = 1,
|
|
||||||
defaultLimit = 15,
|
|
||||||
} = options;
|
|
||||||
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
|
||||||
|
|
||||||
// Current extracted params
|
|
||||||
const search = searchParams.get('search') || '';
|
|
||||||
const status = searchParams.get('status') || '';
|
|
||||||
const gateway = searchParams.get('gateway') || '';
|
|
||||||
const category = searchParams.get('category') || '';
|
|
||||||
const role = searchParams.get('role') || '';
|
|
||||||
const type = searchParams.get('type') || '';
|
|
||||||
const fromDate = searchParams.get('fromDate') || '';
|
|
||||||
const toDate = searchParams.get('toDate') || '';
|
|
||||||
const sortBy = searchParams.get('sortBy') || defaultSortBy;
|
|
||||||
const sortOrder = (searchParams.get('sortOrder') as 'asc' | 'desc') || defaultSortOrder;
|
|
||||||
const page = parseInt(searchParams.get('page') || String(defaultPage), 10) || defaultPage;
|
|
||||||
const limit = parseInt(searchParams.get('limit') || String(defaultLimit), 10) || defaultLimit;
|
|
||||||
|
|
||||||
// Set single or multiple params while preserving others and resetting page when filtering
|
|
||||||
const setParam = useCallback(
|
|
||||||
(key: string, value: string | number | null | undefined, resetPage = true) => {
|
|
||||||
setSearchParams((prev) => {
|
|
||||||
const next = new URLSearchParams(prev);
|
|
||||||
if (value === null || value === undefined || value === '' || value === 'ALL' || value === 'all') {
|
|
||||||
next.delete(key);
|
|
||||||
} else {
|
|
||||||
next.set(key, String(value));
|
|
||||||
}
|
|
||||||
if (resetPage && key !== 'page') {
|
|
||||||
next.set('page', '1');
|
|
||||||
}
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[setSearchParams]
|
|
||||||
);
|
|
||||||
|
|
||||||
const setMultipleParams = useCallback(
|
|
||||||
(updates: Record<string, string | number | null | undefined>, resetPage = true) => {
|
|
||||||
setSearchParams((prev) => {
|
|
||||||
const next = new URLSearchParams(prev);
|
|
||||||
Object.entries(updates).forEach(([key, value]) => {
|
|
||||||
if (value === null || value === undefined || value === '' || value === 'ALL' || value === 'all') {
|
|
||||||
next.delete(key);
|
|
||||||
} else {
|
|
||||||
next.set(key, String(value));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (resetPage && !('page' in updates)) {
|
|
||||||
next.set('page', '1');
|
|
||||||
}
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[setSearchParams]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Helper to toggle sort column
|
|
||||||
const handleSort = useCallback(
|
|
||||||
(column: string) => {
|
|
||||||
setSearchParams((prev) => {
|
|
||||||
const next = new URLSearchParams(prev);
|
|
||||||
const currentSortBy = next.get('sortBy') || defaultSortBy;
|
|
||||||
const currentSortOrder = next.get('sortOrder') || defaultSortOrder;
|
|
||||||
|
|
||||||
if (currentSortBy === column) {
|
|
||||||
next.set('sortOrder', currentSortOrder === 'asc' ? 'desc' : 'asc');
|
|
||||||
} else {
|
|
||||||
next.set('sortBy', column);
|
|
||||||
next.set('sortOrder', 'desc');
|
|
||||||
}
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[setSearchParams, defaultSortBy, defaultSortOrder]
|
|
||||||
);
|
|
||||||
|
|
||||||
const clearFilters = useCallback(() => {
|
|
||||||
setSearchParams((prev) => {
|
|
||||||
const next = new URLSearchParams();
|
|
||||||
if (prev.has('sortBy')) next.set('sortBy', prev.get('sortBy')!);
|
|
||||||
if (prev.has('sortOrder')) next.set('sortOrder', prev.get('sortOrder')!);
|
|
||||||
next.set('page', '1');
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}, [setSearchParams]);
|
|
||||||
|
|
||||||
const paramsObject = useMemo(() => {
|
|
||||||
const res: Record<string, string> = {};
|
|
||||||
searchParams.forEach((val, key) => {
|
|
||||||
res[key] = val;
|
|
||||||
});
|
|
||||||
return res;
|
|
||||||
}, [searchParams]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
searchParams,
|
|
||||||
paramsObject,
|
|
||||||
search,
|
|
||||||
status,
|
|
||||||
gateway,
|
|
||||||
category,
|
|
||||||
role,
|
|
||||||
type,
|
|
||||||
fromDate,
|
|
||||||
toDate,
|
|
||||||
sortBy,
|
|
||||||
sortOrder,
|
|
||||||
page,
|
|
||||||
limit,
|
|
||||||
setParam,
|
|
||||||
setMultipleParams,
|
|
||||||
handleSort,
|
|
||||||
clearFilters,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,316 +1,328 @@
|
|||||||
{
|
{
|
||||||
"0": "Roles",
|
"0": "OrdersService",
|
||||||
"1": "app.module.ts",
|
"1": "productService.ts",
|
||||||
"2": "SettingsController",
|
"2": "CmsController",
|
||||||
"3": "useCartStore",
|
"3": "app.module.ts",
|
||||||
"4": "SafeImage.tsx",
|
"4": "ReviewsService",
|
||||||
"5": "CmsController",
|
"5": "tickets.controller.ts",
|
||||||
"6": "tickets.controller.ts",
|
"6": "useSettingsStore",
|
||||||
"7": "PaginationDto",
|
"7": "MediaSelector.tsx",
|
||||||
"8": "admin.module.ts",
|
"8": "PetProfile.tsx",
|
||||||
"9": "devDependencies",
|
"9": "Roles",
|
||||||
"10": "ReviewsService",
|
"10": "DoctorsService",
|
||||||
"11": "api",
|
"11": "MenuService",
|
||||||
"12": "PetProfile.tsx",
|
"12": "adminRoutes.tsx",
|
||||||
"13": "app-audit-verification.e2e-spec.js",
|
"13": "PrismaService",
|
||||||
"14": "lib/services/api.ts",
|
"14": "PetsController",
|
||||||
"15": "src/services/api.ts",
|
"15": "ProductsService",
|
||||||
"16": "DoctorQueryDto",
|
"16": "UserDashboard.tsx",
|
||||||
"17": "admin.service.ts",
|
"17": "CreateVideoDto",
|
||||||
"18": "JwtAuthGuard",
|
"18": "src/services/api.ts",
|
||||||
"19": "ProductsService",
|
"19": "AuthController",
|
||||||
"20": "CreateVideoDto",
|
"20": "BE-001",
|
||||||
"21": "SmsService",
|
"21": "SettingsController",
|
||||||
"22": "UserDashboard.tsx",
|
"22": "FE-001",
|
||||||
"23": "MenuService",
|
"23": "ADM-001",
|
||||||
"24": "BE-001",
|
"24": "DB-001",
|
||||||
"25": "FE-001",
|
"25": "TS-001",
|
||||||
"26": "ADM-001",
|
"26": "TEST-001",
|
||||||
"27": "DB-001",
|
"27": "DEVOPS-001",
|
||||||
"28": "TS-001",
|
"28": "DOC-001",
|
||||||
"29": "TEST-001",
|
"29": "WholesaleApplyDto",
|
||||||
"30": "DEVOPS-001",
|
"30": "app-audit-verification.e2e-spec.js",
|
||||||
"31": "DOC-001",
|
"31": "JwtAuthGuard",
|
||||||
"32": "Spinner.tsx",
|
"32": "ZibalService",
|
||||||
"33": "WholesaleApplyDto",
|
"33": "راهنمای تست سیستم (Software Testing)",
|
||||||
"34": "B2BService",
|
"34": "CategoriesController",
|
||||||
"35": "auth.controller.ts",
|
"35": "B2BService",
|
||||||
"36": "FaqService",
|
"36": "What You Must Do When Invoked",
|
||||||
"37": "راهنمای تست سیستم (Software Testing)",
|
"37": "PaymentService",
|
||||||
"38": "Transactions.tsx",
|
"38": "UsersService",
|
||||||
"39": "CategoriesController",
|
"39": "What You Must Do When Invoked",
|
||||||
"40": "MediaController",
|
"40": "SslController",
|
||||||
"41": "What You Must Do When Invoked",
|
"41": "PodcastPlayerModal.tsx",
|
||||||
"42": "SslController",
|
"42": "IngredientsService",
|
||||||
"43": "BannersService",
|
"43": "FaqService",
|
||||||
"44": "TestimonialsService",
|
"44": "MediaController",
|
||||||
"45": "What You Must Do When Invoked",
|
"45": "Coupons.tsx",
|
||||||
"46": "Body",
|
"46": "ContactService",
|
||||||
"47": "IngredientsService",
|
"47": "zibal.service.ts",
|
||||||
"48": "adminRoutes.tsx",
|
"48": "prescriptions.module.ts",
|
||||||
"49": "devDependencies",
|
"49": "SmartAdvisorService",
|
||||||
"50": "devDependencies",
|
"50": "TestimonialsService",
|
||||||
"51": "BlogsController",
|
"51": "Role & Core Objective",
|
||||||
"52": "prescriptions.module.ts",
|
"52": "SmsService",
|
||||||
"53": "SmartAdvisorService",
|
"53": "ZibalEBankService",
|
||||||
"54": "UsersService",
|
"54": "compilerOptions",
|
||||||
"55": "UITexts.tsx",
|
"55": "Media.tsx",
|
||||||
"56": "SettingsService",
|
"56": "compilerOptions",
|
||||||
"57": "Role & Core Objective",
|
"57": "PetsController",
|
||||||
"58": "ContactService",
|
"58": "PaginationDto",
|
||||||
"59": "compilerOptions",
|
"59": "dependencies",
|
||||||
"60": "CreateUserDto",
|
"60": "compilerOptions",
|
||||||
"61": "CreateEBankCheckoutDto",
|
"61": "toPersian",
|
||||||
"62": "CreateOrderDto",
|
"62": "BlogsController",
|
||||||
"63": "dependencies",
|
"63": "ApiOperation",
|
||||||
"64": "compilerOptions",
|
"64": "BannersService",
|
||||||
"65": "ProductPage.tsx",
|
"65": "Required Review Group Closures",
|
||||||
"66": "AdminController",
|
"66": "Products.tsx",
|
||||||
"67": "PetsController",
|
"67": "Operational Rules & Boundaries",
|
||||||
"68": "BlogsController",
|
"68": "Operational Rules & Boundaries",
|
||||||
"69": "Required Review Group Closures",
|
"69": "WikiController",
|
||||||
"70": "compilerOptions",
|
"70": "admin.service.ts",
|
||||||
"71": "getPageMetadata",
|
"71": "getSeoConfig",
|
||||||
"72": "Operational Rules & Boundaries",
|
"72": "seo.module.ts",
|
||||||
"73": "Operational Rules & Boundaries",
|
"73": "seo.ts",
|
||||||
"74": "WikiController",
|
"74": "🏢 AI Software Agency — Master Orchestration Protocol v3",
|
||||||
"75": "PetsController",
|
"75": "Operational Rules & Boundaries",
|
||||||
"76": "CreateReviewDto",
|
"76": "Operational Rules & Boundaries",
|
||||||
"77": "seo.module.ts",
|
"77": "scripts",
|
||||||
"78": "Param",
|
"78": "Role & Core Objective",
|
||||||
"79": "🏢 AI Software Agency — Master Orchestration Protocol v3",
|
"79": "Transactions.tsx",
|
||||||
"80": "Operational Rules & Boundaries",
|
"80": "B2BPortal.tsx",
|
||||||
"81": "Operational Rules & Boundaries",
|
"81": "devDependencies",
|
||||||
"82": "scripts",
|
"82": "auth.controller.ts",
|
||||||
"83": "dependencies",
|
"83": "Orders.tsx",
|
||||||
"84": "Role & Core Objective",
|
"84": "devDependencies",
|
||||||
"85": "sms.service.ts",
|
"85": "seed-products.ts",
|
||||||
"86": "zibal.service.ts",
|
"86": "Reconciled Audit Roles & Assignments",
|
||||||
"87": "dependencies",
|
"87": "WikiController",
|
||||||
"88": "HomeClient.tsx",
|
"88": "Phase 3.1 — Human Review Preparation and Master Backlog Critique",
|
||||||
"89": "seed-products.ts",
|
"89": "dependencies",
|
||||||
"90": "SmsLogQueryDto",
|
"90": "ProductPage.tsx",
|
||||||
"91": "Reconciled Audit Roles & Assignments",
|
"91": "compilerOptions",
|
||||||
"92": "OrdersService",
|
"92": "scripts",
|
||||||
"93": "admin.controller.ts",
|
"93": "Deep Audit Summary Report",
|
||||||
"94": "Phase 3.1 — Human Review Preparation and Master Backlog Critique",
|
"94": "dependencies",
|
||||||
"95": "getSeoConfig",
|
"95": "Operational Rules & Boundaries",
|
||||||
"96": "compilerOptions",
|
"96": "exclude",
|
||||||
"97": "prisma",
|
"97": "jest",
|
||||||
"98": "scripts",
|
"98": "Param",
|
||||||
"99": "trust-seals/page.tsx",
|
"99": "Comprehensive Change Log",
|
||||||
"100": "Deep Audit Summary Report",
|
"100": "Operational Rules & Boundaries",
|
||||||
"101": "Operational Rules & Boundaries",
|
"101": "UITexts.tsx",
|
||||||
"102": "jest",
|
"102": "BlogsController",
|
||||||
"103": "Comprehensive Change Log",
|
"103": "payment.controller.ts",
|
||||||
"104": "Coupons.tsx",
|
"104": ".initiateOrderPayment",
|
||||||
"105": "Operational Rules & Boundaries",
|
"105": "1. Summary of Integrity Repairs Performed",
|
||||||
"106": "catalog/page.tsx",
|
"106": "@nestjs/cli",
|
||||||
"107": "wiki/page.tsx",
|
"107": "Operational Rules & Boundaries",
|
||||||
"108": "PrismaService",
|
"108": "Operational Rules & Boundaries",
|
||||||
"109": "1. Summary of Integrity Repairs Performed",
|
"109": "Operational Rules & Boundaries",
|
||||||
"110": "Operational Rules & Boundaries",
|
"110": "AppService",
|
||||||
"111": "Operational Rules & Boundaries",
|
"111": "SmsSettingsPage.tsx",
|
||||||
"112": "Operational Rules & Boundaries",
|
"112": "SettingsService",
|
||||||
"113": "ProductDto",
|
"113": "Vazirmatn Changelog",
|
||||||
"114": "AppService",
|
"114": "Vazirmatn Font فونت وزیرمتن",
|
||||||
"115": "helmet",
|
"115": "Operational Rules & Boundaries",
|
||||||
"116": "Vazirmatn Changelog",
|
"116": "compilerOptions",
|
||||||
"117": "Vazirmatn Font فونت وزیرمتن",
|
"117": "compilerOptions",
|
||||||
"118": "Operational Rules & Boundaries",
|
"118": "backend/README.md",
|
||||||
"119": "compilerOptions",
|
"119": "eslint",
|
||||||
"120": "compilerOptions",
|
"120": "Repository Map",
|
||||||
"121": "backend/README.md",
|
"121": "validate_integrity.js",
|
||||||
"122": ".adjustWallet",
|
"122": "admin-panel/package.json",
|
||||||
"123": "js-yaml",
|
"123": "Sahel-Font",
|
||||||
"124": "Repository Map",
|
"124": "Spinner.tsx",
|
||||||
"125": "validate_integrity.js",
|
"125": "Sahel-Font",
|
||||||
"126": "admin-panel/package.json",
|
"126": "Role & Core Objective",
|
||||||
"127": "Sahel-Font",
|
"127": "orchestrate.py",
|
||||||
"128": "@nestjs/core",
|
"128": "backend/package.json",
|
||||||
"129": "seo.ts",
|
"129": "blog/page.tsx",
|
||||||
"130": "Sahel-Font",
|
"130": "graphify reference: extra exports and benchmark",
|
||||||
"131": "Role & Core Objective",
|
"131": "Phase 2 Final Quality Gate Summary Report",
|
||||||
"132": "orchestrate.py",
|
"132": "Task Modifications Log",
|
||||||
"133": "backend/package.json",
|
"133": "Install",
|
||||||
"134": "@nestjs/throttler",
|
"134": "ErrorBoundary",
|
||||||
"135": "graphify reference: extra exports and benchmark",
|
"135": "application/package.json",
|
||||||
"136": "Phase 2 Final Quality Gate Summary Report",
|
"136": "generate-openapi.js",
|
||||||
"137": "Task Modifications Log",
|
"137": "contact/page.tsx",
|
||||||
"138": "Install",
|
"138": "HomeController",
|
||||||
"139": "RouteErrorBoundary",
|
"139": "System Discovery",
|
||||||
"140": "ErrorBoundary",
|
"140": "Product Requirement Document (PRD)",
|
||||||
"141": "application/package.json",
|
"141": "DoctorQueryDto",
|
||||||
"142": "start-dev.js",
|
"142": "lib/services/api.ts",
|
||||||
"143": "generate-openapi.js",
|
"143": "RedisService",
|
||||||
"144": "AdminService",
|
"144": "Baseline Command Plan & Reconciled Command History",
|
||||||
"145": "eslint-config-prettier",
|
"145": "catalog/page.tsx",
|
||||||
"146": "System Discovery",
|
"146": "ErrorPages.tsx",
|
||||||
"147": "Media.tsx",
|
"147": "with-vpn.sh",
|
||||||
"148": "@eslint/eslintrc",
|
"148": "Architecture Specification",
|
||||||
"149": "SmsSettingsPage.tsx",
|
"149": "Project Health Audit Report",
|
||||||
"150": "Product Requirement Document (PRD)",
|
"150": "nest-cli.json",
|
||||||
"151": "jest",
|
"151": "prettier",
|
||||||
"152": "@nestjs/cli",
|
"152": "graphify reference: query, path, explain",
|
||||||
"153": "exclude",
|
"153": "Open Questions",
|
||||||
"154": "Baseline Command Plan & Reconciled Command History",
|
"154": "Final Phase 2 Audit Closure Report",
|
||||||
"155": "@nestjs/schematics",
|
"155": "prisma",
|
||||||
"156": "contact/page.tsx",
|
"156": "open-browsers.js",
|
||||||
"157": "ErrorPages.tsx",
|
"157": "start-dev.js",
|
||||||
"158": "@nestjs/testing",
|
"158": "📝 Active Agent Working Scratchpad",
|
||||||
"159": "with-vpn.sh",
|
"159": "🔍 Code Health Audit Review (01_auditor)",
|
||||||
"160": "Architecture Specification",
|
"160": "paginated-response.schema.ts",
|
||||||
"161": "Project Health Audit Report",
|
"161": "Vazirmatn Font README",
|
||||||
"162": "nest-cli.json",
|
"162": "Omitted File Inspection Report",
|
||||||
"163": "graphify reference: query, path, explain",
|
"163": "Phase 3.2 / 3.3 — Implementation Readiness & Architectural Finalization Report",
|
||||||
"164": "Open Questions",
|
"164": "Phase 3 Audit Traceability Matrix",
|
||||||
"165": "Final Phase 2 Audit Closure Report",
|
"165": "rebuild_honest_ledger.js",
|
||||||
"166": "open-browsers.js",
|
"166": "validate_evidence_grade.js",
|
||||||
"167": "📝 Active Agent Working Scratchpad",
|
"167": "supertest",
|
||||||
"168": "🔍 Code Health Audit Review (01_auditor)",
|
"168": "app/page.tsx",
|
||||||
"169": "paginated-response.schema.ts",
|
"169": "API Contract Specification",
|
||||||
"170": "Vazirmatn Font README",
|
"170": "⚙️ Backend Technical Review (05_dev_backend)",
|
||||||
"171": "Omitted File Inspection Report",
|
"171": "🎨 Frontend & Admin Panel Technical Review (06_dev_frontend)",
|
||||||
"172": "Phase 3.2 / 3.3 — Implementation Readiness & Architectural Finalization Report",
|
"172": "🚀 SEO & Content Strategy Review (12_seo_content)",
|
||||||
"173": "Phase 3 Audit Traceability Matrix",
|
"173": "@types/node",
|
||||||
"174": "rebuild_honest_ledger.js",
|
"174": "reviews.controller.ts",
|
||||||
"175": "validate_evidence_grade.js",
|
"175": "seed-ui-texts.ts",
|
||||||
"176": "Reviews.tsx",
|
"176": "seed-wiki.ts",
|
||||||
"177": "blog/page.tsx",
|
"177": "update-blog.dto.ts",
|
||||||
"178": "prettier",
|
"178": "update-home.dto.ts",
|
||||||
"179": "PodcastPlayerModal.tsx",
|
"179": "update-wiki.dto.ts",
|
||||||
"180": "API Contract Specification",
|
"180": "graphify reference: add a URL and watch a folder",
|
||||||
"181": "⚙️ Backend Technical Review (05_dev_backend)",
|
"181": "graphify reference: commit hook and native CLAUDE.md integration",
|
||||||
"182": "🎨 Frontend & Admin Panel Technical Review (06_dev_frontend)",
|
"182": "graphify reference: incremental update and cluster-only",
|
||||||
"183": "🚀 SEO & Content Strategy Review (12_seo_content)",
|
"183": "Raw Finding Verification & Disposition Report",
|
||||||
"184": "eslint",
|
"184": "React + TypeScript + Vite",
|
||||||
"185": "@types/node",
|
"185": "Select.tsx",
|
||||||
"186": "seed-ui-texts.ts",
|
"186": "RegisterDto",
|
||||||
"187": "seed-wiki.ts",
|
"187": "videos.controller.ts",
|
||||||
"188": "update-blog.dto.ts",
|
"188": "application/README.md",
|
||||||
"189": "update-home.dto.ts",
|
"189": "@types/express",
|
||||||
"190": "update-wiki.dto.ts",
|
"190": "deploy.sh",
|
||||||
"191": "graphify reference: add a URL and watch a folder",
|
"191": "🔒 Security & Performance Review (09_devops_security)",
|
||||||
"192": "graphify reference: commit hook and native CLAUDE.md integration",
|
"192": "👁️ UX & Persona Interface Review (08_visual_qa)",
|
||||||
"193": "graphify reference: incremental update and cluster-only",
|
"193": "@types/jest",
|
||||||
"194": "Raw Finding Verification & Disposition Report",
|
"194": "typescript-eslint",
|
||||||
"195": "React + TypeScript + Vite",
|
"195": "CreateOrderDto",
|
||||||
"196": "Select.tsx",
|
"196": "prisma/scientificTerms.ts",
|
||||||
"197": "source-map-support",
|
"197": "seed-blogs.ts",
|
||||||
"198": "videos/page.tsx",
|
"198": "seed-custom.ts",
|
||||||
"199": "useSettingsStore",
|
"199": "@types/js-yaml",
|
||||||
"200": "application/README.md",
|
"200": "graphify reference: GitHub clone and cross-repo merge",
|
||||||
"201": "deploy.sh",
|
"201": "graphify reference: transcribe video and audio",
|
||||||
"202": "🔒 Security & Performance Review (09_devops_security)",
|
"202": "Compiler Diagnostic Dispositions",
|
||||||
"203": "👁️ UX & Persona Interface Review (08_visual_qa)",
|
"203": "Master Task Backlog (Phase 3.3)",
|
||||||
"204": "supertest",
|
"204": "build_manifest.js",
|
||||||
"205": "prisma/scientificTerms.ts",
|
"205": "generate_classification.js",
|
||||||
"206": "seed-blogs.ts",
|
"206": "generate_evidence.js",
|
||||||
"207": "seed-custom.ts",
|
"207": "generate_ledger.js",
|
||||||
"208": "graphify reference: GitHub clone and cross-repo merge",
|
"208": "generate_manifest.js",
|
||||||
"209": "graphify reference: transcribe video and audio",
|
"209": "sync_honest_manifest.js",
|
||||||
"210": "Compiler Diagnostic Dispositions",
|
"210": "sync_manifest.js",
|
||||||
"211": "Master Task Backlog (Phase 3.3)",
|
"211": "@types/multer",
|
||||||
"212": "build_manifest.js",
|
"212": "videos/page.tsx",
|
||||||
"213": "generate_classification.js",
|
"213": "@testing-library/jest-dom",
|
||||||
"214": "generate_evidence.js",
|
"214": "bcryptjs",
|
||||||
"215": "generate_ledger.js",
|
"215": "AdminService",
|
||||||
"216": "generate_manifest.js",
|
"216": "FormField.tsx",
|
||||||
"217": "sync_honest_manifest.js",
|
"217": "Input.tsx",
|
||||||
"218": "sync_manifest.js",
|
"218": "Textarea.tsx",
|
||||||
"219": "FormField.tsx",
|
"219": "admin-panel/tsconfig.json",
|
||||||
"220": "Input.tsx",
|
"220": "getPageMetadata",
|
||||||
"221": "Textarea.tsx",
|
"221": "api",
|
||||||
"222": "admin-panel/tsconfig.json",
|
"222": "next.config.ts",
|
||||||
"223": "ts-jest",
|
"223": "Shabnam Font README",
|
||||||
"224": "dashboard/page.tsx",
|
"224": "AGENTS.md",
|
||||||
"225": "next.config.ts",
|
"225": "rules/graphify.md",
|
||||||
"226": "Shabnam Font README",
|
"226": ".agents/workflows/graphify.md",
|
||||||
"227": "AGENTS.md",
|
"227": "instructions.md",
|
||||||
"228": "rules/graphify.md",
|
"228": "eslint-plugin-react-hooks",
|
||||||
"229": ".agents/workflows/graphify.md",
|
"229": "eslint-plugin-react-refresh",
|
||||||
"230": "instructions.md",
|
"230": "@tailwindcss/postcss",
|
||||||
"231": "bcryptjs",
|
"231": "typescript",
|
||||||
"232": "ts-loader",
|
"232": "ProductDto",
|
||||||
"233": "ts-node",
|
"233": "@testing-library/react",
|
||||||
"234": "tsconfig-paths",
|
"234": "@types/react",
|
||||||
"235": "@nestjs/jwt",
|
"235": "sms.service.ts",
|
||||||
"236": "@types/bcrypt",
|
"236": "vitest",
|
||||||
"237": "@nestjs/swagger",
|
"237": "axios",
|
||||||
"238": "passport-jwt",
|
"238": "AdminController",
|
||||||
"239": "@prisma/client",
|
"239": "app.e2e-spec.js",
|
||||||
"240": "swagger-ui-express",
|
"240": "ts-loader",
|
||||||
"241": "blog.entity.ts",
|
"241": "auth.service.ts",
|
||||||
"242": "home.entity.ts",
|
"242": "@types/bcrypt",
|
||||||
"243": "wiki.entity.ts",
|
"243": "SmsLogQueryDto",
|
||||||
"244": "User Profile Photo",
|
"244": "blog.entity.ts",
|
||||||
"245": "CLAUDE.md",
|
"245": "home.entity.ts",
|
||||||
"246": ".claude/CLAUDE.md",
|
"246": "wiki.entity.ts",
|
||||||
"247": "extraction-spec.md",
|
"247": "User Profile Photo",
|
||||||
"248": "Products Table",
|
"248": "CLAUDE.md",
|
||||||
"249": "Users Table",
|
"249": ".claude/CLAUDE.md",
|
||||||
"250": "Architectural Audit Findings",
|
"250": "extraction-spec.md",
|
||||||
"251": "Cross Boundary Dependencies & Backend Architecture Specification",
|
"251": "Products Table",
|
||||||
"252": "Next.js Agent Rules & Brand Guidelines",
|
"252": "Users Table",
|
||||||
"253": "robots.ts",
|
"253": "Architectural Audit Findings",
|
||||||
"254": "application/eslint.config.mjs",
|
"254": "Cross Boundary Dependencies & Backend Architecture Specification",
|
||||||
"255": "postcss.config.mjs",
|
"255": "Next.js Agent Rules & Brand Guidelines",
|
||||||
"256": "vitest.setup.ts",
|
"256": "robots.ts",
|
||||||
"257": "backup_db.sh",
|
"257": "application/eslint.config.mjs",
|
||||||
"258": "start.sh",
|
"258": "postcss.config.mjs",
|
||||||
"259": "reviews/README.md",
|
"259": "vitest.setup.ts",
|
||||||
"260": "backend/eslint.config.mjs",
|
"260": "backup_db.sh",
|
||||||
"261": "User Login API",
|
"261": "start.sh",
|
||||||
"262": "User Logout API",
|
"262": "reviews/README.md",
|
||||||
"263": "generate-openapi.d.ts",
|
"263": "backend/eslint.config.mjs",
|
||||||
"264": "@types/bcryptjs",
|
"264": "User Login API",
|
||||||
"265": "@types/express",
|
"265": "User Logout API",
|
||||||
"266": "@types/jest",
|
"266": "generate-openapi.d.ts",
|
||||||
"267": "@types/js-yaml",
|
"267": "Canina Pharma GmbH",
|
||||||
"268": "@types/multer",
|
"268": "Pets Table",
|
||||||
"269": "eslint-plugin-react-hooks",
|
"269": "Canina Iran Project Introduction",
|
||||||
"270": "app-audit-verification.e2e-spec.d.ts",
|
"270": "Developer Standards and Architecture",
|
||||||
"271": "app.e2e-spec.d.ts",
|
"271": "Frontend & Admin Architecture Route Map Specification",
|
||||||
"272": "Canina Pharma GmbH",
|
"272": "Project Backlog and Tasks",
|
||||||
"273": "Pets Table",
|
"273": "eslint.config.js",
|
||||||
"274": "Canina Iran Project Introduction",
|
"274": "postcss.config.js",
|
||||||
"275": "Developer Standards and Architecture",
|
"275": "admin-panel/public/fonts/sahel-font-v3.4.0/CHANGELOG.md",
|
||||||
"276": "Frontend & Admin Architecture Route Map Specification",
|
"276": "shabnam-font-v5.0.1/CHANGELOG.md",
|
||||||
"277": "Project Backlog and Tasks",
|
"277": "tailwind.config.js",
|
||||||
"278": "eslint.config.js",
|
"278": "vite.config.ts",
|
||||||
"279": "postcss.config.js",
|
"279": "application/CLAUDE.md",
|
||||||
"280": "admin-panel/public/fonts/sahel-font-v3.4.0/CHANGELOG.md",
|
"280": "application/public/fonts/sahel-font-v3.4.0/CHANGELOG.md",
|
||||||
"281": "shabnam-font-v5.0.1/CHANGELOG.md",
|
"281": "Sahel Font Sample",
|
||||||
"282": "tailwind.config.js",
|
"282": "Shabnam Font Changelog",
|
||||||
"283": "vite.config.ts",
|
"283": "Vazirmatn Changelog",
|
||||||
"284": "application/CLAUDE.md",
|
"284": "vitest.config.ts",
|
||||||
"285": "application/public/fonts/sahel-font-v3.4.0/CHANGELOG.md",
|
"285": "Sahel Font Variable Sample",
|
||||||
"286": "Sahel Font Sample",
|
"286": "Shabnam Font Sample",
|
||||||
"287": "Shabnam Font Changelog",
|
"287": "Production Docker Compose",
|
||||||
"288": "Vazirmatn Changelog",
|
"288": "Staging Docker Compose",
|
||||||
"289": "vitest.config.ts",
|
"289": "tailwindcss",
|
||||||
"290": "Sahel Font Variable Sample",
|
"290": "eslint-plugin-prettier",
|
||||||
"291": "Shabnam Font Sample",
|
"291": "helmet",
|
||||||
"292": "Production Docker Compose",
|
"292": "MetricsController",
|
||||||
"293": "Staging Docker Compose",
|
"293": "AppModule",
|
||||||
"294": "ZibalService",
|
"294": "app-audit-verification.e2e-spec.d.ts",
|
||||||
"295": "eslint-plugin-react-refresh",
|
"295": "app.e2e-spec.d.ts",
|
||||||
"296": "tailwindcss",
|
"296": "SendOtpDto",
|
||||||
"297": "ZibalEBankService",
|
"297": "@nestjs/core",
|
||||||
"298": ".initiateOrderPayment",
|
"298": "eslint-config-prettier",
|
||||||
"299": "@tailwindcss/postcss",
|
"299": "@nestjs/jwt",
|
||||||
"300": "typescript",
|
"300": "@nestjs/swagger",
|
||||||
"301": "app.e2e-spec.js",
|
"301": "useCartStore",
|
||||||
"302": "typescript-eslint",
|
"302": "@nestjs/throttler",
|
||||||
"303": "@testing-library/jest-dom",
|
"303": "shop/page.tsx",
|
||||||
"304": "@testing-library/react",
|
"304": "checkout/page.tsx",
|
||||||
"305": "@types/react",
|
"305": "RouteErrorBoundary",
|
||||||
"306": "globals",
|
"306": "devDependencies",
|
||||||
"307": "@types/react-dom",
|
"307": "passport-jwt",
|
||||||
"308": "vitest",
|
"308": "@prisma/client",
|
||||||
"309": "axios",
|
"309": "reflect-metadata",
|
||||||
"310": "tailwindcss",
|
"310": "admin.module.ts",
|
||||||
|
"311": "@eslint/eslintrc",
|
||||||
|
"312": "tailwindcss",
|
||||||
|
"313": "swagger-ui-express",
|
||||||
"314": "@eslint/js",
|
"314": "@eslint/js",
|
||||||
"315": "typescript",
|
"315": "typescript",
|
||||||
"324": "typescript-eslint"
|
"316": "jest",
|
||||||
|
"317": "@nestjs/schematics",
|
||||||
|
"318": "@nestjs/testing",
|
||||||
|
"319": "source-map-support",
|
||||||
|
"320": "ts-jest",
|
||||||
|
"321": "ts-node",
|
||||||
|
"322": "tsconfig-paths",
|
||||||
|
"323": "@types/bcryptjs",
|
||||||
|
"324": "typescript-eslint",
|
||||||
|
"325": "eslint-config-next"
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"output_tokens": 7105}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_claude_skills_graphify_references_update_md", "label": "update.md", "file_type": "document", "source_file": ".claude/skills/graphify/references/update.md", "source_location": "L1"}, {"id": "$graphify-root$_claude_skills_graphify_references_update_graphify_reference_incremental_update_and_cluster_only", "label": "graphify reference: incremental update and cluster-only", "file_type": "document", "source_file": ".claude/skills/graphify/references/update.md", "source_location": "L1"}, {"id": "$graphify-root$_claude_skills_graphify_references_update_for_update_incremental_re_extraction", "label": "For --update (incremental re-extraction)", "file_type": "document", "source_file": ".claude/skills/graphify/references/update.md", "source_location": "L5"}, {"id": "$graphify-root$_claude_skills_graphify_references_update_for_cluster_only", "label": "For --cluster-only", "file_type": "document", "source_file": ".claude/skills/graphify/references/update.md", "source_location": "L202"}], "edges": [{"source": "$graphify-root$_claude_skills_graphify_references_update_md", "target": "$graphify-root$_claude_skills_graphify_references_update_graphify_reference_incremental_update_and_cluster_only", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".claude/skills/graphify/references/update.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_claude_skills_graphify_references_update_graphify_reference_incremental_update_and_cluster_only", "target": "$graphify-root$_claude_skills_graphify_references_update_for_update_incremental_re_extraction", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".claude/skills/graphify/references/update.md", "source_location": "L5", "weight": 1.0}, {"source": "$graphify-root$_claude_skills_graphify_references_update_graphify_reference_incremental_update_and_cluster_only", "target": "$graphify-root$_claude_skills_graphify_references_update_for_cluster_only", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".claude/skills/graphify/references/update.md", "source_location": "L202", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_docs_audit_04_open_questions_md", "label": "04-open-questions.md", "file_type": "document", "source_file": "docs/audit/04-open-questions.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_audit_04_open_questions_open_questions", "label": "Open Questions", "file_type": "document", "source_file": "docs/audit/04-open-questions.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_audit_04_open_questions_1_storefront_migration_roadmap_frontend_application", "label": "1. Storefront Migration Roadmap (`frontend/application`)", "file_type": "document", "source_file": "docs/audit/04-open-questions.md", "source_location": "L5"}, {"id": "$graphify-root$_docs_audit_04_open_questions_2_payment_gateway_integration_provider", "label": "2. Payment Gateway Integration Provider", "file_type": "document", "source_file": "docs/audit/04-open-questions.md", "source_location": "L9"}, {"id": "$graphify-root$_docs_audit_04_open_questions_3_deployment_ci_cd_pipeline_specifications", "label": "3. Deployment & CI/CD Pipeline Specifications", "file_type": "document", "source_file": "docs/audit/04-open-questions.md", "source_location": "L13"}, {"id": "$graphify-root$_docs_audit_04_open_questions_4_sms_otp_service_provider", "label": "4. SMS / OTP Service Provider", "file_type": "document", "source_file": "docs/audit/04-open-questions.md", "source_location": "L17"}], "edges": [{"source": "$graphify-root$_docs_audit_04_open_questions_md", "target": "$graphify-root$_docs_audit_04_open_questions_open_questions", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/04-open-questions.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_04_open_questions_open_questions", "target": "$graphify-root$_docs_audit_04_open_questions_1_storefront_migration_roadmap_frontend_application", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/04-open-questions.md", "source_location": "L5", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_04_open_questions_open_questions", "target": "$graphify-root$_docs_audit_04_open_questions_2_payment_gateway_integration_provider", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/04-open-questions.md", "source_location": "L9", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_04_open_questions_open_questions", "target": "$graphify-root$_docs_audit_04_open_questions_3_deployment_ci_cd_pipeline_specifications", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/04-open-questions.md", "source_location": "L13", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_04_open_questions_open_questions", "target": "$graphify-root$_docs_audit_04_open_questions_4_sms_otp_service_provider", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/04-open-questions.md", "source_location": "L17", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_frontend_application_claude_md", "label": "CLAUDE.md", "file_type": "document", "source_file": "frontend/application/CLAUDE.md", "source_location": "L1"}], "edges": [], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_docs_audit_phase3_1_change_log_md", "label": "phase3.1-change-log.md", "file_type": "document", "source_file": "docs/audit/phase3.1-change-log.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_audit_phase3_1_change_log_phase_3_1_master_task_backlog_change_log", "label": "Phase 3.1 \u2014 Master Task Backlog Change Log", "file_type": "document", "source_file": "docs/audit/phase3.1-change-log.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_audit_phase3_1_change_log_task_modifications_log", "label": "Task Modifications Log", "file_type": "document", "source_file": "docs/audit/phase3.1-change-log.md", "source_location": "L8"}, {"id": "$graphify-root$_docs_audit_phase3_1_change_log_1_task_auth_001", "label": "1. `TASK-AUTH-001`", "file_type": "document", "source_file": "docs/audit/phase3.1-change-log.md", "source_location": "L10"}, {"id": "$graphify-root$_docs_audit_phase3_1_change_log_2_task_fin_001", "label": "2. `TASK-FIN-001`", "file_type": "document", "source_file": "docs/audit/phase3.1-change-log.md", "source_location": "L19"}, {"id": "$graphify-root$_docs_audit_phase3_1_change_log_3_decision_002", "label": "3. `DECISION-002`", "file_type": "document", "source_file": "docs/audit/phase3.1-change-log.md", "source_location": "L28"}, {"id": "$graphify-root$_docs_audit_phase3_1_change_log_4_task_verify_001", "label": "4. `TASK-VERIFY-001`", "file_type": "document", "source_file": "docs/audit/phase3.1-change-log.md", "source_location": "L37"}, {"id": "$graphify-root$_docs_audit_phase3_1_change_log_5_task_sec_001_task_sec_002", "label": "5. `TASK-SEC-001` & `TASK-SEC-002`", "file_type": "document", "source_file": "docs/audit/phase3.1-change-log.md", "source_location": "L46"}, {"id": "$graphify-root$_docs_audit_phase3_1_change_log_6_task_build_001_execution_waves", "label": "6. `TASK-BUILD-001` & Execution Waves", "file_type": "document", "source_file": "docs/audit/phase3.1-change-log.md", "source_location": "L55"}], "edges": [{"source": "$graphify-root$_docs_audit_phase3_1_change_log_md", "target": "$graphify-root$_docs_audit_phase3_1_change_log_phase_3_1_master_task_backlog_change_log", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/phase3.1-change-log.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_phase3_1_change_log_phase_3_1_master_task_backlog_change_log", "target": "$graphify-root$_docs_audit_phase3_1_change_log_task_modifications_log", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/phase3.1-change-log.md", "source_location": "L8", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_phase3_1_change_log_task_modifications_log", "target": "$graphify-root$_docs_audit_phase3_1_change_log_1_task_auth_001", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/phase3.1-change-log.md", "source_location": "L10", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_phase3_1_change_log_task_modifications_log", "target": "$graphify-root$_docs_audit_phase3_1_change_log_2_task_fin_001", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/phase3.1-change-log.md", "source_location": "L19", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_phase3_1_change_log_task_modifications_log", "target": "$graphify-root$_docs_audit_phase3_1_change_log_3_decision_002", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/phase3.1-change-log.md", "source_location": "L28", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_phase3_1_change_log_task_modifications_log", "target": "$graphify-root$_docs_audit_phase3_1_change_log_4_task_verify_001", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/phase3.1-change-log.md", "source_location": "L37", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_phase3_1_change_log_task_modifications_log", "target": "$graphify-root$_docs_audit_phase3_1_change_log_5_task_sec_001_task_sec_002", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/phase3.1-change-log.md", "source_location": "L46", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_phase3_1_change_log_task_modifications_log", "target": "$graphify-root$_docs_audit_phase3_1_change_log_6_task_build_001_execution_waves", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/phase3.1-change-log.md", "source_location": "L55", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_docs_readme_md", "label": "README.md", "file_type": "document", "source_file": "docs/README.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_readme_\u0641\u0647\u0631\u0633\u062a_\u0645\u0637\u0627\u0644\u0628_table_of_contents", "label": "\u0641\u0647\u0631\u0633\u062a \u0645\u0637\u0627\u0644\u0628 (Table of Contents)", "file_type": "document", "source_file": "docs/README.md", "source_location": "L7"}], "edges": [{"source": "$graphify-root$_docs_readme_md", "target": "$graphify-root$_docs_readme_\u0641\u0647\u0631\u0633\u062a_\u0645\u0637\u0627\u0644\u0628_table_of_contents", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/README.md", "source_location": "L7", "weight": 1.0}, {"source": "$graphify-root$_docs_readme_md", "target": "$graphify-root$_docs_01_introduction_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "docs/README.md", "source_location": "L11", "weight": 1.0, "target_file": "$graphify-root$/docs/01-introduction.md"}, {"source": "$graphify-root$_docs_readme_md", "target": "$graphify-root$_docs_02_user_guide_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "docs/README.md", "source_location": "L14", "weight": 1.0, "target_file": "$graphify-root$/docs/02-user-guide.md"}, {"source": "$graphify-root$_docs_readme_md", "target": "$graphify-root$_docs_03_developer_guide_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "docs/README.md", "source_location": "L17", "weight": 1.0, "target_file": "$graphify-root$/docs/03-developer-guide.md"}, {"source": "$graphify-root$_docs_readme_md", "target": "$graphify-root$_docs_04_setup_and_deployment_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "docs/README.md", "source_location": "L20", "weight": 1.0, "target_file": "$graphify-root$/docs/04-setup-and-deployment.md"}, {"source": "$graphify-root$_docs_readme_md", "target": "$graphify-root$_docs_05_devops_and_monitoring_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "docs/README.md", "source_location": "L23", "weight": 1.0, "target_file": "$graphify-root$/docs/05-devops-and-monitoring.md"}, {"source": "$graphify-root$_docs_readme_md", "target": "$graphify-root$_docs_06_testing_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "docs/README.md", "source_location": "L26", "weight": 1.0, "target_file": "$graphify-root$/docs/06-testing.md"}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_ai_agency_specs_prd_md", "label": "prd.md", "file_type": "document", "source_file": ".ai_agency/specs/prd.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_specs_prd_product_requirement_document_prd", "label": "Product Requirement Document (PRD)", "file_type": "document", "source_file": ".ai_agency/specs/prd.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_specs_prd_1_executive_vision", "label": "1. Executive Vision", "file_type": "document", "source_file": ".ai_agency/specs/prd.md", "source_location": "L3"}, {"id": "$graphify-root$_ai_agency_specs_prd_2_target_audience", "label": "2. Target Audience", "file_type": "document", "source_file": ".ai_agency/specs/prd.md", "source_location": "L6"}, {"id": "$graphify-root$_ai_agency_specs_prd_3_functional_requirements", "label": "3. Functional Requirements", "file_type": "document", "source_file": ".ai_agency/specs/prd.md", "source_location": "L9"}, {"id": "$graphify-root$_ai_agency_specs_prd_4_non_functional_requirements_performance_security", "label": "4. Non-Functional Requirements (Performance, Security)", "file_type": "document", "source_file": ".ai_agency/specs/prd.md", "source_location": "L12"}, {"id": "$graphify-root$_ai_agency_specs_prd_5_epic_feature_breakdown", "label": "5. Epic / Feature Breakdown", "file_type": "document", "source_file": ".ai_agency/specs/prd.md", "source_location": "L15"}], "edges": [{"source": "$graphify-root$_ai_agency_specs_prd_md", "target": "$graphify-root$_ai_agency_specs_prd_product_requirement_document_prd", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/prd.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_prd_product_requirement_document_prd", "target": "$graphify-root$_ai_agency_specs_prd_1_executive_vision", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/prd.md", "source_location": "L3", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_prd_product_requirement_document_prd", "target": "$graphify-root$_ai_agency_specs_prd_2_target_audience", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/prd.md", "source_location": "L6", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_prd_product_requirement_document_prd", "target": "$graphify-root$_ai_agency_specs_prd_3_functional_requirements", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/prd.md", "source_location": "L9", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_prd_product_requirement_document_prd", "target": "$graphify-root$_ai_agency_specs_prd_4_non_functional_requirements_performance_security", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/prd.md", "source_location": "L12", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_prd_product_requirement_document_prd", "target": "$graphify-root$_ai_agency_specs_prd_5_epic_feature_breakdown", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/prd.md", "source_location": "L15", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_docs_audit_19_finding_verification_report_md", "label": "19-finding-verification-report.md", "file_type": "document", "source_file": "docs/audit/19-finding-verification-report.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_audit_19_finding_verification_report_raw_finding_verification_disposition_report", "label": "Raw Finding Verification & Disposition Report", "file_type": "document", "source_file": "docs/audit/19-finding-verification-report.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_audit_19_finding_verification_report_1_executive_summary_verification_reconciliation_table", "label": "1. Executive Summary & Verification Reconciliation Table", "file_type": "document", "source_file": "docs/audit/19-finding-verification-report.md", "source_location": "L3"}, {"id": "$graphify-root$_docs_audit_19_finding_verification_report_2_newly_discovered_split_findings_canonical_ids", "label": "2. Newly Discovered & Split Findings (Canonical IDs)", "file_type": "document", "source_file": "docs/audit/19-finding-verification-report.md", "source_location": "L22"}], "edges": [{"source": "$graphify-root$_docs_audit_19_finding_verification_report_md", "target": "$graphify-root$_docs_audit_19_finding_verification_report_raw_finding_verification_disposition_report", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/19-finding-verification-report.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_19_finding_verification_report_raw_finding_verification_disposition_report", "target": "$graphify-root$_docs_audit_19_finding_verification_report_1_executive_summary_verification_reconciliation_table", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/19-finding-verification-report.md", "source_location": "L3", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_19_finding_verification_report_raw_finding_verification_disposition_report", "target": "$graphify-root$_docs_audit_19_finding_verification_report_2_newly_discovered_split_findings_canonical_ids", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/19-finding-verification-report.md", "source_location": "L22", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_claude_skills_graphify_references_transcribe_md", "label": "transcribe.md", "file_type": "document", "source_file": ".claude/skills/graphify/references/transcribe.md", "source_location": "L1"}, {"id": "$graphify-root$_claude_skills_graphify_references_transcribe_graphify_reference_transcribe_video_and_audio", "label": "graphify reference: transcribe video and audio", "file_type": "document", "source_file": ".claude/skills/graphify/references/transcribe.md", "source_location": "L1"}, {"id": "$graphify-root$_claude_skills_graphify_references_transcribe_step_2_5_transcribe_video_audio_files_only_if_video_files_detected", "label": "Step 2.5 - Transcribe video / audio files (only if video files detected)", "file_type": "document", "source_file": ".claude/skills/graphify/references/transcribe.md", "source_location": "L5"}], "edges": [{"source": "$graphify-root$_claude_skills_graphify_references_transcribe_md", "target": "$graphify-root$_claude_skills_graphify_references_transcribe_graphify_reference_transcribe_video_and_audio", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".claude/skills/graphify/references/transcribe.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_claude_skills_graphify_references_transcribe_graphify_reference_transcribe_video_and_audio", "target": "$graphify-root$_claude_skills_graphify_references_transcribe_step_2_5_transcribe_video_audio_files_only_if_video_files_detected", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".claude/skills/graphify/references/transcribe.md", "source_location": "L5", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_docs_audit_33_final_phase2_audit_closure_md", "label": "33-final-phase2-audit-closure.md", "file_type": "document", "source_file": "docs/audit/33-final-phase2-audit-closure.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_audit_33_final_phase2_audit_closure_final_phase_2_audit_closure_report", "label": "Final Phase 2 Audit Closure Report", "file_type": "document", "source_file": "docs/audit/33-final-phase2-audit-closure.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_audit_33_final_phase2_audit_closure_1_executive_summary_honest_review_tier_metrics", "label": "1. Executive Summary & Honest Review-Tier Metrics", "file_type": "document", "source_file": "docs/audit/33-final-phase2-audit-closure.md", "source_location": "L9"}, {"id": "$graphify-root$_docs_audit_33_final_phase2_audit_closure_2_reconciled_findings_canonical_identifier_normalization", "label": "2. Reconciled Findings & Canonical Identifier Normalization", "file_type": "document", "source_file": "docs/audit/33-final-phase2-audit-closure.md", "source_location": "L22"}, {"id": "$graphify-root$_docs_audit_33_final_phase2_audit_closure_3_validation_integrity_verification", "label": "3. Validation & Integrity Verification", "file_type": "document", "source_file": "docs/audit/33-final-phase2-audit-closure.md", "source_location": "L34"}, {"id": "$graphify-root$_docs_audit_33_final_phase2_audit_closure_4_final_quality_gate_conclusion", "label": "4. Final Quality Gate Conclusion", "file_type": "document", "source_file": "docs/audit/33-final-phase2-audit-closure.md", "source_location": "L42"}], "edges": [{"source": "$graphify-root$_docs_audit_33_final_phase2_audit_closure_md", "target": "$graphify-root$_docs_audit_33_final_phase2_audit_closure_final_phase_2_audit_closure_report", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/33-final-phase2-audit-closure.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_33_final_phase2_audit_closure_final_phase_2_audit_closure_report", "target": "$graphify-root$_docs_audit_33_final_phase2_audit_closure_1_executive_summary_honest_review_tier_metrics", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/33-final-phase2-audit-closure.md", "source_location": "L9", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_33_final_phase2_audit_closure_final_phase_2_audit_closure_report", "target": "$graphify-root$_docs_audit_33_final_phase2_audit_closure_2_reconciled_findings_canonical_identifier_normalization", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/33-final-phase2-audit-closure.md", "source_location": "L22", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_33_final_phase2_audit_closure_final_phase_2_audit_closure_report", "target": "$graphify-root$_docs_audit_33_final_phase2_audit_closure_3_validation_integrity_verification", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/33-final-phase2-audit-closure.md", "source_location": "L34", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_33_final_phase2_audit_closure_final_phase_2_audit_closure_report", "target": "$graphify-root$_docs_audit_33_final_phase2_audit_closure_4_final_quality_gate_conclusion", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/33-final-phase2-audit-closure.md", "source_location": "L42", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_docs_audit_18_compiler_diagnostic_dispositions_md", "label": "18-compiler-diagnostic-dispositions.md", "file_type": "document", "source_file": "docs/audit/18-compiler-diagnostic-dispositions.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_audit_18_compiler_diagnostic_dispositions_compiler_diagnostic_dispositions", "label": "Compiler Diagnostic Dispositions", "file_type": "document", "source_file": "docs/audit/18-compiler-diagnostic-dispositions.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_audit_18_compiler_diagnostic_dispositions_reconciled_compiler_diagnostic_table", "label": "Reconciled Compiler Diagnostic Table", "file_type": "document", "source_file": "docs/audit/18-compiler-diagnostic-dispositions.md", "source_location": "L11"}], "edges": [{"source": "$graphify-root$_docs_audit_18_compiler_diagnostic_dispositions_md", "target": "$graphify-root$_docs_audit_18_compiler_diagnostic_dispositions_compiler_diagnostic_dispositions", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/18-compiler-diagnostic-dispositions.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_18_compiler_diagnostic_dispositions_compiler_diagnostic_dispositions", "target": "$graphify-root$_docs_audit_18_compiler_diagnostic_dispositions_reconciled_compiler_diagnostic_table", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/18-compiler-diagnostic-dispositions.md", "source_location": "L11", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_agents_rules_graphify_md", "label": "graphify.md", "file_type": "document", "source_file": ".agents/rules/graphify.md", "source_location": "L1"}, {"id": "$graphify-root$_agents_rules_graphify_graphify", "label": "graphify", "file_type": "document", "source_file": ".agents/rules/graphify.md", "source_location": "L6"}], "edges": [{"source": "$graphify-root$_agents_rules_graphify_md", "target": "$graphify-root$_agents_rules_graphify_graphify", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".agents/rules/graphify.md", "source_location": "L6", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_claude_md", "label": "CLAUDE.md", "file_type": "document", "source_file": "CLAUDE.md", "source_location": "L1"}, {"id": "$graphify-root$_claude_graphify", "label": "graphify", "file_type": "document", "source_file": "CLAUDE.md", "source_location": "L1"}], "edges": [{"source": "$graphify-root$_claude_md", "target": "$graphify-root$_claude_graphify", "relation": "contains", "confidence": "EXTRACTED", "source_file": "CLAUDE.md", "source_location": "L1", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_frontend_admin_panel_readme_md", "label": "README.md", "file_type": "document", "source_file": "frontend/admin-panel/README.md", "source_location": "L1"}, {"id": "$graphify-root$_frontend_admin_panel_readme_react_typescript_vite", "label": "React + TypeScript + Vite", "file_type": "document", "source_file": "frontend/admin-panel/README.md", "source_location": "L1"}, {"id": "$graphify-root$_frontend_admin_panel_readme_react_compiler", "label": "React Compiler", "file_type": "document", "source_file": "frontend/admin-panel/README.md", "source_location": "L10"}, {"id": "$graphify-root$_frontend_admin_panel_readme_expanding_the_eslint_configuration", "label": "Expanding the ESLint configuration", "file_type": "document", "source_file": "frontend/admin-panel/README.md", "source_location": "L14"}], "edges": [{"source": "$graphify-root$_frontend_admin_panel_readme_md", "target": "$graphify-root$_frontend_admin_panel_readme_react_typescript_vite", "relation": "contains", "confidence": "EXTRACTED", "source_file": "frontend/admin-panel/README.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_frontend_admin_panel_readme_react_typescript_vite", "target": "$graphify-root$_frontend_admin_panel_readme_react_compiler", "relation": "contains", "confidence": "EXTRACTED", "source_file": "frontend/admin-panel/README.md", "source_location": "L10", "weight": 1.0}, {"source": "$graphify-root$_frontend_admin_panel_readme_react_typescript_vite", "target": "$graphify-root$_frontend_admin_panel_readme_expanding_the_eslint_configuration", "relation": "contains", "confidence": "EXTRACTED", "source_file": "frontend/admin-panel/README.md", "source_location": "L14", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_frontend_admin_panel_public_fonts_sahel_font_v3_4_0_changelog_md", "label": "CHANGELOG.md", "file_type": "document", "source_file": "frontend/admin-panel/public/fonts/sahel-font-v3.4.0/CHANGELOG.md", "source_location": "L1"}], "edges": [], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_ai_agency_specs_reviews_security_review_md", "label": "security_review.md", "file_type": "document", "source_file": ".ai_agency/specs/reviews/security_review.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_specs_reviews_security_review_security_performance_review_09_devops_security", "label": "\ud83d\udd12 Security & Performance Review (09_devops_security)", "file_type": "document", "source_file": ".ai_agency/specs/reviews/security_review.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_specs_reviews_security_review_security_architecture_best_practices", "label": "Security Architecture & Best Practices", "file_type": "document", "source_file": ".ai_agency/specs/reviews/security_review.md", "source_location": "L3"}], "edges": [{"source": "$graphify-root$_ai_agency_specs_reviews_security_review_md", "target": "$graphify-root$_ai_agency_specs_reviews_security_review_security_performance_review_09_devops_security", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/reviews/security_review.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_reviews_security_review_security_performance_review_09_devops_security", "target": "$graphify-root$_ai_agency_specs_reviews_security_review_security_architecture_best_practices", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/reviews/security_review.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_ai_agency_specs_reviews_code_health_review_md", "label": "code_health_review.md", "file_type": "document", "source_file": ".ai_agency/specs/reviews/code_health_review.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_specs_reviews_code_health_review_code_health_audit_review_01_auditor", "label": "\ud83d\udd0d Code Health Audit Review (01_auditor)", "file_type": "document", "source_file": ".ai_agency/specs/reviews/code_health_review.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_specs_reviews_code_health_review_executive_summary", "label": "Executive Summary", "file_type": "document", "source_file": ".ai_agency/specs/reviews/code_health_review.md", "source_location": "L3"}, {"id": "$graphify-root$_ai_agency_specs_reviews_code_health_review_key_findings", "label": "Key Findings", "file_type": "document", "source_file": ".ai_agency/specs/reviews/code_health_review.md", "source_location": "L8"}, {"id": "$graphify-root$_ai_agency_specs_reviews_code_health_review_recommendations", "label": "Recommendations", "file_type": "document", "source_file": ".ai_agency/specs/reviews/code_health_review.md", "source_location": "L19"}], "edges": [{"source": "$graphify-root$_ai_agency_specs_reviews_code_health_review_md", "target": "$graphify-root$_ai_agency_specs_reviews_code_health_review_code_health_audit_review_01_auditor", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/reviews/code_health_review.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_reviews_code_health_review_code_health_audit_review_01_auditor", "target": "$graphify-root$_ai_agency_specs_reviews_code_health_review_executive_summary", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/reviews/code_health_review.md", "source_location": "L3", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_reviews_code_health_review_code_health_audit_review_01_auditor", "target": "$graphify-root$_ai_agency_specs_reviews_code_health_review_key_findings", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/reviews/code_health_review.md", "source_location": "L8", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_reviews_code_health_review_code_health_audit_review_01_auditor", "target": "$graphify-root$_ai_agency_specs_reviews_code_health_review_recommendations", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/reviews/code_health_review.md", "source_location": "L19", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_docs_05_devops_and_monitoring_md", "label": "05-devops-and-monitoring.md", "file_type": "document", "source_file": "docs/05-devops-and-monitoring.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_05_devops_and_monitoring_\u062f\u0648\u0627\u067e\u0633_\u0648_\u0645\u0627\u0646\u06cc\u062a\u0648\u0631\u06cc\u0646\u06af_\u0633\u06cc\u0633\u062a\u0645_devops_monitoring", "label": "\u062f\u0648\u0627\u067e\u0633 \u0648 \u0645\u0627\u0646\u06cc\u062a\u0648\u0631\u06cc\u0646\u06af \u0633\u06cc\u0633\u062a\u0645 (DevOps & Monitoring)", "file_type": "document", "source_file": "docs/05-devops-and-monitoring.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_05_devops_and_monitoring_\u06f1_\u0641\u0631\u0622\u06cc\u0646\u062f_\u0627\u0633\u062a\u0642\u0631\u0627\u0631_\u062e\u0648\u062f\u06a9\u0627\u0631_ci_cd", "label": "\u06f1. \u0641\u0631\u0622\u06cc\u0646\u062f \u0627\u0633\u062a\u0642\u0631\u0627\u0631 \u062e\u0648\u062f\u06a9\u0627\u0631 (CI/CD)", "file_type": "document", "source_file": "docs/05-devops-and-monitoring.md", "source_location": "L5"}, {"id": "$graphify-root$_docs_05_devops_and_monitoring_\u06f2_\u0645\u0627\u0646\u06cc\u062a\u0648\u0631\u06cc\u0646\u06af_\u0639\u0645\u0644\u06a9\u0631\u062f_pm2_dashboard", "label": "\u06f2. \u0645\u0627\u0646\u06cc\u062a\u0648\u0631\u06cc\u0646\u06af \u0639\u0645\u0644\u06a9\u0631\u062f (PM2 Dashboard)", "file_type": "document", "source_file": "docs/05-devops-and-monitoring.md", "source_location": "L12"}, {"id": "$graphify-root$_docs_05_devops_and_monitoring_\u06f3_\u0628\u0631\u0631\u0633\u06cc_\u0644\u0627\u06af_\u0647\u0627\u06cc_\u0633\u06cc\u0633\u062a\u0645_logs_management", "label": "\u06f3. \u0628\u0631\u0631\u0633\u06cc \u0644\u0627\u06af\u200c\u0647\u0627\u06cc \u0633\u06cc\u0633\u062a\u0645 (Logs Management)", "file_type": "document", "source_file": "docs/05-devops-and-monitoring.md", "source_location": "L27"}, {"id": "$graphify-root$_docs_05_devops_and_monitoring_\u06f4_\u0645\u0627\u0646\u06cc\u062a\u0648\u0631\u06cc\u0646\u06af_\u062f\u06cc\u062a\u0627\u0628\u06cc\u0633_postgresql", "label": "\u06f4. \u0645\u0627\u0646\u06cc\u062a\u0648\u0631\u06cc\u0646\u06af \u062f\u06cc\u062a\u0627\u0628\u06cc\u0633 (PostgreSQL)", "file_type": "document", "source_file": "docs/05-devops-and-monitoring.md", "source_location": "L39"}], "edges": [{"source": "$graphify-root$_docs_05_devops_and_monitoring_md", "target": "$graphify-root$_docs_05_devops_and_monitoring_\u062f\u0648\u0627\u067e\u0633_\u0648_\u0645\u0627\u0646\u06cc\u062a\u0648\u0631\u06cc\u0646\u06af_\u0633\u06cc\u0633\u062a\u0645_devops_monitoring", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/05-devops-and-monitoring.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_docs_05_devops_and_monitoring_\u062f\u0648\u0627\u067e\u0633_\u0648_\u0645\u0627\u0646\u06cc\u062a\u0648\u0631\u06cc\u0646\u06af_\u0633\u06cc\u0633\u062a\u0645_devops_monitoring", "target": "$graphify-root$_docs_05_devops_and_monitoring_\u06f1_\u0641\u0631\u0622\u06cc\u0646\u062f_\u0627\u0633\u062a\u0642\u0631\u0627\u0631_\u062e\u0648\u062f\u06a9\u0627\u0631_ci_cd", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/05-devops-and-monitoring.md", "source_location": "L5", "weight": 1.0}, {"source": "$graphify-root$_docs_05_devops_and_monitoring_\u062f\u0648\u0627\u067e\u0633_\u0648_\u0645\u0627\u0646\u06cc\u062a\u0648\u0631\u06cc\u0646\u06af_\u0633\u06cc\u0633\u062a\u0645_devops_monitoring", "target": "$graphify-root$_docs_05_devops_and_monitoring_\u06f2_\u0645\u0627\u0646\u06cc\u062a\u0648\u0631\u06cc\u0646\u06af_\u0639\u0645\u0644\u06a9\u0631\u062f_pm2_dashboard", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/05-devops-and-monitoring.md", "source_location": "L12", "weight": 1.0}, {"source": "$graphify-root$_docs_05_devops_and_monitoring_\u062f\u0648\u0627\u067e\u0633_\u0648_\u0645\u0627\u0646\u06cc\u062a\u0648\u0631\u06cc\u0646\u06af_\u0633\u06cc\u0633\u062a\u0645_devops_monitoring", "target": "$graphify-root$_docs_05_devops_and_monitoring_\u06f3_\u0628\u0631\u0631\u0633\u06cc_\u0644\u0627\u06af_\u0647\u0627\u06cc_\u0633\u06cc\u0633\u062a\u0645_logs_management", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/05-devops-and-monitoring.md", "source_location": "L27", "weight": 1.0}, {"source": "$graphify-root$_docs_05_devops_and_monitoring_\u062f\u0648\u0627\u067e\u0633_\u0648_\u0645\u0627\u0646\u06cc\u062a\u0648\u0631\u06cc\u0646\u06af_\u0633\u06cc\u0633\u062a\u0645_devops_monitoring", "target": "$graphify-root$_docs_05_devops_and_monitoring_\u06f4_\u0645\u0627\u0646\u06cc\u062a\u0648\u0631\u06cc\u0646\u06af_\u062f\u06cc\u062a\u0627\u0628\u06cc\u0633_postgresql", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/05-devops-and-monitoring.md", "source_location": "L39", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_claude_skills_graphify_references_query_md", "label": "query.md", "file_type": "document", "source_file": ".claude/skills/graphify/references/query.md", "source_location": "L1"}, {"id": "$graphify-root$_claude_skills_graphify_references_query_graphify_reference_query_path_explain", "label": "graphify reference: query, path, explain", "file_type": "document", "source_file": ".claude/skills/graphify/references/query.md", "source_location": "L1"}, {"id": "$graphify-root$_claude_skills_graphify_references_query_step_0_constrained_query_expansion_required_before_traversal", "label": "Step 0 \u2014 Constrained query expansion (REQUIRED before traversal)", "file_type": "document", "source_file": ".claude/skills/graphify/references/query.md", "source_location": "L23"}, {"id": "$graphify-root$_claude_skills_graphify_references_query_step_1_traversal", "label": "Step 1 \u2014 Traversal", "file_type": "document", "source_file": ".claude/skills/graphify/references/query.md", "source_location": "L61"}, {"id": "$graphify-root$_claude_skills_graphify_references_query_for_graphify_path", "label": "For /graphify path", "file_type": "document", "source_file": ".claude/skills/graphify/references/query.md", "source_location": "L186"}, {"id": "$graphify-root$_claude_skills_graphify_references_query_for_graphify_explain", "label": "For /graphify explain", "file_type": "document", "source_file": ".claude/skills/graphify/references/query.md", "source_location": "L254"}], "edges": [{"source": "$graphify-root$_claude_skills_graphify_references_query_md", "target": "$graphify-root$_claude_skills_graphify_references_query_graphify_reference_query_path_explain", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".claude/skills/graphify/references/query.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_claude_skills_graphify_references_query_graphify_reference_query_path_explain", "target": "$graphify-root$_claude_skills_graphify_references_query_step_0_constrained_query_expansion_required_before_traversal", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".claude/skills/graphify/references/query.md", "source_location": "L23", "weight": 1.0}, {"source": "$graphify-root$_claude_skills_graphify_references_query_graphify_reference_query_path_explain", "target": "$graphify-root$_claude_skills_graphify_references_query_step_1_traversal", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".claude/skills/graphify/references/query.md", "source_location": "L61", "weight": 1.0}, {"source": "$graphify-root$_claude_skills_graphify_references_query_graphify_reference_query_path_explain", "target": "$graphify-root$_claude_skills_graphify_references_query_for_graphify_path", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".claude/skills/graphify/references/query.md", "source_location": "L186", "weight": 1.0}, {"source": "$graphify-root$_claude_skills_graphify_references_query_graphify_reference_query_path_explain", "target": "$graphify-root$_claude_skills_graphify_references_query_for_graphify_explain", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".claude/skills/graphify/references/query.md", "source_location": "L254", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_claude_skills_graphify_references_github_and_merge_md", "label": "github-and-merge.md", "file_type": "document", "source_file": ".claude/skills/graphify/references/github-and-merge.md", "source_location": "L1"}, {"id": "$graphify-root$_claude_skills_graphify_references_github_and_merge_graphify_reference_github_clone_and_cross_repo_merge", "label": "graphify reference: GitHub clone and cross-repo merge", "file_type": "document", "source_file": ".claude/skills/graphify/references/github-and-merge.md", "source_location": "L1"}, {"id": "$graphify-root$_claude_skills_graphify_references_github_and_merge_step_0_clone_github_repo_s_only_if_a_github_url_was_given", "label": "Step 0 - Clone GitHub repo(s) (only if a GitHub URL was given)", "file_type": "document", "source_file": ".claude/skills/graphify/references/github-and-merge.md", "source_location": "L5"}], "edges": [{"source": "$graphify-root$_claude_skills_graphify_references_github_and_merge_md", "target": "$graphify-root$_claude_skills_graphify_references_github_and_merge_graphify_reference_github_clone_and_cross_repo_merge", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".claude/skills/graphify/references/github-and-merge.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_claude_skills_graphify_references_github_and_merge_graphify_reference_github_clone_and_cross_repo_merge", "target": "$graphify-root$_claude_skills_graphify_references_github_and_merge_step_0_clone_github_repo_s_only_if_a_github_url_was_given", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".claude/skills/graphify/references/github-and-merge.md", "source_location": "L5", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_ai_agency_specs_architecture_spec_md", "label": "architecture_spec.md", "file_type": "document", "source_file": ".ai_agency/specs/architecture_spec.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_specs_architecture_spec_architecture_specification", "label": "Architecture Specification", "file_type": "document", "source_file": ".ai_agency/specs/architecture_spec.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_specs_architecture_spec_1_single_non_negotiable_tech_stack", "label": "1. Single Non-Negotiable Tech Stack", "file_type": "document", "source_file": ".ai_agency/specs/architecture_spec.md", "source_location": "L3"}, {"id": "$graphify-root$_ai_agency_specs_architecture_spec_2_directory_structure_tree", "label": "2. Directory Structure Tree", "file_type": "document", "source_file": ".ai_agency/specs/architecture_spec.md", "source_location": "L10"}, {"id": "$graphify-root$_ai_agency_specs_architecture_spec_3_state_management_strategy", "label": "3. State Management Strategy", "file_type": "document", "source_file": ".ai_agency/specs/architecture_spec.md", "source_location": "L25"}, {"id": "$graphify-root$_ai_agency_specs_architecture_spec_4_deployment_docker_architecture", "label": "4. Deployment / Docker Architecture", "file_type": "document", "source_file": ".ai_agency/specs/architecture_spec.md", "source_location": "L28"}], "edges": [{"source": "$graphify-root$_ai_agency_specs_architecture_spec_md", "target": "$graphify-root$_ai_agency_specs_architecture_spec_architecture_specification", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/architecture_spec.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_architecture_spec_architecture_specification", "target": "$graphify-root$_ai_agency_specs_architecture_spec_1_single_non_negotiable_tech_stack", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/architecture_spec.md", "source_location": "L3", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_architecture_spec_architecture_specification", "target": "$graphify-root$_ai_agency_specs_architecture_spec_2_directory_structure_tree", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/architecture_spec.md", "source_location": "L10", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_architecture_spec_architecture_specification", "target": "$graphify-root$_ai_agency_specs_architecture_spec_3_state_management_strategy", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/architecture_spec.md", "source_location": "L25", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_architecture_spec_architecture_specification", "target": "$graphify-root$_ai_agency_specs_architecture_spec_4_deployment_docker_architecture", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/architecture_spec.md", "source_location": "L28", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_ai_agency_specs_project_health_md", "label": "project_health.md", "file_type": "document", "source_file": ".ai_agency/specs/project_health.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_specs_project_health_project_health_audit_report", "label": "Project Health Audit Report", "file_type": "document", "source_file": ".ai_agency/specs/project_health.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_specs_project_health_1_audit_score_summary", "label": "1. Audit Score Summary", "file_type": "document", "source_file": ".ai_agency/specs/project_health.md", "source_location": "L3"}, {"id": "$graphify-root$_ai_agency_specs_project_health_2_technical_debt_inventory", "label": "2. Technical Debt Inventory", "file_type": "document", "source_file": ".ai_agency/specs/project_health.md", "source_location": "L7"}, {"id": "$graphify-root$_ai_agency_specs_project_health_3_outdated_dependencies_list", "label": "3. Outdated Dependencies List", "file_type": "document", "source_file": ".ai_agency/specs/project_health.md", "source_location": "L10"}, {"id": "$graphify-root$_ai_agency_specs_project_health_4_security_risks_env_leaks_unprotected_ports", "label": "4. Security Risks (.env leaks, unprotected ports)", "file_type": "document", "source_file": ".ai_agency/specs/project_health.md", "source_location": "L13"}], "edges": [{"source": "$graphify-root$_ai_agency_specs_project_health_md", "target": "$graphify-root$_ai_agency_specs_project_health_project_health_audit_report", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/project_health.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_project_health_project_health_audit_report", "target": "$graphify-root$_ai_agency_specs_project_health_1_audit_score_summary", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/project_health.md", "source_location": "L3", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_project_health_project_health_audit_report", "target": "$graphify-root$_ai_agency_specs_project_health_2_technical_debt_inventory", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/project_health.md", "source_location": "L7", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_project_health_project_health_audit_report", "target": "$graphify-root$_ai_agency_specs_project_health_3_outdated_dependencies_list", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/project_health.md", "source_location": "L10", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_project_health_project_health_audit_report", "target": "$graphify-root$_ai_agency_specs_project_health_4_security_risks_env_leaks_unprotected_ports", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/project_health.md", "source_location": "L13", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_docs_audit_01_system_discovery_md", "label": "01-system-discovery.md", "file_type": "document", "source_file": "docs/audit/01-system-discovery.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_audit_01_system_discovery_system_discovery", "label": "System Discovery", "file_type": "document", "source_file": "docs/audit/01-system-discovery.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_audit_01_system_discovery_current_architecture", "label": "Current Architecture", "file_type": "document", "source_file": "docs/audit/01-system-discovery.md", "source_location": "L3"}, {"id": "$graphify-root$_docs_audit_01_system_discovery_active_core_applications", "label": "Active Core Applications", "file_type": "document", "source_file": "docs/audit/01-system-discovery.md", "source_location": "L6"}, {"id": "$graphify-root$_docs_audit_01_system_discovery_excluded_non_auditable_artifacts", "label": "Excluded / Non-Auditable Artifacts", "file_type": "document", "source_file": "docs/audit/01-system-discovery.md", "source_location": "L12"}, {"id": "$graphify-root$_docs_audit_01_system_discovery_major_business_domains_discovered", "label": "Major Business Domains Discovered", "file_type": "document", "source_file": "docs/audit/01-system-discovery.md", "source_location": "L18"}, {"id": "$graphify-root$_docs_audit_01_system_discovery_technical_architecture_summary", "label": "Technical Architecture Summary", "file_type": "document", "source_file": "docs/audit/01-system-discovery.md", "source_location": "L27"}, {"id": "$graphify-root$_docs_audit_01_system_discovery_evidence_based_status_matrix", "label": "Evidence-Based Status Matrix", "file_type": "document", "source_file": "docs/audit/01-system-discovery.md", "source_location": "L36"}], "edges": [{"source": "$graphify-root$_docs_audit_01_system_discovery_md", "target": "$graphify-root$_docs_audit_01_system_discovery_system_discovery", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/01-system-discovery.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_01_system_discovery_system_discovery", "target": "$graphify-root$_docs_audit_01_system_discovery_current_architecture", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/01-system-discovery.md", "source_location": "L3", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_01_system_discovery_current_architecture", "target": "$graphify-root$_docs_audit_01_system_discovery_active_core_applications", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/01-system-discovery.md", "source_location": "L6", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_01_system_discovery_current_architecture", "target": "$graphify-root$_docs_audit_01_system_discovery_excluded_non_auditable_artifacts", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/01-system-discovery.md", "source_location": "L12", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_01_system_discovery_system_discovery", "target": "$graphify-root$_docs_audit_01_system_discovery_major_business_domains_discovered", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/01-system-discovery.md", "source_location": "L18", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_01_system_discovery_system_discovery", "target": "$graphify-root$_docs_audit_01_system_discovery_technical_architecture_summary", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/01-system-discovery.md", "source_location": "L27", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_01_system_discovery_system_discovery", "target": "$graphify-root$_docs_audit_01_system_discovery_evidence_based_status_matrix", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/01-system-discovery.md", "source_location": "L36", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_claude_skills_graphify_references_hooks_md", "label": "hooks.md", "file_type": "document", "source_file": ".claude/skills/graphify/references/hooks.md", "source_location": "L1"}, {"id": "$graphify-root$_claude_skills_graphify_references_hooks_graphify_reference_commit_hook_and_native_claude_md_integration", "label": "graphify reference: commit hook and native CLAUDE.md integration", "file_type": "document", "source_file": ".claude/skills/graphify/references/hooks.md", "source_location": "L1"}, {"id": "$graphify-root$_claude_skills_graphify_references_hooks_for_git_commit_hook", "label": "For git commit hook", "file_type": "document", "source_file": ".claude/skills/graphify/references/hooks.md", "source_location": "L5"}, {"id": "$graphify-root$_claude_skills_graphify_references_hooks_for_native_claude_md_integration", "label": "For native CLAUDE.md integration", "file_type": "document", "source_file": ".claude/skills/graphify/references/hooks.md", "source_location": "L21"}], "edges": [{"source": "$graphify-root$_claude_skills_graphify_references_hooks_md", "target": "$graphify-root$_claude_skills_graphify_references_hooks_graphify_reference_commit_hook_and_native_claude_md_integration", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".claude/skills/graphify/references/hooks.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_claude_skills_graphify_references_hooks_graphify_reference_commit_hook_and_native_claude_md_integration", "target": "$graphify-root$_claude_skills_graphify_references_hooks_for_git_commit_hook", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".claude/skills/graphify/references/hooks.md", "source_location": "L5", "weight": 1.0}, {"source": "$graphify-root$_claude_skills_graphify_references_hooks_graphify_reference_commit_hook_and_native_claude_md_integration", "target": "$graphify-root$_claude_skills_graphify_references_hooks_for_native_claude_md_integration", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".claude/skills/graphify/references/hooks.md", "source_location": "L21", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_ai_agency_specs_reviews_readme_md", "label": "README.md", "file_type": "document", "source_file": ".ai_agency/specs/reviews/README.md", "source_location": "L1"}], "edges": [], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_antigravity_instructions_md", "label": "instructions.md", "file_type": "document", "source_file": ".antigravity/instructions.md", "source_location": "L1"}, {"id": "$graphify-root$_antigravity_instructions_graphify", "label": "graphify", "file_type": "document", "source_file": ".antigravity/instructions.md", "source_location": "L1"}], "edges": [{"source": "$graphify-root$_antigravity_instructions_md", "target": "$graphify-root$_antigravity_instructions_graphify", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".antigravity/instructions.md", "source_location": "L1", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_claude_skills_graphify_references_extraction_spec_md", "label": "extraction-spec.md", "file_type": "document", "source_file": ".claude/skills/graphify/references/extraction-spec.md", "source_location": "L1"}, {"id": "$graphify-root$_claude_skills_graphify_references_extraction_spec_graphify_reference_extraction_subagent_prompt", "label": "graphify reference: extraction subagent prompt", "file_type": "document", "source_file": ".claude/skills/graphify/references/extraction-spec.md", "source_location": "L1"}], "edges": [{"source": "$graphify-root$_claude_skills_graphify_references_extraction_spec_md", "target": "$graphify-root$_claude_skills_graphify_references_extraction_spec_graphify_reference_extraction_subagent_prompt", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".claude/skills/graphify/references/extraction-spec.md", "source_location": "L1", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_ai_agency_memory_scratchpad_md", "label": "scratchpad.md", "file_type": "document", "source_file": ".ai_agency/memory/scratchpad.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_memory_scratchpad_active_agent_working_scratchpad", "label": "\ud83d\udcdd Active Agent Working Scratchpad", "file_type": "document", "source_file": ".ai_agency/memory/scratchpad.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_memory_scratchpad_project_canina_veterinary_e_commerce_system", "label": "Project: Canina Veterinary E-Commerce System", "file_type": "document", "source_file": ".ai_agency/memory/scratchpad.md", "source_location": "L3"}, {"id": "$graphify-root$_ai_agency_memory_scratchpad_requirements_persona_mandates_intake_user_request", "label": "\ud83d\udccb Requirements & Persona Mandates (Intake & User Request)", "file_type": "document", "source_file": ".ai_agency/memory/scratchpad.md", "source_location": "L8"}, {"id": "$graphify-root$_ai_agency_memory_scratchpad_execution_target", "label": "\ud83c\udfd7\ufe0f Execution Target", "file_type": "document", "source_file": ".ai_agency/memory/scratchpad.md", "source_location": "L24"}], "edges": [{"source": "$graphify-root$_ai_agency_memory_scratchpad_md", "target": "$graphify-root$_ai_agency_memory_scratchpad_active_agent_working_scratchpad", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/memory/scratchpad.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_memory_scratchpad_active_agent_working_scratchpad", "target": "$graphify-root$_ai_agency_memory_scratchpad_project_canina_veterinary_e_commerce_system", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/memory/scratchpad.md", "source_location": "L3", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_memory_scratchpad_active_agent_working_scratchpad", "target": "$graphify-root$_ai_agency_memory_scratchpad_requirements_persona_mandates_intake_user_request", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/memory/scratchpad.md", "source_location": "L8", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_memory_scratchpad_active_agent_working_scratchpad", "target": "$graphify-root$_ai_agency_memory_scratchpad_execution_target", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/memory/scratchpad.md", "source_location": "L24", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_ai_agency_agents_00_intake_md", "label": "00_intake.md", "file_type": "document", "source_file": ".ai_agency/agents/00_intake.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_agents_00_intake_role_core_objective", "label": "Role & Core Objective", "file_type": "document", "source_file": ".ai_agency/agents/00_intake.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_agents_00_intake_strict_input_specifications_what_files_to_read", "label": "Strict Input Specifications (What files to read)", "file_type": "document", "source_file": ".ai_agency/agents/00_intake.md", "source_location": "L9"}, {"id": "$graphify-root$_ai_agency_agents_00_intake_brownfield_detection", "label": "Brownfield Detection", "file_type": "document", "source_file": ".ai_agency/agents/00_intake.md", "source_location": "L17"}, {"id": "$graphify-root$_ai_agency_agents_00_intake_operational_rules_boundaries", "label": "Operational Rules & Boundaries", "file_type": "document", "source_file": ".ai_agency/agents/00_intake.md", "source_location": "L28"}, {"id": "$graphify-root$_ai_agency_agents_00_intake_1_ask_don_t_assume", "label": "1. Ask \u2014 Don't Assume", "file_type": "document", "source_file": ".ai_agency/agents/00_intake.md", "source_location": "L30"}, {"id": "$graphify-root$_ai_agency_agents_00_intake_2_forbidden_actions", "label": "2. Forbidden Actions", "file_type": "document", "source_file": ".ai_agency/agents/00_intake.md", "source_location": "L58"}, {"id": "$graphify-root$_ai_agency_agents_00_intake_required_output_artifacts_what_files_to_write_update", "label": "Required Output Artifacts (What files to write/update)", "file_type": "document", "source_file": ".ai_agency/agents/00_intake.md", "source_location": "L65"}, {"id": "$graphify-root$_ai_agency_agents_00_intake_expected_json_output_schema", "label": "Expected JSON Output Schema", "file_type": "document", "source_file": ".ai_agency/agents/00_intake.md", "source_location": "L105"}], "edges": [{"source": "$graphify-root$_ai_agency_agents_00_intake_md", "target": "$graphify-root$_ai_agency_agents_00_intake_role_core_objective", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/agents/00_intake.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_agents_00_intake_role_core_objective", "target": "$graphify-root$_ai_agency_agents_00_intake_strict_input_specifications_what_files_to_read", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/agents/00_intake.md", "source_location": "L9", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_agents_00_intake_role_core_objective", "target": "$graphify-root$_ai_agency_agents_00_intake_brownfield_detection", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/agents/00_intake.md", "source_location": "L17", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_agents_00_intake_role_core_objective", "target": "$graphify-root$_ai_agency_agents_00_intake_operational_rules_boundaries", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/agents/00_intake.md", "source_location": "L28", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_agents_00_intake_operational_rules_boundaries", "target": "$graphify-root$_ai_agency_agents_00_intake_1_ask_don_t_assume", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/agents/00_intake.md", "source_location": "L30", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_agents_00_intake_operational_rules_boundaries", "target": "$graphify-root$_ai_agency_agents_00_intake_2_forbidden_actions", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/agents/00_intake.md", "source_location": "L58", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_agents_00_intake_role_core_objective", "target": "$graphify-root$_ai_agency_agents_00_intake_required_output_artifacts_what_files_to_write_update", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/agents/00_intake.md", "source_location": "L65", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_agents_00_intake_role_core_objective", "target": "$graphify-root$_ai_agency_agents_00_intake_expected_json_output_schema", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/agents/00_intake.md", "source_location": "L105", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_docs_audit_phase3_2_implementation_readiness_md", "label": "phase3.2-implementation-readiness.md", "file_type": "document", "source_file": "docs/audit/phase3.2-implementation-readiness.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_audit_phase3_2_implementation_readiness_phase_3_2_3_3_implementation_readiness_architectural_finalization_report", "label": "Phase 3.2 / 3.3 \u2014 Implementation Readiness & Architectural Finalization Report", "file_type": "document", "source_file": "docs/audit/phase3.2-implementation-readiness.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_audit_phase3_2_implementation_readiness_1_executive_summary_architecture_decisions", "label": "1. Executive Summary & Architecture Decisions", "file_type": "document", "source_file": "docs/audit/phase3.2-implementation-readiness.md", "source_location": "L12"}, {"id": "$graphify-root$_docs_audit_phase3_2_implementation_readiness_2_updated_task_specifications_overview", "label": "2. Updated Task Specifications Overview", "file_type": "document", "source_file": "docs/audit/phase3.2-implementation-readiness.md", "source_location": "L30"}, {"id": "$graphify-root$_docs_audit_phase3_2_implementation_readiness_3_final_readiness_statement", "label": "3. Final Readiness Statement", "file_type": "document", "source_file": "docs/audit/phase3.2-implementation-readiness.md", "source_location": "L47"}], "edges": [{"source": "$graphify-root$_docs_audit_phase3_2_implementation_readiness_md", "target": "$graphify-root$_docs_audit_phase3_2_implementation_readiness_phase_3_2_3_3_implementation_readiness_architectural_finalization_report", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/phase3.2-implementation-readiness.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_phase3_2_implementation_readiness_phase_3_2_3_3_implementation_readiness_architectural_finalization_report", "target": "$graphify-root$_docs_audit_phase3_2_implementation_readiness_1_executive_summary_architecture_decisions", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/phase3.2-implementation-readiness.md", "source_location": "L12", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_phase3_2_implementation_readiness_phase_3_2_3_3_implementation_readiness_architectural_finalization_report", "target": "$graphify-root$_docs_audit_phase3_2_implementation_readiness_2_updated_task_specifications_overview", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/phase3.2-implementation-readiness.md", "source_location": "L30", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_phase3_2_implementation_readiness_phase_3_2_3_3_implementation_readiness_architectural_finalization_report", "target": "$graphify-root$_docs_audit_phase3_2_implementation_readiness_3_final_readiness_statement", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/phase3.2-implementation-readiness.md", "source_location": "L47", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_ai_agency_specs_reviews_frontend_review_md", "label": "frontend_review.md", "file_type": "document", "source_file": ".ai_agency/specs/reviews/frontend_review.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_specs_reviews_frontend_review_frontend_admin_panel_technical_review_06_dev_frontend", "label": "\ud83c\udfa8 Frontend & Admin Panel Technical Review (06_dev_frontend)", "file_type": "document", "source_file": ".ai_agency/specs/reviews/frontend_review.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_specs_reviews_frontend_review_architectural_overview", "label": "Architectural Overview", "file_type": "document", "source_file": ".ai_agency/specs/reviews/frontend_review.md", "source_location": "L3"}, {"id": "$graphify-root$_ai_agency_specs_reviews_frontend_review_critical_review_findings_required_enhancements", "label": "Critical Review Findings & Required Enhancements", "file_type": "document", "source_file": ".ai_agency/specs/reviews/frontend_review.md", "source_location": "L7"}], "edges": [{"source": "$graphify-root$_ai_agency_specs_reviews_frontend_review_md", "target": "$graphify-root$_ai_agency_specs_reviews_frontend_review_frontend_admin_panel_technical_review_06_dev_frontend", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/reviews/frontend_review.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_reviews_frontend_review_frontend_admin_panel_technical_review_06_dev_frontend", "target": "$graphify-root$_ai_agency_specs_reviews_frontend_review_architectural_overview", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/reviews/frontend_review.md", "source_location": "L3", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_reviews_frontend_review_frontend_admin_panel_technical_review_06_dev_frontend", "target": "$graphify-root$_ai_agency_specs_reviews_frontend_review_critical_review_findings_required_enhancements", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/reviews/frontend_review.md", "source_location": "L7", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_frontend_admin_panel_public_fonts_shabnam_font_v5_0_1_changelog_md", "label": "CHANGELOG.md", "file_type": "document", "source_file": "frontend/admin-panel/public/fonts/shabnam-font-v5.0.1/CHANGELOG.md", "source_location": "L1"}], "edges": [], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_agents_workflows_graphify_md", "label": "graphify.md", "file_type": "document", "source_file": ".agents/workflows/graphify.md", "source_location": "L1"}, {"id": "$graphify-root$_agents_workflows_graphify_workflow_graphify", "label": "Workflow: graphify", "file_type": "document", "source_file": ".agents/workflows/graphify.md", "source_location": "L6"}], "edges": [{"source": "$graphify-root$_agents_workflows_graphify_md", "target": "$graphify-root$_agents_workflows_graphify_workflow_graphify", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".agents/workflows/graphify.md", "source_location": "L6", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_ai_agency_agents_02_ceo_md", "label": "02_ceo.md", "file_type": "document", "source_file": ".ai_agency/agents/02_ceo.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_agents_02_ceo_role_core_objective", "label": "Role & Core Objective", "file_type": "document", "source_file": ".ai_agency/agents/02_ceo.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_agents_02_ceo_strict_input_specifications_what_files_to_read", "label": "Strict Input Specifications (What files to read)", "file_type": "document", "source_file": ".ai_agency/agents/02_ceo.md", "source_location": "L7"}, {"id": "$graphify-root$_ai_agency_agents_02_ceo_operational_rules_boundaries", "label": "Operational Rules & Boundaries", "file_type": "document", "source_file": ".ai_agency/agents/02_ceo.md", "source_location": "L15"}, {"id": "$graphify-root$_ai_agency_agents_02_ceo_1_mode_direction_decision", "label": "1. Mode & Direction Decision", "file_type": "document", "source_file": ".ai_agency/agents/02_ceo.md", "source_location": "L17"}, {"id": "$graphify-root$_ai_agency_agents_02_ceo_2_brownfield_strategic_evaluation", "label": "2. Brownfield Strategic Evaluation", "file_type": "document", "source_file": ".ai_agency/agents/02_ceo.md", "source_location": "L25"}, {"id": "$graphify-root$_ai_agency_agents_02_ceo_3_risk_assessment", "label": "3. Risk Assessment", "file_type": "document", "source_file": ".ai_agency/agents/02_ceo.md", "source_location": "L32"}, {"id": "$graphify-root$_ai_agency_agents_02_ceo_4_forbidden_actions", "label": "4. Forbidden Actions", "file_type": "document", "source_file": ".ai_agency/agents/02_ceo.md", "source_location": "L39"}, {"id": "$graphify-root$_ai_agency_agents_02_ceo_required_output_artifacts_what_files_to_write_update", "label": "Required Output Artifacts (What files to write/update)", "file_type": "document", "source_file": ".ai_agency/agents/02_ceo.md", "source_location": "L47"}, {"id": "$graphify-root$_ai_agency_agents_02_ceo_expected_json_output_schema", "label": "Expected JSON Output Schema", "file_type": "document", "source_file": ".ai_agency/agents/02_ceo.md", "source_location": "L56"}], "edges": [{"source": "$graphify-root$_ai_agency_agents_02_ceo_md", "target": "$graphify-root$_ai_agency_agents_02_ceo_role_core_objective", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/agents/02_ceo.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_agents_02_ceo_role_core_objective", "target": "$graphify-root$_ai_agency_agents_02_ceo_strict_input_specifications_what_files_to_read", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/agents/02_ceo.md", "source_location": "L7", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_agents_02_ceo_role_core_objective", "target": "$graphify-root$_ai_agency_agents_02_ceo_operational_rules_boundaries", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/agents/02_ceo.md", "source_location": "L15", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_agents_02_ceo_operational_rules_boundaries", "target": "$graphify-root$_ai_agency_agents_02_ceo_1_mode_direction_decision", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/agents/02_ceo.md", "source_location": "L17", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_agents_02_ceo_operational_rules_boundaries", "target": "$graphify-root$_ai_agency_agents_02_ceo_2_brownfield_strategic_evaluation", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/agents/02_ceo.md", "source_location": "L25", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_agents_02_ceo_operational_rules_boundaries", "target": "$graphify-root$_ai_agency_agents_02_ceo_3_risk_assessment", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/agents/02_ceo.md", "source_location": "L32", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_agents_02_ceo_operational_rules_boundaries", "target": "$graphify-root$_ai_agency_agents_02_ceo_4_forbidden_actions", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/agents/02_ceo.md", "source_location": "L39", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_agents_02_ceo_role_core_objective", "target": "$graphify-root$_ai_agency_agents_02_ceo_required_output_artifacts_what_files_to_write_update", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/agents/02_ceo.md", "source_location": "L47", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_agents_02_ceo_role_core_objective", "target": "$graphify-root$_ai_agency_agents_02_ceo_expected_json_output_schema", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/agents/02_ceo.md", "source_location": "L56", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_agents_md", "label": "AGENTS.md", "file_type": "document", "source_file": "AGENTS.md", "source_location": "L1"}, {"id": "$graphify-root$_agents_graphify", "label": "graphify", "file_type": "document", "source_file": "AGENTS.md", "source_location": "L1"}], "edges": [{"source": "$graphify-root$_agents_md", "target": "$graphify-root$_agents_graphify", "relation": "contains", "confidence": "EXTRACTED", "source_file": "AGENTS.md", "source_location": "L1", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_ai_agency_specs_reviews_ux_review_md", "label": "ux_review.md", "file_type": "document", "source_file": ".ai_agency/specs/reviews/ux_review.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_specs_reviews_ux_review_ux_persona_interface_review_08_visual_qa", "label": "\ud83d\udc41\ufe0f UX & Persona Interface Review (08_visual_qa)", "file_type": "document", "source_file": ".ai_agency/specs/reviews/ux_review.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_specs_reviews_ux_review_persona_experience_alignment", "label": "Persona Experience Alignment", "file_type": "document", "source_file": ".ai_agency/specs/reviews/ux_review.md", "source_location": "L3"}], "edges": [{"source": "$graphify-root$_ai_agency_specs_reviews_ux_review_md", "target": "$graphify-root$_ai_agency_specs_reviews_ux_review_ux_persona_interface_review_08_visual_qa", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/reviews/ux_review.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_reviews_ux_review_ux_persona_interface_review_08_visual_qa", "target": "$graphify-root$_ai_agency_specs_reviews_ux_review_persona_experience_alignment", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/reviews/ux_review.md", "source_location": "L3", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_ai_agency_specs_reviews_seo_content_review_md", "label": "seo_content_review.md", "file_type": "document", "source_file": ".ai_agency/specs/reviews/seo_content_review.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_specs_reviews_seo_content_review_seo_content_strategy_review_12_seo_content", "label": "\ud83d\ude80 SEO & Content Strategy Review (12_seo_content)", "file_type": "document", "source_file": ".ai_agency/specs/reviews/seo_content_review.md", "source_location": "L1"}, {"id": "$graphify-root$_ai_agency_specs_reviews_seo_content_review_overview_content_foundation", "label": "Overview & Content Foundation", "file_type": "document", "source_file": ".ai_agency/specs/reviews/seo_content_review.md", "source_location": "L3"}, {"id": "$graphify-root$_ai_agency_specs_reviews_seo_content_review_seo_content_requirements", "label": "SEO & Content Requirements", "file_type": "document", "source_file": ".ai_agency/specs/reviews/seo_content_review.md", "source_location": "L6"}], "edges": [{"source": "$graphify-root$_ai_agency_specs_reviews_seo_content_review_md", "target": "$graphify-root$_ai_agency_specs_reviews_seo_content_review_seo_content_strategy_review_12_seo_content", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/reviews/seo_content_review.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_reviews_seo_content_review_seo_content_strategy_review_12_seo_content", "target": "$graphify-root$_ai_agency_specs_reviews_seo_content_review_overview_content_foundation", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/reviews/seo_content_review.md", "source_location": "L3", "weight": 1.0}, {"source": "$graphify-root$_ai_agency_specs_reviews_seo_content_review_seo_content_strategy_review_12_seo_content", "target": "$graphify-root$_ai_agency_specs_reviews_seo_content_review_seo_content_requirements", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".ai_agency/specs/reviews/seo_content_review.md", "source_location": "L6", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_frontend_application_public_fonts_sahel_font_v3_4_0_changelog_md", "label": "CHANGELOG.md", "file_type": "document", "source_file": "frontend/application/public/fonts/sahel-font-v3.4.0/CHANGELOG.md", "source_location": "L1"}], "edges": [], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_backend_readme_md", "label": "README.md", "file_type": "document", "source_file": "backend/README.md", "source_location": "L1"}, {"id": "$graphify-root$_backend_readme_description", "label": "Description", "file_type": "document", "source_file": "backend/README.md", "source_location": "L24"}, {"id": "$graphify-root$_backend_readme_project_setup", "label": "Project setup", "file_type": "document", "source_file": "backend/README.md", "source_location": "L28"}, {"id": "$graphify-root$_backend_readme_compile_and_run_the_project", "label": "Compile and run the project", "file_type": "document", "source_file": "backend/README.md", "source_location": "L34"}, {"id": "$graphify-root$_backend_readme_run_tests", "label": "Run tests", "file_type": "document", "source_file": "backend/README.md", "source_location": "L47"}, {"id": "$graphify-root$_backend_readme_deployment", "label": "Deployment", "file_type": "document", "source_file": "backend/README.md", "source_location": "L60"}, {"id": "$graphify-root$_backend_readme_resources", "label": "Resources", "file_type": "document", "source_file": "backend/README.md", "source_location": "L73"}, {"id": "$graphify-root$_backend_readme_support", "label": "Support", "file_type": "document", "source_file": "backend/README.md", "source_location": "L86"}, {"id": "$graphify-root$_backend_readme_stay_in_touch", "label": "Stay in touch", "file_type": "document", "source_file": "backend/README.md", "source_location": "L90"}, {"id": "$graphify-root$_backend_readme_license", "label": "License", "file_type": "document", "source_file": "backend/README.md", "source_location": "L96"}], "edges": [{"source": "$graphify-root$_backend_readme_md", "target": "$graphify-root$_backend_readme_description", "relation": "contains", "confidence": "EXTRACTED", "source_file": "backend/README.md", "source_location": "L24", "weight": 1.0}, {"source": "$graphify-root$_backend_readme_md", "target": "$graphify-root$_backend_readme_project_setup", "relation": "contains", "confidence": "EXTRACTED", "source_file": "backend/README.md", "source_location": "L28", "weight": 1.0}, {"source": "$graphify-root$_backend_readme_md", "target": "$graphify-root$_backend_readme_compile_and_run_the_project", "relation": "contains", "confidence": "EXTRACTED", "source_file": "backend/README.md", "source_location": "L34", "weight": 1.0}, {"source": "$graphify-root$_backend_readme_md", "target": "$graphify-root$_backend_readme_run_tests", "relation": "contains", "confidence": "EXTRACTED", "source_file": "backend/README.md", "source_location": "L47", "weight": 1.0}, {"source": "$graphify-root$_backend_readme_md", "target": "$graphify-root$_backend_readme_deployment", "relation": "contains", "confidence": "EXTRACTED", "source_file": "backend/README.md", "source_location": "L60", "weight": 1.0}, {"source": "$graphify-root$_backend_readme_md", "target": "$graphify-root$_backend_readme_resources", "relation": "contains", "confidence": "EXTRACTED", "source_file": "backend/README.md", "source_location": "L73", "weight": 1.0}, {"source": "$graphify-root$_backend_readme_md", "target": "$graphify-root$_backend_readme_support", "relation": "contains", "confidence": "EXTRACTED", "source_file": "backend/README.md", "source_location": "L86", "weight": 1.0}, {"source": "$graphify-root$_backend_readme_md", "target": "$graphify-root$_backend_readme_stay_in_touch", "relation": "contains", "confidence": "EXTRACTED", "source_file": "backend/README.md", "source_location": "L90", "weight": 1.0}, {"source": "$graphify-root$_backend_readme_md", "target": "$graphify-root$_backend_readme_license", "relation": "contains", "confidence": "EXTRACTED", "source_file": "backend/README.md", "source_location": "L96", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_docs_audit_phase3_traceability_matrix_md", "label": "phase3-traceability-matrix.md", "file_type": "document", "source_file": "docs/audit/phase3-traceability-matrix.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_audit_phase3_traceability_matrix_phase_3_audit_traceability_matrix", "label": "Phase 3 Audit Traceability Matrix", "file_type": "document", "source_file": "docs/audit/phase3-traceability-matrix.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_audit_phase3_traceability_matrix_complete_finding_to_task_traceability_matrix", "label": "Complete Finding-to-Task Traceability Matrix", "file_type": "document", "source_file": "docs/audit/phase3-traceability-matrix.md", "source_location": "L11"}, {"id": "$graphify-root$_docs_audit_phase3_traceability_matrix_special_task_traceability_non_finding_tasks", "label": "Special Task Traceability (Non-Finding Tasks)", "file_type": "document", "source_file": "docs/audit/phase3-traceability-matrix.md", "source_location": "L32"}, {"id": "$graphify-root$_docs_audit_phase3_traceability_matrix_finding_disposition_accounting_verification", "label": "Finding Disposition & Accounting Verification", "file_type": "document", "source_file": "docs/audit/phase3-traceability-matrix.md", "source_location": "L40"}], "edges": [{"source": "$graphify-root$_docs_audit_phase3_traceability_matrix_md", "target": "$graphify-root$_docs_audit_phase3_traceability_matrix_phase_3_audit_traceability_matrix", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/phase3-traceability-matrix.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_phase3_traceability_matrix_phase_3_audit_traceability_matrix", "target": "$graphify-root$_docs_audit_phase3_traceability_matrix_complete_finding_to_task_traceability_matrix", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/phase3-traceability-matrix.md", "source_location": "L11", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_phase3_traceability_matrix_phase_3_audit_traceability_matrix", "target": "$graphify-root$_docs_audit_phase3_traceability_matrix_special_task_traceability_non_finding_tasks", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/phase3-traceability-matrix.md", "source_location": "L32", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_phase3_traceability_matrix_phase_3_audit_traceability_matrix", "target": "$graphify-root$_docs_audit_phase3_traceability_matrix_finding_disposition_accounting_verification", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/phase3-traceability-matrix.md", "source_location": "L40", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_claude_claude_md", "label": "CLAUDE.md", "file_type": "document", "source_file": ".claude/CLAUDE.md", "source_location": "L1"}, {"id": "$graphify-root$_claude_claude_graphify", "label": "graphify", "file_type": "document", "source_file": ".claude/CLAUDE.md", "source_location": "L1"}], "edges": [{"source": "$graphify-root$_claude_claude_md", "target": "$graphify-root$_claude_claude_graphify", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".claude/CLAUDE.md", "source_location": "L1", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_claude_skills_graphify_references_add_watch_md", "label": "add-watch.md", "file_type": "document", "source_file": ".claude/skills/graphify/references/add-watch.md", "source_location": "L1"}, {"id": "$graphify-root$_claude_skills_graphify_references_add_watch_graphify_reference_add_a_url_and_watch_a_folder", "label": "graphify reference: add a URL and watch a folder", "file_type": "document", "source_file": ".claude/skills/graphify/references/add-watch.md", "source_location": "L1"}, {"id": "$graphify-root$_claude_skills_graphify_references_add_watch_for_graphify_add", "label": "For /graphify add", "file_type": "document", "source_file": ".claude/skills/graphify/references/add-watch.md", "source_location": "L5"}, {"id": "$graphify-root$_claude_skills_graphify_references_add_watch_for_watch", "label": "For --watch", "file_type": "document", "source_file": ".claude/skills/graphify/references/add-watch.md", "source_location": "L39"}], "edges": [{"source": "$graphify-root$_claude_skills_graphify_references_add_watch_md", "target": "$graphify-root$_claude_skills_graphify_references_add_watch_graphify_reference_add_a_url_and_watch_a_folder", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".claude/skills/graphify/references/add-watch.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_claude_skills_graphify_references_add_watch_graphify_reference_add_a_url_and_watch_a_folder", "target": "$graphify-root$_claude_skills_graphify_references_add_watch_for_graphify_add", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".claude/skills/graphify/references/add-watch.md", "source_location": "L5", "weight": 1.0}, {"source": "$graphify-root$_claude_skills_graphify_references_add_watch_graphify_reference_add_a_url_and_watch_a_folder", "target": "$graphify-root$_claude_skills_graphify_references_add_watch_for_watch", "relation": "contains", "confidence": "EXTRACTED", "source_file": ".claude/skills/graphify/references/add-watch.md", "source_location": "L39", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
{"nodes": [{"id": "$graphify-root$_docs_audit_24_omitted_file_inspection_report_md", "label": "24-omitted-file-inspection-report.md", "file_type": "document", "source_file": "docs/audit/24-omitted-file-inspection-report.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_audit_24_omitted_file_inspection_report_omitted_file_inspection_report", "label": "Omitted File Inspection Report", "file_type": "document", "source_file": "docs/audit/24-omitted-file-inspection-report.md", "source_location": "L1"}, {"id": "$graphify-root$_docs_audit_24_omitted_file_inspection_report_overview_of_omitted_file_inspections", "label": "Overview of Omitted File Inspections", "file_type": "document", "source_file": "docs/audit/24-omitted-file-inspection-report.md", "source_location": "L11"}, {"id": "$graphify-root$_docs_audit_24_omitted_file_inspection_report_key_domain_findings_from_omitted_file_audit", "label": "Key Domain Findings from Omitted File Audit", "file_type": "document", "source_file": "docs/audit/24-omitted-file-inspection-report.md", "source_location": "L14"}, {"id": "$graphify-root$_docs_audit_24_omitted_file_inspection_report_conclusion", "label": "Conclusion", "file_type": "document", "source_file": "docs/audit/24-omitted-file-inspection-report.md", "source_location": "L32"}], "edges": [{"source": "$graphify-root$_docs_audit_24_omitted_file_inspection_report_md", "target": "$graphify-root$_docs_audit_24_omitted_file_inspection_report_omitted_file_inspection_report", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/24-omitted-file-inspection-report.md", "source_location": "L1", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_24_omitted_file_inspection_report_omitted_file_inspection_report", "target": "$graphify-root$_docs_audit_24_omitted_file_inspection_report_overview_of_omitted_file_inspections", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/24-omitted-file-inspection-report.md", "source_location": "L11", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_24_omitted_file_inspection_report_overview_of_omitted_file_inspections", "target": "$graphify-root$_docs_audit_24_omitted_file_inspection_report_key_domain_findings_from_omitted_file_audit", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/24-omitted-file-inspection-report.md", "source_location": "L14", "weight": 1.0}, {"source": "$graphify-root$_docs_audit_24_omitted_file_inspection_report_omitted_file_inspection_report", "target": "$graphify-root$_docs_audit_24_omitted_file_inspection_report_conclusion", "relation": "contains", "confidence": "EXTRACTED", "source_file": "docs/audit/24-omitted-file-inspection-report.md", "source_location": "L32", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user