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 { AdminService } from './admin.service';
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import { RedisService } from '../redis/redis.service'; import { RedisService } from '../redis/redis.service';
import { RevalidationService } from '../common/revalidation/revalidation.service';
describe('AdminService', () => { describe('AdminService', () => {
let service: AdminService; let service: AdminService;
@ -12,6 +13,10 @@ describe('AdminService', () => {
AdminService, AdminService,
{ provide: PrismaService, useValue: {} }, { provide: PrismaService, useValue: {} },
{ provide: RedisService, useValue: {} }, { provide: RedisService, useValue: {} },
{
provide: RevalidationService,
useValue: { revalidateTag: jest.fn(), revalidatePath: jest.fn() },
},
], ],
}).compile(); }).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 { ReportsService } from './reports.service';
import { JwtAuthGuard } from '../auth/jwt-auth.guard'; import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; import {
ApiTags,
ApiBearerAuth,
ApiOperation,
ApiQuery,
} from '@nestjs/swagger';
@ApiTags('Admin - گزارشات') @ApiTags('Admin - گزارشات')
@ApiBearerAuth() @ApiBearerAuth()
@ -11,9 +16,22 @@ export class ReportsController {
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Get() @Get()
@ApiOperation({ summary: 'دریافت گزارشات داشبورد' }) @ApiOperation({
async getReports() { summary: 'دریافت گزارشات داشبورد با قابلیت فیلتر زمانی و نوع کاربر',
const data = await this.reportsService.getDashboardReports(); })
@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 }; return { success: true, data };
} }
} }

View File

@ -5,29 +5,71 @@ import { PrismaService } from '../prisma/prisma.service';
export class ReportsService { export class ReportsService {
constructor(private prisma: PrismaService) {} constructor(private prisma: PrismaService) {}
async getDashboardReports() { async getDashboardReports(query?: {
// 1. Total revenue and charity 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({ const orders = await this.prisma.order.findMany({
where: { status: { not: 'cancelled' } }, where: whereClause,
select: { include: {
totalAmount: true,
charityDonation: true,
createdAt: true,
user: { select: { role: true } }, user: { select: { role: true } },
coupon: true,
orderItems: {
include: {
product: {
select: {
id: true,
buyPrice: true,
priceValue: true,
},
},
},
},
}, },
orderBy: { createdAt: 'asc' },
}); });
let totalRevenue = 0; let totalRevenue = 0; // Total paid/deposited by users
let totalCharity = 0; let totalCharity = 0; // Charity portion
let b2bRevenue = 0; let b2bRevenue = 0;
let b2cRevenue = 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) { 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; totalRevenue += amount;
totalCharity += Number(order.charityDonation); totalCharity += charity;
if (order.user?.role === 'B2B') { if (order.user?.role === 'B2B') {
b2bRevenue += amount; b2bRevenue += amount;
@ -35,23 +77,74 @@ export class ReportsService {
b2cRevenue += amount; 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 // Format date YYYY-MM-DD
const dateKey = order.createdAt.toISOString().split('T')[0]; const dateKey = order.createdAt.toISOString().split('T')[0];
if (!salesByDate[dateKey]) salesByDate[dateKey] = 0; if (!salesByDate[dateKey]) {
salesByDate[dateKey] += amount; 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 // Format sales timeline for charts
const salesTimeline = Object.entries(salesByDate) 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()); .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({ const orderItems = await this.prisma.orderItem.groupBy({
by: ['productId'], by: ['productId'],
_sum: { quantity: true }, _sum: { quantity: true },
where: { where: {
order: { status: { not: 'cancelled' } }, order: whereClause,
productId: { not: null }, productId: { not: null },
}, },
orderBy: { _sum: { quantity: 'desc' } }, orderBy: { _sum: { quantity: 'desc' } },
@ -89,7 +182,7 @@ export class ReportsService {
}) })
.filter((c) => c.value > 0); .filter((c) => c.value > 0);
// 4. Coupons Usage // 4. Coupons Usage in range
const topCoupons = await this.prisma.coupon.findMany({ const topCoupons = await this.prisma.coupon.findMany({
orderBy: { usedCount: 'desc' }, orderBy: { usedCount: 'desc' },
take: 5, take: 5,
@ -100,9 +193,15 @@ export class ReportsService {
overview: { overview: {
totalRevenue, totalRevenue,
totalCharity, totalCharity,
productSalesRevenue,
totalOrders: orders.length, totalOrders: orders.length,
b2bRevenue, b2bRevenue,
b2cRevenue, b2cRevenue,
totalTax,
totalShipping,
totalDiscounts,
totalCostOfGoods,
netProfit,
}, },
salesTimeline, salesTimeline,
bestSellers, bestSellers,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -44,7 +44,7 @@ describe('UsersService', () => {
mockPrisma.user.findUnique.mockResolvedValue(mockUser); mockPrisma.user.findUnique.mockResolvedValue(mockUser);
const result = await service.findById('user-id'); const result = await service.findById('user-id');
expect(prisma.user.findUnique).toHaveBeenCalled(); expect(prisma.user.findUnique).toHaveBeenCalled();
expect(result).toEqual(mockUser); expect(result).toEqual({ ...mockUser, hasPassword: false });
}); });
it('should call prisma update in update', async () => { 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 || ''}` : 'نامشخص'} {blog.author?.firstName ? `${blog.author.firstName} ${blog.author.lastName || ''}` : 'نامشخص'}
</td> </td>
<td className="py-4 px-6 text-gray-500 text-xs font-medium"> <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" /> <Clock className="w-3.5 h-3.5 text-gray-400" />
<span>{blog.readingTime || 3} دقیقه</span> <span>{blog.readingTime || 3} دقیقه</span>
</div> </div>
<span className="text-[10px] text-gray-400 mt-0.5 block"> <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">
{blog.viewCount || 0} بازدید <Eye className="w-3 h-3 text-purple-600" />
</span> <span>{(blog.viewCount || 0).toLocaleString('fa-IR')} بازدید</span>
</div>
</td> </td>
<td className="py-4 px-6">{renderStatusBadge(blog.status, blog.isPublished)}</td> <td className="py-4 px-6">{renderStatusBadge(blog.status, blog.isPublished)}</td>
<td className="py-4 px-6"> <td className="py-4 px-6">

View File

@ -1,11 +1,21 @@
import { useState, useEffect } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { TrendingUp, DollarSign, ShoppingBag, Users, HeartHandshake, Download } from 'lucide-react'; import {
TrendingUp,
DollarSign,
ShoppingBag,
Users,
HeartHandshake,
Download,
Calendar,
Filter,
Receipt,
Percent,
Package,
ShieldCheck
} from 'lucide-react';
import api from '../services/api'; import api from '../services/api';
import Button from '../components/ui/Button'; import Button from '../components/ui/Button';
import Spinner from '../components/ui/Spinner'; import Spinner from '../components/ui/Spinner';
import { import {
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer, AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer,
BarChart, Bar, PieChart, Pie, Cell, Legend BarChart, Bar, PieChart, Pie, Cell, Legend
@ -15,16 +25,21 @@ const COLORS = ['#8B5CF6', '#EC4899', '#3B82F6', '#10B981', '#F59E0B'];
interface CustomTooltipProps { interface CustomTooltipProps {
active?: boolean; active?: boolean;
payload?: Array<{ value: number | string }>; payload?: Array<{ value: number | string; name?: string; color?: string }>;
label?: string; label?: string;
} }
const CustomTooltip = ({ active, payload, label }: CustomTooltipProps) => { const CustomTooltip = ({ active, payload, label }: CustomTooltipProps) => {
if (active && payload && payload.length) { if (active && payload && payload.length) {
return ( 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="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> </div>
); );
} }
@ -50,13 +65,20 @@ export interface TopCouponItem {
export interface ReportData { export interface ReportData {
overview?: { overview?: {
totalRevenue: number; totalRevenue: number;
totalOrders: number;
totalUsers: number;
avgOrderValue: number;
totalCharity?: number; totalCharity?: number;
productSalesRevenue?: number;
totalOrders: number;
totalUsers?: number;
avgOrderValue?: number;
b2bRevenue?: 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[]; bestSellers?: BestSellerItem[];
categoryDistribution?: CategoryDistItem[]; categoryDistribution?: CategoryDistItem[];
topCoupons?: TopCouponItem[]; topCoupons?: TopCouponItem[];
@ -66,40 +88,72 @@ export default function Reports() {
const [reportData, setReportData] = useState<ReportData | null>(null); const [reportData, setReportData] = useState<ReportData | null>(null);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
useEffect(() => { // Filter States
let isSubscribed = true; const [dateFilterType, setDateFilterType] = useState<'all' | 'today' | '7days' | '30days' | 'custom'>('30days');
api.get('/admin/reports').then(reportRes => { const [startDate, setStartDate] = useState('');
if (!isSubscribed) return; const [endDate, setEndDate] = useState('');
if (reportRes.data?.success) { const [roleFilter, setRoleFilter] = useState('ALL');
setReportData(reportRes.data.data);
}
}).catch(err => {
console.error(err);
}).finally(() => {
if (isSubscribed) setIsLoading(false);
});
return () => {
isSubscribed = false;
};
}, []);
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>; 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 ( return (
<div className="space-y-6"> <div className="space-y-6 font-vazir text-right" dir="rtl">
<div className="flex flex-col sm:flex-row justify-between gap-4"> {/* 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> <div>
<h2 className="text-2xl font-black text-gray-900 flex items-center gap-2"> <h2 className="text-2xl font-black text-gray-900 flex items-center gap-2">
<TrendingUp className="w-6 h-6 text-purple-600" /> <TrendingUp className="w-6 h-6 text-purple-600" />
گزارشات و تحلیل‌ها گزارشات و تحلیل‌های مالی پیشرفته
</h2> </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> </div>
<Button <Button
variant="outline" variant="outline"
@ -108,73 +162,212 @@ export default function Reports() {
onClick={() => { onClick={() => {
if (!reportData) return; if (!reportData) return;
const rows = [ const rows = [
['گزارش کلی سیستم کنینا ایران'], ['گزارش مالی و عملکرد فروش کنینا ایران'],
['درآمد کل (تومان)', overview?.totalRevenue || 0], ['تاریخ گزارش', new Date().toLocaleDateString('fa-IR')],
['تعداد سفارشات', overview?.totalOrders || 0],
['تعداد کاربران', overview?.totalUsers || 0],
['میانگین ارزش سفارش', overview?.avgOrderValue || 0],
[], [],
['محصولات پرفروش', 'تعداد فروش'], ['شاخص مالی', 'مبلغ (تومان) / مقدار'],
['درآمد کل واریزی‌ها', 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]), ...(bestSellers || []).map(b => [b.name, b.quantity]),
]; ];
const csvContent = 'data:text/csv;charset=utf-8,\uFEFF' + rows.map(e => e.join(',')).join('\n'); const csvContent = 'data:text/csv;charset=utf-8,\uFEFF' + rows.map(e => e.join(',')).join('\n');
const encodedUri = encodeURI(csvContent); const encodedUri = encodeURI(csvContent);
const link = document.createElement('a'); const link = document.createElement('a');
link.setAttribute('href', encodedUri); 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); document.body.appendChild(link);
link.click(); link.click();
document.body.removeChild(link); document.body.removeChild(link);
}} }}
> >
خروجی اکسل (Excel) خروجی اکسل مالی (Excel / CSV)
</Button> </Button>
</div> </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="flex flex-wrap items-center gap-1.5 bg-gray-50 p-1 rounded-2xl border border-gray-200 text-xs font-bold">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4"> <button
<div className="bg-gradient-to-br from-purple-500 to-indigo-600 p-6 rounded-2xl text-white shadow-lg shadow-purple-200"> onClick={() => setDateFilterType('30days')}
<div className="flex items-center justify-between mb-4"> 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'}`}
<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> </button>
<p className="text-3xl font-black">{Number(overview.totalRevenue).toLocaleString()}</p> <button
<p className="text-purple-200 text-sm mt-1">تومان</p> 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>
<div className="bg-gradient-to-br from-pink-500 to-rose-600 p-6 rounded-2xl text-white shadow-lg shadow-pink-200"> {dateFilterType === 'custom' && (
<div className="flex items-center justify-between mb-4"> <div className="flex items-center gap-2 text-xs">
<h3 className="font-bold text-pink-100">سهم خیریه (مسئولیت اجتماعی)</h3> <input
<div className="p-2 bg-white/20 rounded-xl"><HeartHandshake className="w-6 h-6" /></div> 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> </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="flex items-center gap-2 mr-auto">
<span className="text-xs font-black text-gray-700">نوع مشتری:</span>
<div className="bg-gradient-to-br from-blue-500 to-cyan-600 p-6 rounded-2xl text-white shadow-lg shadow-blue-200"> <select
<div className="flex items-center justify-between mb-4"> value={roleFilter}
<h3 className="font-bold text-blue-100">فروش همکاران (B2B)</h3> onChange={(e) => setRoleFilter(e.target.value)}
<div className="p-2 bg-white/20 rounded-xl"><Users className="w-6 h-6" /></div> className="px-3 py-1.5 rounded-xl border border-gray-200 text-xs font-bold outline-none bg-gray-50/50"
</div> >
<p className="text-3xl font-black">{Number(overview.b2bRevenue).toLocaleString()}</p> <option value="ALL">همه (B2B + B2C)</option>
<p className="text-blue-200 text-sm mt-1">تومان</p> <option value="B2B">خریداران عمده (B2B)</option>
</div> <option value="User_PetOwner">مصرف‌کنندگان (B2C)</option>
</select>
<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> </div>
</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"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Sales Chart */} {/* Timeline Chart with Dual Area for Total & Charity */}
<div className="lg:col-span-2 bg-white p-6 rounded-2xl shadow-sm border border-gray-100"> <div className="lg:col-span-2 bg-white p-6 rounded-3xl shadow-xs border border-gray-200">
<h3 className="text-lg font-bold text-gray-900 mb-6">نمودار فروش روزانه</h3> <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"> <div className="h-80 w-full" dir="ltr">
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
<AreaChart data={salesTimeline} margin={{ top: 10, right: 10, left: 0, bottom: 0 }}> <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="5%" stopColor="#8B5CF6" stopOpacity={0.3}/>
<stop offset="95%" stopColor="#8B5CF6" stopOpacity={0}/> <stop offset="95%" stopColor="#8B5CF6" stopOpacity={0}/>
</linearGradient> </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> </defs>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f3f4f6" /> <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f3f4f6" />
<XAxis dataKey="date" stroke="#9ca3af" fontSize={12} tickLine={false} axisLine={false} /> <XAxis dataKey="date" stroke="#9ca3af" fontSize={11} tickLine={false} axisLine={false} />
<YAxis stroke="#9ca3af" fontSize={12} tickLine={false} axisLine={false} tickFormatter={(val) => `${val / 1000}k`} /> <YAxis stroke="#9ca3af" fontSize={11} tickLine={false} axisLine={false} tickFormatter={(val) => `${val >= 1000000 ? `${(val/1000000).toFixed(1)}M` : `${val / 1000}k`}`} />
<RechartsTooltip content={<CustomTooltip />} /> <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> </AreaChart>
</ResponsiveContainer> </ResponsiveContainer>
</div> </div>
</div> </div>
{/* Category Distribution */} {/* Category Distribution */}
<div className="bg-white p-6 rounded-2xl shadow-sm border border-gray-100"> <div className="bg-white p-6 rounded-3xl shadow-xs border border-gray-200">
<h3 className="text-lg font-bold text-gray-900 mb-6">سهم فروش دسته‌بندی‌ها</h3> <h3 className="text-base font-black text-gray-900 mb-6">سهم فروش دسته‌بندی‌ها</h3>
<div className="h-64 w-full flex justify-center"> <div className="h-64 w-full flex justify-center">
{categoryDistribution && categoryDistribution.length > 0 ? ( {categoryDistribution && categoryDistribution.length > 0 ? (
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
@ -211,19 +409,20 @@ export default function Reports() {
</PieChart> </PieChart>
</ResponsiveContainer> </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> </div>
</div> </div>
{/* Best Sellers & Top Coupons */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Best Sellers */} {/* Best Sellers */}
<div className="bg-white p-6 rounded-2xl shadow-sm border border-gray-100"> <div className="bg-white p-6 rounded-3xl shadow-xs border border-gray-200">
<h3 className="text-lg font-bold text-gray-900 mb-6">پرفروش‌ترین محصولات</h3> <h3 className="text-base font-black text-gray-900 mb-6">پرفروش‌ترین محصولات در بازه انتخابی</h3>
<div className="min-h-[320px] w-full" dir="ltr"> <div className="min-h-[300px] w-full" dir="ltr">
{bestSellers && bestSellers.length > 0 ? ( {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 <BarChart
data={bestSellers} data={bestSellers}
layout="vertical" layout="vertical"
@ -251,31 +450,31 @@ export default function Reports() {
</BarChart> </BarChart>
</ResponsiveContainer> </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>
</div> </div>
{/* Top Coupons */} {/* Top Coupons */}
<div className="bg-white p-6 rounded-2xl shadow-sm border border-gray-100"> <div className="bg-white p-6 rounded-3xl shadow-xs border border-gray-200">
<h3 className="text-lg font-bold text-gray-900 mb-6">پرکاربردترین کدهای تخفیف</h3> <h3 className="text-base font-black text-gray-900 mb-6">کدهای تخفیف پراستفاده</h3>
<div className="space-y-4"> <div className="space-y-3">
{topCoupons && topCoupons.length > 0 ? ( {topCoupons && topCoupons.length > 0 ? (
topCoupons.map((coupon: TopCouponItem, idx: number) => ( 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="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} #{idx + 1}
</div> </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>
<div className="text-gray-600 font-medium"> <div className="text-gray-600 text-xs font-medium">
<span className="font-black text-gray-900 mx-1">{coupon.usedCount ?? coupon.value ?? 0}</span>بار استفاده <span className="font-black text-gray-900 mx-1 text-sm">{coupon.usedCount ?? coupon.value ?? 0}</span>بار استفاده
</div> </div>
</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>
</div> </div>

View File

@ -50,8 +50,36 @@ export default function ClientLayout({ children }: { children: React.ReactNode }
} else { } else {
useUserStore.getState().logout(); useUserStore.getState().logout();
} }
// Preserve scroll restoration on back/forward
if (typeof window !== 'undefined' && 'scrollRestoration' in window.history) {
window.history.scrollRestoration = 'auto';
}
}, [fetchProfile, fetchSettings]); }, [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) // Check Maintenance Mode (Admin / Partner bypass)
const isMaintenanceMode = getText('MAINTENANCE_MODE', 'false') === 'true' || getText('maintenance_mode', 'false') === 'true'; const isMaintenanceMode = getText('MAINTENANCE_MODE', 'false') === 'true' || getText('maintenance_mode', 'false') === 'true';
const isAdmin = role === 'User_Partner' || (typeof window !== 'undefined' && Boolean(localStorage.getItem('adminToken'))); const isAdmin = role === 'User_Partner' || (typeof window !== 'undefined' && Boolean(localStorage.getItem('adminToken')));

View File

@ -297,49 +297,40 @@ export default function ArchivePage({
return labels; return labels;
}, [categories]); }, [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(() => { useEffect(() => {
// Normalize Input (Trim and lowercase) const isNewCategory = lastInitialPropsRef.current.initialCategory !== initialCategory;
const normSearch = (initialSearch || "").trim().toLowerCase(); const isNewSearch = lastInitialPropsRef.current.initialSearch !== initialSearch;
const normCat = initialCategory.trim().toLowerCase(); const isNewPet = lastInitialPropsRef.current.initialPetType !== initialPetType;
const isNewSymptoms = lastInitialPropsRef.current.initialSymptoms !== initialSymptoms;
const mapKey = normSearch || normCat; if (!isNewCategory && !isNewSearch && !isNewPet && !isNewSymptoms) {
const mapping = CATEGORY_MAP[mapKey];
if (selectedCategory === initialCategory && searchQuery === initialSearch) {
return; return;
} }
Promise.resolve().then(() => { lastInitialPropsRef.current = { initialCategory, initialSearch, initialPetType, initialSymptoms };
setIsUpdating(true);
// 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"); setSelectedPet("all");
setActiveSymptoms([]); setActiveSymptoms(mapping.symptoms || []);
setSearchQuery(""); } else {
setSelectedCategory(initialCategory || "all");
if (mapping) { setSearchQuery(initialSearch || "");
if (mapping.category) setSelectedCategory(mapping.category); setSelectedPet((initialPetType as PetType) || "all");
if (mapping.query) setSearchQuery(mapping.query); setActiveSymptoms(initialSymptoms ? initialSymptoms.split(',').filter(Boolean) : []);
if (mapping.symptoms) { }
setActiveSymptoms(mapping.symptoms); }, [initialCategory, initialSearch, initialPetType, initialSymptoms]);
}
} 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]);
// Update URL Query Parameters // Update URL Query Parameters
useEffect(() => { useEffect(() => {

View File

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

View File

@ -270,6 +270,10 @@ export default function BlogPage({
<Clock className="w-3.5 h-3.5" /> <Clock className="w-3.5 h-3.5" />
{featuredPost.readingTime || 4} دقیقه مطالعه {featuredPost.readingTime || 4} دقیقه مطالعه
</div> </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> </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"> <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} {featuredPost.title}
@ -324,6 +328,11 @@ export default function BlogPage({
{post.category} {post.category}
</span> </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>
<div className="p-6 sm:p-7"> <div className="p-6 sm:p-7">
<div className="flex items-center justify-between text-[11px] font-bold text-medical-gray-400 mb-3"> <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" /> <Calendar className="w-3 h-3" />
{post.date} {post.date}
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-3">
<Clock className="w-3 h-3" /> <div className="flex items-center gap-1">
{post.readingTime || 3} دقیقه <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>
</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"> <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 ( return (
<motion.div <motion.div
initial={{ opacity: 0, y: 24 }} initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
whileHover={{ y: -6 }} whileHover={{ y: -4 }}
transition={{ duration: 0.35, ease: "easeOut" }} transition={{ duration: 0.3, 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" 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 */} {/* 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} {product.category}
</div> </div>
{/* Compatibility Tag */} {/* Compatibility Tag */}
{compatibility && ( {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 === "alert" ? "bg-amber-100 text-amber-700" :
compatibility.type === "success" ? "bg-green-100 text-green-700" : "bg-medical-gray-100 text-medical-gray-600" 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> </div>
)} )}
{/* Image */} {/* Image Container */}
<div className="relative aspect-[4/3] bg-medical-gray-50 flex items-center justify-center overflow-hidden p-5"> <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 <SafeImage
src={product.image} src={product.image}
alt={nameFa} alt={nameFa}
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 25vw)" sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 25vw)"
className="w-full h-full group-hover:scale-105 transition-transform duration-500" 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 */} {/* Hover Actions for desktop */}
<Link href={`/shop/${product.slug || product.id}`} className="absolute inset-0 z-10" /> <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">
{/* 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">
<div <div
onClick={(e) => { e.stopPropagation(); router.push(`/shop/${product.slug || product.id}`); }} 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="مشاهده جزئیات" title="مشاهده جزئیات"
> >
<Eye className="w-5 h-5" /> <Eye className="w-5 h-5" />
@ -93,7 +92,7 @@ function ProductCard({ product }: { product: Product }) {
addItem(product, 1); addItem(product, 1);
toast.success(`${nameFa} به سبد خرید اضافه شد.`); 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="افزودن به سبد خرید" title="افزودن به سبد خرید"
> >
<ShoppingCart className="w-5 h-5" /> <ShoppingCart className="w-5 h-5" />
@ -103,40 +102,40 @@ function ProductCard({ product }: { product: Product }) {
</div> </div>
{/* Content */} {/* 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 */} {/* Dual-language Name */}
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-0.5">
<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"> <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} {nameFa}
</h3> </h3>
{nameEn && ( {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} {nameEn}
</span> </span>
)} )}
</div> </div>
{/* Short description */} {/* 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 || ''} {product.shortDescription || product.description || ''}
</p> </p>
{/* Footer: Price + CTA */} {/* Footer: Price + CTA */}
<div className="flex items-center justify-between pt-3 border-t border-medical-gray-100"> <div className="flex items-center justify-between pt-2 sm:pt-3 border-t border-medical-gray-100">
<div className="text-base font-black text-medical-gray-900 font-vazir whitespace-nowrap"> <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) : 'تماس بگیرید') : 'تماس بگیرید'} {showPrices ? (typeof product.price === 'string' || typeof product.price === 'number' ? String(product.price) : 'تماس بگیرید') : 'تماس بگیرید'}
</div> </div>
{showPreorderBtn ? ( {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>
) : ( ) : (
<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", "مشاهده جزئیات")} {getText("product_view_details", "مشاهده جزئیات")}
</span> </span>
)} )}
</div> </div>
</Link> </div>
</motion.div> </motion.div>
); );
} }

View File

@ -554,37 +554,155 @@ export default function Header({
initial={{ opacity: 0, height: 0 }} initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }} animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }} 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 <input
type="text" type="text"
placeholder="جستجو در محصولات..." placeholder="جستجو در محصولات..."
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} 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" /> <Search className="w-4 h-4 text-medical-gray-400 absolute right-3 top-1/2 -translate-y-1/2" />
</form> </form>
<div className="space-y-2 text-sm font-black"> {/* Section: Shop & Catalog */}
<Link href="/shop" onClick={() => setIsMobileMenuOpen(false)} className="block p-3 rounded-xl bg-medical-gray-50 text-medical-gray-900"> <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>
<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>
<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>
<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>
<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>
<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> </Link>
</div> </div>
</motion.div> </motion.div>

View File

@ -301,14 +301,14 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
</div> </div>
</motion.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 */} {/* 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 <motion.div
className="h-full bg-canina-blue" className="h-full bg-canina-blue"
initial={{ width: "0%" }} initial={{ width: "0%" }}
animate={{ width: `${(step / 4) * 100}%` }} animate={{ width: `${(step / 4) * 100}%` }}
transition={{ duration: 0.5 }} transition={{ duration: 0.4 }}
/> />
</div> </div>
@ -319,33 +319,35 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
initial={{ opacity: 0, x: 20 }} initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }} exit={{ opacity: 0, x: -20 }}
className="space-y-8" className="space-y-6"
> >
<div className="text-center"> <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> <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-medical-gray-600 font-bold font-vazir">{getText('advisor_step1_desc', "همدم شما رو با چه اسمی صدا می‌زنید؟")}</p> <p className="text-xs sm:text-sm text-medical-gray-500 font-bold font-vazir">{getText('advisor_step1_desc', "همدم شما رو با چه اسمی صدا می‌زنید؟")}</p>
</div> </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 <button
type="button"
onClick={() => setType("سگ")} 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" /> <Dog className="w-8 h-8 sm:w-10 sm:h-10" />
<span className="text-lg font-black font-vazir">سگ</span> <span className="text-sm sm:text-base font-black font-vazir">سگ</span>
</button> </button>
<button <button
type="button"
onClick={() => setType("گربه")} 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" /> <Cat className="w-8 h-8 sm:w-10 sm:h-10" />
<span className="text-lg font-black font-vazir">گربه</span> <span className="text-sm sm:text-base font-black font-vazir">گربه</span>
</button> </button>
</div> </div>
<div className="grid md:grid-cols-2 gap-6"> <div className="grid md:grid-cols-2 gap-4 max-w-xl mx-auto">
<div className="space-y-2"> <div className="space-y-1.5">
<label className="text-xs font-black text-medical-gray-600 pr-2">{getText('advisor_pet_name_label', "نام پت")}</label> <label className="text-xs font-black text-medical-gray-600 pr-1">{getText('advisor_pet_name_label', "نام پت")}</label>
<input <input
type="text" type="text"
value={name} value={name}
@ -353,17 +355,17 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
setName(e.target.value); setName(e.target.value);
if (e.target.value.trim() && breed.trim()) setShowError(false); if (e.target.value.trim() && breed.trim()) setShowError(false);
}} }}
placeholder={getText('advisor_pet_name_placeholder', "لوسی، تدی...")} placeholder={getText('advisor_pet_name_placeholder', "مثال: لوسی")}
className={cn( 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() showError && !name.trim()
? "border-red-500 focus:ring-red-500/30" ? "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>
<div className="space-y-2"> <div className="space-y-1.5">
<label className="text-xs font-black text-medical-gray-600 pr-2">{getText('advisor_pet_breed_label', "نژاد")}</label> <label className="text-xs font-black text-medical-gray-600 pr-1">{getText('advisor_pet_breed_label', "نژاد")}</label>
<input <input
type="text" type="text"
value={breed} value={breed}
@ -371,34 +373,37 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
setBreed(e.target.value); setBreed(e.target.value);
if (name.trim() && e.target.value.trim()) setShowError(false); if (name.trim() && e.target.value.trim()) setShowError(false);
}} }}
placeholder={getText('advisor_pet_breed_placeholder', "ژرمن، پرشین...")} placeholder={getText('advisor_pet_breed_placeholder', "مثال: ژرمن شپرد")}
className={cn( 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() showError && !breed.trim()
? "border-red-500 focus:ring-red-500/30" ? "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>
</div> </div>
<motion.button <div className="max-w-md mx-auto pt-2">
onClick={handleNext} <motion.button
animate={shake ? { x: [-10, 10, -10, 10, -5, 5, -2, 2, 0] } : { x: 0 }} type="button"
transition={{ duration: 0.4 }} onClick={handleNext}
className={cn( animate={shake ? { x: [-10, 10, -10, 10, -5, 5, -2, 2, 0] } : { x: 0 }}
"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", transition={{ duration: 0.4 }}
(!name.trim() || !breed.trim()) ? "opacity-60 cursor-pointer" : "" 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>
)} )}
> </div>
{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>
)}
</motion.div> </motion.div>
)} )}
@ -408,60 +413,95 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
initial={{ opacity: 0, x: 20 }} initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }} exit={{ opacity: 0, x: -20 }}
className="space-y-8" className="space-y-6 max-w-xl mx-auto"
> >
<div className="text-center"> <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> <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-medical-gray-600 font-bold font-vazir">{getText('advisor_step2_desc', "اطلاعات فیزیکی دقیق به دوزبندی صحیح مکمل‌ها کمک می‌کند")}</p> <p className="text-xs sm:text-sm text-medical-gray-500 font-bold font-vazir">{getText('advisor_step2_desc', "اطلاعات فیزیکی به دوزبندی دقیق مکمل‌ها کمک می‌کند")}</p>
</div> </div>
<div className="grid grid-cols-2 gap-6"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="space-y-4"> {/* Age Input Box */}
<label className="block text-center text-sm font-black text-medical-gray-800 font-vazir">{getText('advisor_age_label', "سن حیوان (سال)")}</label> <div className="bg-white p-3.5 rounded-2xl border border-medical-gray-200 shadow-xs space-y-2">
<div className="flex items-center gap-2 bg-white rounded-3xl p-2 border-2 border-medical-gray-200"> <label className="block text-center text-xs font-black text-medical-gray-700 font-vazir">{getText('advisor_age_label', "سن حیوان (سال)")}</label>
<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 items-center justify-between gap-1.5 bg-medical-gray-50 rounded-xl p-1 border border-medical-gray-200">
<div className="flex-1 flex items-center justify-center gap-1"> <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 <input
type="number" type="text"
min="0" inputMode="numeric"
max="30" pattern="[0-9]*"
value={age === 0 ? '' : age} value={age === 0 ? '' : age.toString()}
onChange={(e) => setAge(Math.max(0, parseInt(e.target.value) || 0))} onChange={(e) => {
className="w-12 text-center text-xl font-black text-canina-blue font-vazir outline-none bg-transparent" 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> </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> </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> {/* Weight Input Box */}
<div className="flex items-center gap-2 bg-white rounded-3xl p-2 border-2 border-medical-gray-200"> <div className="bg-white p-3.5 rounded-2xl border border-medical-gray-200 shadow-xs space-y-2">
<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> <label className="block text-center text-xs font-black text-medical-gray-700 font-vazir">{getText('advisor_weight_label', "وزن حیوان (کیلوگرم)")}</label>
<div className="flex-1 flex items-center justify-center gap-1"> <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 <input
type="number" type="text"
min="1" inputMode="numeric"
max="120" pattern="[0-9]*"
value={weight === 0 ? '' : weight} value={weight === 0 ? '' : weight.toString()}
onChange={(e) => setWeight(Math.max(1, parseInt(e.target.value) || 1))} onChange={(e) => {
className="w-16 text-center text-xl font-black text-canina-blue font-vazir outline-none bg-transparent" 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> </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>
</div> </div>
<div className="space-y-4"> {/* Activity Level */}
<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="space-y-2">
<div className="grid grid-cols-3 gap-3"> <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 => ( {["کم", "متوسط", "زیاد"].map(level => (
<button <button
key={level} key={level}
type="button"
onClick={() => setActivityLevel(level as "کم" | "متوسط" | "زیاد")} 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} {level}
</button> </button>
@ -469,20 +509,23 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
</div> </div>
</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 <button
type="button"
onClick={handleBack} 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" /> <ChevronRight className="w-4 h-4" />
{getText('advisor_back', "قبلی")} <span>{getText('advisor_back', "قبلی")}</span>
</button> </button>
<button <button
onClick={handleNext} type="button"
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]" 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"
{getText('advisor_next_symptoms', "بررسی علائم بالینی فعلی")} >
<ChevronLeft className="w-5 h-5" /> <span>{getText('advisor_next_symptoms', "علائم بالینی")}</span>
<ChevronLeft className="w-4 h-4" />
</button> </button>
</div> </div>
</motion.div> </motion.div>
@ -494,42 +537,45 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
initial={{ opacity: 0, x: 20 }} initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }} exit={{ opacity: 0, x: -20 }}
className="space-y-8" className="space-y-5 max-w-2xl mx-auto"
> >
<div className="text-center"> <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> <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-medical-gray-600 font-bold font-vazir">{getText('advisor_step3_symptoms_desc', "آیا همدم شما در حال حاضر هیچ‌کدام از علائم زیر را تجربه می‌کند؟")}</p> <p className="text-xs sm:text-sm text-medical-gray-500 font-bold font-vazir">{getText('advisor_step3_symptoms_desc', "آیا همدم شما در حال حاضر علائم زیر را دارد؟")}</p>
</div> </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) => ( {CURRENT_SYMPTOMS.map((opt) => (
<button <button
key={opt.id} key={opt.id}
type="button"
onClick={() => toggleSymptom(opt.condition)} 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'}`}> <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'}`}>
<ShieldCheck className="w-5 h-5" /> {currentSymptoms.includes(opt.condition) && <Check className="w-3.5 h-3.5 stroke-[3]" />}
</div> </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> </button>
))} ))}
</div> </div>
<div className="flex gap-4 pt-4"> <div className="whitespace-nowrap flex items-center justify-between gap-3 pt-3">
<button <button
type="button"
onClick={handleBack} 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" /> <ChevronRight className="w-4 h-4" />
{getText('advisor_back', "قبلی")} <span>{getText('advisor_back', "قبلی")}</span>
</button> </button>
<button <button
type="button"
onClick={handleNext} 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', "سوابق پزشکی و جراحی")} <span>{getText('advisor_next_history', "سوابق پزشکی")}</span>
<ChevronLeft className="w-5 h-5" /> <ChevronLeft className="w-4 h-4" />
</button> </button>
</div> </div>
</motion.div> </motion.div>
@ -541,42 +587,45 @@ export default function SmartAdvisor({ onComplete, rules = [] }: SmartAdvisorPro
initial={{ opacity: 0, x: 20 }} initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }} exit={{ opacity: 0, x: -20 }}
className="space-y-8" className="space-y-5 max-w-2xl mx-auto"
> >
<div className="text-center"> <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> <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-medical-gray-600 font-bold font-vazir">{getText('advisor_step4_history_desc', "در صورت وجود سابقه جراحی، بارداری یا نارسایی، آن را مشخص کنید")}</p> <p className="text-xs sm:text-sm text-medical-gray-500 font-bold font-vazir">{getText('advisor_step4_history_desc', "در صورت وجود سابقه جراحی، بارداری یا نارسایی، آن را مشخص کنید")}</p>
</div> </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) => ( {MEDICAL_HISTORIES.map((opt) => (
<button <button
key={opt.id} key={opt.id}
type="button"
onClick={() => toggleMedicalHistory(opt.condition)} 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'}`}> <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'}`}>
<ShieldCheck className="w-5 h-5" /> {medicalConditions.includes(opt.condition) && <Check className="w-3.5 h-3.5 stroke-[3]" />}
</div> </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> </button>
))} ))}
</div> </div>
<div className="flex gap-4 pt-4"> <div className="whitespace-nowrap flex items-center justify-between gap-3 pt-3">
<button <button
type="button"
onClick={handleBack} 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" /> <ChevronRight className="w-4 h-4" />
{getText('advisor_back', "قبلی")} <span>{getText('advisor_back', "قبلی")}</span>
</button> </button>
<button <button
type="button"
onClick={handleSubmit} 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', "تکمیل و صدور شناسنامه")} <span>{getText('advisor_complete', "صدور شناسنامه و پیشنهاد")}</span>
<ChevronLeft className="w-5 h-5" /> <ChevronLeft className="w-4 h-4" />
</button> </button>
</div> </div>
</motion.div> </motion.div>

View File

@ -71,20 +71,37 @@ export default function VideoModalPlayer({ isOpen, onClose, video }: VideoModalP
}; };
}, [showRateMenu]); }, [showRateMenu]);
// Reset states on open/close // Reset / Restore states on open/close
useEffect(() => { useEffect(() => {
if (isOpen) { if (isOpen && video?.id) {
const savedTime = localStorage.getItem(`video_time_${video.id}`);
const initialTime = savedTime ? parseFloat(savedTime) : 0;
setIsPlaying(true); setIsPlaying(true);
setShowControls(true); setShowControls(true);
setIsExpandedDesc(false); setIsExpandedDesc(false);
setShowRateMenu(false); setShowRateMenu(false);
setBufferedEnd(0); 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); setIsPlaying(false);
setCurrentTime(0);
setBufferedEnd(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 // Update buffered track
const updateBuffer = useCallback(() => { const updateBuffer = useCallback(() => {
@ -254,164 +271,147 @@ export default function VideoModalPlayer({ isOpen, onClose, video }: VideoModalP
return ( return (
<AnimatePresence> <AnimatePresence>
<div className="fixed inset-0 z-[120] flex items-center justify-center p-3 sm:p-6 font-vazir" dir="rtl"> <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 (backdrop dismiss disabled to prevent accidental closure) */}
<motion.div <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
onClick={onClose} className="fixed inset-0 bg-black/90 backdrop-blur-xl"
className="absolute inset-0 bg-black/90 backdrop-blur-xl"
/> />
{/* Modal Window */} {/* Modal Window Wrapper */}
<motion.div <div className="relative w-full max-w-5xl flex flex-col gap-3 my-auto z-10">
ref={containerRef} <motion.div
initial={{ opacity: 0, scale: 0.95, y: 15 }} ref={containerRef}
animate={{ opacity: 1, scale: 1, y: 0 }} initial={{ opacity: 0, scale: 0.95, y: 15 }}
exit={{ opacity: 0, scale: 0.95, y: 15 }} animate={{ opacity: 1, scale: 1, y: 0 }}
transition={{ duration: 0.25 }} exit={{ opacity: 0, scale: 0.95, y: 15 }}
onMouseMove={resetControlsTimer} transition={{ duration: 0.25 }}
onTouchStart={resetControlsTimer} onMouseMove={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" 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 ? ( {/* Iframe Support (Aparat / Youtube) */}
<div className="w-full h-full flex items-center justify-center relative"> {isIframe ? (
<button <div className="w-full h-full flex items-center justify-center relative">
onClick={onClose} <button
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" type="button"
title="بستن" 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"
<X className="w-5 h-5" /> title="بستن"
</button> >
<div <X className="w-5 h-5" />
className="w-full h-full [&>iframe]:w-full [&>iframe]:h-full" </button>
dangerouslySetInnerHTML={{ __html: video.videoUrl || '' }} <div
/> className="w-full h-full [&>iframe]:w-full [&>iframe]:h-full"
</div> dangerouslySetInnerHTML={{ __html: video.videoUrl || '' }}
) : (
<>
{/* 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"
/> />
{/* 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> </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 */} {/* Buffering Spinner */}
<div {isBuffering && (
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 ${ <div className="absolute inset-0 flex items-center justify-center bg-black/30 backdrop-blur-2xs pointer-events-none">
showControls ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none" <Loader2 className="w-12 h-12 text-white animate-spin" />
}`} </div>
> )}
<div className="flex items-center gap-3 text-right"> </div>
<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" /> {/* 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>
<div>
<h3 className="text-white text-base sm:text-lg font-black line-clamp-1"> <div className="flex items-center gap-2">
{video.title} {/* Share button */}
</h3> <button
<p className="text-white/60 text-xs font-bold mt-0.5"> type="button"
{video.doctor || "کادر علمی کنینا"} onClick={(e) => { e.stopPropagation(); handleShare(); }}
</p> 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> </div>
<div className="flex items-center gap-2"> {/* Bottom Custom Controls Bar */}
{/* Share button */} <div
<button 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 ${
type="button" showControls ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none"
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" onClick={(e) => e.stopPropagation()}
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>
)}
{/* Progress / Seek Bar with filled progress & buffer tracks */} {/* Progress / Seek Bar with filled progress & buffer tracks */}
<div className="flex items-center gap-3 dir-ltr" dir="ltr"> <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> </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> </div>
</AnimatePresence> </div>
</AnimatePresence>
); );
} }

View File

@ -1,9 +1,10 @@
"use client"; "use client";
import React, { useState } from "react"; import React, { useState } from "react";
import { motion } from "motion/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 SafeImage from "./SafeImage";
import BackButton from "./BackButton";
import { toast } from "sonner";
import { videoService, Video } from "../lib/services/videoService"; import { videoService, Video } from "../lib/services/videoService";
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@ -15,6 +16,7 @@ export default function VideosPage() {
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [selectedVideo, setSelectedVideo] = useState<Video | null>(null); const [selectedVideo, setSelectedVideo] = useState<Video | null>(null);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [isCopied, setIsCopied] = useState(false);
React.useEffect(() => { React.useEffect(() => {
setIsLoading(true); 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 => const filteredVideos = videos.filter(v =>
(v.title || '').toLowerCase().includes(search.toLowerCase()) || (v.title || '').toLowerCase().includes(search.toLowerCase()) ||
(v.doctor || '').toLowerCase().includes(search.toLowerCase()) || (v.doctor || '').toLowerCase().includes(search.toLowerCase()) ||
@ -42,26 +53,36 @@ export default function VideosPage() {
); );
return ( 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"> <div className="max-w-7xl mx-auto">
{/* Header */} {/* Standard Page Top Bar */}
<div className="flex flex-col md:flex-row items-center justify-between mb-16 gap-6"> <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="text-right"> <div className="flex items-center gap-3">
<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"> <BackButton className="bg-white/10 text-white border-white/15 hover:bg-white/20 hover:text-white" />
<PlayCircle className="w-3 h-3" /> <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">
Video Academy <PlayCircle className="w-3.5 h-3.5" />
</div> <span>آکادمی تخصصی کنینا</span>
<h1 className="text-4xl lg:text-6xl font-black text-white italic leading-tight">
آکادمی ویدئویی <span className="text-canina-blue">کنینا</span>
</h1>
</div> </div>
<button </div>
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" <button
> type="button"
<span>بازگشت</span> onClick={handleSharePage}
<ChevronLeft className="w-5 h-5" /> 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"
</button> >
{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> </div>
{/* Search Bar */} {/* Search Bar */}
@ -72,7 +93,7 @@ export default function VideosPage() {
placeholder="جستجو در ویدئوهای آموزشی..." placeholder="جستجو در ویدئوهای آموزشی..."
value={search} value={search}
onChange={e => setSearch(e.target.value)} 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> </div>
@ -107,39 +128,50 @@ export default function VideosPage() {
videoService.incrementViews(video.id); 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 <SafeImage
src={video.thumbnail} src={video.thumbnail}
alt={video.title} alt={video.title}
className="w-full h-full" 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="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"> <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-10 h-10" /> <PlayCircle className="w-9 h-9" />
</div> </div>
</div> </div>
{video.duration && ( {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} {video.duration}
</div> </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>
<div className="px-4"> <div className="px-2">
<h3 className="text-xl font-black text-white group-hover:text-canina-blue transition-colors mb-2 leading-tight"> <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} {video.title}
</h3> </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"> <div className="flex items-center gap-1">
<User className="w-3 h-3" /> <User className="w-3.5 h-3.5" />
{video.doctor || 'کادر علمی کنینا'} <span>{video.doctor || 'کادر علمی کنینا'}</span>
</div> </div>
{video.duration && ( <div className="flex items-center gap-3">
<div className="flex items-center gap-1"> {video.duration && (
<Clock className="w-3 h-3" /> <div className="flex items-center gap-1">
<span>{video.duration}</span> <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> </div>
</div> </div>
</motion.div> </motion.div>

View File

@ -32,6 +32,13 @@ const mockProducts = [
}, },
]; ];
const mockPush = vi.fn();
vi.mock('next/navigation', () => ({
useRouter: () => ({
push: mockPush,
}),
}));
describe('FeaturedProducts', () => { describe('FeaturedProducts', () => {
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
@ -42,14 +49,14 @@ describe('FeaturedProducts', () => {
it('renders loading skeletons initially', () => { it('renders loading skeletons initially', () => {
vi.mocked(productService.getFeaturedProducts).mockReturnValue(new Promise(() => {})); 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) // Check if skeletons are present (e.g. searching for animate-pulse)
expect(container.getElementsByClassName('animate-pulse').length).toBeGreaterThan(0); expect(container.getElementsByClassName('animate-pulse').length).toBeGreaterThan(0);
}); });
it('renders products once loaded', async () => { it('renders products once loaded', async () => {
vi.mocked(productService.getFeaturedProducts).mockResolvedValue(mockProducts as unknown as ReturnType<typeof productService.getFeaturedProducts>); 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(() => { await waitFor(() => {
expect(screen.getByText('Canhydrox GAG')).toBeInTheDocument(); expect(screen.getByText('Canhydrox GAG')).toBeInTheDocument();
@ -57,26 +64,24 @@ describe('FeaturedProducts', () => {
expect(screen.getByText('Joint helper')).toBeInTheDocument(); expect(screen.getByText('Joint helper')).toBeInTheDocument();
}); });
it('calls onProductClick when product card is clicked', async () => { it('navigates to product details when product card is clicked', async () => {
const handleProductClick = vi.fn();
vi.mocked(productService.getFeaturedProducts).mockResolvedValue(mockProducts as unknown as ReturnType<typeof productService.getFeaturedProducts>); vi.mocked(productService.getFeaturedProducts).mockResolvedValue(mockProducts as unknown as ReturnType<typeof productService.getFeaturedProducts>);
render(<FeaturedProducts onProductClick={handleProductClick} onShopNavigate={vi.fn()} />); render(<FeaturedProducts />);
await waitFor(() => { await waitFor(() => {
expect(screen.getByText('Canhydrox GAG')).toBeInTheDocument(); expect(screen.getByText('Canhydrox GAG')).toBeInTheDocument();
}); });
fireEvent.click(screen.getByText('Canhydrox GAG')); 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 () => { it('navigates to shop when view all link is clicked', async () => {
const handleShopNavigate = vi.fn();
vi.mocked(productService.getFeaturedProducts).mockResolvedValue(mockProducts as unknown as ReturnType<typeof productService.getFeaturedProducts>); 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('مشاهده تمامی محصولات'); const navBtn = screen.getByText('مشاهده تمامی محصولات');
fireEvent.click(navBtn); 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 { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react'; import { render, screen, fireEvent } from '@testing-library/react';
import React from 'react'; import React from 'react';
@ -6,39 +6,23 @@ import Footer from '../Footer';
describe('Footer', () => { describe('Footer', () => {
it('renders footer brand text and standard layout elements', () => { 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('Canina')).toBeInTheDocument();
expect(screen.getByText('نماینده رسمی در ایران')).toBeInTheDocument(); expect(screen.getByText('ایران')).toBeInTheDocument();
expect(screen.getByText('تهران، جردن، خیابان سعیدی، ساختمان کنینا، طبقه ۵، واحد ۱۹')).toBeInTheDocument(); expect(screen.getByText('تهران، جردن، خیابان سعیدی، ساختمان کنینا، طبقه ۵، واحد ۱۹')).toBeInTheDocument();
}); });
it('triggers onNavigate and onShopNavigate when quick links are clicked', () => { it('renders navigation links properly', () => {
const handleNavigate = vi.fn(); render(<Footer onB2BOpen={vi.fn()} />);
const handleShopNavigate = vi.fn();
const handleB2BOpen = vi.fn();
render( const shopLink = screen.getByText('محصولات تخصصی ۲۰۲۴').closest('a');
<Footer expect(shopLink).toHaveAttribute('href', '/shop');
onNavigate={handleNavigate}
onShopNavigate={handleShopNavigate}
onB2BOpen={handleB2BOpen}
/>
);
// Click quick link for shop const blogLink = screen.getByText('مجله سلامت پت (وبلاگ)').closest('a');
const shopLink = screen.getByText('محصولات تخصصی ۲۰۲۴'); expect(blogLink).toHaveAttribute('href', '/blog');
fireEvent.click(shopLink);
expect(handleShopNavigate).toHaveBeenCalled();
// Click quick link for blog const trustLink = screen.getByText('نمادهای اعتماد و مجوزهای رسمی').closest('a');
const blogLink = screen.getByText('مجله سلامت پت (وبلاگ)'); expect(trustLink).toHaveAttribute('href', '/trust-seals');
fireEvent.click(blogLink);
expect(handleNavigate).toHaveBeenCalledWith('blog');
// Click B2B link
const b2bLink = screen.getByText('پنل سفارش عمده (B2B)');
fireEvent.click(b2bLink);
expect(handleB2BOpen).toHaveBeenCalled();
}); });
}); });

View File

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

View File

@ -187,7 +187,13 @@ export const useUserStore = create<UserStore>()(
}, },
fetchProfile: async () => { fetchProfile: async () => {
try { 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) { if (!token) {
try { usePetStore.getState().reset(); } catch {} try { usePetStore.getState().reset(); } catch {}
try { localStorage.removeItem("canina-pets"); } catch {} try { localStorage.removeItem("canina-pets"); } catch {}

View File

@ -13,6 +13,37 @@ Object.defineProperty(window, 'IntersectionObserver', {
value: MockIntersectionObserver, 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', () => ({ vi.mock('next/navigation', () => ({
useRouter: () => ({ useRouter: () => ({
push: vi.fn(), push: vi.fn(),

View File

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