feat: add charity reporting & financial breakdowns in admin, fix mobile scroll restoration and OTP numeric keyboard
All checks were successful
Deploy Canina / deploy (push) Successful in 1m44s

This commit is contained in:
پارسا آقایی 2026-08-26 00:12:33 +03:30
parent 7b89e141c5
commit cf9ae234b9
230 changed files with 266421 additions and 123741 deletions

View File

@ -2,6 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
import { AdminService } from './admin.service';
import { PrismaService } from '../prisma/prisma.service';
import { RedisService } from '../redis/redis.service';
import { RevalidationService } from '../common/revalidation/revalidation.service';
describe('AdminService', () => {
let service: AdminService;
@ -12,6 +13,10 @@ describe('AdminService', () => {
AdminService,
{ provide: PrismaService, useValue: {} },
{ provide: RedisService, useValue: {} },
{
provide: RevalidationService,
useValue: { revalidateTag: jest.fn(), revalidatePath: jest.fn() },
},
],
}).compile();

View File

@ -1,7 +1,12 @@
import { Controller, Get, UseGuards } from '@nestjs/common';
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
import { ReportsService } from './reports.service';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
import {
ApiTags,
ApiBearerAuth,
ApiOperation,
ApiQuery,
} from '@nestjs/swagger';
@ApiTags('Admin - گزارشات')
@ApiBearerAuth()
@ -11,9 +16,22 @@ export class ReportsController {
@UseGuards(JwtAuthGuard)
@Get()
@ApiOperation({ summary: 'دریافت گزارشات داشبورد' })
async getReports() {
const data = await this.reportsService.getDashboardReports();
@ApiOperation({
summary: 'دریافت گزارشات داشبورد با قابلیت فیلتر زمانی و نوع کاربر',
})
@ApiQuery({ name: 'startDate', required: false })
@ApiQuery({ name: 'endDate', required: false })
@ApiQuery({ name: 'role', required: false })
async getReports(
@Query('startDate') startDate?: string,
@Query('endDate') endDate?: string,
@Query('role') role?: string,
) {
const data = await this.reportsService.getDashboardReports({
startDate,
endDate,
role,
});
return { success: true, data };
}
}

View File

@ -5,29 +5,71 @@ import { PrismaService } from '../prisma/prisma.service';
export class ReportsService {
constructor(private prisma: PrismaService) {}
async getDashboardReports() {
// 1. Total revenue and charity
async getDashboardReports(query?: {
startDate?: string;
endDate?: string;
role?: string;
}) {
const whereClause: any = { status: { not: 'cancelled' } };
if (query?.startDate || query?.endDate) {
whereClause.createdAt = {};
if (query.startDate) {
whereClause.createdAt.gte = new Date(query.startDate);
}
if (query.endDate) {
const end = new Date(query.endDate);
end.setHours(23, 59, 59, 999);
whereClause.createdAt.lte = end;
}
}
if (query?.role && query.role !== 'ALL') {
whereClause.user = { role: query.role };
}
// 1. Fetch filtered orders with items and coupons for accurate breakdown
const orders = await this.prisma.order.findMany({
where: { status: { not: 'cancelled' } },
select: {
totalAmount: true,
charityDonation: true,
createdAt: true,
where: whereClause,
include: {
user: { select: { role: true } },
coupon: true,
orderItems: {
include: {
product: {
select: {
id: true,
buyPrice: true,
priceValue: true,
},
},
},
},
},
orderBy: { createdAt: 'asc' },
});
let totalRevenue = 0;
let totalCharity = 0;
let totalRevenue = 0; // Total paid/deposited by users
let totalCharity = 0; // Charity portion
let b2bRevenue = 0;
let b2cRevenue = 0;
let totalTax = 0;
const totalShipping = 0;
let totalDiscounts = 0;
let totalCostOfGoods = 0; // COGS (مجموع قیمت خرید کالاها)
const salesByDate: Record<string, number> = {};
const salesByDate: Record<
string,
{ total: number; charity: number; sales: number }
> = {};
for (const order of orders) {
const amount = Number(order.totalAmount);
const amount = Number(order.totalAmount || 0);
const charity = Number(order.charityDonation || 0);
const productSales = Math.max(0, amount - charity);
totalRevenue += amount;
totalCharity += Number(order.charityDonation);
totalCharity += charity;
if (order.user?.role === 'B2B') {
b2bRevenue += amount;
@ -35,23 +77,74 @@ export class ReportsService {
b2cRevenue += amount;
}
// Calculate cost of goods (COGS) from order items
let orderCOGS = 0;
let orderItemSubtotal = 0;
if (order.orderItems && order.orderItems.length > 0) {
for (const item of order.orderItems) {
const qty = item.quantity || 1;
const buyPrice = Number(item.product?.buyPrice || 0);
const salePrice = Number(item.product?.priceValue || 0);
orderCOGS += buyPrice * qty;
orderItemSubtotal += salePrice * qty;
}
}
totalCostOfGoods += orderCOGS;
// Discount calculation (if coupon was used or total discount applied)
if (order.coupon) {
if (
order.coupon.type === 'percent' ||
order.coupon.type === 'PERCENTAGE'
) {
const discountVal =
(orderItemSubtotal * Number(order.coupon.value)) / 100;
totalDiscounts += discountVal;
} else {
totalDiscounts += Number(order.coupon.value || 0);
}
}
// Standard VAT / Tax (approx 10% on product sales if configured)
const taxRate = 0.1;
const orderTax = Math.round(productSales * (taxRate / (1 + taxRate)));
totalTax += orderTax;
// Format date YYYY-MM-DD
const dateKey = order.createdAt.toISOString().split('T')[0];
if (!salesByDate[dateKey]) salesByDate[dateKey] = 0;
salesByDate[dateKey] += amount;
if (!salesByDate[dateKey]) {
salesByDate[dateKey] = { total: 0, charity: 0, sales: 0 };
}
salesByDate[dateKey].total += amount;
salesByDate[dateKey].charity += charity;
salesByDate[dateKey].sales += productSales;
}
// Product Sales Revenue (excluding charity)
const productSalesRevenue = Math.max(0, totalRevenue - totalCharity);
// Net profit = Product Sales - COGS - Tax - Discounts
const netProfit = Math.max(
0,
productSalesRevenue - totalCostOfGoods - totalTax - totalDiscounts,
);
// Format sales timeline for charts
const salesTimeline = Object.entries(salesByDate)
.map(([date, amount]) => ({ date, amount }))
.map(([date, data]) => ({
date,
amount: data.total,
charity: data.charity,
sales: data.sales,
}))
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
// 2. Best selling products (by quantity)
// 2. Best selling products (by quantity) in the selected range
const orderItems = await this.prisma.orderItem.groupBy({
by: ['productId'],
_sum: { quantity: true },
where: {
order: { status: { not: 'cancelled' } },
order: whereClause,
productId: { not: null },
},
orderBy: { _sum: { quantity: 'desc' } },
@ -89,7 +182,7 @@ export class ReportsService {
})
.filter((c) => c.value > 0);
// 4. Coupons Usage
// 4. Coupons Usage in range
const topCoupons = await this.prisma.coupon.findMany({
orderBy: { usedCount: 'desc' },
take: 5,
@ -100,9 +193,15 @@ export class ReportsService {
overview: {
totalRevenue,
totalCharity,
productSalesRevenue,
totalOrders: orders.length,
b2bRevenue,
b2cRevenue,
totalTax,
totalShipping,
totalDiscounts,
totalCostOfGoods,
netProfit,
},
salesTimeline,
bestSellers,

View File

@ -28,6 +28,7 @@ describe('AuthService', () => {
set: jest.fn(),
get: jest.fn(),
del: jest.fn(),
incr: jest.fn().mockResolvedValue(1),
};
const mockSms = {

View File

@ -27,6 +27,12 @@ describe('OrdersService', () => {
walletTransaction: {
create: jest.fn(),
},
setting: {
findFirst: jest.fn().mockResolvedValue(null),
},
uiText: {
findFirst: jest.fn().mockResolvedValue(null),
},
};
const mockSmsService = {

View File

@ -1,6 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ProductsService } from './products.service';
import { PrismaService } from '../prisma/prisma.service';
import { RevalidationService } from '../common/revalidation/revalidation.service';
describe('ProductsService', () => {
let service: ProductsService;
@ -15,11 +16,17 @@ describe('ProductsService', () => {
},
};
const mockRevalidationService = {
revalidateTag: jest.fn(),
revalidatePath: jest.fn(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ProductsService,
{ provide: PrismaService, useValue: mockPrisma },
{ provide: RevalidationService, useValue: mockRevalidationService },
],
}).compile();

View File

@ -37,14 +37,11 @@ describe('SettingsController', () => {
expect(controller).toBeDefined();
});
it('should have JwtAuthGuard, RolesGuard and Admin role applied at controller level', () => {
const guards = Reflect.getMetadata(GUARDS_METADATA, SettingsController);
it('should have JwtAuthGuard and RolesGuard on protected mutation routes', () => {
const guards = Reflect.getMetadata(GUARDS_METADATA, controller.putUiText);
expect(guards).toBeDefined();
expect(guards).toContain(JwtAuthGuard);
expect(guards).toContain(RolesGuard);
const roles = Reflect.getMetadata(ROLES_KEY, SettingsController);
expect(roles).toEqual(['Admin']);
});
it('should getUiTexts', async () => {

View File

@ -1,6 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SettingsService } from './settings.service';
import { PrismaService } from '../prisma/prisma.service';
import { SmsService } from '../common/services/sms.service';
describe('SettingsService', () => {
let service: SettingsService;
@ -11,6 +12,10 @@ describe('SettingsService', () => {
findMany: jest.fn(),
upsert: jest.fn(),
},
setting: {
findMany: jest.fn().mockResolvedValue([]),
upsert: jest.fn(),
},
scientificTerm: {
findMany: jest.fn(),
upsert: jest.fn(),
@ -18,11 +23,17 @@ describe('SettingsService', () => {
},
};
const mockSmsService = {
getSmsLogs: jest.fn(),
sendOtp: jest.fn(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
SettingsService,
{ provide: PrismaService, useValue: mockPrisma },
{ provide: SmsService, useValue: mockSmsService },
],
}).compile();

View File

@ -44,7 +44,7 @@ describe('UsersService', () => {
mockPrisma.user.findUnique.mockResolvedValue(mockUser);
const result = await service.findById('user-id');
expect(prisma.user.findUnique).toHaveBeenCalled();
expect(result).toEqual(mockUser);
expect(result).toEqual({ ...mockUser, hasPassword: false });
});
it('should call prisma update in update', async () => {

View File

@ -684,13 +684,14 @@ export default function Blogs() {
{blog.author?.firstName ? `${blog.author.firstName} ${blog.author.lastName || ''}` : 'نامشخص'}
</td>
<td className="py-4 px-6 text-gray-500 text-xs font-medium">
<div className="flex items-center gap-1">
<div className="flex items-center gap-1 mb-1">
<Clock className="w-3.5 h-3.5 text-gray-400" />
<span>{blog.readingTime || 3} دقیقه</span>
</div>
<span className="text-[10px] text-gray-400 mt-0.5 block">
{blog.viewCount || 0} بازدید
</span>
<div className="inline-flex items-center gap-1 px-2 py-0.5 bg-gray-100 text-gray-700 rounded-md font-mono text-[11px] font-bold">
<Eye className="w-3 h-3 text-purple-600" />
<span>{(blog.viewCount || 0).toLocaleString('fa-IR')} بازدید</span>
</div>
</td>
<td className="py-4 px-6">{renderStatusBadge(blog.status, blog.isPublished)}</td>
<td className="py-4 px-6">

View File

@ -1,11 +1,21 @@
import { useState, useEffect } from 'react';
import { TrendingUp, DollarSign, ShoppingBag, Users, HeartHandshake, Download } from 'lucide-react';
import { useState, useEffect, useCallback } from 'react';
import {
TrendingUp,
DollarSign,
ShoppingBag,
Users,
HeartHandshake,
Download,
Calendar,
Filter,
Receipt,
Percent,
Package,
ShieldCheck
} from 'lucide-react';
import api from '../services/api';
import Button from '../components/ui/Button';
import Spinner from '../components/ui/Spinner';
import {
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer,
BarChart, Bar, PieChart, Pie, Cell, Legend
@ -15,16 +25,21 @@ const COLORS = ['#8B5CF6', '#EC4899', '#3B82F6', '#10B981', '#F59E0B'];
interface CustomTooltipProps {
active?: boolean;
payload?: Array<{ value: number | string }>;
payload?: Array<{ value: number | string; name?: string; color?: string }>;
label?: string;
}
const CustomTooltip = ({ active, payload, label }: CustomTooltipProps) => {
if (active && payload && payload.length) {
return (
<div className="bg-white p-4 rounded-xl shadow-lg border border-gray-100">
<div className="bg-white p-4 rounded-xl shadow-lg border border-gray-100 text-right font-vazir" dir="rtl">
<p className="font-bold text-gray-900 mb-2">{label}</p>
<p className="text-purple-600 font-bold">{Number(payload[0].value).toLocaleString()} تومان</p>
{payload.map((item, idx) => (
<div key={idx} className="flex items-center justify-between gap-4 text-xs font-bold my-1">
<span style={{ color: item.color }}>{item.name || 'مبلغ'}:</span>
<span>{Number(item.value).toLocaleString()} تومان</span>
</div>
))}
</div>
);
}
@ -50,13 +65,20 @@ export interface TopCouponItem {
export interface ReportData {
overview?: {
totalRevenue: number;
totalOrders: number;
totalUsers: number;
avgOrderValue: number;
totalCharity?: number;
productSalesRevenue?: number;
totalOrders: number;
totalUsers?: number;
avgOrderValue?: number;
b2bRevenue?: number;
b2cRevenue?: number;
totalTax?: number;
totalShipping?: number;
totalDiscounts?: number;
totalCostOfGoods?: number;
netProfit?: number;
};
salesTimeline?: Array<{ date: string; amount: number }>;
salesTimeline?: Array<{ date: string; amount: number; charity?: number; sales?: number }>;
bestSellers?: BestSellerItem[];
categoryDistribution?: CategoryDistItem[];
topCoupons?: TopCouponItem[];
@ -66,40 +88,72 @@ export default function Reports() {
const [reportData, setReportData] = useState<ReportData | null>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
let isSubscribed = true;
api.get('/admin/reports').then(reportRes => {
if (!isSubscribed) return;
if (reportRes.data?.success) {
setReportData(reportRes.data.data);
}
}).catch(err => {
console.error(err);
}).finally(() => {
if (isSubscribed) setIsLoading(false);
});
return () => {
isSubscribed = false;
};
}, []);
// Filter States
const [dateFilterType, setDateFilterType] = useState<'all' | 'today' | '7days' | '30days' | 'custom'>('30days');
const [startDate, setStartDate] = useState('');
const [endDate, setEndDate] = useState('');
const [roleFilter, setRoleFilter] = useState('ALL');
if (isLoading) {
const fetchReports = useCallback(async () => {
try {
setIsLoading(true);
const params: Record<string, string> = {};
const now = new Date();
if (dateFilterType === 'today') {
const start = new Date(now);
start.setHours(0, 0, 0, 0);
params.startDate = start.toISOString();
} else if (dateFilterType === '7days') {
const start = new Date(now);
start.setDate(now.getDate() - 7);
params.startDate = start.toISOString();
} else if (dateFilterType === '30days') {
const start = new Date(now);
start.setDate(now.getDate() - 30);
params.startDate = start.toISOString();
} else if (dateFilterType === 'custom') {
if (startDate) params.startDate = startDate;
if (endDate) params.endDate = endDate;
}
if (roleFilter !== 'ALL') {
params.role = roleFilter;
}
const res = await api.get('/admin/reports', { params });
if (res.data?.success) {
setReportData(res.data.data);
}
} catch (err) {
console.error('Failed to fetch reports:', err);
} finally {
setIsLoading(false);
}
}, [dateFilterType, startDate, endDate, roleFilter]);
useEffect(() => {
fetchReports();
}, [fetchReports]);
if (isLoading && !reportData) {
return <div className="flex justify-center items-center h-96"><Spinner size="lg" className="text-purple-600" /></div>;
}
if (!reportData) return <div className="text-center py-12 text-gray-500 font-bold">اطلاعاتی یافت نشد</div>;
const { overview, salesTimeline, bestSellers, categoryDistribution, topCoupons } = reportData;
const { overview, salesTimeline, bestSellers, categoryDistribution, topCoupons } = reportData || {};
return (
<div className="space-y-6">
<div className="flex flex-col sm:flex-row justify-between gap-4">
<div className="space-y-6 font-vazir text-right" dir="rtl">
{/* Header */}
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 bg-white p-6 rounded-3xl border border-gray-200 shadow-xs">
<div>
<h2 className="text-2xl font-black text-gray-900 flex items-center gap-2">
<TrendingUp className="w-6 h-6 text-purple-600" />
گزارشات و تحلیل‌ها
گزارشات و تحلیل‌های مالی پیشرفته
</h2>
<p className="text-gray-500 font-medium mt-1">آمار فروش، رفتار کاربران و عملکرد تخفیف‌ها</p>
<p className="text-gray-500 font-medium text-xs sm:text-sm mt-1">
تفکیک درآمد کالاها، مبالغ واریزی مهربانی، مالیات، تخفیف‌ها و سود خالص
</p>
</div>
<Button
variant="outline"
@ -108,73 +162,212 @@ export default function Reports() {
onClick={() => {
if (!reportData) return;
const rows = [
['گزارش کلی سیستم کنینا ایران'],
['درآمد کل (تومان)', overview?.totalRevenue || 0],
['تعداد سفارشات', overview?.totalOrders || 0],
['تعداد کاربران', overview?.totalUsers || 0],
['میانگین ارزش سفارش', overview?.avgOrderValue || 0],
['گزارش مالی و عملکرد فروش کنینا ایران'],
['تاریخ گزارش', new Date().toLocaleDateString('fa-IR')],
[],
['محصولات پرفروش', 'تعداد فروش'],
['شاخص مالی', 'مبلغ (تومان) / مقدار'],
['درآمد کل واریزی‌ها', overview?.totalRevenue || 0],
['سهم مهربانی (خیریه)', overview?.totalCharity || 0],
['خالص فروش کالاها', overview?.productSalesRevenue || 0],
['بهای تمام‌شده کالاها (COGS)', overview?.totalCostOfGoods || 0],
['مجموع مالیات بر ارزش افزوده', overview?.totalTax || 0],
['مجموع تخفیف‌های اعمال شده', overview?.totalDiscounts || 0],
['سود خالص تخمینی', overview?.netProfit || 0],
['تعداد کل سفارشات', overview?.totalOrders || 0],
['فروش همکاران (B2B)', overview?.b2bRevenue || 0],
['فروش مصرف‌کنندگان (B2C)', overview?.b2cRevenue || 0],
[],
['محصولات پرفروش در این بازه', 'تعداد فروش'],
...(bestSellers || []).map(b => [b.name, b.quantity]),
];
const csvContent = 'data:text/csv;charset=utf-8,\uFEFF' + rows.map(e => e.join(',')).join('\n');
const encodedUri = encodeURI(csvContent);
const link = document.createElement('a');
link.setAttribute('href', encodedUri);
link.setAttribute('download', `Canina_Sales_Report_${new Date().toISOString().split('T')[0]}.csv`);
link.setAttribute('download', `Canina_Financial_Report_${new Date().toISOString().split('T')[0]}.csv`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}}
>
خروجی اکسل (Excel)
خروجی اکسل مالی (Excel / CSV)
</Button>
</div>
{/* Filter Toolbar */}
<div className="bg-white p-4 rounded-3xl shadow-xs border border-gray-200 flex flex-wrap items-center gap-3">
<div className="flex items-center gap-2">
<Filter className="w-4 h-4 text-purple-600" />
<span className="text-xs font-black text-gray-700">بازه زمانی:</span>
</div>
{/* Overview Cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="bg-gradient-to-br from-purple-500 to-indigo-600 p-6 rounded-2xl text-white shadow-lg shadow-purple-200">
<div className="flex items-center justify-between mb-4">
<h3 className="font-bold text-purple-100">درآمد کل</h3>
<div className="p-2 bg-white/20 rounded-xl"><DollarSign className="w-6 h-6" /></div>
</div>
<p className="text-3xl font-black">{Number(overview.totalRevenue).toLocaleString()}</p>
<p className="text-purple-200 text-sm mt-1">تومان</p>
<div className="flex flex-wrap items-center gap-1.5 bg-gray-50 p-1 rounded-2xl border border-gray-200 text-xs font-bold">
<button
onClick={() => setDateFilterType('30days')}
className={`px-3 py-1.5 rounded-xl transition-all ${dateFilterType === '30days' ? 'bg-purple-600 text-white shadow-xs' : 'text-gray-600 hover:text-purple-600'}`}
>
۳۰ روز اخیر
</button>
<button
onClick={() => setDateFilterType('7days')}
className={`px-3 py-1.5 rounded-xl transition-all ${dateFilterType === '7days' ? 'bg-purple-600 text-white shadow-xs' : 'text-gray-600 hover:text-purple-600'}`}
>
۷ روز اخیر
</button>
<button
onClick={() => setDateFilterType('today')}
className={`px-3 py-1.5 rounded-xl transition-all ${dateFilterType === 'today' ? 'bg-purple-600 text-white shadow-xs' : 'text-gray-600 hover:text-purple-600'}`}
>
امروز
</button>
<button
onClick={() => setDateFilterType('all')}
className={`px-3 py-1.5 rounded-xl transition-all ${dateFilterType === 'all' ? 'bg-purple-600 text-white shadow-xs' : 'text-gray-600 hover:text-purple-600'}`}
>
کل تاریخچه
</button>
<button
onClick={() => setDateFilterType('custom')}
className={`px-3 py-1.5 rounded-xl transition-all ${dateFilterType === 'custom' ? 'bg-purple-600 text-white shadow-xs' : 'text-gray-600 hover:text-purple-600'}`}
>
بازه سفارشی
</button>
</div>
<div className="bg-gradient-to-br from-pink-500 to-rose-600 p-6 rounded-2xl text-white shadow-lg shadow-pink-200">
<div className="flex items-center justify-between mb-4">
<h3 className="font-bold text-pink-100">سهم خیریه (مسئولیت اجتماعی)</h3>
<div className="p-2 bg-white/20 rounded-xl"><HeartHandshake className="w-6 h-6" /></div>
{dateFilterType === 'custom' && (
<div className="flex items-center gap-2 text-xs">
<input
type="date"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
className="px-3 py-1.5 rounded-xl border border-gray-200 outline-none text-xs"
/>
<span className="text-gray-400">تا</span>
<input
type="date"
value={endDate}
onChange={(e) => setEndDate(e.target.value)}
className="px-3 py-1.5 rounded-xl border border-gray-200 outline-none text-xs"
/>
</div>
<p className="text-3xl font-black">{Number(overview.totalCharity).toLocaleString()}</p>
<p className="text-pink-200 text-sm mt-1">تومان</p>
</div>
<div className="bg-gradient-to-br from-blue-500 to-cyan-600 p-6 rounded-2xl text-white shadow-lg shadow-blue-200">
<div className="flex items-center justify-between mb-4">
<h3 className="font-bold text-blue-100">فروش همکاران (B2B)</h3>
<div className="p-2 bg-white/20 rounded-xl"><Users className="w-6 h-6" /></div>
</div>
<p className="text-3xl font-black">{Number(overview.b2bRevenue).toLocaleString()}</p>
<p className="text-blue-200 text-sm mt-1">تومان</p>
</div>
<div className="bg-gradient-to-br from-emerald-500 to-teal-600 p-6 rounded-2xl text-white shadow-lg shadow-emerald-200">
<div className="flex items-center justify-between mb-4">
<h3 className="font-bold text-emerald-100">تعداد سفارشات</h3>
<div className="p-2 bg-white/20 rounded-xl"><ShoppingBag className="w-6 h-6" /></div>
</div>
<p className="text-3xl font-black">{overview.totalOrders}</p>
<p className="text-emerald-200 text-sm mt-1">موفق</p>
)}
<div className="flex items-center gap-2 mr-auto">
<span className="text-xs font-black text-gray-700">نوع مشتری:</span>
<select
value={roleFilter}
onChange={(e) => setRoleFilter(e.target.value)}
className="px-3 py-1.5 rounded-xl border border-gray-200 text-xs font-bold outline-none bg-gray-50/50"
>
<option value="ALL">همه (B2B + B2C)</option>
<option value="B2B">خریداران عمده (B2B)</option>
<option value="User_PetOwner">مصرف‌کنندگان (B2C)</option>
</select>
</div>
</div>
{/* Overview Cards (Core Revenue & Charity) */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{/* Total Deposited */}
<div className="bg-gradient-to-br from-purple-600 to-indigo-700 p-6 rounded-3xl text-white shadow-lg shadow-purple-200">
<div className="flex items-center justify-between mb-4">
<h3 className="font-bold text-purple-100 text-sm">مجموع کل واریزی‌ها</h3>
<div className="p-2.5 bg-white/20 rounded-2xl"><DollarSign className="w-5 h-5" /></div>
</div>
<p className="text-3xl font-black">{Number(overview?.totalRevenue || 0).toLocaleString()}</p>
<p className="text-purple-200 text-xs mt-1">تومان (شامل کالا و مهربانی)</p>
</div>
{/* Charity Share */}
<div className="bg-gradient-to-br from-rose-500 to-pink-600 p-6 rounded-3xl text-white shadow-lg shadow-pink-200">
<div className="flex items-center justify-between mb-4">
<h3 className="font-bold text-rose-100 text-sm">سهم مبالغ مهربانی (خیریه)</h3>
<div className="p-2.5 bg-white/20 rounded-2xl"><HeartHandshake className="w-5 h-5" /></div>
</div>
<p className="text-3xl font-black">{Number(overview?.totalCharity || 0).toLocaleString()}</p>
<p className="text-rose-200 text-xs mt-1">
{overview?.totalRevenue ? `${((Number(overview.totalCharity || 0) / Number(overview.totalRevenue)) * 100).toFixed(1)}% از کل دریافتی` : 'تومان'}
</p>
</div>
{/* Product Sales Only */}
<div className="bg-gradient-to-br from-blue-600 to-cyan-700 p-6 rounded-3xl text-white shadow-lg shadow-blue-200">
<div className="flex items-center justify-between mb-4">
<h3 className="font-bold text-blue-100 text-sm">خالص فروش محصولات</h3>
<div className="p-2.5 bg-white/20 rounded-2xl"><ShoppingBag className="w-5 h-5" /></div>
</div>
<p className="text-3xl font-black">{Number(overview?.productSalesRevenue || (Number(overview?.totalRevenue || 0) - Number(overview?.totalCharity || 0))).toLocaleString()}</p>
<p className="text-blue-200 text-xs mt-1">تومان (بدون احتساب مبالغ خیریه)</p>
</div>
{/* Net Profit */}
<div className="bg-gradient-to-br from-emerald-600 to-teal-700 p-6 rounded-3xl text-white shadow-lg shadow-emerald-200">
<div className="flex items-center justify-between mb-4">
<h3 className="font-bold text-emerald-100 text-sm">سود خالص تخمینی</h3>
<div className="p-2.5 bg-white/20 rounded-2xl"><TrendingUp className="w-5 h-5" /></div>
</div>
<p className="text-3xl font-black">{Number(overview?.netProfit || 0).toLocaleString()}</p>
<p className="text-emerald-200 text-xs mt-1">تومان (پس از کسر خرید، مالیات و تخفیف)</p>
</div>
</div>
{/* Financial Breakdown Section (Item 2 Requirement) */}
<div className="bg-white p-6 rounded-3xl border border-gray-200 shadow-xs">
<h3 className="text-lg font-black text-gray-900 mb-4 flex items-center gap-2">
<Receipt className="w-5 h-5 text-purple-600" />
تراز و تفکیک اجزای مالی
</h3>
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3">
<div className="p-4 bg-gray-50 rounded-2xl border border-gray-100">
<span className="text-[11px] font-bold text-gray-400 block mb-1">بهای خرید کالاها (COGS)</span>
<span className="text-base font-black text-gray-800">{Number(overview?.totalCostOfGoods || 0).toLocaleString()}</span>
<span className="text-[10px] text-gray-400 block mt-0.5">تومان</span>
</div>
<div className="p-4 bg-gray-50 rounded-2xl border border-gray-100">
<span className="text-[11px] font-bold text-gray-400 block mb-1">مالیات بر ارزش افزوده</span>
<span className="text-base font-black text-amber-600">{Number(overview?.totalTax || 0).toLocaleString()}</span>
<span className="text-[10px] text-gray-400 block mt-0.5">تومان</span>
</div>
<div className="p-4 bg-gray-50 rounded-2xl border border-gray-100">
<span className="text-[11px] font-bold text-gray-400 block mb-1">مجموع تخفیف‌ها</span>
<span className="text-base font-black text-red-500">{Number(overview?.totalDiscounts || 0).toLocaleString()}</span>
<span className="text-[10px] text-gray-400 block mt-0.5">تومان</span>
</div>
<div className="p-4 bg-gray-50 rounded-2xl border border-gray-100">
<span className="text-[11px] font-bold text-gray-400 block mb-1">فروش عمده (B2B)</span>
<span className="text-base font-black text-blue-600">{Number(overview?.b2bRevenue || 0).toLocaleString()}</span>
<span className="text-[10px] text-gray-400 block mt-0.5">تومان</span>
</div>
<div className="p-4 bg-gray-50 rounded-2xl border border-gray-100">
<span className="text-[11px] font-bold text-gray-400 block mb-1">فروش تکی (B2C)</span>
<span className="text-base font-black text-indigo-600">{Number(overview?.b2cRevenue || 0).toLocaleString()}</span>
<span className="text-[10px] text-gray-400 block mt-0.5">تومان</span>
</div>
<div className="p-4 bg-gray-50 rounded-2xl border border-gray-100">
<span className="text-[11px] font-bold text-gray-400 block mb-1">تعداد کل سفارشات</span>
<span className="text-base font-black text-emerald-600">{overview?.totalOrders || 0}</span>
<span className="text-[10px] text-gray-400 block mt-0.5">سفارش موفق</span>
</div>
</div>
</div>
{/* Charts Section */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Sales Chart */}
<div className="lg:col-span-2 bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
<h3 className="text-lg font-bold text-gray-900 mb-6">نمودار فروش روزانه</h3>
{/* Timeline Chart with Dual Area for Total & Charity */}
<div className="lg:col-span-2 bg-white p-6 rounded-3xl shadow-xs border border-gray-200">
<div className="flex items-center justify-between mb-6">
<h3 className="text-base font-black text-gray-900">روند فروش و واریزی‌های مهربانی</h3>
<div className="flex items-center gap-4 text-xs font-bold">
<span className="flex items-center gap-1.5 text-purple-600">
<span className="w-2.5 h-2.5 rounded-full bg-purple-600" />
کل واریزی
</span>
<span className="flex items-center gap-1.5 text-rose-500">
<span className="w-2.5 h-2.5 rounded-full bg-rose-500" />
سهم مهربانی
</span>
</div>
</div>
<div className="h-80 w-full" dir="ltr">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={salesTimeline} margin={{ top: 10, right: 10, left: 0, bottom: 0 }}>
@ -183,20 +376,25 @@ export default function Reports() {
<stop offset="5%" stopColor="#8B5CF6" stopOpacity={0.3}/>
<stop offset="95%" stopColor="#8B5CF6" stopOpacity={0}/>
</linearGradient>
<linearGradient id="colorCharity" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#F43F5E" stopOpacity={0.3}/>
<stop offset="95%" stopColor="#F43F5E" stopOpacity={0}/>
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f3f4f6" />
<XAxis dataKey="date" stroke="#9ca3af" fontSize={12} tickLine={false} axisLine={false} />
<YAxis stroke="#9ca3af" fontSize={12} tickLine={false} axisLine={false} tickFormatter={(val) => `${val / 1000}k`} />
<XAxis dataKey="date" stroke="#9ca3af" fontSize={11} tickLine={false} axisLine={false} />
<YAxis stroke="#9ca3af" fontSize={11} tickLine={false} axisLine={false} tickFormatter={(val) => `${val >= 1000000 ? `${(val/1000000).toFixed(1)}M` : `${val / 1000}k`}`} />
<RechartsTooltip content={<CustomTooltip />} />
<Area type="monotone" dataKey="amount" stroke="#8B5CF6" strokeWidth={3} fillOpacity={1} fill="url(#colorSales)" />
<Area type="monotone" name="کل دریافتی" dataKey="amount" stroke="#8B5CF6" strokeWidth={2.5} fillOpacity={1} fill="url(#colorSales)" />
<Area type="monotone" name="سهم مهربانی" dataKey="charity" stroke="#F43F5E" strokeWidth={2} fillOpacity={1} fill="url(#colorCharity)" />
</AreaChart>
</ResponsiveContainer>
</div>
</div>
{/* Category Distribution */}
<div className="bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
<h3 className="text-lg font-bold text-gray-900 mb-6">سهم فروش دسته‌بندی‌ها</h3>
<div className="bg-white p-6 rounded-3xl shadow-xs border border-gray-200">
<h3 className="text-base font-black text-gray-900 mb-6">سهم فروش دسته‌بندی‌ها</h3>
<div className="h-64 w-full flex justify-center">
{categoryDistribution && categoryDistribution.length > 0 ? (
<ResponsiveContainer width="100%" height="100%">
@ -211,19 +409,20 @@ export default function Reports() {
</PieChart>
</ResponsiveContainer>
) : (
<div className="text-gray-400 font-medium flex items-center justify-center h-full">داده‌ای موجود نیست</div>
<div className="text-gray-400 font-medium flex items-center justify-center h-full text-xs">داده‌ای موجود نیست</div>
)}
</div>
</div>
</div>
{/* Best Sellers & Top Coupons */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Best Sellers */}
<div className="bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
<h3 className="text-lg font-bold text-gray-900 mb-6">پرفروش‌ترین محصولات</h3>
<div className="min-h-[320px] w-full" dir="ltr">
<div className="bg-white p-6 rounded-3xl shadow-xs border border-gray-200">
<h3 className="text-base font-black text-gray-900 mb-6">پرفروش‌ترین محصولات در بازه انتخابی</h3>
<div className="min-h-[300px] w-full" dir="ltr">
{bestSellers && bestSellers.length > 0 ? (
<ResponsiveContainer width="100%" height={Math.max(300, bestSellers.length * 45)}>
<ResponsiveContainer width="100%" height={Math.max(280, bestSellers.length * 45)}>
<BarChart
data={bestSellers}
layout="vertical"
@ -251,31 +450,31 @@ export default function Reports() {
</BarChart>
</ResponsiveContainer>
) : (
<div className="text-gray-400 font-medium flex items-center justify-center h-64">داده‌ای موجود نیست</div>
<div className="text-gray-400 font-medium flex items-center justify-center h-64 text-xs">داده‌ای موجود نیست</div>
)}
</div>
</div>
{/* Top Coupons */}
<div className="bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
<h3 className="text-lg font-bold text-gray-900 mb-6">پرکاربردترین کدهای تخفیف</h3>
<div className="space-y-4">
<div className="bg-white p-6 rounded-3xl shadow-xs border border-gray-200">
<h3 className="text-base font-black text-gray-900 mb-6">کدهای تخفیف پراستفاده</h3>
<div className="space-y-3">
{topCoupons && topCoupons.length > 0 ? (
topCoupons.map((coupon: TopCouponItem, idx: number) => (
<div key={idx} className="flex items-center justify-between p-4 bg-gray-50 rounded-xl border border-gray-100">
<div key={idx} className="flex items-center justify-between p-4 bg-gray-50 rounded-2xl border border-gray-100">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-orange-100 text-orange-600 font-bold flex items-center justify-center text-sm">
<div className="w-9 h-9 rounded-xl bg-purple-100 text-purple-600 font-black flex items-center justify-center text-xs">
#{idx + 1}
</div>
<span className="font-mono font-bold tracking-widest text-gray-800">{coupon.name}</span>
<span className="font-mono font-bold tracking-widest text-gray-800 text-sm">{coupon.name}</span>
</div>
<div className="text-gray-600 font-medium">
<span className="font-black text-gray-900 mx-1">{coupon.usedCount ?? coupon.value ?? 0}</span>بار استفاده
<div className="text-gray-600 text-xs font-medium">
<span className="font-black text-gray-900 mx-1 text-sm">{coupon.usedCount ?? coupon.value ?? 0}</span>بار استفاده
</div>
</div>
))
) : (
<div className="text-gray-400 font-medium flex items-center justify-center h-32">داده‌ای موجود نیست</div>
<div className="text-gray-400 font-medium flex items-center justify-center h-32 text-xs">داده‌ای موجود نیست</div>
)}
</div>
</div>

View File

@ -50,8 +50,36 @@ export default function ClientLayout({ children }: { children: React.ReactNode }
} else {
useUserStore.getState().logout();
}
// Preserve scroll restoration on back/forward
if (typeof window !== 'undefined' && 'scrollRestoration' in window.history) {
window.history.scrollRestoration = 'auto';
}
}, [fetchProfile, fetchSettings]);
// Handle scroll position tracking per pathname for reliable mobile back navigation
useEffect(() => {
if (typeof window === 'undefined') return;
const key = `scroll_pos_${pathname}`;
const savedPos = sessionStorage.getItem(key);
if (savedPos !== null) {
const targetY = parseInt(savedPos, 10);
requestAnimationFrame(() => {
window.scrollTo({ top: targetY, behavior: 'instant' });
});
}
const handleScroll = () => {
sessionStorage.setItem(`scroll_pos_${pathname}`, window.scrollY.toString());
};
window.addEventListener('scroll', handleScroll, { passive: true });
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, [pathname]);
// Check Maintenance Mode (Admin / Partner bypass)
const isMaintenanceMode = getText('MAINTENANCE_MODE', 'false') === 'true' || getText('maintenance_mode', 'false') === 'true';
const isAdmin = role === 'User_Partner' || (typeof window !== 'undefined' && Boolean(localStorage.getItem('adminToken')));

View File

@ -297,49 +297,40 @@ export default function ArchivePage({
return labels;
}, [categories]);
// Sync state with prop change (e.g. from Header menu)
const lastInitialPropsRef = React.useRef({ initialCategory, initialSearch, initialPetType, initialSymptoms });
// Sync state when incoming initial props change from navigation
useEffect(() => {
// Normalize Input (Trim and lowercase)
const normSearch = (initialSearch || "").trim().toLowerCase();
const normCat = initialCategory.trim().toLowerCase();
const isNewCategory = lastInitialPropsRef.current.initialCategory !== initialCategory;
const isNewSearch = lastInitialPropsRef.current.initialSearch !== initialSearch;
const isNewPet = lastInitialPropsRef.current.initialPetType !== initialPetType;
const isNewSymptoms = lastInitialPropsRef.current.initialSymptoms !== initialSymptoms;
const mapKey = normSearch || normCat;
const mapping = CATEGORY_MAP[mapKey];
if (selectedCategory === initialCategory && searchQuery === initialSearch) {
if (!isNewCategory && !isNewSearch && !isNewPet && !isNewSymptoms) {
return;
}
Promise.resolve().then(() => {
setIsUpdating(true);
lastInitialPropsRef.current = { initialCategory, initialSearch, initialPetType, initialSymptoms };
// Reset ALL other filters when a new category/solution is selected from menu
// Normalize Input
const normSearch = (initialSearch || "").trim().toLowerCase();
const normCat = (initialCategory || "").trim().toLowerCase();
const mapKey = normSearch || normCat;
const mapping = CATEGORY_MAP[mapKey];
setIsUpdating(true);
if (mapping) {
setSelectedCategory(mapping.category || "all");
setSearchQuery(mapping.query || "");
setSelectedPet("all");
setActiveSymptoms([]);
setSearchQuery("");
if (mapping) {
if (mapping.category) setSelectedCategory(mapping.category);
if (mapping.query) setSearchQuery(mapping.query);
if (mapping.symptoms) {
setActiveSymptoms(mapping.symptoms);
}
} else {
setSelectedCategory(initialCategory);
setSearchQuery(initialSearch);
if (initialSearch && symptoms.includes(initialSearch)) {
setActiveSymptoms([initialSearch]);
}
}
});
const timer = setTimeout(() => {
window.scrollTo({ top: 0, behavior: "smooth" });
}, 400);
return () => clearTimeout(timer);
}, [initialCategory, initialSearch, selectedCategory, searchQuery, symptoms]);
setActiveSymptoms(mapping.symptoms || []);
} else {
setSelectedCategory(initialCategory || "all");
setSearchQuery(initialSearch || "");
setSelectedPet((initialPetType as PetType) || "all");
setActiveSymptoms(initialSymptoms ? initialSymptoms.split(',').filter(Boolean) : []);
}
}, [initialCategory, initialSearch, initialPetType, initialSymptoms]);
// Update URL Query Parameters
useEffect(() => {

View File

@ -369,23 +369,23 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
return (
<AnimatePresence>
{isOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center p-0 sm:p-4 font-vazir overflow-y-auto" dir="rtl">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
className="absolute inset-0 bg-medical-gray-900/60 backdrop-blur-sm"
className="fixed inset-0 bg-medical-gray-900/60 backdrop-blur-sm"
/>
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 20 }}
initial={{ opacity: 0, scale: 0.95, y: 30 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 20 }}
className="relative w-full max-w-md bg-white rounded-[2.5rem] shadow-2xl overflow-hidden border border-medical-gray-100 z-10"
exit={{ opacity: 0, scale: 0.95, y: 30 }}
className="relative w-full max-w-md bg-white rounded-t-[2.5rem] sm:rounded-[2.5rem] shadow-2xl overflow-hidden border border-medical-gray-100 z-10 max-h-[92vh] flex flex-col my-auto pb-safe"
>
{/* Header */}
<div className="p-6 pb-0 flex justify-between items-center">
<div className="p-6 pb-0 flex justify-between items-center shrink-0">
<div className="flex items-center gap-2 text-canina-blue font-black text-sm">
<ShieldCheck className="w-5 h-5" />
<span>ورود امن به کنینا</span>
@ -425,6 +425,8 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
<input
autoFocus
type="tel"
inputMode="numeric"
pattern="[0-9]*"
maxLength={11}
required
placeholder="۰۹۱۲۳۴۵۶۷۸۹"
@ -739,6 +741,8 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
<input
autoFocus
type="tel"
inputMode="numeric"
pattern="[0-9]*"
maxLength={11}
placeholder="مثال: ۰۹۱۲۳۴۵۶۷۸۹"
value={phoneNumber}
@ -794,8 +798,9 @@ export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
ref={otpInputRef}
id="otp-code-input"
name="one-time-code"
type="text"
type="tel"
inputMode="numeric"
pattern="[0-9]*"
autoComplete="one-time-code"
placeholder="کد ۵ رقمی"
value={otpCode}

View File

@ -270,6 +270,10 @@ export default function BlogPage({
<Clock className="w-3.5 h-3.5" />
{featuredPost.readingTime || 4} دقیقه مطالعه
</div>
<div className="flex items-center gap-1 text-medical-gray-400">
<Eye className="w-3.5 h-3.5" />
<span>{(featuredPost.viewCount ?? 0).toLocaleString('fa-IR')} بازدید</span>
</div>
</div>
<h2 className="text-2xl sm:text-3xl lg:text-4xl font-black text-medical-gray-900 mb-5 leading-tight group-hover:text-canina-blue transition-colors">
{featuredPost.title}
@ -324,6 +328,11 @@ export default function BlogPage({
{post.category}
</span>
)}
{/* View Count Badge */}
<span className="absolute top-3 left-3 px-2.5 py-1 bg-black/60 backdrop-blur-md text-white rounded-lg text-[10px] font-bold z-10 flex items-center gap-1">
<Eye className="w-3 h-3 text-canina-blue" />
<span>{(post.viewCount ?? 0).toLocaleString('fa-IR')}</span>
</span>
</div>
<div className="p-6 sm:p-7">
<div className="flex items-center justify-between text-[11px] font-bold text-medical-gray-400 mb-3">
@ -331,9 +340,15 @@ export default function BlogPage({
<Calendar className="w-3 h-3" />
{post.date}
</div>
<div className="flex items-center gap-1">
<Clock className="w-3 h-3" />
{post.readingTime || 3} دقیقه
<div className="flex items-center gap-3">
<div className="flex items-center gap-1">
<Clock className="w-3 h-3" />
{post.readingTime || 3} دقیقه
</div>
<div className="flex items-center gap-1 text-medical-gray-400">
<Eye className="w-3 h-3" />
<span>{(post.viewCount ?? 0).toLocaleString('fa-IR')}</span>
</div>
</div>
</div>
<h3 className="text-lg font-black text-medical-gray-900 mb-3 group-hover:text-canina-blue transition-colors line-clamp-2 leading-snug">

View File

@ -43,21 +43,22 @@ function ProductCard({ product }: { product: Product }) {
return (
<motion.div
initial={{ opacity: 0, y: 24 }}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
whileHover={{ y: -6 }}
transition={{ duration: 0.35, ease: "easeOut" }}
className="group bg-white/80 backdrop-blur-md rounded-3xl border border-medical-gray-200/80 overflow-hidden hover:shadow-2xl hover:shadow-canina-blue/15 hover:border-canina-blue/30 transition-all duration-500 flex flex-col cursor-pointer relative"
whileHover={{ y: -4 }}
transition={{ duration: 0.3, ease: "easeOut" }}
onClick={() => router.push(`/shop/${product.slug || product.id}`)}
className="group bg-white/90 backdrop-blur-md rounded-2xl sm:rounded-3xl border border-medical-gray-200/80 overflow-hidden hover:shadow-xl hover:shadow-canina-blue/10 hover:border-canina-blue/30 transition-all duration-300 flex flex-col cursor-pointer relative"
>
{/* Category Badge */}
<div className="absolute top-4 right-4 z-30 px-3 py-1.5 bg-white/90 backdrop-blur-md rounded-full border border-medical-gray-200/80 text-[10px] font-black text-medical-gray-700 uppercase tracking-widest shadow-sm group-hover:bg-canina-blue group-hover:text-white transition-all">
<div className="absolute top-3 right-3 sm:top-4 sm:right-4 z-30 px-2.5 py-1 bg-white/90 backdrop-blur-md rounded-full border border-medical-gray-200/80 text-[9px] sm:text-[10px] font-black text-medical-gray-700 uppercase tracking-wider shadow-xs group-hover:bg-canina-blue group-hover:text-white transition-all">
{product.category}
</div>
{/* Compatibility Tag */}
{compatibility && (
<div className={`absolute top-[3.25rem] right-4 z-10 text-[8px] font-black uppercase tracking-widest px-3 py-1 rounded-full shadow-md flex items-center gap-1 ${
<div className={`absolute top-[2.8rem] sm:top-[3.25rem] right-3 sm:right-4 z-10 text-[8px] font-black uppercase tracking-widest px-2.5 py-0.5 rounded-full shadow-xs flex items-center gap-1 ${
compatibility.type === "alert" ? "bg-amber-100 text-amber-700" :
compatibility.type === "success" ? "bg-green-100 text-green-700" : "bg-medical-gray-100 text-medical-gray-600"
}`}>
@ -66,22 +67,20 @@ function ProductCard({ product }: { product: Product }) {
</div>
)}
{/* Image */}
<div className="relative aspect-[4/3] bg-medical-gray-50 flex items-center justify-center overflow-hidden p-5">
{/* Image Container */}
<div className="relative aspect-[16/11] sm:aspect-[4/3] bg-medical-gray-50 flex items-center justify-center overflow-hidden p-3 sm:p-5">
<SafeImage
src={product.image}
alt={nameFa}
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 25vw)"
className="w-full h-full group-hover:scale-105 transition-transform duration-500"
imgClassName="object-contain max-h-[170px] mx-auto"
imgClassName="object-contain max-h-[130px] sm:max-h-[170px] mx-auto"
/>
{/* PDP Link overlay */}
<Link href={`/shop/${product.slug || product.id}`} className="absolute inset-0 z-10" />
{/* Hover Actions */}
<div className="absolute inset-0 bg-canina-blue/60 opacity-0 group-hover:opacity-100 backdrop-blur-sm transition-all duration-300 flex items-center justify-center gap-4 z-20">
{/* Hover Actions for desktop */}
<div className="hidden sm:flex absolute inset-0 bg-canina-blue/60 opacity-0 group-hover:opacity-100 backdrop-blur-xs transition-all duration-300 items-center justify-center gap-3 z-20">
<div
onClick={(e) => { e.stopPropagation(); router.push(`/shop/${product.slug || product.id}`); }}
className="w-11 h-11 rounded-full bg-white text-canina-blue flex items-center justify-center hover:scale-110 transition-transform shadow-lg cursor-pointer"
className="w-10 h-10 rounded-full bg-white text-canina-blue flex items-center justify-center hover:scale-110 transition-transform shadow-lg cursor-pointer"
title="مشاهده جزئیات"
>
<Eye className="w-5 h-5" />
@ -93,7 +92,7 @@ function ProductCard({ product }: { product: Product }) {
addItem(product, 1);
toast.success(`${nameFa} به سبد خرید اضافه شد.`);
}}
className="w-11 h-11 rounded-full bg-canina-blue text-white flex items-center justify-center hover:scale-110 transition-transform shadow-lg border border-white/20 cursor-pointer"
className="w-10 h-10 rounded-full bg-canina-blue text-white flex items-center justify-center hover:scale-110 transition-transform shadow-lg border border-white/20 cursor-pointer"
title="افزودن به سبد خرید"
>
<ShoppingCart className="w-5 h-5" />
@ -103,40 +102,40 @@ function ProductCard({ product }: { product: Product }) {
</div>
{/* Content */}
<Link href={`/shop/${product.slug || product.id}`} className="px-5 py-4 flex flex-col flex-1 gap-3">
<div className="px-3.5 py-3 sm:px-5 sm:py-4 flex flex-col flex-1 gap-2 sm:gap-3">
{/* Dual-language Name */}
<div className="flex flex-col gap-1">
<h3 dir="rtl" className="text-[14px] font-black text-medical-gray-900 group-hover:text-canina-blue transition-colors leading-snug text-right line-clamp-2 font-vazir">
<div className="flex flex-col gap-0.5">
<h3 dir="rtl" className="text-xs sm:text-[14px] font-black text-medical-gray-900 group-hover:text-canina-blue transition-colors leading-snug text-right line-clamp-1 sm:line-clamp-2 font-vazir">
{nameFa}
</h3>
{nameEn && (
<span dir="ltr" className="text-[11px] font-semibold text-slate-400 font-sans text-left block line-clamp-1">
<span dir="ltr" className="text-[10px] sm:text-[11px] font-semibold text-slate-400 font-sans text-left block line-clamp-1">
{nameEn}
</span>
)}
</div>
{/* Short description */}
<p className="text-xs text-medical-gray-500 leading-relaxed line-clamp-2 flex-1">
<p className="text-[11px] sm:text-xs text-medical-gray-500 leading-relaxed line-clamp-1 sm:line-clamp-2 flex-1">
{product.shortDescription || product.description || ''}
</p>
{/* Footer: Price + CTA */}
<div className="flex items-center justify-between pt-3 border-t border-medical-gray-100">
<div className="text-base font-black text-medical-gray-900 font-vazir whitespace-nowrap">
<div className="flex items-center justify-between pt-2 sm:pt-3 border-t border-medical-gray-100">
<div className="text-xs sm:text-sm md:text-base font-black text-medical-gray-900 font-vazir whitespace-nowrap">
{showPrices ? (typeof product.price === 'string' || typeof product.price === 'number' ? String(product.price) : 'تماس بگیرید') : 'تماس بگیرید'}
</div>
{showPreorderBtn ? (
<span className="text-[11px] font-black text-amber-600 bg-amber-50 px-3 py-1 rounded-full border border-amber-200">
<span className="text-[10px] sm:text-[11px] font-black text-amber-600 bg-amber-50 px-2.5 py-1 rounded-full border border-amber-200">
ثبت پیش‌خرید
</span>
) : (
<span className="text-[11px] font-black text-canina-blue uppercase tracking-widest border-b-2 border-canina-blue pb-0.5 hover:text-medical-gray-900 hover:border-medical-gray-900 transition-all whitespace-nowrap">
<span className="text-[10px] sm:text-[11px] font-black text-canina-blue uppercase tracking-wider border-b border-canina-blue pb-0.5 group-hover:text-medical-gray-900 group-hover:border-medical-gray-900 transition-all whitespace-nowrap">
{getText("product_view_details", "مشاهده جزئیات")}
</span>
)}
</div>
</Link>
</div>
</motion.div>
);
}

View File

@ -554,37 +554,155 @@ export default function Header({
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
className="lg:hidden relative z-50 bg-white border-b border-medical-gray-200 px-4 py-6 space-y-4 shadow-xl"
className="lg:hidden relative z-50 bg-white border-b border-medical-gray-200 px-4 py-5 space-y-4 shadow-xl max-h-[85vh] overflow-y-auto font-vazir text-right"
dir="rtl"
>
<form onSubmit={handleSearch} className="relative mb-4">
<form onSubmit={handleSearch} className="relative mb-3">
<input
type="text"
placeholder="جستجو در محصولات..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full bg-medical-gray-50 border border-medical-gray-200 rounded-xl py-3 pr-10 pl-4 text-xs font-bold"
className="w-full bg-medical-gray-50 border border-medical-gray-200 rounded-xl py-2.5 pr-10 pl-4 text-xs font-bold focus:ring-2 focus:ring-canina-blue/20 outline-none"
/>
<Search className="w-4 h-4 text-medical-gray-400 absolute right-3 top-1/2 -translate-y-1/2" />
</form>
<div className="space-y-2 text-sm font-black">
<Link href="/shop" onClick={() => setIsMobileMenuOpen(false)} className="block p-3 rounded-xl bg-medical-gray-50 text-medical-gray-900">
🛒 محصولات تخصصی کنینا
{/* Section: Shop & Catalog */}
<div className="space-y-1">
<div className="text-[11px] font-black text-medical-gray-400 px-2 py-1 uppercase tracking-wider">
فروشگاه و محصولات
</div>
<Link
href="/shop"
onClick={() => setIsMobileMenuOpen(false)}
className="flex items-center gap-3 p-3 rounded-xl bg-medical-gray-50 text-medical-gray-900 font-bold text-xs hover:bg-canina-blue/10 hover:text-canina-blue transition-all"
>
<ShoppingBag className="w-4 h-4 text-canina-blue" />
<span>فروشگاه تخصصی محصولات کنینا</span>
</Link>
<Link href="/catalog" onClick={() => setIsMobileMenuOpen(false)} className="block p-3 rounded-xl hover:bg-medical-gray-50 text-medical-gray-800">
📑 کاتالوگ آنلاین و دوز مصرفی
<Link
href="/catalog"
onClick={() => setIsMobileMenuOpen(false)}
className="flex items-center gap-3 p-3 rounded-xl hover:bg-medical-gray-50 text-medical-gray-800 font-bold text-xs transition-all"
>
<BookOpen className="w-4 h-4 text-medical-gray-500" />
<span>کاتالوگ دیجیتال و راهنمای بالینی</span>
</Link>
<Link href="/wiki" onClick={() => setIsMobileMenuOpen(false)} className="block p-3 rounded-xl hover:bg-medical-gray-50 text-medical-gray-800">
🧬 دانشنامه علمی
</div>
{/* Section: Categories */}
<div className="space-y-1 border-t border-medical-gray-100 pt-3">
<div className="text-[11px] font-black text-medical-gray-400 px-2 py-1 uppercase tracking-wider">
دسته‌بندی‌های درمانی
</div>
<div className="grid grid-cols-2 gap-2">
{menuItems.map((item, idx) => (
<Link
key={idx}
href={`/shop?category=${item.id}`}
onClick={() => setIsMobileMenuOpen(false)}
className="flex items-center gap-2 p-2.5 rounded-xl border border-medical-gray-100 bg-white hover:border-canina-blue/30 text-medical-gray-800 text-[11px] font-bold"
>
<span className="text-canina-blue">{item.icon}</span>
<span className="truncate">{item.title}</span>
</Link>
))}
</div>
</div>
{/* Section: Academy & Science */}
<div className="space-y-1 border-t border-medical-gray-100 pt-3">
<div className="text-[11px] font-black text-medical-gray-400 px-2 py-1 uppercase tracking-wider">
دانشنامه و آموزش
</div>
<Link
href="/wiki"
onClick={() => setIsMobileMenuOpen(false)}
className="flex items-center gap-3 p-3 rounded-xl hover:bg-medical-gray-50 text-medical-gray-800 font-bold text-xs transition-all"
>
<BookOpen className="w-4 h-4 text-canina-blue" />
<span>دانشنامه علمی کنینا</span>
</Link>
<Link href="/blog" onClick={() => setIsMobileMenuOpen(false)} className="block p-3 rounded-xl hover:bg-medical-gray-50 text-medical-gray-800">
📰 مجله سلامت پت
<Link
href="/blog"
onClick={() => setIsMobileMenuOpen(false)}
className="flex items-center gap-3 p-3 rounded-xl hover:bg-medical-gray-50 text-medical-gray-800 font-bold text-xs transition-all"
>
<FileText className="w-4 h-4 text-purple-600" />
<span>مجله سلامت پت (وبلاگ)</span>
</Link>
<Link href="/videos" onClick={() => setIsMobileMenuOpen(false)} className="block p-3 rounded-xl hover:bg-medical-gray-50 text-medical-gray-800">
🎥 آکادمی ویدئویی و مشاوره دامپزشک
<Link
href="/videos"
onClick={() => setIsMobileMenuOpen(false)}
className="flex items-center gap-3 p-3 rounded-xl hover:bg-medical-gray-50 text-medical-gray-800 font-bold text-xs transition-all"
>
<Video className="w-4 h-4 text-rose-500" />
<span>آکادمی ویدئویی و مشاوره دامپزشک</span>
</Link>
<Link href="/profile" onClick={() => setIsMobileMenuOpen(false)} className="block p-3 rounded-xl hover:bg-medical-gray-50 text-medical-gray-800">
🐾 شناسنامه و سوابق سلامت پت
</div>
{/* Section: Tools & Profile */}
<div className="space-y-1 border-t border-medical-gray-100 pt-3">
<div className="text-[11px] font-black text-medical-gray-400 px-2 py-1 uppercase tracking-wider">
ابزارهای هوشمند
</div>
<Link
href="/profile"
onClick={() => setIsMobileMenuOpen(false)}
className="flex items-center gap-3 p-3 rounded-xl hover:bg-medical-gray-50 text-medical-gray-800 font-bold text-xs transition-all"
>
<Dog className="w-4 h-4 text-amber-500" />
<span>شناسنامه و سوابق سلامت پت</span>
</Link>
<Link
href="/dashboard"
onClick={() => setIsMobileMenuOpen(false)}
className="flex items-center gap-3 p-3 rounded-xl hover:bg-medical-gray-50 text-medical-gray-800 font-bold text-xs transition-all"
>
<Activity className="w-4 h-4 text-emerald-500" />
<span>پایش هوشمند مصرف مکمل‌ها</span>
</Link>
</div>
{/* Section: Company & Contact */}
<div className="space-y-1 border-t border-medical-gray-100 pt-3">
<div className="text-[11px] font-black text-medical-gray-400 px-2 py-1 uppercase tracking-wider">
اطلاعات و تماس
</div>
<Link
href="/about"
onClick={() => setIsMobileMenuOpen(false)}
className="flex items-center gap-3 p-3 rounded-xl hover:bg-medical-gray-50 text-medical-gray-800 font-bold text-xs transition-all"
>
<Award className="w-4 h-4 text-canina-blue" />
<span>درباره کمپانی کنینا آلمان</span>
</Link>
<Link
href="/trust-seals"
onClick={() => setIsMobileMenuOpen(false)}
className="flex items-center gap-3 p-3 rounded-xl hover:bg-medical-gray-50 text-emerald-600 font-bold text-xs transition-all"
>
<ShieldCheck className="w-4 h-4 text-emerald-600" />
<span>نمادهای اعتماد و مجوزهای رسمی</span>
</Link>
{isB2BEnabled && (
<Link
href="/b2b"
onClick={() => setIsMobileMenuOpen(false)}
className="flex items-center gap-3 p-3 rounded-xl hover:bg-medical-gray-50 text-medical-gray-800 font-bold text-xs transition-all"
>
<Building2 className="w-4 h-4 text-canina-blue" />
<span>درخواست نمایندگی و خرید عمده (B2B)</span>
</Link>
)}
<Link
href="/contact"
onClick={() => setIsMobileMenuOpen(false)}
className="flex items-center gap-3 p-3 rounded-xl hover:bg-medical-gray-50 text-medical-gray-800 font-bold text-xs transition-all"
>
<PhoneCall className="w-4 h-4 text-canina-blue" />
<span>تماس با مرکز پشتیبانی</span>
</Link>
</div>
</motion.div>

View File

@ -301,14 +301,14 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
</div>
</motion.div>
<div className="bg-medical-gray-50 rounded-[3.5rem] border border-medical-gray-100 p-8 lg:p-12 shadow-2xl shadow-medical-gray-200/50 relative overflow-hidden">
<div className="bg-medical-gray-50 rounded-[2.5rem] border border-medical-gray-200/80 p-5 sm:p-8 lg:p-10 shadow-xl shadow-medical-gray-200/40 relative overflow-hidden">
{/* Progress Indicator */}
<div className="absolute top-0 left-0 w-full h-2 bg-medical-gray-200">
<div className="absolute top-0 left-0 w-full h-1.5 bg-medical-gray-200">
<motion.div
className="h-full bg-canina-blue"
initial={{ width: "0%" }}
animate={{ width: `${(step / 4) * 100}%` }}
transition={{ duration: 0.5 }}
transition={{ duration: 0.4 }}
/>
</div>
@ -319,33 +319,35 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
className="space-y-8"
className="space-y-6"
>
<div className="text-center">
<h3 className="text-2xl font-black text-medical-gray-900 mb-2 font-vazir uppercase italic tracking-tight">{getText('advisor_step1_title', "گام اول: هویت بصری")}</h3>
<p className="text-medical-gray-600 font-bold font-vazir">{getText('advisor_step1_desc', "همدم شما رو با چه اسمی صدا می‌زنید؟")}</p>
<h3 className="text-xl sm:text-2xl font-black text-medical-gray-900 mb-1.5 font-vazir tracking-tight">{getText('advisor_step1_title', "گام اول: مشخصات پت")}</h3>
<p className="text-xs sm:text-sm text-medical-gray-500 font-bold font-vazir">{getText('advisor_step1_desc', "همدم شما رو با چه اسمی صدا می‌زنید؟")}</p>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="grid grid-cols-2 gap-3 sm:gap-4 max-w-md mx-auto">
<button
type="button"
onClick={() => setType("سگ")}
className={`flex flex-col items-center gap-4 p-8 rounded-[2.5rem] border-4 transition-all focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none cursor-pointer ${type === "سگ" ? 'border-canina-blue bg-white shadow-xl shadow-canina-blue/10 scale-105 text-canina-blue' : 'border-medical-gray-250 bg-white text-medical-gray-500 hover:border-canina-blue/30 hover:text-canina-blue/70'}`}
className={`flex flex-col items-center gap-2.5 p-5 sm:p-6 rounded-2xl border-2 transition-all cursor-pointer ${type === "سگ" ? 'border-canina-blue bg-white shadow-md shadow-canina-blue/10 text-canina-blue scale-102' : 'border-medical-gray-200 bg-white text-medical-gray-500 hover:border-canina-blue/30'}`}
>
<Dog className="w-12 h-12" />
<span className="text-lg font-black font-vazir">سگ</span>
<Dog className="w-8 h-8 sm:w-10 sm:h-10" />
<span className="text-sm sm:text-base font-black font-vazir">سگ</span>
</button>
<button
type="button"
onClick={() => setType("گربه")}
className={`flex flex-col items-center gap-4 p-8 rounded-[2.5rem] border-4 transition-all focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none cursor-pointer ${type === "گربه" ? 'border-canina-blue bg-white shadow-xl shadow-canina-blue/10 scale-105 text-canina-blue' : 'border-medical-gray-250 bg-white text-medical-gray-500 hover:border-canina-blue/30 hover:text-canina-blue/70'}`}
className={`flex flex-col items-center gap-2.5 p-5 sm:p-6 rounded-2xl border-2 transition-all cursor-pointer ${type === "گربه" ? 'border-canina-blue bg-white shadow-md shadow-canina-blue/10 text-canina-blue scale-102' : 'border-medical-gray-200 bg-white text-medical-gray-500 hover:border-canina-blue/30'}`}
>
<Cat className="w-12 h-12" />
<span className="text-lg font-black font-vazir">گربه</span>
<Cat className="w-8 h-8 sm:w-10 sm:h-10" />
<span className="text-sm sm:text-base font-black font-vazir">گربه</span>
</button>
</div>
<div className="grid md:grid-cols-2 gap-6">
<div className="space-y-2">
<label className="text-xs font-black text-medical-gray-600 pr-2">{getText('advisor_pet_name_label', "نام پت")}</label>
<div className="grid md:grid-cols-2 gap-4 max-w-xl mx-auto">
<div className="space-y-1.5">
<label className="text-xs font-black text-medical-gray-600 pr-1">{getText('advisor_pet_name_label', "نام پت")}</label>
<input
type="text"
value={name}
@ -353,17 +355,17 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
setName(e.target.value);
if (e.target.value.trim() && breed.trim()) setShowError(false);
}}
placeholder={getText('advisor_pet_name_placeholder', "لوسی، تدی...")}
placeholder={getText('advisor_pet_name_placeholder', "مثال: لوسی")}
className={cn(
"w-full bg-white border rounded-2xl py-4 px-6 focus:ring-2 outline-none font-black text-lg font-vazir placeholder-medical-gray-500 text-medical-gray-900 transition-all",
"w-full bg-white border rounded-xl py-3 px-4 focus:ring-2 outline-none font-bold text-sm font-vazir placeholder-medical-gray-400 text-medical-gray-900 transition-all",
showError && !name.trim()
? "border-red-500 focus:ring-red-500/30"
: "border-medical-gray-300 focus:ring-canina-blue/30 focus-visible:ring-4 focus-visible:ring-canina-blue/40"
: "border-medical-gray-200 focus:ring-canina-blue/30 focus:border-canina-blue"
)}
/>
</div>
<div className="space-y-2">
<label className="text-xs font-black text-medical-gray-600 pr-2">{getText('advisor_pet_breed_label', "نژاد")}</label>
<div className="space-y-1.5">
<label className="text-xs font-black text-medical-gray-600 pr-1">{getText('advisor_pet_breed_label', "نژاد")}</label>
<input
type="text"
value={breed}
@ -371,34 +373,37 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
setBreed(e.target.value);
if (name.trim() && e.target.value.trim()) setShowError(false);
}}
placeholder={getText('advisor_pet_breed_placeholder', "ژرمن، پرشین...")}
placeholder={getText('advisor_pet_breed_placeholder', "مثال: ژرمن شپرد")}
className={cn(
"w-full bg-white border rounded-2xl py-4 px-6 focus:ring-2 outline-none font-bold font-vazir placeholder-medical-gray-500 text-medical-gray-900 transition-all",
"w-full bg-white border rounded-xl py-3 px-4 focus:ring-2 outline-none font-bold text-sm font-vazir placeholder-medical-gray-400 text-medical-gray-900 transition-all",
showError && !breed.trim()
? "border-red-500 focus:ring-red-500/30"
: "border-medical-gray-300 focus:ring-canina-blue/30 focus-visible:ring-4 focus-visible:ring-canina-blue/40"
: "border-medical-gray-200 focus:ring-canina-blue/30 focus:border-canina-blue"
)}
/>
</div>
</div>
<motion.button
onClick={handleNext}
animate={shake ? { x: [-10, 10, -10, 10, -5, 5, -2, 2, 0] } : { x: 0 }}
transition={{ duration: 0.4 }}
className={cn(
"w-full py-6 rounded-3xl font-black text-lg flex items-center justify-center gap-3 font-vazir min-h-[48px] transition-all focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none bg-medical-gray-900 text-white hover:bg-canina-blue shadow-xl shadow-medical-gray-900/10",
(!name.trim() || !breed.trim()) ? "opacity-60 cursor-pointer" : ""
<div className="max-w-md mx-auto pt-2">
<motion.button
type="button"
onClick={handleNext}
animate={shake ? { x: [-10, 10, -10, 10, -5, 5, -2, 2, 0] } : { x: 0 }}
transition={{ duration: 0.4 }}
className={cn(
"w-full whitespace-nowrap bg-canina-blue text-white px-6 sm:px-10 py-3 sm:py-4 rounded-full font-bold text-sm sm:text-base hover:bg-canina-blue/90 hover:shadow-xl focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all flex items-center justify-center gap-2 group font-vazir min-h-[48px] shadow-md shadow-canina-blue/20 cursor-pointer",
(!name.trim() || !breed.trim()) ? "opacity-60" : ""
)}
>
<span>{getText('advisor_submit_btn', "ثبت هویت و ادامه")}</span>
<ChevronLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
</motion.button>
{showError && (!name.trim() || !breed.trim()) && (
<p className="text-center text-xs font-bold text-red-500 mt-2 font-vazir animate-pulse">
{getText('advisor_validation_warning', "⚠️ لطفاً ابتدا نام و نژاد پت را وارد کنید.")}
</p>
)}
>
{getText('advisor_submit_btn', "ثبت هویت و ادامه")}
<ChevronLeft className="w-5 h-5" />
</motion.button>
{showError && (!name.trim() || !breed.trim()) && (
<p className="text-center text-xs font-black text-red-500 mt-2 font-vazir animate-pulse">
{getText('advisor_validation_warning', "⚠️ لطفاً ابتدا نام و نژاد پت را در بالا وارد کنید.")}
</p>
)}
</div>
</motion.div>
)}
@ -408,60 +413,95 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
className="space-y-8"
className="space-y-6 max-w-xl mx-auto"
>
<div className="text-center">
<h3 className="text-2xl font-black text-medical-gray-900 mb-2 font-vazir uppercase italic tracking-tight">{getText('advisor_step2_title', "گام دوم: پایش فیزیکی")}</h3>
<p className="text-medical-gray-600 font-bold font-vazir">{getText('advisor_step2_desc', "اطلاعات فیزیکی دقیق به دوزبندی صحیح مکمل‌ها کمک می‌کند")}</p>
<h3 className="text-xl sm:text-2xl font-black text-medical-gray-900 mb-1.5 font-vazir tracking-tight">{getText('advisor_step2_title', "گام دوم: پایش فیزیکی")}</h3>
<p className="text-xs sm:text-sm text-medical-gray-500 font-bold font-vazir">{getText('advisor_step2_desc', "اطلاعات فیزیکی به دوزبندی دقیق مکمل‌ها کمک می‌کند")}</p>
</div>
<div className="grid grid-cols-2 gap-6">
<div className="space-y-4">
<label className="block text-center text-sm font-black text-medical-gray-800 font-vazir">{getText('advisor_age_label', "سن حیوان (سال)")}</label>
<div className="flex items-center gap-2 bg-white rounded-3xl p-2 border-2 border-medical-gray-200">
<button type="button" onClick={() => setAge(Math.max(0, age - 1))} className="w-10 h-10 shrink-0 flex items-center justify-center bg-medical-gray-50 rounded-xl text-medical-gray-900 hover:bg-canina-blue hover:text-white transition-all shadow-sm font-black text-lg">-</button>
<div className="flex-1 flex items-center justify-center gap-1">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{/* Age Input Box */}
<div className="bg-white p-3.5 rounded-2xl border border-medical-gray-200 shadow-xs space-y-2">
<label className="block text-center text-xs font-black text-medical-gray-700 font-vazir">{getText('advisor_age_label', "سن حیوان (سال)")}</label>
<div className="flex items-center justify-between gap-1.5 bg-medical-gray-50 rounded-xl p-1 border border-medical-gray-200">
<button
type="button"
onClick={() => setAge(Math.max(0, age - 1))}
className="w-9 h-9 shrink-0 flex items-center justify-center bg-white rounded-lg text-medical-gray-800 hover:bg-canina-blue hover:text-white transition-all shadow-xs font-black text-base cursor-pointer"
>
-
</button>
<div className="flex-1 flex items-center justify-center gap-1" dir="ltr">
<input
type="number"
min="0"
max="30"
value={age === 0 ? '' : age}
onChange={(e) => setAge(Math.max(0, parseInt(e.target.value) || 0))}
className="w-12 text-center text-xl font-black text-canina-blue font-vazir outline-none bg-transparent"
type="text"
inputMode="numeric"
pattern="[0-9]*"
value={age === 0 ? '' : age.toString()}
onChange={(e) => {
const val = e.target.value.replace(/[^0-9]/g, '');
setAge(val === '' ? 0 : Math.min(30, parseInt(val, 10)));
}}
className="w-14 text-center text-lg font-black text-canina-blue font-mono outline-none bg-transparent"
/>
<span className="text-xs font-bold text-medical-gray-400">سال</span>
<span className="text-[11px] font-bold text-medical-gray-400 font-vazir">سال</span>
</div>
<button type="button" onClick={() => setAge(age + 1)} className="w-10 h-10 shrink-0 flex items-center justify-center bg-medical-gray-50 rounded-xl text-medical-gray-900 hover:bg-canina-blue hover:text-white transition-all shadow-sm font-black text-lg">+</button>
<button
type="button"
onClick={() => setAge(Math.min(30, age + 1))}
className="w-9 h-9 shrink-0 flex items-center justify-center bg-white rounded-lg text-medical-gray-800 hover:bg-canina-blue hover:text-white transition-all shadow-xs font-black text-base cursor-pointer"
>
+
</button>
</div>
</div>
<div className="space-y-4">
<label className="block text-center text-sm font-black text-medical-gray-800 font-vazir">{getText('advisor_weight_label', "وزن حیوان (کیلوگرم)")}</label>
<div className="flex items-center gap-2 bg-white rounded-3xl p-2 border-2 border-medical-gray-200">
<button type="button" onClick={() => setWeight(Math.max(1, weight - 1))} className="w-10 h-10 shrink-0 flex items-center justify-center bg-medical-gray-50 rounded-xl text-medical-gray-900 hover:bg-canina-blue hover:text-white transition-all shadow-sm font-black text-lg">-</button>
<div className="flex-1 flex items-center justify-center gap-1">
{/* Weight Input Box */}
<div className="bg-white p-3.5 rounded-2xl border border-medical-gray-200 shadow-xs space-y-2">
<label className="block text-center text-xs font-black text-medical-gray-700 font-vazir">{getText('advisor_weight_label', "وزن حیوان (کیلوگرم)")}</label>
<div className="flex items-center justify-between gap-1.5 bg-medical-gray-50 rounded-xl p-1 border border-medical-gray-200">
<button
type="button"
onClick={() => setWeight(Math.max(1, weight - 1))}
className="w-9 h-9 shrink-0 flex items-center justify-center bg-white rounded-lg text-medical-gray-800 hover:bg-canina-blue hover:text-white transition-all shadow-xs font-black text-base cursor-pointer"
>
-
</button>
<div className="flex-1 flex items-center justify-center gap-1" dir="ltr">
<input
type="number"
min="1"
max="120"
value={weight === 0 ? '' : weight}
onChange={(e) => setWeight(Math.max(1, parseInt(e.target.value) || 1))}
className="w-16 text-center text-xl font-black text-canina-blue font-vazir outline-none bg-transparent"
type="text"
inputMode="numeric"
pattern="[0-9]*"
value={weight === 0 ? '' : weight.toString()}
onChange={(e) => {
const val = e.target.value.replace(/[^0-9]/g, '');
setWeight(val === '' ? 0 : Math.min(120, parseInt(val, 10)));
}}
className="w-14 text-center text-lg font-black text-canina-blue font-mono outline-none bg-transparent"
/>
<span className="text-xs font-bold text-medical-gray-400">kg</span>
<span className="text-[11px] font-bold text-medical-gray-400 font-vazir">kg</span>
</div>
<button type="button" onClick={() => setWeight(weight + 1)} className="w-10 h-10 shrink-0 flex items-center justify-center bg-medical-gray-50 rounded-xl text-medical-gray-900 hover:bg-canina-blue hover:text-white transition-all shadow-sm font-black text-lg">+</button>
<button
type="button"
onClick={() => setWeight(Math.min(120, weight + 1))}
className="w-9 h-9 shrink-0 flex items-center justify-center bg-white rounded-lg text-medical-gray-800 hover:bg-canina-blue hover:text-white transition-all shadow-xs font-black text-base cursor-pointer"
>
+
</button>
</div>
</div>
</div>
<div className="space-y-4">
<label className="block text-center text-xs font-black text-medical-gray-600 uppercase tracking-widest font-vazir">{getText('advisor_activity_label', "سطح فعالیت روزانه")}</label>
<div className="grid grid-cols-3 gap-3">
{/* Activity Level */}
<div className="space-y-2">
<label className="block text-center text-xs font-black text-medical-gray-600 font-vazir">{getText('advisor_activity_label', "سطح فعالیت روزانه")}</label>
<div className="grid grid-cols-3 gap-2">
{["کم", "متوسط", "زیاد"].map(level => (
<button
key={level}
type="button"
onClick={() => setActivityLevel(level as "کم" | "متوسط" | "زیاد")}
className={`py-4 rounded-2xl border-2 transition-all font-black italic focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none min-h-[48px] ${activityLevel === level ? 'border-canina-blue bg-white text-canina-blue shadow-lg' : 'border-transparent bg-white/50 text-medical-gray-500 hover:bg-white'}`}
className={`py-2.5 rounded-xl border-2 transition-all font-bold text-xs sm:text-sm cursor-pointer ${activityLevel === level ? 'border-canina-blue bg-white text-canina-blue shadow-sm' : 'border-transparent bg-white/70 text-medical-gray-500 hover:bg-white'}`}
>
{level}
</button>
@ -469,20 +509,23 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
</div>
</div>
<div className="flex gap-4 pt-4">
{/* Buttons (matching Hero layout) */}
<div className="whitespace-nowrap flex items-center justify-between gap-3 pt-3">
<button
type="button"
onClick={handleBack}
className="flex-1 py-6 bg-medical-gray-100 text-medical-gray-600 rounded-3xl font-black text-lg hover:bg-medical-gray-200 focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all flex items-center justify-center gap-3 font-vazir min-h-[48px]"
className="whitespace-nowrap bg-white border-2 border-medical-gray-300 text-medical-gray-700 px-4 sm:px-6 py-2.5 sm:py-3.5 rounded-full font-bold text-xs sm:text-sm hover:bg-medical-gray-50 transition-all flex items-center gap-1.5 cursor-pointer"
>
<ChevronRight className="w-5 h-5" />
{getText('advisor_back', "قبلی")}
</button>
<button
onClick={handleNext}
className="flex-[2] py-6 bg-medical-gray-900 text-white rounded-3xl font-black text-lg hover:bg-canina-blue focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all shadow-xl shadow-medical-gray-900/10 flex items-center justify-center gap-3 font-vazir min-h-[48px]"
>
{getText('advisor_next_symptoms', "بررسی علائم بالینی فعلی")}
<ChevronLeft className="w-5 h-5" />
<ChevronRight className="w-4 h-4" />
<span>{getText('advisor_back', "قبلی")}</span>
</button>
<button
type="button"
onClick={handleNext}
className="whitespace-nowrap bg-canina-blue text-white px-5 sm:px-8 py-2.5 sm:py-3.5 rounded-full font-bold text-xs sm:text-sm hover:bg-canina-blue/90 hover:shadow-lg transition-all flex items-center gap-1.5 shadow-md shadow-canina-blue/20 cursor-pointer"
>
<span>{getText('advisor_next_symptoms', "علائم بالینی")}</span>
<ChevronLeft className="w-4 h-4" />
</button>
</div>
</motion.div>
@ -494,42 +537,45 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
className="space-y-8"
className="space-y-5 max-w-2xl mx-auto"
>
<div className="text-center">
<h3 className="text-2xl font-black text-medical-gray-900 mb-2 font-vazir uppercase italic tracking-tight">{getText('advisor_step3_symptoms_title', "گام سوم: علائم بالینی فعلی")}</h3>
<p className="text-medical-gray-600 font-bold font-vazir">{getText('advisor_step3_symptoms_desc', "آیا همدم شما در حال حاضر هیچ‌کدام از علائم زیر را تجربه می‌کند؟")}</p>
<h3 className="text-xl sm:text-2xl font-black text-medical-gray-900 mb-1 font-vazir tracking-tight">{getText('advisor_step3_symptoms_title', "گام سوم: علائم بالینی فعلی")}</h3>
<p className="text-xs sm:text-sm text-medical-gray-500 font-bold font-vazir">{getText('advisor_step3_symptoms_desc', "آیا همدم شما در حال حاضر علائم زیر را دارد؟")}</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2.5 max-h-[50vh] sm:max-h-none overflow-y-auto pr-1">
{CURRENT_SYMPTOMS.map((opt) => (
<button
key={opt.id}
type="button"
onClick={() => toggleSymptom(opt.condition)}
className={`flex items-center gap-4 p-5 rounded-2xl border-2 transition-all text-right focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none min-h-[48px] ${currentSymptoms.includes(opt.condition) ? 'border-canina-blue bg-white shadow-md' : 'border-transparent bg-white/50 text-medical-gray-600 hover:bg-white'}`}
className={`flex items-center gap-3 p-3.5 rounded-xl border transition-all text-right cursor-pointer ${currentSymptoms.includes(opt.condition) ? 'border-canina-blue bg-canina-blue/5 shadow-xs text-canina-blue' : 'border-medical-gray-200 bg-white text-medical-gray-700 hover:border-canina-blue/40'}`}
>
<div className={`w-8 h-8 rounded-lg flex items-center justify-center border ${currentSymptoms.includes(opt.condition) ? 'bg-canina-blue border-canina-blue text-white' : 'border-medical-gray-300 text-transparent'}`}>
<ShieldCheck className="w-5 h-5" />
<div className={`w-5 h-5 rounded-md flex items-center justify-center border shrink-0 ${currentSymptoms.includes(opt.condition) ? 'bg-canina-blue border-canina-blue text-white' : 'border-medical-gray-300 bg-white'}`}>
{currentSymptoms.includes(opt.condition) && <Check className="w-3.5 h-3.5 stroke-[3]" />}
</div>
<span className={`text-sm font-black font-vazir ${currentSymptoms.includes(opt.condition) ? 'text-canina-blue' : 'text-medical-gray-900'}`}>{opt.label}</span>
<span className="text-xs sm:text-sm font-bold font-vazir leading-tight">{opt.label}</span>
</button>
))}
</div>
<div className="flex gap-4 pt-4">
<div className="whitespace-nowrap flex items-center justify-between gap-3 pt-3">
<button
type="button"
onClick={handleBack}
className="flex-1 py-6 bg-medical-gray-100 text-medical-gray-600 rounded-3xl font-black text-lg hover:bg-medical-gray-200 focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all flex items-center justify-center gap-3 font-vazir min-h-[48px]"
className="whitespace-nowrap bg-white border-2 border-medical-gray-300 text-medical-gray-700 px-4 sm:px-6 py-2.5 sm:py-3.5 rounded-full font-bold text-xs sm:text-sm hover:bg-medical-gray-50 transition-all flex items-center gap-1.5 cursor-pointer"
>
<ChevronRight className="w-5 h-5" />
{getText('advisor_back', "قبلی")}
<ChevronRight className="w-4 h-4" />
<span>{getText('advisor_back', "قبلی")}</span>
</button>
<button
type="button"
onClick={handleNext}
className="flex-[2] py-6 bg-medical-gray-900 text-white rounded-3xl font-black text-lg hover:bg-canina-blue focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all shadow-xl shadow-medical-gray-900/10 flex items-center justify-center gap-3 font-vazir min-h-[48px]"
className="whitespace-nowrap bg-canina-blue text-white px-5 sm:px-8 py-2.5 sm:py-3.5 rounded-full font-bold text-xs sm:text-sm hover:bg-canina-blue/90 hover:shadow-lg transition-all flex items-center gap-1.5 shadow-md shadow-canina-blue/20 cursor-pointer"
>
{getText('advisor_next_history', "سوابق پزشکی و جراحی")}
<ChevronLeft className="w-5 h-5" />
<span>{getText('advisor_next_history', "سوابق پزشکی")}</span>
<ChevronLeft className="w-4 h-4" />
</button>
</div>
</motion.div>
@ -541,42 +587,45 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
className="space-y-8"
className="space-y-5 max-w-2xl mx-auto"
>
<div className="text-center">
<h3 className="text-2xl font-black text-medical-gray-900 mb-2 font-vazir uppercase italic tracking-tight">{getText('advisor_step4_history_title', "گام چهارم: سوابق پزشکی و جراحی")}</h3>
<p className="text-medical-gray-600 font-bold font-vazir">{getText('advisor_step4_history_desc', "در صورت وجود سابقه جراحی، بارداری یا نارسایی، آن را مشخص کنید")}</p>
<h3 className="text-xl sm:text-2xl font-black text-medical-gray-900 mb-1 font-vazir tracking-tight">{getText('advisor_step4_history_title', "گام چهارم: سوابق پزشکی")}</h3>
<p className="text-xs sm:text-sm text-medical-gray-500 font-bold font-vazir">{getText('advisor_step4_history_desc', "در صورت وجود سابقه جراحی، بارداری یا نارسایی، آن را مشخص کنید")}</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2.5 max-h-[50vh] sm:max-h-none overflow-y-auto pr-1">
{MEDICAL_HISTORIES.map((opt) => (
<button
key={opt.id}
type="button"
onClick={() => toggleMedicalHistory(opt.condition)}
className={`flex items-center gap-4 p-5 rounded-2xl border-2 transition-all text-right focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none min-h-[48px] ${medicalConditions.includes(opt.condition) ? 'border-canina-blue bg-white shadow-md' : 'border-transparent bg-white/50 text-medical-gray-600 hover:bg-white'}`}
className={`flex items-center gap-3 p-3.5 rounded-xl border transition-all text-right cursor-pointer ${medicalConditions.includes(opt.condition) ? 'border-canina-blue bg-canina-blue/5 shadow-xs text-canina-blue' : 'border-medical-gray-200 bg-white text-medical-gray-700 hover:border-canina-blue/40'}`}
>
<div className={`w-8 h-8 rounded-lg flex items-center justify-center border ${medicalConditions.includes(opt.condition) ? 'bg-canina-blue border-canina-blue text-white' : 'border-medical-gray-300 text-transparent'}`}>
<ShieldCheck className="w-5 h-5" />
<div className={`w-5 h-5 rounded-md flex items-center justify-center border shrink-0 ${medicalConditions.includes(opt.condition) ? 'bg-canina-blue border-canina-blue text-white' : 'border-medical-gray-300 bg-white'}`}>
{medicalConditions.includes(opt.condition) && <Check className="w-3.5 h-3.5 stroke-[3]" />}
</div>
<span className={`text-sm font-black font-vazir ${medicalConditions.includes(opt.condition) ? 'text-canina-blue' : 'text-medical-gray-900'}`}>{opt.label}</span>
<span className="text-xs sm:text-sm font-bold font-vazir leading-tight">{opt.label}</span>
</button>
))}
</div>
<div className="flex gap-4 pt-4">
<div className="whitespace-nowrap flex items-center justify-between gap-3 pt-3">
<button
type="button"
onClick={handleBack}
className="flex-1 py-6 bg-medical-gray-100 text-medical-gray-600 rounded-3xl font-black text-lg hover:bg-medical-gray-200 focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all flex items-center justify-center gap-3 font-vazir min-h-[48px]"
className="whitespace-nowrap bg-white border-2 border-medical-gray-300 text-medical-gray-700 px-4 sm:px-6 py-2.5 sm:py-3.5 rounded-full font-bold text-xs sm:text-sm hover:bg-medical-gray-50 transition-all flex items-center gap-1.5 cursor-pointer"
>
<ChevronRight className="w-5 h-5" />
{getText('advisor_back', "قبلی")}
<ChevronRight className="w-4 h-4" />
<span>{getText('advisor_back', "قبلی")}</span>
</button>
<button
type="button"
onClick={handleSubmit}
className="flex-[2] py-6 bg-canina-blue text-white rounded-3xl font-black text-lg hover:bg-canina-blue/90 focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all shadow-xl shadow-canina-blue/30 flex items-center justify-center gap-3 font-vazir min-h-[48px]"
className="whitespace-nowrap bg-emerald-600 text-white px-6 sm:px-9 py-2.5 sm:py-3.5 rounded-full font-bold text-xs sm:text-sm hover:bg-emerald-700 hover:shadow-lg transition-all flex items-center gap-1.5 shadow-md shadow-emerald-600/20 cursor-pointer"
>
{getText('advisor_complete', "تکمیل و صدور شناسنامه")}
<ChevronLeft className="w-5 h-5" />
<span>{getText('advisor_complete', "صدور شناسنامه و پیشنهاد")}</span>
<ChevronLeft className="w-4 h-4" />
</button>
</div>
</motion.div>

View File

@ -71,20 +71,37 @@ export default function VideoModalPlayer({ isOpen, onClose, video }: VideoModalP
};
}, [showRateMenu]);
// Reset states on open/close
// Reset / Restore states on open/close
useEffect(() => {
if (isOpen) {
if (isOpen && video?.id) {
const savedTime = localStorage.getItem(`video_time_${video.id}`);
const initialTime = savedTime ? parseFloat(savedTime) : 0;
setIsPlaying(true);
setShowControls(true);
setIsExpandedDesc(false);
setShowRateMenu(false);
setBufferedEnd(0);
} else {
if (videoRef.current && initialTime > 0) {
videoRef.current.currentTime = initialTime;
setCurrentTime(initialTime);
}
} else if (!isOpen && video?.id) {
if (currentTime > 0) {
localStorage.setItem(`video_time_${video.id}`, currentTime.toString());
}
setIsPlaying(false);
setCurrentTime(0);
setBufferedEnd(0);
}
}, [isOpen, video]);
}, [isOpen, video?.id]);
// Save playback time periodically
useEffect(() => {
if (video?.id && currentTime > 0) {
localStorage.setItem(`video_time_${video.id}`, currentTime.toString());
}
}, [currentTime, video?.id]);
// Update buffered track
const updateBuffer = useCallback(() => {
@ -254,164 +271,147 @@ export default function VideoModalPlayer({ isOpen, onClose, video }: VideoModalP
return (
<AnimatePresence>
<div className="fixed inset-0 z-[120] flex items-center justify-center p-3 sm:p-6 font-vazir" dir="rtl">
{/* Backdrop */}
<div className="fixed inset-0 z-[120] flex items-center justify-center p-3 sm:p-6 font-vazir overflow-y-auto" dir="rtl">
{/* Backdrop (backdrop dismiss disabled to prevent accidental closure) */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
className="absolute inset-0 bg-black/90 backdrop-blur-xl"
className="fixed inset-0 bg-black/90 backdrop-blur-xl"
/>
{/* Modal Window */}
<motion.div
ref={containerRef}
initial={{ opacity: 0, scale: 0.95, y: 15 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 15 }}
transition={{ duration: 0.25 }}
onMouseMove={resetControlsTimer}
onTouchStart={resetControlsTimer}
className="relative bg-black w-full max-w-5xl aspect-video rounded-[2.5rem] overflow-hidden shadow-2xl border border-white/10 flex flex-col justify-between select-none"
>
{/* Iframe Support (Aparat / Youtube) */}
{isIframe ? (
<div className="w-full h-full flex items-center justify-center relative">
<button
onClick={onClose}
className="absolute top-4 left-4 z-30 p-2.5 rounded-full bg-black/60 hover:bg-black/90 text-white transition-all cursor-pointer"
title="بستن"
>
<X className="w-5 h-5" />
</button>
<div
className="w-full h-full [&>iframe]:w-full [&>iframe]:h-full"
dangerouslySetInnerHTML={{ __html: video.videoUrl || '' }}
/>
</div>
) : (
<>
{/* Native Video Element */}
<div
className="absolute inset-0 flex items-center justify-center cursor-pointer"
onClick={togglePlay}
>
<video
ref={videoRef}
src={video.videoUrl}
poster={video.thumbnail}
autoPlay
playsInline
onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
onTimeUpdate={() => {
if (videoRef.current) {
setCurrentTime(videoRef.current.currentTime);
updateBuffer();
}
}}
onProgress={updateBuffer}
onLoadedMetadata={() => {
if (videoRef.current) setDuration(videoRef.current.duration);
setIsBuffering(false);
updateBuffer();
}}
onWaiting={() => setIsBuffering(true)}
onPlaying={() => setIsBuffering(false)}
onEnded={() => {
setIsPlaying(false);
setShowControls(true);
}}
className="w-full h-full object-contain"
{/* Modal Window Wrapper */}
<div className="relative w-full max-w-5xl flex flex-col gap-3 my-auto z-10">
<motion.div
ref={containerRef}
initial={{ opacity: 0, scale: 0.95, y: 15 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 15 }}
transition={{ duration: 0.25 }}
onMouseMove={resetControlsTimer}
onTouchStart={resetControlsTimer}
className="relative bg-black w-full aspect-video rounded-3xl sm:rounded-[2.5rem] overflow-hidden shadow-2xl border border-white/10 flex flex-col justify-between select-none"
>
{/* Iframe Support (Aparat / Youtube) */}
{isIframe ? (
<div className="w-full h-full flex items-center justify-center relative">
<button
type="button"
onClick={onClose}
className="absolute top-4 left-4 z-30 p-2.5 rounded-full bg-black/60 hover:bg-black/90 text-white transition-all cursor-pointer"
title="بستن"
>
<X className="w-5 h-5" />
</button>
<div
className="w-full h-full [&>iframe]:w-full [&>iframe]:h-full"
dangerouslySetInnerHTML={{ __html: video.videoUrl || '' }}
/>
{/* Buffering Spinner */}
{isBuffering && (
<div className="absolute inset-0 flex items-center justify-center bg-black/30 backdrop-blur-2xs pointer-events-none">
<Loader2 className="w-12 h-12 text-white animate-spin" />
</div>
)}
</div>
) : (
<>
{/* Native Video Element */}
<div
className="absolute inset-0 flex items-center justify-center cursor-pointer"
onClick={togglePlay}
>
<video
ref={videoRef}
src={video.videoUrl}
poster={video.thumbnail}
autoPlay
playsInline
onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
onTimeUpdate={() => {
if (videoRef.current) {
setCurrentTime(videoRef.current.currentTime);
updateBuffer();
}
}}
onProgress={updateBuffer}
onLoadedMetadata={() => {
if (videoRef.current) setDuration(videoRef.current.duration);
setIsBuffering(false);
updateBuffer();
}}
onWaiting={() => setIsBuffering(true)}
onPlaying={() => setIsBuffering(false)}
onEnded={() => {
setIsPlaying(false);
setShowControls(true);
}}
className="w-full h-full object-contain"
/>
{/* Top Header Overlay */}
<div
className={`absolute top-0 left-0 right-0 p-4 sm:p-6 bg-gradient-to-b from-black/80 via-black/40 to-transparent transition-opacity duration-300 z-20 flex items-center justify-between ${
showControls ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none"
}`}
>
<div className="flex items-center gap-3 text-right">
<div className="w-8 h-8 rounded-full bg-canina-blue/20 text-canina-blue flex items-center justify-center shrink-0">
<Sparkles className="w-4 h-4" />
{/* Buffering Spinner */}
{isBuffering && (
<div className="absolute inset-0 flex items-center justify-center bg-black/30 backdrop-blur-2xs pointer-events-none">
<Loader2 className="w-12 h-12 text-white animate-spin" />
</div>
)}
</div>
{/* Top Header Overlay */}
<div
className={`absolute top-0 left-0 right-0 p-4 sm:p-6 bg-gradient-to-b from-black/80 via-black/40 to-transparent transition-opacity duration-300 z-20 flex items-center justify-between ${
showControls ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none"
}`}
>
<div className="flex items-center gap-3 text-right">
<div className="w-8 h-8 rounded-full bg-canina-blue/20 text-canina-blue flex items-center justify-center shrink-0">
<Sparkles className="w-4 h-4" />
</div>
<div>
<h3 className="text-white text-sm sm:text-base md:text-lg font-black line-clamp-1">
{video.title}
</h3>
<p className="text-white/60 text-xs font-bold mt-0.5">
{video.doctor || "کادر علمی کنینا"}
</p>
</div>
</div>
<div>
<h3 className="text-white text-base sm:text-lg font-black line-clamp-1">
{video.title}
</h3>
<p className="text-white/60 text-xs font-bold mt-0.5">
{video.doctor || "کادر علمی کنینا"}
</p>
<div className="flex items-center gap-2">
{/* Share button */}
<button
type="button"
onClick={(e) => { e.stopPropagation(); handleShare(); }}
className="p-2.5 rounded-full bg-white/10 hover:bg-white/20 text-white transition-all cursor-pointer relative"
title="اشتراک‌گذاری"
>
{isCopied ? <Check className="w-4 h-4 text-emerald-400" /> : <Share2 className="w-4 h-4" />}
</button>
{/* Download button */}
<button
type="button"
onClick={(e) => { e.stopPropagation(); handleDownload(); }}
className="p-2.5 rounded-full bg-white/10 hover:bg-white/20 text-white transition-all cursor-pointer"
title="دانلود ویدئو"
>
<Download className="w-4 h-4" />
</button>
{/* Close button */}
<button
type="button"
onClick={(e) => { e.stopPropagation(); onClose(); }}
className="p-2.5 rounded-full bg-white/10 hover:bg-red-500/80 text-white transition-all cursor-pointer"
title="بستن"
>
<X className="w-4 h-4" />
</button>
</div>
</div>
<div className="flex items-center gap-2">
{/* Share button */}
<button
type="button"
onClick={(e) => { e.stopPropagation(); handleShare(); }}
className="p-2.5 rounded-full bg-white/10 hover:bg-white/20 text-white transition-all cursor-pointer relative"
title="اشتراک‌گذاری"
>
{isCopied ? <Check className="w-4 h-4 text-emerald-400" /> : <Share2 className="w-4 h-4" />}
</button>
{/* Download button */}
<button
type="button"
onClick={(e) => { e.stopPropagation(); handleDownload(); }}
className="p-2.5 rounded-full bg-white/10 hover:bg-white/20 text-white transition-all cursor-pointer"
title="دانلود ویدئو"
>
<Download className="w-4 h-4" />
</button>
{/* Close button */}
<button
type="button"
onClick={(e) => { e.stopPropagation(); onClose(); }}
className="p-2.5 rounded-full bg-white/10 hover:bg-red-500/80 text-white transition-all cursor-pointer"
title="بستن"
>
<X className="w-4 h-4" />
</button>
</div>
</div>
{/* Bottom Custom Controls Bar */}
<div
className={`absolute bottom-0 left-0 right-0 p-4 sm:p-6 bg-gradient-to-t from-black/90 via-black/60 to-transparent transition-opacity duration-300 z-20 space-y-3 ${
showControls ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none"
}`}
onClick={(e) => e.stopPropagation()}
>
{/* Expandable Description Box if available */}
{video.description && (
<div className="bg-white/10 backdrop-blur-md rounded-2xl p-3 text-white text-xs border border-white/10">
<p className={`font-medium leading-relaxed ${isExpandedDesc ? "" : "line-clamp-1"}`}>
{video.description}
</p>
{video.description.length > 80 && (
<button
type="button"
onClick={() => setIsExpandedDesc(!isExpandedDesc)}
className="text-canina-blue hover:underline font-bold mt-1 inline-flex items-center gap-1 cursor-pointer"
>
<span>{isExpandedDesc ? "بستن توضیحات" : "مشاهده بیشتر توضیحات"}</span>
{isExpandedDesc ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />}
</button>
)}
</div>
)}
{/* Bottom Custom Controls Bar */}
<div
className={`absolute bottom-0 left-0 right-0 p-4 sm:p-6 bg-gradient-to-t from-black/90 via-black/60 to-transparent transition-opacity duration-300 z-20 space-y-3 ${
showControls ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none"
}`}
onClick={(e) => e.stopPropagation()}
>
{/* Progress / Seek Bar with filled progress & buffer tracks */}
<div className="flex items-center gap-3 dir-ltr" dir="ltr">
@ -570,7 +570,30 @@ export default function VideoModalPlayer({ isOpen, onClose, video }: VideoModalP
</>
)}
</motion.div>
{/* Video Description Box (Rendered outside & below the video player for clean viewing) */}
{video.description && (
<div className="bg-neutral-900/90 backdrop-blur-md rounded-2xl p-4 text-white text-xs border border-white/10 shadow-xl space-y-2 text-right">
<div className="flex items-center justify-between">
<span className="font-black text-canina-blue text-xs">توضیحات و نکات علمی ویدئو:</span>
{video.description.length > 120 && (
<button
type="button"
onClick={() => setIsExpandedDesc(!isExpandedDesc)}
className="text-white/60 hover:text-white text-[11px] font-bold inline-flex items-center gap-1 cursor-pointer"
>
<span>{isExpandedDesc ? "بستن متن" : "مشاهده کامل متن"}</span>
{isExpandedDesc ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />}
</button>
)}
</div>
<p className={`font-medium text-white/80 leading-relaxed ${isExpandedDesc ? "" : "line-clamp-2"}`}>
{video.description}
</p>
</div>
)}
</div>
</AnimatePresence>
</div>
</AnimatePresence>
);
}

View File

@ -1,9 +1,10 @@
"use client";
import React, { useState } from "react";
import { motion } from "motion/react";
import { ChevronLeft, PlayCircle, Search, Clock, User } from "lucide-react";
import { ChevronLeft, PlayCircle, Search, Clock, User, Eye, Share2, Check } from "lucide-react";
import SafeImage from "./SafeImage";
import BackButton from "./BackButton";
import { toast } from "sonner";
import { videoService, Video } from "../lib/services/videoService";
import { useRouter } from 'next/navigation';
@ -15,6 +16,7 @@ export default function VideosPage() {
const [isLoading, setIsLoading] = useState(true);
const [selectedVideo, setSelectedVideo] = useState<Video | null>(null);
const [search, setSearch] = useState("");
const [isCopied, setIsCopied] = useState(false);
React.useEffect(() => {
setIsLoading(true);
@ -35,6 +37,15 @@ export default function VideosPage() {
});
}, []);
const handleSharePage = () => {
if (typeof window !== 'undefined') {
navigator.clipboard.writeText(window.location.href);
setIsCopied(true);
toast.success("لینک آکادمی ویدئویی کپی شد.");
setTimeout(() => setIsCopied(false), 2000);
}
};
const filteredVideos = videos.filter(v =>
(v.title || '').toLowerCase().includes(search.toLowerCase()) ||
(v.doctor || '').toLowerCase().includes(search.toLowerCase()) ||
@ -42,26 +53,36 @@ export default function VideosPage() {
);
return (
<div className="min-h-screen bg-medical-gray-900 pt-8 pb-20 px-4 font-vazir" dir="rtl">
<div className="min-h-screen bg-medical-gray-900 pt-6 pb-20 px-4 font-vazir" dir="rtl">
<div className="max-w-7xl mx-auto">
{/* Header */}
<div className="flex flex-col md:flex-row items-center justify-between mb-16 gap-6">
<div className="text-right">
<div className="inline-flex items-center gap-2 px-3 py-1 bg-canina-blue/10 text-canina-blue rounded-full text-[10px] font-black uppercase tracking-widest mb-4">
<PlayCircle className="w-3 h-3" />
Video Academy
</div>
<h1 className="text-4xl lg:text-6xl font-black text-white italic leading-tight">
آکادمی ویدئویی <span className="text-canina-blue">کنینا</span>
</h1>
{/* Standard Page Top Bar */}
<div className="flex items-center justify-between gap-4 mb-8 bg-white/5 p-4 rounded-3xl border border-white/10 backdrop-blur-md">
<div className="flex items-center gap-3">
<BackButton className="bg-white/10 text-white border-white/15 hover:bg-white/20 hover:text-white" />
<div className="hidden sm:inline-flex items-center gap-1.5 px-3 py-1 bg-canina-blue/20 text-canina-blue rounded-full text-xs font-black">
<PlayCircle className="w-3.5 h-3.5" />
<span>آکادمی تخصصی کنینا</span>
</div>
<button
onClick={() => router.push('/')}
className="px-8 py-3 bg-white/5 border border-white/10 rounded-2xl text-white shadow-sm flex items-center gap-2 font-black hover:bg-white/10 transition-all font-vazir"
>
<span>بازگشت</span>
<ChevronLeft className="w-5 h-5" />
</button>
</div>
<button
type="button"
onClick={handleSharePage}
className="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-xl bg-white/10 hover:bg-white/20 text-white text-xs font-bold transition-all border border-white/10 cursor-pointer"
>
{isCopied ? <Check className="w-3.5 h-3.5 text-emerald-400" /> : <Share2 className="w-3.5 h-3.5" />}
<span>اشتراک‌گذاری صفحه</span>
</button>
</div>
{/* Title Header */}
<div className="text-right mb-10">
<h1 className="text-3xl lg:text-5xl font-black text-white italic leading-tight mb-2">
آکادمی ویدئویی <span className="text-canina-blue">کنینا</span>
</h1>
<p className="text-white/60 text-xs sm:text-sm font-medium">
ویدئوهای آموزشی، مشاوره‌های تخصصی دامپزشکی و راهنمای مصرف مکمل‌ها
</p>
</div>
{/* Search Bar */}
@ -72,7 +93,7 @@ export default function VideosPage() {
placeholder="جستجو در ویدئوهای آموزشی..."
value={search}
onChange={e => setSearch(e.target.value)}
className="w-full bg-white/5 border border-white/10 rounded-[2rem] py-5 pr-14 pl-6 text-white outline-none focus:bg-white/10 transition-all font-bold"
className="w-full bg-white/5 border border-white/10 rounded-[2rem] py-4 pr-14 pl-6 text-white outline-none focus:bg-white/10 transition-all font-bold text-sm"
/>
</div>
@ -107,39 +128,50 @@ export default function VideosPage() {
videoService.incrementViews(video.id);
}}
>
<div className="relative aspect-video rounded-[2.5rem] overflow-hidden mb-6 shadow-2xl border border-white/5 bg-black/40">
<div className="relative aspect-video rounded-3xl overflow-hidden mb-4 shadow-2xl border border-white/5 bg-black/40">
<SafeImage
src={video.thumbnail}
alt={video.title}
className="w-full h-full"
imgClassName="object-cover group-hover:scale-110 transition-transform duration-700 opacity-60 group-hover:opacity-100"
imgClassName="object-cover group-hover:scale-105 transition-transform duration-500 opacity-70 group-hover:opacity-100"
/>
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-16 h-16 bg-canina-blue rounded-full flex items-center justify-center text-white shadow-2xl group-hover:scale-110 transition-transform">
<PlayCircle className="w-10 h-10" />
<div className="w-14 h-14 bg-canina-blue rounded-full flex items-center justify-center text-white shadow-xl group-hover:scale-110 transition-transform">
<PlayCircle className="w-9 h-9" />
</div>
</div>
{video.duration && (
<div className="absolute bottom-4 right-4 bg-black/60 backdrop-blur-md text-white px-3 py-1 rounded-lg text-[10px] font-black">
<div className="absolute bottom-3 right-3 bg-black/70 backdrop-blur-md text-white px-2.5 py-1 rounded-lg text-[10px] font-black">
{video.duration}
</div>
)}
{/* View Count Badge */}
<div className="absolute top-3 left-3 bg-black/60 backdrop-blur-md text-white/90 px-2.5 py-1 rounded-lg text-[10px] font-bold flex items-center gap-1">
<Eye className="w-3 h-3 text-canina-blue" />
<span>{(video.viewsCount ?? 0).toLocaleString('fa-IR')}</span>
</div>
</div>
<div className="px-4">
<h3 className="text-xl font-black text-white group-hover:text-canina-blue transition-colors mb-2 leading-tight">
<div className="px-2">
<h3 className="text-base sm:text-lg font-black text-white group-hover:text-canina-blue transition-colors mb-2 leading-snug line-clamp-2">
{video.title}
</h3>
<div className="flex items-center gap-4 text-xs font-bold text-white/40">
<div className="flex items-center justify-between text-xs font-bold text-white/40 pt-1">
<div className="flex items-center gap-1">
<User className="w-3 h-3" />
{video.doctor || 'کادر علمی کنینا'}
<User className="w-3.5 h-3.5" />
<span>{video.doctor || 'کادر علمی کنینا'}</span>
</div>
{video.duration && (
<div className="flex items-center gap-1">
<Clock className="w-3 h-3" />
<span>{video.duration}</span>
<div className="flex items-center gap-3">
{video.duration && (
<div className="flex items-center gap-1">
<Clock className="w-3 h-3" />
<span>{video.duration}</span>
</div>
)}
<div className="flex items-center gap-1 text-white/60">
<Eye className="w-3 h-3" />
<span>{(video.viewsCount ?? 0).toLocaleString('fa-IR')}</span>
</div>
)}
</div>
</div>
</div>
</motion.div>

View File

@ -32,6 +32,13 @@ const mockProducts = [
},
];
const mockPush = vi.fn();
vi.mock('next/navigation', () => ({
useRouter: () => ({
push: mockPush,
}),
}));
describe('FeaturedProducts', () => {
beforeEach(() => {
vi.clearAllMocks();
@ -42,14 +49,14 @@ describe('FeaturedProducts', () => {
it('renders loading skeletons initially', () => {
vi.mocked(productService.getFeaturedProducts).mockReturnValue(new Promise(() => {}));
const { container } = render(<FeaturedProducts onProductClick={vi.fn()} onShopNavigate={vi.fn()} />);
const { container } = render(<FeaturedProducts />);
// Check if skeletons are present (e.g. searching for animate-pulse)
expect(container.getElementsByClassName('animate-pulse').length).toBeGreaterThan(0);
});
it('renders products once loaded', async () => {
vi.mocked(productService.getFeaturedProducts).mockResolvedValue(mockProducts as unknown as ReturnType<typeof productService.getFeaturedProducts>);
render(<FeaturedProducts onProductClick={vi.fn()} onShopNavigate={vi.fn()} />);
render(<FeaturedProducts />);
await waitFor(() => {
expect(screen.getByText('Canhydrox GAG')).toBeInTheDocument();
@ -57,26 +64,24 @@ describe('FeaturedProducts', () => {
expect(screen.getByText('Joint helper')).toBeInTheDocument();
});
it('calls onProductClick when product card is clicked', async () => {
const handleProductClick = vi.fn();
it('navigates to product details when product card is clicked', async () => {
vi.mocked(productService.getFeaturedProducts).mockResolvedValue(mockProducts as unknown as ReturnType<typeof productService.getFeaturedProducts>);
render(<FeaturedProducts onProductClick={handleProductClick} onShopNavigate={vi.fn()} />);
render(<FeaturedProducts />);
await waitFor(() => {
expect(screen.getByText('Canhydrox GAG')).toBeInTheDocument();
});
fireEvent.click(screen.getByText('Canhydrox GAG'));
expect(handleProductClick).toHaveBeenCalledWith(mockProducts[0]);
expect(mockPush).toHaveBeenCalledWith('/shop/prod-1');
});
it('calls onShopNavigate when navigation link is clicked', async () => {
const handleShopNavigate = vi.fn();
it('navigates to shop when view all link is clicked', async () => {
vi.mocked(productService.getFeaturedProducts).mockResolvedValue(mockProducts as unknown as ReturnType<typeof productService.getFeaturedProducts>);
render(<FeaturedProducts onProductClick={vi.fn()} onShopNavigate={handleShopNavigate} />);
render(<FeaturedProducts />);
const navBtn = screen.getByText('مشاهده تمامی محصولات');
fireEvent.click(navBtn);
expect(handleShopNavigate).toHaveBeenCalled();
expect(mockPush).toHaveBeenCalledWith('/shop');
});
});

View File

@ -1,4 +1,4 @@
"use client";
"use client";
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import React from 'react';
@ -6,39 +6,23 @@ import Footer from '../Footer';
describe('Footer', () => {
it('renders footer brand text and standard layout elements', () => {
render(<Footer onNavigate={vi.fn()} onShopNavigate={vi.fn()} onB2BOpen={vi.fn()} />);
render(<Footer onB2BOpen={vi.fn()} />);
expect(screen.getByText('کنینا ایران')).toBeInTheDocument();
expect(screen.getByText('نماینده رسمی در ایران')).toBeInTheDocument();
expect(screen.getByText('Canina')).toBeInTheDocument();
expect(screen.getByText('ایران')).toBeInTheDocument();
expect(screen.getByText('تهران، جردن، خیابان سعیدی، ساختمان کنینا، طبقه ۵، واحد ۱۹')).toBeInTheDocument();
});
it('triggers onNavigate and onShopNavigate when quick links are clicked', () => {
const handleNavigate = vi.fn();
const handleShopNavigate = vi.fn();
const handleB2BOpen = vi.fn();
it('renders navigation links properly', () => {
render(<Footer onB2BOpen={vi.fn()} />);
render(
<Footer
onNavigate={handleNavigate}
onShopNavigate={handleShopNavigate}
onB2BOpen={handleB2BOpen}
/>
);
const shopLink = screen.getByText('محصولات تخصصی ۲۰۲۴').closest('a');
expect(shopLink).toHaveAttribute('href', '/shop');
// Click quick link for shop
const shopLink = screen.getByText('محصولات تخصصی ۲۰۲۴');
fireEvent.click(shopLink);
expect(handleShopNavigate).toHaveBeenCalled();
const blogLink = screen.getByText('مجله سلامت پت (وبلاگ)').closest('a');
expect(blogLink).toHaveAttribute('href', '/blog');
// Click quick link for blog
const blogLink = screen.getByText('مجله سلامت پت (وبلاگ)');
fireEvent.click(blogLink);
expect(handleNavigate).toHaveBeenCalledWith('blog');
// Click B2B link
const b2bLink = screen.getByText('پنل سفارش عمده (B2B)');
fireEvent.click(b2bLink);
expect(handleB2BOpen).toHaveBeenCalled();
const trustLink = screen.getByText('نمادهای اعتماد و مجوزهای رسمی').closest('a');
expect(trustLink).toHaveAttribute('href', '/trust-seals');
});
});

View File

@ -86,7 +86,9 @@ describe('Header', () => {
);
expect(screen.queryByText('ورود / ثبت‌نام')).not.toBeInTheDocument();
expect(screen.getByText('کوروش')).toBeInTheDocument();
const userBtn = screen.getByText('کوروش');
expect(userBtn).toBeInTheDocument();
fireEvent.click(userBtn);
expect(screen.getByText('ملوس')).toBeInTheDocument();
});

View File

@ -187,7 +187,13 @@ export const useUserStore = create<UserStore>()(
},
fetchProfile: async () => {
try {
const token = typeof window !== 'undefined' && window.localStorage ? localStorage.getItem('accessToken') : (globalThis as unknown as { _testToken?: string })._testToken;
let token: string | null = null;
try {
token = localStorage.getItem('accessToken');
} catch {}
if (!token) {
token = (globalThis as unknown as { _testToken?: string })._testToken || null;
}
if (!token) {
try { usePetStore.getState().reset(); } catch {}
try { localStorage.removeItem("canina-pets"); } catch {}

View File

@ -13,6 +13,37 @@ Object.defineProperty(window, 'IntersectionObserver', {
value: MockIntersectionObserver,
});
const localStorageMock = (() => {
let store: Record<string, string> = {};
return {
getItem: (key: string) => store[key] || null,
setItem: (key: string, value: string) => {
store[key] = value.toString();
},
removeItem: (key: string) => {
delete store[key];
},
clear: () => {
store = {};
},
};
})();
Object.defineProperty(window, 'localStorage', {
value: localStorageMock,
writable: true,
});
Object.defineProperty(window, 'sessionStorage', {
value: localStorageMock,
writable: true,
});
if (typeof globalThis !== 'undefined') {
(globalThis as any).localStorage = localStorageMock;
(globalThis as any).sessionStorage = localStorageMock;
}
vi.mock('next/navigation', () => ({
useRouter: () => ({
push: vi.fn(),

View File

@ -3,25 +3,25 @@
"1": "app.module.ts",
"2": "SmsService",
"3": "ProductService",
"4": "SafeImage.tsx",
"4": "utils.ts",
"5": "CmsController",
"6": "tickets.controller.ts",
"7": "pets/pets.controller.ts",
"8": "admin.module.ts",
"7": "Button.tsx",
"8": "ReportsController",
"9": "devDependencies",
"10": "ReviewsService",
"11": "Spinner.tsx",
"11": "MediaSelector.tsx",
"12": "PetProfile.tsx",
"13": "app-audit-verification.e2e-spec.js",
"14": "lib/services/api.ts",
"14": "UserDashboard.tsx",
"15": "src/services/api.ts",
"16": "DoctorQueryDto",
"17": "schema.ts",
"18": "JwtAuthGuard",
"19": "ProductsController",
"19": "ProductsService",
"20": "CreateVideoDto",
"21": "ProductPage.tsx",
"22": "UserDashboard.tsx",
"22": "lib/services/api.ts",
"23": "MenuService",
"24": "BE-001",
"25": "FE-001",
@ -32,42 +32,42 @@
"30": "DEVOPS-001",
"31": "DOC-001",
"32": "adminRoutes.tsx",
"33": "WholesaleApplyDto",
"33": "WholesaleService",
"34": "B2BService",
"35": "AuthController",
"36": "FaqService",
"37": "راهنمای تست سیستم (Software Testing)",
"38": "Button.tsx",
"38": "Button",
"39": "CategoriesController",
"40": "MediaController",
"41": "What You Must Do When Invoked",
"42": "SslController",
"43": "BannersService",
"44": "TestimonialsController",
"44": "TestimonialsService",
"45": "What You Must Do When Invoked",
"46": "api",
"46": "HomeController",
"47": "IngredientsService",
"48": "cartStore.ts",
"48": "auth.module.ts",
"49": "devDependencies",
"50": "devDependencies",
"51": "BlogsController",
"52": "PrescriptionsController",
"53": "SmartAdvisorController",
"54": "UsersService",
"52": "PrescriptionsService",
"53": "SmartAdvisorService",
"54": "UsersController",
"55": "UITexts.tsx",
"56": "Orders.tsx",
"57": "Role & Core Objective",
"58": "ContactService",
"59": "compilerOptions",
"60": "Blogs.tsx",
"61": "payment.controller.ts",
"62": "ProductsService",
"60": "users.controller.ts",
"61": "payment.service.ts",
"62": "ProductDto",
"63": "dependencies",
"64": "compilerOptions",
"65": "app.e2e-spec.js",
"66": "AdminQueryDto",
"67": "PetsController",
"68": "20260526145407_init/migration.sql",
"66": "ApiOperation",
"67": "admin.module.ts",
"68": "PaymentService",
"69": "Required Review Group Closures",
"70": "compilerOptions",
"71": "getPageMetadata",
@ -75,7 +75,7 @@
"73": "Operational Rules & Boundaries",
"74": "WikiController",
"75": "PetsController",
"76": "shop/page.tsx",
"76": "AdminService",
"77": "seo.module.ts",
"78": "route.ts",
"79": "🏢 AI Software Agency — Master Orchestration Protocol v3",
@ -87,9 +87,9 @@
"85": "eslint-config-prettier",
"86": "zibal.service.ts",
"87": "dependencies",
"88": "useSettingsStore",
"88": "toPersian",
"89": "seed-products.ts",
"90": "HomeClient.tsx",
"90": "useSettingsStore",
"91": "Reconciled Audit Roles & Assignments",
"92": "OrdersService",
"93": "@nestjs/schematics",
@ -105,30 +105,30 @@
"103": "Comprehensive Change Log",
"104": "Coupons.tsx",
"105": "Operational Rules & Boundaries",
"106": "Reports.tsx",
"106": "Media.tsx",
"107": "@types/supertest",
"108": "PrismaService",
"109": "1. Summary of Integrity Repairs Performed",
"110": "Operational Rules & Boundaries",
"111": "Operational Rules & Boundaries",
"112": "Operational Rules & Boundaries",
"113": "reviews.controller.ts",
"113": "CreateReviewDto",
"114": "AppService",
"115": "@types/react-dom",
"115": "UsersService",
"116": "Vazirmatn Changelog",
"117": "Vazirmatn Font فونت وزیرمتن",
"118": "Operational Rules & Boundaries",
"119": "compilerOptions",
"120": "compilerOptions",
"121": "backend/README.md",
"122": "AuthService",
"123": "BlogsService",
"122": "Param",
"123": "CreateEBankCheckoutDto",
"124": "Repository Map",
"125": "validate_integrity.js",
"126": "admin-panel/package.json",
"127": "Sahel-Font",
"128": "AdminService",
"129": "RouteErrorBoundary",
"128": "AdminController",
"129": "InitiatePaymentDto",
"130": "Sahel-Font",
"131": "Role & Core Objective",
"132": "orchestrate.py",
@ -151,7 +151,7 @@
"149": "SmsSettingsPage.tsx",
"150": "Product Requirement Document (PRD)",
"151": "auth.service.ts",
"152": "trust-seals/page.tsx",
"152": ".createCoupon",
"153": "exclude",
"154": "Baseline Command Plan & Reconciled Command History",
"155": "seo-backfill.ts",
@ -233,10 +233,10 @@
"231": "@eslint/eslintrc",
"232": "@eslint/js",
"233": "eslint-plugin-prettier",
"234": "20260526160916_add_ui_texts_and_scientific_terms/migration.sql",
"234": "WholesaleApplyDto",
"235": "@nestjs/cli",
"236": "@nestjs/testing",
"237": "eslint",
"237": "app/page.tsx",
"238": "prisma",
"239": "source-map-support",
"240": "supertest",
@ -312,9 +312,11 @@
"310": "tailwindcss",
"311": "@types/multer",
"312": "@types/passport-jwt",
"313": "MenuManager.tsx",
"313": "Modal.tsx",
"314": "typescript-eslint",
"315": "typescript",
"316": "ts-jest",
"317": "revalidate/route.ts",
"318": "MaskableField.tsx"
"318": "MaskableField.tsx",
"319": "eslint-config-next"
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,320 @@
{
"0": "Roles",
"1": "app.module.ts",
"2": "SmsService",
"3": "ProductService",
"4": "SafeImage.tsx",
"5": "CmsController",
"6": "tickets.controller.ts",
"7": "pets/pets.controller.ts",
"8": "admin.module.ts",
"9": "devDependencies",
"10": "ReviewsService",
"11": "Spinner.tsx",
"12": "PetProfile.tsx",
"13": "app-audit-verification.e2e-spec.js",
"14": "lib/services/api.ts",
"15": "src/services/api.ts",
"16": "DoctorQueryDto",
"17": "schema.ts",
"18": "JwtAuthGuard",
"19": "ProductsController",
"20": "CreateVideoDto",
"21": "ProductPage.tsx",
"22": "UserDashboard.tsx",
"23": "MenuService",
"24": "BE-001",
"25": "FE-001",
"26": "ADM-001",
"27": "DB-001",
"28": "TS-001",
"29": "TEST-001",
"30": "DEVOPS-001",
"31": "DOC-001",
"32": "adminRoutes.tsx",
"33": "WholesaleApplyDto",
"34": "B2BService",
"35": "AuthController",
"36": "FaqService",
"37": "راهنمای تست سیستم (Software Testing)",
"38": "Button.tsx",
"39": "CategoriesController",
"40": "MediaController",
"41": "What You Must Do When Invoked",
"42": "SslController",
"43": "BannersService",
"44": "TestimonialsController",
"45": "What You Must Do When Invoked",
"46": "api",
"47": "IngredientsService",
"48": "cartStore.ts",
"49": "devDependencies",
"50": "devDependencies",
"51": "BlogsController",
"52": "PrescriptionsController",
"53": "SmartAdvisorController",
"54": "UsersService",
"55": "UITexts.tsx",
"56": "Orders.tsx",
"57": "Role & Core Objective",
"58": "ContactService",
"59": "compilerOptions",
"60": "Blogs.tsx",
"61": "payment.controller.ts",
"62": "ProductsService",
"63": "dependencies",
"64": "compilerOptions",
"65": "app.e2e-spec.js",
"66": "AdminQueryDto",
"67": "PetsController",
"68": "20260526145407_init/migration.sql",
"69": "Required Review Group Closures",
"70": "compilerOptions",
"71": "getPageMetadata",
"72": "Operational Rules & Boundaries",
"73": "Operational Rules & Boundaries",
"74": "WikiController",
"75": "PetsController",
"76": "shop/page.tsx",
"77": "seo.module.ts",
"78": "route.ts",
"79": "🏢 AI Software Agency — Master Orchestration Protocol v3",
"80": "Operational Rules & Boundaries",
"81": "Operational Rules & Boundaries",
"82": "scripts",
"83": "dependencies",
"84": "Role & Core Objective",
"85": "eslint-config-prettier",
"86": "zibal.service.ts",
"87": "dependencies",
"88": "useSettingsStore",
"89": "seed-products.ts",
"90": "HomeClient.tsx",
"91": "Reconciled Audit Roles & Assignments",
"92": "OrdersService",
"93": "@nestjs/schematics",
"94": "Phase 3.1 — Human Review Preparation and Master Backlog Critique",
"95": "blog/[slug]/page.tsx",
"96": "compilerOptions",
"97": "prettier",
"98": "scripts",
"99": "BlogsController",
"100": "Deep Audit Summary Report",
"101": "Operational Rules & Boundaries",
"102": "jest",
"103": "Comprehensive Change Log",
"104": "Coupons.tsx",
"105": "Operational Rules & Boundaries",
"106": "Reports.tsx",
"107": "@types/supertest",
"108": "PrismaService",
"109": "1. Summary of Integrity Repairs Performed",
"110": "Operational Rules & Boundaries",
"111": "Operational Rules & Boundaries",
"112": "Operational Rules & Boundaries",
"113": "reviews.controller.ts",
"114": "AppService",
"115": "@types/react-dom",
"116": "Vazirmatn Changelog",
"117": "Vazirmatn Font فونت وزیرمتن",
"118": "Operational Rules & Boundaries",
"119": "compilerOptions",
"120": "compilerOptions",
"121": "backend/README.md",
"122": "AuthService",
"123": "BlogsService",
"124": "Repository Map",
"125": "validate_integrity.js",
"126": "admin-panel/package.json",
"127": "Sahel-Font",
"128": "AdminService",
"129": "RouteErrorBoundary",
"130": "Sahel-Font",
"131": "Role & Core Objective",
"132": "orchestrate.py",
"133": "backend/package.json",
"134": "blog/page.tsx",
"135": "graphify reference: extra exports and benchmark",
"136": "Phase 2 Final Quality Gate Summary Report",
"137": "Task Modifications Log",
"138": "Install",
"139": "layout.tsx",
"140": "ErrorBoundary",
"141": "application/package.json",
"142": "start-dev.js",
"143": "generate-openapi.js",
"144": "@testing-library/react",
"145": "admin.service.ts",
"146": "System Discovery",
"147": "RevalidationService",
"148": "wiki/[slug]/page.tsx",
"149": "SmsSettingsPage.tsx",
"150": "Product Requirement Document (PRD)",
"151": "auth.service.ts",
"152": "trust-seals/page.tsx",
"153": "exclude",
"154": "Baseline Command Plan & Reconciled Command History",
"155": "seo-backfill.ts",
"156": "@nestjs/swagger",
"157": "ErrorPages.tsx",
"158": "class-transformer",
"159": "with-vpn.sh",
"160": "Architecture Specification",
"161": "Project Health Audit Report",
"162": "nest-cli.json",
"163": "graphify reference: query, path, explain",
"164": "Open Questions",
"165": "Final Phase 2 Audit Closure Report",
"166": "open-browsers.js",
"167": "📝 Active Agent Working Scratchpad",
"168": "🔍 Code Health Audit Review (01_auditor)",
"169": "paginated-response.schema.ts",
"170": "Vazirmatn Font README",
"171": "Omitted File Inspection Report",
"172": "Phase 3.2 / 3.3 — Implementation Readiness & Architectural Finalization Report",
"173": "Phase 3 Audit Traceability Matrix",
"174": "rebuild_honest_ledger.js",
"175": "validate_evidence_grade.js",
"176": "helmet",
"177": "js-yaml",
"178": "@nestjs/core",
"179": "PaginationDto",
"180": "API Contract Specification",
"181": "⚙️ Backend Technical Review (05_dev_backend)",
"182": "🎨 Frontend & Admin Panel Technical Review (06_dev_frontend)",
"183": "🚀 SEO & Content Strategy Review (12_seo_content)",
"184": "MetricsController",
"185": "@nestjs/jwt",
"186": "seed-ui-texts.ts",
"187": "seed-wiki.ts",
"188": "update-blog.dto.ts",
"189": "update-home.dto.ts",
"190": "update-wiki.dto.ts",
"191": "graphify reference: add a URL and watch a folder",
"192": "graphify reference: commit hook and native CLAUDE.md integration",
"193": "graphify reference: incremental update and cluster-only",
"194": "Raw Finding Verification & Disposition Report",
"195": "React + TypeScript + Vite",
"196": "Select.tsx",
"197": "@nestjs/throttler",
"198": "passport",
"199": "auth.controller.ts",
"200": "application/README.md",
"201": "deploy.sh",
"202": "🔒 Security & Performance Review (09_devops_security)",
"203": "👁️ UX & Persona Interface Review (08_visual_qa)",
"204": "reflect-metadata",
"205": "prisma/scientificTerms.ts",
"206": "seed-blogs.ts",
"207": "seed-custom.ts",
"208": "graphify reference: GitHub clone and cross-repo merge",
"209": "graphify reference: transcribe video and audio",
"210": "Compiler Diagnostic Dispositions",
"211": "Master Task Backlog (Phase 3.3)",
"212": "build_manifest.js",
"213": "generate_classification.js",
"214": "generate_evidence.js",
"215": "generate_ledger.js",
"216": "generate_manifest.js",
"217": "sync_honest_manifest.js",
"218": "sync_manifest.js",
"219": "FormField.tsx",
"220": "Input.tsx",
"221": "Textarea.tsx",
"222": "admin-panel/tsconfig.json",
"223": "swagger-ui-express",
"224": "jest",
"225": "next.config.ts",
"226": "Shabnam Font README",
"227": "AGENTS.md",
"228": "rules/graphify.md",
"229": ".agents/workflows/graphify.md",
"230": "instructions.md",
"231": "@eslint/eslintrc",
"232": "@eslint/js",
"233": "eslint-plugin-prettier",
"234": "20260526160916_add_ui_texts_and_scientific_terms/migration.sql",
"235": "@nestjs/cli",
"236": "@nestjs/testing",
"237": "eslint",
"238": "prisma",
"239": "source-map-support",
"240": "supertest",
"241": "blog.entity.ts",
"242": "home.entity.ts",
"243": "wiki.entity.ts",
"244": "User Profile Photo",
"245": "CLAUDE.md",
"246": ".claude/CLAUDE.md",
"247": "extraction-spec.md",
"248": "Products Table",
"249": "Users Table",
"250": "Architectural Audit Findings",
"251": "Cross Boundary Dependencies & Backend Architecture Specification",
"252": "Next.js Agent Rules & Brand Guidelines",
"253": "robots.ts",
"254": "application/eslint.config.mjs",
"255": "postcss.config.mjs",
"256": "vitest.setup.ts",
"257": "backup_db.sh",
"258": "start.sh",
"259": "reviews/README.md",
"260": "backend/eslint.config.mjs",
"261": "User Login API",
"262": "User Logout API",
"263": "generate-openapi.d.ts",
"264": "ts-loader",
"265": "@testing-library/jest-dom",
"266": "ts-node",
"267": "tsconfig-paths",
"268": "@types/bcrypt",
"269": "eslint-plugin-react-hooks",
"270": "app-audit-verification.e2e-spec.d.ts",
"271": "app.e2e-spec.d.ts",
"272": "Canina Pharma GmbH",
"273": "Pets Table",
"274": "Canina Iran Project Introduction",
"275": "Developer Standards and Architecture",
"276": "Frontend & Admin Architecture Route Map Specification",
"277": "Project Backlog and Tasks",
"278": "eslint.config.js",
"279": "postcss.config.js",
"280": "admin-panel/public/fonts/sahel-font-v3.4.0/CHANGELOG.md",
"281": "shabnam-font-v5.0.1/CHANGELOG.md",
"282": "tailwind.config.js",
"283": "vite.config.ts",
"284": "application/CLAUDE.md",
"285": "application/public/fonts/sahel-font-v3.4.0/CHANGELOG.md",
"286": "Sahel Font Sample",
"287": "Shabnam Font Changelog",
"288": "Vazirmatn Changelog",
"289": "vitest.config.ts",
"290": "Sahel Font Variable Sample",
"291": "Shabnam Font Sample",
"292": "Production Docker Compose",
"293": "Staging Docker Compose",
"294": "ZibalService",
"295": "eslint-plugin-react-refresh",
"296": "tailwindcss",
"297": "ZibalEBankService",
"298": ".initiateOrderPayment",
"299": "@tailwindcss/postcss",
"300": "typescript",
"301": "@types/compression",
"302": "typescript-eslint",
"303": "@types/express",
"304": "@types/jest",
"305": "@types/react",
"306": "globals",
"307": "@types/js-yaml",
"308": "vitest",
"309": "axios",
"310": "tailwindcss",
"311": "@types/multer",
"312": "@types/passport-jwt",
"313": "MenuManager.tsx",
"314": "typescript-eslint",
"315": "typescript",
"317": "revalidate/route.ts",
"318": "MaskableField.tsx"
}

View File

@ -0,0 +1 @@
{"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

View File

@ -1,16 +1,16 @@
# Graph Report - canina (2026-08-25)
# Graph Report - caninairan (2026-08-26)
## Corpus Check
- 557 files · ~1,318,566 words
- 563 files · ~1,321,574 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 4034 nodes · 7290 edges · 318 communities (201 shown, 117 thin omitted)
- Extraction: 96% EXTRACTED · 4% INFERRED · 0% AMBIGUOUS · INFERRED: 281 edges (avg confidence: 0.79)
- 4041 nodes · 7301 edges · 320 communities (202 shown, 118 thin omitted)
- Extraction: 96% EXTRACTED · 4% INFERRED · 0% AMBIGUOUS · INFERRED: 281 edges (avg confidence: 0.8)
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `fa69aa4c`
- Built from commit: `7b89e141`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
@ -19,25 +19,25 @@
- app.module.ts
- SmsService
- ProductService
- SafeImage.tsx
- utils.ts
- CmsController
- tickets.controller.ts
- pets/pets.controller.ts
- admin.module.ts
- Button.tsx
- ReportsController
- devDependencies
- ReviewsService
- Spinner.tsx
- MediaSelector.tsx
- PetProfile.tsx
- app-audit-verification.e2e-spec.js
- lib/services/api.ts
- UserDashboard.tsx
- src/services/api.ts
- DoctorQueryDto
- schema.ts
- JwtAuthGuard
- ProductsController
- ProductsService
- CreateVideoDto
- ProductPage.tsx
- UserDashboard.tsx
- lib/services/api.ts
- MenuService
- BE-001
- FE-001
@ -48,42 +48,42 @@
- DEVOPS-001
- DOC-001
- adminRoutes.tsx
- WholesaleApplyDto
- WholesaleService
- B2BService
- AuthController
- FaqService
- راهنمای تست سیستم (Software Testing)
- Button.tsx
- Button
- CategoriesController
- MediaController
- What You Must Do When Invoked
- SslController
- BannersService
- TestimonialsController
- TestimonialsService
- What You Must Do When Invoked
- api
- HomeController
- IngredientsService
- cartStore.ts
- auth.module.ts
- devDependencies
- devDependencies
- BlogsController
- PrescriptionsController
- SmartAdvisorController
- UsersService
- PrescriptionsService
- SmartAdvisorService
- UsersController
- UITexts.tsx
- Orders.tsx
- Role & Core Objective
- ContactService
- compilerOptions
- Blogs.tsx
- payment.controller.ts
- ProductsService
- users.controller.ts
- payment.service.ts
- ProductDto
- dependencies
- compilerOptions
- app.e2e-spec.js
- AdminQueryDto
- PetsController
- 20260526145407_init/migration.sql
- ApiOperation
- admin.module.ts
- PaymentService
- Required Review Group Closures
- compilerOptions
- getPageMetadata
@ -91,7 +91,7 @@
- Operational Rules & Boundaries
- WikiController
- PetsController
- shop/page.tsx
- AdminService
- seo.module.ts
- route.ts
- 🏢 AI Software Agency — Master Orchestration Protocol v3
@ -103,9 +103,9 @@
- eslint-config-prettier
- zibal.service.ts
- dependencies
- useSettingsStore
- toPersian
- seed-products.ts
- HomeClient.tsx
- useSettingsStore
- Reconciled Audit Roles & Assignments
- OrdersService
- @nestjs/schematics
@ -121,30 +121,30 @@
- Comprehensive Change Log
- Coupons.tsx
- Operational Rules & Boundaries
- Reports.tsx
- Media.tsx
- @types/supertest
- PrismaService
- 1. Summary of Integrity Repairs Performed
- Operational Rules & Boundaries
- Operational Rules & Boundaries
- Operational Rules & Boundaries
- reviews.controller.ts
- CreateReviewDto
- AppService
- @types/react-dom
- UsersService
- Vazirmatn Changelog
- Vazirmatn Font فونت وزیرمتن
- Operational Rules & Boundaries
- compilerOptions
- compilerOptions
- backend/README.md
- AuthService
- BlogsService
- Param
- CreateEBankCheckoutDto
- Repository Map
- validate_integrity.js
- admin-panel/package.json
- Sahel-Font
- AdminService
- RouteErrorBoundary
- AdminController
- InitiatePaymentDto
- Sahel-Font
- Role & Core Objective
- orchestrate.py
@ -167,7 +167,7 @@
- SmsSettingsPage.tsx
- Product Requirement Document (PRD)
- auth.service.ts
- trust-seals/page.tsx
- .createCoupon
- exclude
- Baseline Command Plan & Reconciled Command History
- seo-backfill.ts
@ -249,10 +249,10 @@
- @eslint/eslintrc
- @eslint/js
- eslint-plugin-prettier
- 20260526160916_add_ui_texts_and_scientific_terms/migration.sql
- WholesaleApplyDto
- @nestjs/cli
- @nestjs/testing
- eslint
- app/page.tsx
- prisma
- source-map-support
- supertest
@ -311,18 +311,20 @@
- vitest
- @types/multer
- @types/passport-jwt
- MenuManager.tsx
- Modal.tsx
- typescript-eslint
- typescript
- ts-jest
- revalidate/route.ts
- MaskableField.tsx
- eslint-config-next
## God Nodes (most connected - your core abstractions)
1. `Roles()` - 106 edges
2. `PrismaService` - 87 edges
3. `useSettingsStore` - 55 edges
4. `api` - 44 edges
5. `SmsService` - 42 edges
5. `SmsService` - 43 edges
6. `PaginationDto` - 41 edges
7. `Button()` - 39 edges
8. `PaymentController` - 38 edges
@ -334,39 +336,39 @@
docs/02-user-guide.md → backend/uploads/1781288429353-508765350.jpg
- `AuthController` --references--> `ApiResponse` [EXTRACTED]
backend/src/auth/auth.controller.ts → frontend/admin-panel/src/types/admin.ts
- `BlogsController` --references--> `ApiResponse` [EXTRACTED]
backend/src/blogs/blogs.controller.ts → frontend/admin-panel/src/types/admin.ts
- `HomeController` --references--> `ApiResponse` [EXTRACTED]
backend/src/home/home.controller.ts → frontend/admin-panel/src/types/admin.ts
- `OrdersController` --references--> `ApiResponse` [EXTRACTED]
backend/src/orders/orders.controller.ts → frontend/admin-panel/src/types/admin.ts
- `PetsController` --references--> `ApiResponse` [EXTRACTED]
backend/src/pets/pets.controller.ts → frontend/admin-panel/src/types/admin.ts
- `ProductsController` --references--> `ApiResponse` [EXTRACTED]
backend/src/products/products.controller.ts → frontend/admin-panel/src/types/admin.ts
## Import Cycles
- 3-file cycle: `frontend/application/lib/services/api.ts -> frontend/application/lib/store/userStore.ts -> frontend/application/lib/services/authService.ts -> frontend/application/lib/services/api.ts`
- 3-file cycle: `frontend/application/lib/services/api.ts -> frontend/application/lib/store/userStore.ts -> frontend/application/lib/store/cartStore.ts -> frontend/application/lib/services/api.ts`
- 3-file cycle: `frontend/application/lib/services/api.ts -> frontend/application/lib/store/userStore.ts -> frontend/application/lib/services/authService.ts -> frontend/application/lib/services/api.ts`
- 4-file cycle: `frontend/application/lib/services/api.ts -> frontend/application/lib/store/userStore.ts -> frontend/application/lib/store/cartStore.ts -> frontend/application/lib/services/orderService.ts -> frontend/application/lib/services/api.ts`
## Communities (318 total, 117 thin omitted)
## Communities (320 total, 118 thin omitted)
### Community 0 - "Roles"
Cohesion: 0.24
Cohesion: 0.23
Nodes (13): Roles(), PaymentController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get (+5 more)
### Community 1 - "app.module.ts"
Cohesion: 0.07
Nodes (34): BannersModule, Module, CmsModule, Module, SmsModule, Global, Module, ContactModule (+26 more)
Cohesion: 0.08
Nodes (34): B2BModule, Module, BannersModule, Module, CmsModule, Module, SmsModule, Global (+26 more)
### Community 2 - "SmsService"
Cohesion: 0.06
Nodes (27): SmsLogQuery, SmsService, Injectable, SmsLogQueryDto, ApiPropertyOptional, IsIn, IsNumber, IsOptional (+19 more)
Cohesion: 0.05
Nodes (27): SmsService, Injectable, SmsLogQueryDto, ApiPropertyOptional, IsIn, IsNumber, IsOptional, IsString (+19 more)
### Community 3 - "ProductService"
Cohesion: 0.06
Nodes (34): generateMetadata(), dynamic, revalidate, DEFAULT_CATEGORIES, dynamic, revalidate, dynamic, revalidate (+26 more)
Nodes (34): dynamic, revalidate, DEFAULT_CATEGORIES, dynamic, revalidate, dynamic, revalidate, BlogCategory (+26 more)
### Community 4 - "SafeImage.tsx"
Cohesion: 0.09
Nodes (21): BlogCategory, BlogPostItem, ProductDetailModalProps, OrderDetailsModalProps, PLAYBACK_RATES, PodcastInlinePlayerProps, PLAYBACK_RATES, PodcastPlayerModal() (+13 more)
### Community 4 - "utils.ts"
Cohesion: 0.08
Nodes (22): BackButton(), BackButtonProps, BlogPost, BlogPreviewSection(), OrderDetailsModal(), OrderDetailsModalProps, PLAYBACK_RATES, PodcastPlayerModal() (+14 more)
### Community 5 - "CmsController"
Cohesion: 0.09
@ -374,75 +376,75 @@ Nodes (24): CmsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controlle
### Community 6 - "tickets.controller.ts"
Cohesion: 0.09
Nodes (31): AdminUpdateTicketDto, CreateTicketDto, ReplyTicketDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString (+23 more)
Nodes (29): AdminUpdateTicketDto, CreateTicketDto, ReplyTicketDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString (+21 more)
### Community 7 - "pets/pets.controller.ts"
### Community 7 - "Button.tsx"
Cohesion: 0.11
Nodes (15): CreatePetDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional, IsString (+7 more)
Nodes (14): ButtonProps, ButtonSize, ButtonVariant, Spinner(), Doctor, FAQ, ProductReview, Reviews() (+6 more)
### Community 8 - "admin.module.ts"
Cohesion: 0.07
Nodes (19): AdminModule, Module, MediaService, Injectable, ReportsController, ApiBearerAuth, ApiOperation, ApiTags (+11 more)
### Community 8 - "ReportsController"
Cohesion: 0.14
Nodes (11): ReportsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Get, Query (+3 more)
### Community 9 - "devDependencies"
Cohesion: 0.22
Nodes (9): devDependencies, ts-jest, @types/bcryptjs, @types/node, typescript, @types/node, typescript, ts-jest (+1 more)
Nodes (9): devDependencies, eslint, @types/bcryptjs, @types/node, typescript, eslint, @types/node, typescript (+1 more)
### Community 10 - "ReviewsService"
Cohesion: 0.13
Nodes (16): ReviewsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+8 more)
Cohesion: 0.12
Nodes (17): ReviewsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+9 more)
### Community 11 - "Spinner.tsx"
Cohesion: 0.09
Nodes (30): ConfirmModal(), ConfirmModalProps, getFileType(), Media, MediaSelector(), MediaSelectorProps, maxWidthClasses, Modal() (+22 more)
### Community 11 - "MediaSelector.tsx"
Cohesion: 0.07
Nodes (33): ConfirmModal(), ConfirmModalProps, getFileType(), Media, MediaSelector(), MediaSelectorProps, Pagination(), PaginationProps (+25 more)
### Community 12 - "PetProfile.tsx"
Cohesion: 0.13
Nodes (13): FeaturedProducts(), OrderDetailsModal(), OrderRowSkeleton(), PetProfileSkeleton(), ProductCardSkeleton(), Skeleton(), SkeletonProps, mockProducts (+5 more)
Cohesion: 0.08
Nodes (25): ClientLayout(), CheckoutPage(), FeaturedProducts(), ProductCard(), OrderSuccess(), SearchResultsPage(), OrderRowSkeleton(), PetProfileSkeleton() (+17 more)
### Community 13 - "app-audit-verification.e2e-spec.js"
Cohesion: 0.07
Nodes (22): AppModule, Module, CustomHttpExceptionFilter, Catch, PrismaExceptionFilter, Catch, DecimalInterceptor, Injectable (+14 more)
Cohesion: 0.06
Nodes (28): AppModule, Module, EnvironmentVariables, IsNotEmpty, IsOptional, IsString, validateEnv(), CustomHttpExceptionFilter (+20 more)
### Community 14 - "lib/services/api.ts"
Cohesion: 0.09
Nodes (21): BlogPost, ContactInfoItem, FAQItem, Testimonial, api, ApiErrorPayload, BASE_DOMAIN, baseURL (+13 more)
### Community 14 - "UserDashboard.tsx"
Cohesion: 0.15
Nodes (18): AddressModal(), AddressModalProps, DeleteConfirmModal(), DeleteConfirmModalProps, HeaderButton(), HeaderButtonProps, PetProfile(), SearchableSelect() (+10 more)
### Community 15 - "src/services/api.ts"
Cohesion: 0.09
Nodes (25): ProtectedRoute(), Login(), B2BManager, SeoSettingsPage, SmartAdvisorManager, SystemSettingsPage, ApiErrorPayload, failedQueue (+17 more)
Cohesion: 0.08
Nodes (32): Layout(), ProtectedRoute(), MenuGroup, Sidebar(), SidebarProps, SubMenuItem, LiveMonitoringSummary, SEARCHABLE_PAGES (+24 more)
### Community 16 - "DoctorQueryDto"
Cohesion: 0.09
Nodes (24): DoctorsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+16 more)
### Community 17 - "schema.ts"
Cohesion: 0.14
Nodes (18): B2BPage(), generateMetadata(), ContactPage(), generateMetadata(), generateMetadata(), getInitialVideos(), Videos(), ContactFormClient() (+10 more)
Cohesion: 0.13
Nodes (19): B2BPage(), generateMetadata(), ContactPage(), generateMetadata(), generateMetadata(), getInitialVideos(), Videos(), B2BLandingClient() (+11 more)
### Community 18 - "JwtAuthGuard"
Cohesion: 0.19
Nodes (7): JwtAuthGuard, Injectable, ROLES_KEY, RequestWithUser, RolesGuard, Injectable, UserReqPayload
### Community 19 - "ProductsController"
Cohesion: 0.15
Nodes (9): ProductsController, ApiOperation, ApiTags, Body, Controller, Get, Param, Patch (+1 more)
### Community 19 - "ProductsService"
Cohesion: 0.10
Nodes (19): GetProductsDto, ApiPropertyOptional, IsEnum, IsOptional, IsString, ProductsController, ApiOperation, ApiTags (+11 more)
### Community 20 - "CreateVideoDto"
Cohesion: 0.09
Nodes (24): CreateVideoDto, ApiProperty, ApiPropertyOptional, IsBoolean, IsNotEmpty, IsOptional, IsString, UpdateVideoDto (+16 more)
Cohesion: 0.07
Nodes (31): CreateVideoDto, ApiProperty, ApiPropertyOptional, IsBoolean, IsNotEmpty, IsOptional, IsString, UpdateVideoDto (+23 more)
### Community 21 - "ProductPage.tsx"
Cohesion: 0.14
Nodes (14): PodcastInlinePlayer(), CalculatorState, ICON_MAP, ProductPage(), ProductReviews, useCalculatorStore, ProductReviews(), ProductReviewsProps (+6 more)
Cohesion: 0.13
Nodes (15): PLAYBACK_RATES, PodcastInlinePlayer(), PodcastInlinePlayerProps, CalculatorState, ICON_MAP, ProductPage(), ProductReviews, useCalculatorStore (+7 more)
### Community 22 - "UserDashboard.tsx"
Cohesion: 0.10
Nodes (38): VerifyContent(), AddressModal(), AddressModalProps, ArchiveProductCard(), B2BPortal(), BackButton(), BackButtonProps, CheckoutPage() (+30 more)
### Community 22 - "lib/services/api.ts"
Cohesion: 0.08
Nodes (29): VerifyContent(), B2BPortal(), CartDrawer(), ContactInfoItem, Header(), OrderTracking(), PrescriptionUploadModal(), PrescriptionUploadModalProps (+21 more)
### Community 23 - "MenuService"
Cohesion: 0.12
Nodes (16): MenuController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+8 more)
Cohesion: 0.11
Nodes (18): MenuController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+10 more)
### Community 24 - "BE-001"
Cohesion: 0.06
@ -477,20 +479,20 @@ Cohesion: 0.06
Nodes (31): Acceptance Criteria, Affected Application, Affected Files, Alternative Direction, Category, Completion Statement, Confidence, Dependencies (+23 more)
### Community 32 - "adminRoutes.tsx"
Cohesion: 0.07
Nodes (21): App(), CategoryDist, DashboardData, WholesaleRequest, AdminRouteConfig, BannersManager, Categories, Dashboard (+13 more)
Cohesion: 0.06
Nodes (23): App(), Props, RouteErrorBoundary, State, HeroBanner, VetTestimonial, CategoryDist, DashboardData (+15 more)
### Community 33 - "WholesaleApplyDto"
Cohesion: 0.10
Nodes (20): ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, WholesaleApplyDto, ApiBearerAuth, ApiOperation (+12 more)
### Community 33 - "WholesaleService"
Cohesion: 0.14
Nodes (14): ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param, Post (+6 more)
### Community 34 - "B2BService"
Cohesion: 0.12
Nodes (17): B2BController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param (+9 more)
Nodes (16): B2BController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param (+8 more)
### Community 35 - "AuthController"
Cohesion: 0.27
Nodes (12): AuthController, ApiBadRequestResponse, ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, Body, Controller (+4 more)
Cohesion: 0.25
Nodes (13): AuthController, ApiBadRequestResponse, ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, Body, Controller (+5 more)
### Community 36 - "FaqService"
Cohesion: 0.12
@ -500,17 +502,17 @@ Nodes (16): FaqController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controlle
Cohesion: 0.07
Nodes (25): اجرای بک‌اند, اجرای فرانت‌اند, اجرای پروژه در سرور با PM2, برای راه‌اندازی بک‌اند:, برای راه‌اندازی فرانت‌اند Next.js:, بیلد کردن پروژه (Building), ذخیره پروسه‌ها تا پس از ری‌استارت سرور قطع نشوند:, راه‌اندازی و انتشار سیستم (Setup & Deployment) (+17 more)
### Community 38 - "Button.tsx"
Cohesion: 0.07
Nodes (26): TransactionReceiptData, TransactionReceiptModal(), TransactionReceiptModalProps, Button(), ButtonProps, ButtonSize, ButtonVariant, ThSort() (+18 more)
### Community 38 - "Button"
Cohesion: 0.16
Nodes (13): TransactionReceiptData, TransactionReceiptModal(), TransactionReceiptModalProps, Button(), ThSort(), ThSortProps, GatewayHealth, Stats (+5 more)
### Community 39 - "CategoriesController"
Cohesion: 0.09
Nodes (17): CategoriesController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+9 more)
Cohesion: 0.10
Nodes (16): CategoriesController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+8 more)
### Community 40 - "MediaController"
Cohesion: 0.11
Nodes (14): MediaController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
Nodes (16): MediaController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+8 more)
### Community 41 - "What You Must Do When Invoked"
Cohesion: 0.07
@ -524,25 +526,25 @@ Nodes (14): SslController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controlle
Cohesion: 0.13
Nodes (15): BannersController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+7 more)
### Community 44 - "TestimonialsController"
### Community 44 - "TestimonialsService"
Cohesion: 0.13
Nodes (12): TestimonialsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+4 more)
Nodes (14): TestimonialsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
### Community 45 - "What You Must Do When Invoked"
Cohesion: 0.07
Nodes (26): For /graphify add and --watch, For /graphify query, For the commit hook and native CLAUDE.md integration, For --update and --cluster-only, /graphify, Honesty Rules, Interpreter guard for subcommands, Part A - Structural extraction for code files (+18 more)
### Community 46 - "api"
Cohesion: 0.13
Nodes (13): Layout(), MenuGroup, Sidebar(), SidebarProps, SubMenuItem, LiveMonitoringSummary, SEARCHABLE_PAGES, Topbar() (+5 more)
### Community 46 - "HomeController"
Cohesion: 0.16
Nodes (10): HomeController, ApiOkResponse, ApiOperation, ApiTags, Controller, Get, HomeModule, Module (+2 more)
### Community 47 - "IngredientsService"
Cohesion: 0.13
Nodes (14): IngredientsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
### Community 48 - "cartStore.ts"
Cohesion: 0.13
Nodes (6): OrderSuccess(), OrderTracking(), OrderService, CartItem, CartStore, Order
### Community 48 - "auth.module.ts"
Cohesion: 0.17
Nodes (10): DEFAULT_JWT_REFRESH_SECRET, DEFAULT_JWT_SECRET, getJwtSecret(), AuthModule, Module, JwtPayload, JwtStrategy, Injectable (+2 more)
### Community 49 - "devDependencies"
Cohesion: 0.11
@ -550,27 +552,27 @@ Nodes (19): autoprefixer, devDependencies, autoprefixer, eslint, @eslint/js, glo
### Community 50 - "devDependencies"
Cohesion: 0.13
Nodes (15): eslint-config-next, devDependencies, eslint, eslint-config-next, jsdom, tailwindcss, @tailwindcss/postcss, @types/node (+7 more)
Nodes (15): devDependencies, eslint, jsdom, tailwindcss, @tailwindcss/postcss, @types/node, @types/react-dom, @vitejs/plugin-react (+7 more)
### Community 51 - "BlogsController"
Cohesion: 0.07
Nodes (18): BlogsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+10 more)
### Community 52 - "PrescriptionsService"
Cohesion: 0.14
Nodes (16): BlogsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete (+8 more)
Nodes (14): PrescriptionsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param (+6 more)
### Community 52 - "PrescriptionsController"
Cohesion: 0.15
Nodes (12): PrescriptionsController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Get, Param (+4 more)
### Community 53 - "SmartAdvisorService"
Cohesion: 0.13
Nodes (14): SmartAdvisorController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+6 more)
### Community 53 - "SmartAdvisorController"
Cohesion: 0.14
Nodes (12): SmartAdvisorController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Get (+4 more)
### Community 54 - "UsersService"
Cohesion: 0.06
Nodes (42): DEFAULT_JWT_REFRESH_SECRET, DEFAULT_JWT_SECRET, getJwtSecret(), AuthModule, Module, JwtPayload, JwtStrategy, Injectable (+34 more)
### Community 54 - "UsersController"
Cohesion: 0.19
Nodes (16): ApiBadRequestResponse, ApiBearerAuth, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiTags, Body, Controller (+8 more)
### Community 55 - "UITexts.tsx"
Cohesion: 0.09
Nodes (19): ICON_REGISTRY, IconPickerModal(), IconPickerModalProps, ImagePreviewModal(), ImagePreviewModalProps, ToggleSwitch(), ToggleSwitchProps, ProductItem (+11 more)
Nodes (20): ICON_REGISTRY, IconPickerModal(), IconPickerModalProps, ActiveStates, PRESET_BG_COLORS, PRESET_COLORS, RichTextEditor(), RichTextEditorProps (+12 more)
### Community 56 - "Orders.tsx"
Cohesion: 0.09
@ -582,23 +584,23 @@ Nodes (22): CREATE MODE — Normal Operation, Expected JSON Output Schema, File
### Community 58 - "ContactService"
Cohesion: 0.13
Nodes (11): ContactController, Body, Controller, Get, Param, Post, Put, Query (+3 more)
Nodes (12): ContactController, Body, Controller, Get, Param, Post, Put, Query (+4 more)
### Community 59 - "compilerOptions"
Cohesion: 0.06
Nodes (30): compilerOptions, allowSyntheticDefaultImports, baseUrl, declaration, emitDecoratorMetadata, esModuleInterop, experimentalDecorators, forceConsistentCasingInFileNames (+22 more)
### Community 60 - "Blogs.tsx"
Cohesion: 0.15
Nodes (11): ActiveStates, PRESET_BG_COLORS, PRESET_COLORS, RichTextEditor(), RichTextEditorProps, AuthorUser, BlogCategory, BlogPost (+3 more)
### Community 60 - "users.controller.ts"
Cohesion: 0.14
Nodes (13): AddressDto, ApiProperty, ApiPropertyOptional, IsBoolean, IsNotEmpty, IsOptional, IsString, ApiPropertyOptional (+5 more)
### Community 61 - "payment.controller.ts"
Cohesion: 0.10
Nodes (22): AdminTransactionFilterDto, ApiPropertyOptional, IsIn, IsNumber, IsOptional, IsString, Type, CreateEBankCheckoutDto (+14 more)
### Community 61 - "payment.service.ts"
Cohesion: 0.20
Nodes (8): AdminTransactionFilterDto, ApiPropertyOptional, IsIn, IsNumber, IsOptional, IsString, Type, ClientMetadata
### Community 62 - "ProductsService"
Cohesion: 0.18
Nodes (10): GetProductsDto, ApiPropertyOptional, IsEnum, IsOptional, IsString, Query, ProductsModule, Module (+2 more)
### Community 62 - "ProductDto"
Cohesion: 0.17
Nodes (7): ProductDto, ApiPropertyOptional, IsArray, IsBoolean, IsNumber, IsOptional, IsString
### Community 63 - "dependencies"
Cohesion: 0.09
@ -612,17 +614,13 @@ Nodes (20): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, lib
Cohesion: 0.50
Nodes (3): app_module_1, supertest_1, testing_1
### Community 66 - "AdminQueryDto"
Cohesion: 0.18
Nodes (7): ApiQuery, Get, Query, AdminQueryDto, ApiPropertyOptional, IsOptional, IsString
### Community 66 - "ApiOperation"
Cohesion: 0.22
Nodes (8): ApiOperation, ApiQuery, Get, Query, AdminQueryDto, ApiPropertyOptional, IsOptional, IsString
### Community 67 - "PetsController"
Cohesion: 0.11
Nodes (13): PetsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller, Delete, Get (+5 more)
### Community 68 - "20260526145407_init/migration.sql"
Cohesion: 0.27
Nodes (14): "coupons", "health_logs", "order_items", "orders", "pet_medical_conditions", "pets", "product_ingredients", "product_symptoms" (+6 more)
### Community 67 - "admin.module.ts"
Cohesion: 0.06
Nodes (23): AdminModule, Module, PetsController, ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Controller (+15 more)
### Community 69 - "Required Review Group Closures"
Cohesion: 0.10
@ -633,8 +631,8 @@ Cohesion: 0.08
Nodes (23): compilerOptions, allowImportingTsExtensions, erasableSyntaxOnly, jsx, lib, module, moduleDetection, moduleResolution (+15 more)
### Community 71 - "getPageMetadata"
Cohesion: 0.09
Nodes (16): generateMetadata(), CatalogClient(), generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata(), getHomeData(), Home() (+8 more)
Cohesion: 0.08
Nodes (17): generateMetadata(), CatalogClient(), generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata(), generateMetadata() (+9 more)
### Community 72 - "Operational Rules & Boundaries"
Cohesion: 0.11
@ -649,8 +647,8 @@ Cohesion: 0.13
Nodes (14): ApiBearerAuth, ApiOperation, ApiQuery, ApiTags, Body, Controller, Delete, Get (+6 more)
### Community 75 - "PetsController"
Cohesion: 0.08
Nodes (32): CreateHealthLogDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, CreateReminderDto, ApiProperty (+24 more)
Cohesion: 0.05
Nodes (47): CreateHealthLogDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, CreatePetDto, ApiProperty (+39 more)
### Community 77 - "seo.module.ts"
Cohesion: 0.16
@ -692,24 +690,24 @@ Nodes (9): GatewayHealthResult, IPaymentGateway, PaymentInquiryResult, PaymentRe
Cohesion: 0.12
Nodes (17): dependencies, axios, lucide-react, motion, next, react, react-dom, sonner (+9 more)
### Community 88 - "useSettingsStore"
Cohesion: 0.11
Nodes (24): AuthModal, B2BPortal, CartDrawer, ClientLayout(), LoginModal, AuthModal(), AuthModalProps, extractOtpFromText() (+16 more)
### Community 88 - "toPersian"
Cohesion: 0.10
Nodes (15): AuthModal, B2BPortal, CartDrawer, LoginModal, AuthModal(), AuthModalProps, extractOtpFromText(), LoginModal() (+7 more)
### Community 89 - "seed-products.ts"
Cohesion: 0.17
Nodes (12): prisma, main(), prisma, determineSuitableFor(), findOrCreateCategory(), getProductImageUrl(), main(), parseSize() (+4 more)
### Community 90 - "HomeClient.tsx"
Cohesion: 0.10
Nodes (20): HomeClient(), HomeClientProps, CATEGORY_MAP, ICON_MAP, BannerPlacement(), BannerPlacementProps, BlogPreviewSection(), FAQSection() (+12 more)
### Community 90 - "useSettingsStore"
Cohesion: 0.08
Nodes (34): HomeClient(), HomeClientProps, ArchiveProductCard(), CATEGORY_MAP, ICON_MAP, BannerPlacement(), BannerPlacementProps, BrandLogo() (+26 more)
### Community 91 - "Reconciled Audit Roles & Assignments"
Cohesion: 0.12
Nodes (15): 10. Documentation Engineer, 1. Lead Architect / Orchestrator, 2. React / Vite Storefront Auditor, 3. NestJS Backend Auditor, 4. Admin Features Auditor, 5. Database & Data Integrity Auditor, 6. Security Auditor, 7. TypeScript & Code Quality Auditor (+7 more)
### Community 92 - "OrdersService"
Cohesion: 0.07
Cohesion: 0.08
Nodes (29): CreateOrderDto, OrderItemDto, ApiProperty, ApiPropertyOptional, IsArray, IsNotEmpty, IsNumber, IsOptional (+21 more)
### Community 94 - "Phase 3.1 — Human Review Preparation and Master Backlog Critique"
@ -729,8 +727,8 @@ Cohesion: 0.13
Nodes (15): scripts, build, build:nest, docs:generate, lint, seo:backfill, start, start:debug (+7 more)
### Community 99 - "BlogsController"
Cohesion: 0.06
Nodes (34): BlogsController, ApiBearerAuth, ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiTags, Body, Controller (+26 more)
Cohesion: 0.08
Nodes (19): BlogsController, ApiBearerAuth, ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiTags, Body, Controller (+11 more)
### Community 100 - "Deep Audit Summary Report"
Cohesion: 0.14
@ -749,20 +747,20 @@ Cohesion: 0.15
Nodes (12): 10. `TASK-VERIFY-001`, 1. `TASK-SEC-001`, 2. `TASK-SEC-002`, 3. `TASK-SEC-003`, 4. `TASK-FIN-001`, 5. `TASK-BUILD-001`, 6. `TASK-AUTH-001`, 7. `TASK-FE-001` (+4 more)
### Community 104 - "Coupons.tsx"
Cohesion: 0.13
Nodes (12): formatPriceWithCommas(), PriceInput(), PriceInputProps, toEnglishDigits(), Coupon, CouponFormData, CouponModalProps, CouponTarget (+4 more)
Cohesion: 0.15
Nodes (11): formatPriceWithCommas(), PriceInput(), PriceInputProps, toEnglishDigits(), Coupon, CouponFormData, CouponModalProps, CouponTarget (+3 more)
### Community 105 - "Operational Rules & Boundaries"
Cohesion: 0.17
Nodes (11): 1. Detect Test Runner from Tech Stack, 2. Execution Output Capture (Mandatory), 3. Acceptance Criteria Verification, 4. Failure Routing Protocol, 5. Success Routing Protocol, 6. Forbidden Actions, Expected JSON Output Schema, Operational Rules & Boundaries (+3 more)
### Community 106 - "Reports.tsx"
Cohesion: 0.20
Nodes (7): BestSellerItem, CategoryDistItem, COLORS, CustomTooltipProps, ReportData, TopCouponItem, Reports
### Community 106 - "Media.tsx"
Cohesion: 0.19
Nodes (9): ImagePreviewModal(), ImagePreviewModalProps, getFileType(), Media, MediaManager(), ProductItem, Media, PrescriptionsManager (+1 more)
### Community 108 - "PrismaService"
Cohesion: 0.06
Nodes (26): PetQuery, WikiQuery, MeliPayamakPattern, MeliPayamakResponse, SendPatternSmsOptions, SmsConfig, CreateContactSubmissionDto, UpdateContactInfoItemDto (+18 more)
Cohesion: 0.08
Nodes (18): CategoryQuery, MeliPayamakPattern, MeliPayamakResponse, SendPatternSmsOptions, SmsConfig, SmsLogQuery, CreateContactSubmissionDto, UpdateContactInfoItemDto (+10 more)
### Community 109 - "1. Summary of Integrity Repairs Performed"
Cohesion: 0.17
@ -780,8 +778,8 @@ Nodes (10): 1. Mark Active Task as Complete, 2. Documentation Updates, 3. Dynami
Cohesion: 0.18
Nodes (10): 1. Pre-Deployment Checklist, 2. Build Verification, 3. Deployment Strategy (Based on Target), 4. Post-Deployment Health Check, 5. Forbidden Actions, Expected JSON Output Schema, Operational Rules & Boundaries, Required Output Artifacts (What files to write/update) (+2 more)
### Community 113 - "reviews.controller.ts"
Cohesion: 0.15
### Community 113 - "CreateReviewDto"
Cohesion: 0.14
Nodes (13): CreateReviewDto, ApiProperty, IsInt, IsNotEmpty, IsOptional, IsString, Min, ApiProperty (+5 more)
### Community 114 - "AppService"
@ -812,6 +810,10 @@ Nodes (9): compilerOptions, esModuleInterop, module, moduleResolution, skipLibCh
Cohesion: 0.20
Nodes (9): Compile and run the project, Deployment, Description, License, Project setup, Resources, Run tests, Stay in touch (+1 more)
### Community 123 - "CreateEBankCheckoutDto"
Cohesion: 0.22
Nodes (8): CreateEBankCheckoutDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsNumber, IsOptional, IsString, Min
### Community 124 - "Repository Map"
Cohesion: 0.20
Nodes (9): 1. Active Customer Storefront: React 19 + Vite Application, 2. Active Backend Service: NestJS Application, 3. Excluded Placeholder / Non-Auditable Directories, Active Applications & Non-Auditable Scopes, Documentation Governance, Important Configuration & Infrastructure Files, Mandatory Global Audit Exclusions, Repository Map (+1 more)
@ -828,13 +830,13 @@ Nodes (9): name, private, scripts, build, dev, lint, preview, type (+1 more)
Cohesion: 0.20
Nodes (9): Arch Linux, Contributors, Install, Known problems for variable version, License, Sahel-Font, To Do (variable), طریقه استفاده از نسخه متغیر variable (+1 more)
### Community 128 - "AdminService"
Cohesion: 0.09
Nodes (13): AdminController, ApiBearerAuth, ApiOperation, ApiTags, Body, Controller, Delete, Param (+5 more)
### Community 128 - "AdminController"
Cohesion: 0.14
Nodes (7): AdminController, ApiBearerAuth, ApiTags, Body, Controller, Put, UseGuards
### Community 129 - "RouteErrorBoundary"
Cohesion: 0.22
Nodes (3): Props, RouteErrorBoundary, State
### Community 129 - "InitiatePaymentDto"
Cohesion: 0.43
Nodes (7): InitiatePaymentDto, InitiateWalletTopupDto, ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString
### Community 130 - "Sahel-Font"
Cohesion: 0.20
@ -893,16 +895,16 @@ Cohesion: 0.25
Nodes (6): app_module_1, core_1, fs, path, swagger_1, yaml
### Community 145 - "admin.service.ts"
Cohesion: 0.13
Nodes (20): CouponInput, CouponTargetInput, PaginationQuery, ProductDto, ApiPropertyOptional, IsArray, IsBoolean, IsNumber (+12 more)
Cohesion: 0.17
Nodes (14): CouponInput, CouponTargetInput, PaginationQuery, CreateUserDto, ApiProperty, ApiPropertyOptional, IsEmail, IsNotEmpty (+6 more)
### Community 146 - "System Discovery"
Cohesion: 0.25
Nodes (7): Active Core Applications, Current Architecture, Evidence-Based Status Matrix, Excluded / Non-Auditable Artifacts, Major Business Domains Discovered, System Discovery, Technical Architecture Summary
### Community 147 - "RevalidationService"
Cohesion: 0.17
Nodes (5): RevalidationModule, Global, Module, RevalidationService, Injectable
Cohesion: 0.11
Nodes (7): RevalidationModule, Global, Module, RevalidationService, Injectable, RedisService, Injectable
### Community 148 - "wiki/[slug]/page.tsx"
Cohesion: 0.60
@ -917,8 +919,8 @@ Cohesion: 0.29
Nodes (6): 1. Executive Vision, 2. Target Audience, 3. Functional Requirements, 4. Non-Functional Requirements (Performance, Security), 5. Epic / Feature Breakdown, Product Requirement Document (PRD)
### Community 151 - "auth.service.ts"
Cohesion: 0.08
Nodes (13): AdminLoginInput, AuthService, LoginInput, RegisterInput, Injectable, SendOtpDto, ApiProperty, IsNotEmpty (+5 more)
Cohesion: 0.09
Nodes (17): AdminLoginInput, AuthService, LoginInput, RegisterInput, Injectable, SendOtpDto, ApiProperty, IsNotEmpty (+9 more)
### Community 153 - "exclude"
Cohesion: 0.22
@ -1001,8 +1003,8 @@ Cohesion: 0.40
Nodes (4): activeFiles, errors, validationOutput, warnings
### Community 179 - "PaginationDto"
Cohesion: 0.05
Nodes (29): BlogsModule, Module, BlogFilterDto, BlogsService, Injectable, PaginationDto, SortOrder, ApiPropertyOptional (+21 more)
Cohesion: 0.07
Nodes (29): BlogFilterDto, PaginationDto, SortOrder, ApiPropertyOptional, IsEnum, IsInt, IsOptional, IsString (+21 more)
### Community 180 - "API Contract Specification"
Cohesion: 0.50
@ -1021,7 +1023,7 @@ Cohesion: 0.50
Nodes (3): Overview & Content Foundation, SEO & Content Requirements, 🚀 SEO & Content Strategy Review (12_seo_content)
### Community 184 - "MetricsController"
Cohesion: 0.29
Cohesion: 0.20
Nodes (5): ApiExcludeController, MetricsController, Controller, Get, Res
### Community 191 - "graphify reference: add a URL and watch a folder"
@ -1049,8 +1051,8 @@ Cohesion: 0.50
Nodes (3): Select, SelectOption, SelectProps
### Community 199 - "auth.controller.ts"
Cohesion: 0.08
Nodes (25): AdminLoginDto, ApiProperty, IsEmail, IsNotEmpty, IsString, MinLength, LoginDto, ApiProperty (+17 more)
Cohesion: 0.10
Nodes (19): AdminLoginDto, ApiProperty, IsEmail, IsNotEmpty, IsString, MinLength, LoginDto, ApiProperty (+11 more)
### Community 200 - "application/README.md"
Cohesion: 0.50
@ -1068,17 +1070,21 @@ Nodes (3): ADR-AUTH-001: Admin Panel Authentication Architecture & Token Managem
Cohesion: 0.67
Nodes (3): Shabnam Font README, Shabnam Font Sample, Vazir Font
### Community 294 - "ZibalService"
Cohesion: 0.09
Nodes (4): PaymentService, Injectable, Injectable, ZibalService
### Community 234 - "WholesaleApplyDto"
Cohesion: 0.29
Nodes (6): ApiProperty, ApiPropertyOptional, IsNotEmpty, IsOptional, IsString, WholesaleApplyDto
### Community 237 - "app/page.tsx"
Cohesion: 0.67
Nodes (3): generateMetadata(), getHomeData(), Home()
### Community 298 - ".initiateOrderPayment"
Cohesion: 0.20
Nodes (10): ApiPropertyOptional, IsOptional, IsString, ZibalCallbackQueryDto, ApiBadRequestResponse, ApiOkResponse, Req, Res (+2 more)
### Community 313 - "MenuManager.tsx"
Cohesion: 0.33
Nodes (4): MENU_TABS, MenuItem, MenuType, MenuManager
### Community 313 - "Modal.tsx"
Cohesion: 0.08
Nodes (17): maxWidthClasses, Modal(), ModalProps, ContactInfoItem, ContactSubmission, MENU_TABS, MenuItem, MenuType (+9 more)
### Community 317 - "revalidate/route.ts"
Cohesion: 0.83
@ -1087,22 +1093,22 @@ Nodes (3): GET(), handleRevalidate(), POST()
## Knowledge Gaps
- **1307 isolated node(s):** `$schema`, `collection`, `sourceRoot`, `deleteOutDir`, `name` (+1302 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **117 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
- **118 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `ApiResponse` connect `BlogsController` to `AuthController`, `PetsController`, `src/services/api.ts`, `ProductsController`, `UsersService`, `OrdersService`?**
_High betweenness centrality (0.069) - this node is a cross-community bridge._
- **Why does `Roles()` connect `Roles` to `SmsService`, `CmsController`, `tickets.controller.ts`, `ReviewsService`, `JwtAuthGuard`, `ProductsController`, `MenuService`, `WholesaleApplyDto`, `B2BService`, `FaqService`, `SslController`, `BannersService`, `TestimonialsController`, `IngredientsService`, `PrescriptionsController`, `SmartAdvisorController`, `ContactService`, `payment.controller.ts`, `ProductsService`, `reviews.controller.ts`?**
_High betweenness centrality (0.059) - this node is a cross-community bridge._
- **Why does `JwtAuthGuard` connect `JwtAuthGuard` to `B2BService`, `PetsController`, `FaqService`, `CmsController`, `tickets.controller.ts`, `CategoriesController`, `admin.module.ts`, `auth.controller.ts`, `pets/pets.controller.ts`, `DoctorQueryDto`, `admin.service.ts`, `reviews.controller.ts`, `PaginationDto`, `UsersService`, `payment.controller.ts`, `ProductsService`?**
_High betweenness centrality (0.030) - this node is a cross-community bridge._
- **Why does `ApiResponse` connect `src/services/api.ts` to `BlogsController`, `AuthController`, `PetsController`, `HomeController`, `ProductsService`, `PaginationDto`, `UsersController`, `OrdersService`?**
_High betweenness centrality (0.070) - this node is a cross-community bridge._
- **Why does `Roles()` connect `Roles` to `WholesaleService`, `B2BService`, `SmsService`, `FaqService`, `CmsController`, `tickets.controller.ts`, `SslController`, `BannersService`, `ReviewsService`, `TestimonialsService`, `IngredientsService`, `JwtAuthGuard`, `ProductsService`, `PrescriptionsService`, `SmartAdvisorService`, `MenuService`, `ContactService`?**
_High betweenness centrality (0.062) - this node is a cross-community bridge._
- **Why does `PrismaService` connect `PrismaService` to `app.module.ts`, `SmsService`, `CmsController`, `tickets.controller.ts`, `ReportsController`, `DoctorQueryDto`, `admin.service.ts`, `RevalidationService`, `ProductsService`, `CreateVideoDto`, `auth.service.ts`, `MenuService`, `WholesaleService`, `B2BService`, `FaqService`, `ZibalService`, `CategoriesController`, `MediaController`, `ZibalEBankService`, `BannersService`, `TestimonialsService`, `HomeController`, `IngredientsService`, `PaginationDto`, `PrescriptionsService`, `SmartAdvisorService`, `MetricsController`, `ContactService`, `payment.service.ts`, `admin.module.ts`, `PetsController`, `seo.module.ts`, `zibal.service.ts`, `OrdersService`, `BlogsController`, `CreateReviewDto`, `UsersService`?**
_High betweenness centrality (0.031) - this node is a cross-community bridge._
- **What connects `$schema`, `collection`, `sourceRoot` to the rest of the system?**
_1307 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `app.module.ts` be split into smaller, more focused modules?**
_Cohesion score 0.07215686274509804 - nodes in this community are weakly interconnected._
_Cohesion score 0.07529411764705882 - nodes in this community are weakly interconnected._
- **Should `SmsService` be split into smaller, more focused modules?**
_Cohesion score 0.055040197897340756 - nodes in this community are weakly interconnected._
_Cohesion score 0.052028732284993204 - nodes in this community are weakly interconnected._
- **Should `ProductService` be split into smaller, more focused modules?**
_Cohesion score 0.060814383923849816 - nodes in this community are weakly interconnected._
_Cohesion score 0.06229508196721312 - nodes in this community are weakly interconnected._

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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}

View File

@ -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

View File

@ -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}

View File

@ -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

View File

@ -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}

View File

@ -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}

View File

@ -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}

View File

@ -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

View File

@ -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}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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}

View File

@ -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

File diff suppressed because one or more lines are too long

View File

@ -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}

View File

@ -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}

View File

@ -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

View File

@ -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}

View File

@ -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}

View File

@ -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

View File

@ -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

File diff suppressed because one or more lines are too long

View File

@ -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

View File

@ -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}

View File

@ -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

View File

@ -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}

View File

@ -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}

View File

@ -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}

View File

@ -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}

View File

@ -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

View File

@ -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}

View File

@ -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

View File

@ -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

Some files were not shown because too many files have changed in this diff Show More