From ef9baf33f74c9878bca3da9ccee2caf499d848ce Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Sun, 19 Apr 2026 15:25:43 +0300 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Feat:=20Dashboard=20accuracy,=20Sta?= =?UTF-8?q?ff=20&=20Settings=20modules,=20and=20File=20Auth=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/auth/strategies/jwt.strategy.ts | 7 +- .../modules/dashboard/dashboard.service.ts | 23 +- frontend/src/App.tsx | 6 +- .../src/pages/dashboard/DashboardPage.tsx | 13 +- frontend/src/pages/invoices/InvoicesPage.tsx | 13 +- frontend/src/pages/settings/SettingsPage.tsx | 153 +++++++++++++ frontend/src/pages/staff/StaffPage.tsx | 212 ++++++++++++++++++ 7 files changed, 415 insertions(+), 12 deletions(-) create mode 100644 frontend/src/pages/settings/SettingsPage.tsx create mode 100644 frontend/src/pages/staff/StaffPage.tsx diff --git a/backend/src/modules/auth/strategies/jwt.strategy.ts b/backend/src/modules/auth/strategies/jwt.strategy.ts index fd8eac5..0fc6c92 100644 --- a/backend/src/modules/auth/strategies/jwt.strategy.ts +++ b/backend/src/modules/auth/strategies/jwt.strategy.ts @@ -18,7 +18,12 @@ export class JwtStrategy extends PassportStrategy(Strategy) { private dataSource: DataSource, ) { super({ - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + jwtFromRequest: ExtractJwt.fromExtractors([ + ExtractJwt.fromAuthHeaderAsBearerToken(), + (req) => { + return req.query ? (req.query as any).token : null; + }, + ]), ignoreExpiration: false, secretOrKey: configService.getOrThrow('JWT_SECRET'), }); diff --git a/backend/src/modules/dashboard/dashboard.service.ts b/backend/src/modules/dashboard/dashboard.service.ts index c21551e..5ede99a 100644 --- a/backend/src/modules/dashboard/dashboard.service.ts +++ b/backend/src/modules/dashboard/dashboard.service.ts @@ -25,9 +25,23 @@ export class DashboardService { const pendingInvoices = await this.invoiceRepository.count({ where: { tenant_id: tenantId, - status: InvoiceStatus.EXTRACTING // or any non-final state + status: Buffer.from('approved').toString() === InvoiceStatus.APPROVED ? InvoiceStatus.UPLOADED : InvoiceStatus.UPLOADED // wait, using In operator is better }, }); + + // Using QueryBuilder for better control + const statuses = await this.invoiceRepository + .createQueryBuilder('invoice') + .select('status') + .addSelect('COUNT(*)', 'count') + .where('invoice.tenant_id = :tenantId', { tenantId }) + .groupBy('status') + .getRawMany(); + + const statusMap = statuses.reduce((acc, curr) => { + acc[curr.status] = parseInt(curr.count); + return acc; + }, {}); const companiesCount = await this.companyRepository.count({ where: { tenant_id: tenantId }, @@ -49,11 +63,14 @@ export class DashboardService { relations: ['company'], }); + const approvedInvoicesCount = statusMap[InvoiceStatus.APPROVED] || 0; + const processingInvoices = totalInvoices - approvedInvoicesCount; + return { stats: { totalInvoices, - approvedInvoices, - pendingInvoices, + approvedInvoices: approvedInvoicesCount, + pendingInvoices: processingInvoices, companiesCount, totalTax, }, diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4726d53..c16cc38 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,6 +7,8 @@ import { DashboardPage } from './pages/dashboard/DashboardPage'; import { InvoicesPage } from './pages/invoices/InvoicesPage'; import { CompaniesPage } from './pages/companies/CompaniesPage'; +import { StaffPage } from './pages/staff/StaffPage'; +import { SettingsPage } from './pages/settings/SettingsPage'; // ── Protected Route Guard ───────────────────────────────── const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { @@ -28,8 +30,8 @@ export default function App() { } /> } /> } /> - إدارة الموظفين} /> - الإعدادات} /> + } /> + } /> {/* Fallback */} diff --git a/frontend/src/pages/dashboard/DashboardPage.tsx b/frontend/src/pages/dashboard/DashboardPage.tsx index 2464e85..28e2022 100644 --- a/frontend/src/pages/dashboard/DashboardPage.tsx +++ b/frontend/src/pages/dashboard/DashboardPage.tsx @@ -17,7 +17,10 @@ import { import { motion } from 'framer-motion'; import apiClient from '../../api/client'; +import { useNavigate } from 'react-router-dom'; + export const DashboardPage = () => { + const navigate = useNavigate(); const [stats, setStats] = useState(null); const [isLoading, setIsLoading] = useState(true); @@ -169,7 +172,10 @@ export const DashboardPage = () => {

إجراءات سريعة

-
- + + +
+ + {/* ── Form Content ─────────────────────────────────── */} +
+
+
+
+
+ + setName(e.target.value)} + className="w-full bg-slate-50 border border-slate-200 rounded-2xl px-5 py-4 outline-none focus:border-primary-500 transition-all" + /> +
+
+ + setEmail(e.target.value)} + className="w-full bg-slate-50 border border-slate-200 rounded-2xl px-5 py-4 outline-none focus:border-primary-500 transition-all" + /> +
+
+ +
+ + setPhone(e.target.value)} + className="w-full bg-slate-50 border border-slate-200 rounded-2xl px-5 py-4 outline-none focus:border-primary-500 transition-all" + placeholder="+962 7X XXX XXXX" + /> +
+ +
+ +