From 9f9623eb46ba15a1495a2bf74ecf3b32fa35808a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:09:12 +0000 Subject: [PATCH 1/3] Initial plan From 90a36dd2c2599954e1f9f1ec46d58f3f7cc0c484 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:20:09 +0000 Subject: [PATCH 2/3] Add comprehensive error handling across async operations Co-authored-by: llu77 <163984217+llu77@users.noreply.github.com> --- src/ai/flows/financial-summary.ts | 19 ++- src/app/(app)/layout.tsx | 19 ++- src/components/revenue/revenue-form.tsx | 10 +- src/lib/actions.ts | 4 +- src/lib/openai-responses-client.ts | 215 +++++++++++++++--------- src/services/pdf.service.ts | 39 +++-- 6 files changed, 205 insertions(+), 101 deletions(-) diff --git a/src/ai/flows/financial-summary.ts b/src/ai/flows/financial-summary.ts index e617cfd..5da7e5f 100644 --- a/src/ai/flows/financial-summary.ts +++ b/src/ai/flows/financial-summary.ts @@ -24,7 +24,12 @@ const FinancialSummaryOutputSchema = z.object({ export type FinancialSummaryOutput = z.infer; export async function generateFinancialSummary(input: FinancialSummaryInput): Promise { - return financialSummaryFlow(input); + try { + return await financialSummaryFlow(input); + } catch (error) { + console.error('Error generating financial summary:', error); + throw new Error(`Failed to generate financial summary: ${error instanceof Error ? error.message : 'Unknown error'}`); + } } const prompt = ai.definePrompt({ @@ -48,7 +53,15 @@ const financialSummaryFlow = ai.defineFlow( outputSchema: FinancialSummaryOutputSchema, }, async input => { - const {output} = await prompt(input); - return output!; + try { + const {output} = await prompt(input); + if (!output) { + throw new Error('AI model returned empty output'); + } + return output; + } catch (error) { + console.error('Error in financial summary flow:', error); + throw error; + } } ); diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx index 8374f0a..747b67a 100644 --- a/src/app/(app)/layout.tsx +++ b/src/app/(app)/layout.tsx @@ -223,7 +223,12 @@ export default function AppLayout({ children }: { children: React.ReactNode }) { }, []); const deleteRevenueRecord = useCallback(async (id: string) => { - await deleteDoc(doc(db, 'revenue', id)); + try { + await deleteDoc(doc(db, 'revenue', id)); + } catch (error) { + console.error('Error deleting revenue record:', error); + throw error; + } }, []); const addExpense = useCallback(async (expense: Omit) => { @@ -231,7 +236,12 @@ export default function AppLayout({ children }: { children: React.ReactNode }) { }, []); const deleteExpense = useCallback(async (id: string) => { - await deleteDoc(doc(db, 'expenses', id)); + try { + await deleteDoc(doc(db, 'expenses', id)); + } catch (error) { + console.error('Error deleting expense:', error); + throw error; + } }, []); const addRequest = useCallback(async (request: Omit) => { @@ -239,8 +249,13 @@ export default function AppLayout({ children }: { children: React.ReactNode }) { }, []); const updateRequestStatus = useCallback(async (id: string, status: EmployeeRequest['status'], notes?: string) => { + try { const requestDocRef = doc(db, 'requests', id); await updateDoc(requestDocRef, { status, notes }); + } catch (error) { + console.error('Error updating request status:', error); + throw error; + } }, []); diff --git a/src/components/revenue/revenue-form.tsx b/src/components/revenue/revenue-form.tsx index e2e961c..bc15b46 100644 --- a/src/components/revenue/revenue-form.tsx +++ b/src/components/revenue/revenue-form.tsx @@ -97,9 +97,13 @@ export function RevenueForm({ onSave }: RevenueFormProps) { ); const onSubmit: SubmitHandler = async (data) => { - const success = await onSave(data, currentBranch); - if (success) { - form.reset(); + try { + const success = await onSave(data, currentBranch); + if (success) { + form.reset(); + } + } catch (error) { + console.error('Error submitting revenue form:', error); } }; diff --git a/src/lib/actions.ts b/src/lib/actions.ts index beaa293..439bc51 100644 --- a/src/lib/actions.ts +++ b/src/lib/actions.ts @@ -50,9 +50,11 @@ export async function getFinancialSummary( data: result }; } catch (error) { + console.error('Error generating financial summary:', error); + const errorMessage = error instanceof Error ? error.message : 'حدث خطأ غير معروف'; return { success: false, - message: "حدث خطأ أثناء إنشاء الملخص. الرجاء المحاولة مرة أخرى." + message: `حدث خطأ أثناء إنشاء الملخص: ${errorMessage}. الرجاء المحاولة مرة أخرى.` }; } } diff --git a/src/lib/openai-responses-client.ts b/src/lib/openai-responses-client.ts index e9c6e0e..81e1ded 100644 --- a/src/lib/openai-responses-client.ts +++ b/src/lib/openai-responses-client.ts @@ -35,39 +35,49 @@ class OpenAIResponsesClient implements ResponsesAPIClient { } async createResponse(params: CreateResponseParams): Promise { - const response = await fetch(`${this.baseURL}/responses`, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${this.apiKey}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - model: 'gpt-4.1-2025-04-14', - input: params.input, - instructions: params.instructions, - previous_response_id: this.lastResponseId, - conversation: this.conversationId, - tools: params.tools || [ - { type: 'web_search' }, - { type: 'file_search' }, - { type: 'code_interpreter' } - ], - store: true, - stream: params.stream ?? true, - include: [ - 'web_search_call.action.sources', - 'code_interpreter_call.outputs', - 'file_search_call.results' - ] + try { + const response = await fetch(`${this.baseURL}/responses`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${this.apiKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: 'gpt-4.1-2025-04-14', + input: params.input, + instructions: params.instructions, + previous_response_id: this.lastResponseId, + conversation: this.conversationId, + tools: params.tools || [ + { type: 'web_search' }, + { type: 'file_search' }, + { type: 'code_interpreter' } + ], + store: true, + stream: params.stream ?? true, + include: [ + 'web_search_call.action.sources', + 'code_interpreter_call.outputs', + 'file_search_call.results' + ] + }) }) - }) - - if (params.stream) { - return this.handleStreamResponse(response) - } else { - const data = await response.json() - this.lastResponseId = data.id - return data + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + throw new Error(`API request failed: ${response.status} ${response.statusText}. ${JSON.stringify(errorData)}`) + } + + if (params.stream) { + return this.handleStreamResponse(response) + } else { + const data = await response.json() + this.lastResponseId = data.id + return data + } + } catch (error) { + console.error('Error creating response:', error) + throw error } } @@ -105,67 +115,116 @@ class OpenAIResponsesClient implements ResponsesAPIClient { } async getResponse(responseId: string): Promise { - // Implementation for getResponse - const response = await fetch(`${this.baseURL}/responses/${responseId}`, { + try { + const response = await fetch(`${this.baseURL}/responses/${responseId}`, { headers: { - 'Authorization': `Bearer ${this.apiKey}` + 'Authorization': `Bearer ${this.apiKey}` } - }); - return response.json(); + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + throw new Error(`Failed to get response: ${response.status} ${response.statusText}. ${JSON.stringify(errorData)}`) + } + + return response.json(); + } catch (error) { + console.error('Error getting response:', error) + throw error + } } async createConversation(items: any[] = []): Promise { - const response = await fetch(`${this.baseURL}/conversations`, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${this.apiKey}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ items }) - }) - - const conversation = await response.json() - this.conversationId = conversation.id - return conversation + try { + const response = await fetch(`${this.baseURL}/conversations`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${this.apiKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ items }) + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + throw new Error(`Failed to create conversation: ${response.status} ${response.statusText}. ${JSON.stringify(errorData)}`) + } + + const conversation = await response.json() + this.conversationId = conversation.id + return conversation + } catch (error) { + console.error('Error creating conversation:', error) + throw error + } } async addToConversation(conversationId: string, items: any[]): Promise { - // Implementation for addToConversation - await fetch(`${this.baseURL}/conversations/${conversationId}/items`, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${this.apiKey}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ items }) - }); + try { + const response = await fetch(`${this.baseURL}/conversations/${conversationId}/items`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${this.apiKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ items }) + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + throw new Error(`Failed to add to conversation: ${response.status} ${response.statusText}. ${JSON.stringify(errorData)}`) + } + } catch (error) { + console.error('Error adding to conversation:', error) + throw error + } } connectWebSocket(clientId: string, onMessage: (event: any) => void) { - const wsUrl = process.env.NEXT_PUBLIC_WS_URL || 'ws://localhost:8000' - this.ws = new WebSocket(`${wsUrl}/ws/${clientId}`) - - this.ws.onmessage = (event) => { - const data = JSON.parse(event.data) - onMessage(data) - } - - this.ws.onopen = () => { - console.log('WebSocket connected') - } - - this.ws.onerror = (error) => { - console.error('WebSocket error:', error) + try { + const wsUrl = process.env.NEXT_PUBLIC_WS_URL || 'ws://localhost:8000' + this.ws = new WebSocket(`${wsUrl}/ws/${clientId}`) + + this.ws.onmessage = (event) => { + try { + const data = JSON.parse(event.data) + onMessage(data) + } catch (error) { + console.error('Error parsing WebSocket message:', error) + } + } + + this.ws.onopen = () => { + console.log('WebSocket connected') + } + + this.ws.onerror = (error) => { + console.error('WebSocket error:', error) + } + + this.ws.onclose = (event) => { + console.log('WebSocket closed:', event.code, event.reason) + } + } catch (error) { + console.error('Error connecting WebSocket:', error) + throw error } } sendMessage(type: string, data: any) { - if (this.ws?.readyState === WebSocket.OPEN) { - this.ws.send(JSON.stringify({ - type, - ...data, - previous_response_id: this.lastResponseId - })) + try { + if (this.ws?.readyState === WebSocket.OPEN) { + this.ws.send(JSON.stringify({ + type, + ...data, + previous_response_id: this.lastResponseId + })) + } else { + console.warn('WebSocket is not open. Current state:', this.ws?.readyState) + } + } catch (error) { + console.error('Error sending WebSocket message:', error) + throw error } } } diff --git a/src/services/pdf.service.ts b/src/services/pdf.service.ts index c132ff5..06d468e 100644 --- a/src/services/pdf.service.ts +++ b/src/services/pdf.service.ts @@ -27,8 +27,14 @@ class PDFService { } async loadCompanyLogo() { + try { // In a real app, load from a secure source or config - this.logo = 'https://picsum.photos/seed/branchflow/120/40'; + this.logo = 'https://picsum.photos/seed/branchflow/120/40'; + } catch (error) { + console.error('Error loading company logo:', error); + // Logo is optional, so we can continue without it + this.logo = undefined; + } } public async generatePDF(config: { @@ -38,19 +44,24 @@ class PDFService { userData: any; branchData: any; }) { - this.doc = new jsPDF({ - orientation: 'portrait', - unit: 'mm', - format: 'a4', - compress: true - }); - - this.addHeader(config.title, config.branchData.name); - this.addContent(config.type, config.content); - this.addFooter(config.userData, config.branchData); - this.addPageNumbers(); - - return this.doc; + try { + this.doc = new jsPDF({ + orientation: 'portrait', + unit: 'mm', + format: 'a4', + compress: true + }); + + this.addHeader(config.title, config.branchData.name); + this.addContent(config.type, config.content); + this.addFooter(config.userData, config.branchData); + this.addPageNumbers(); + + return this.doc; + } catch (error) { + console.error('Error generating PDF:', error); + throw new Error(`Failed to generate PDF: ${error instanceof Error ? error.message : 'Unknown error'}`); + } } private addHeader(title: string, branchName: string) { From a9f99bf3c65ebef94ed0227607dbccfbe296bb5b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:23:31 +0000 Subject: [PATCH 3/3] Remove duplicate expenses directory with typo that was causing build errors Co-authored-by: llu77 <163984217+llu77@users.noreply.github.com> --- src/app/(app/expenses/page.tsx | 335 --------------------------------- 1 file changed, 335 deletions(-) delete mode 100644 src/app/(app/expenses/page.tsx diff --git a/src/app/(app/expenses/page.tsx b/src/app/(app/expenses/page.tsx deleted file mode 100644 index 50fc204..0000000 --- a/src/app/(app/expenses/page.tsx +++ /dev/null @@ -1,335 +0,0 @@ - -'use client'; -import React, { useState, useContext, useEffect } from 'react'; -import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Textarea } from "@/components/ui/textarea"; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; -import { Badge } from "@/components/ui/badge"; -import { CirclePlus, ListOrdered, FilePenLine, Trash2, Search, Printer } from "lucide-react"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip"; -import { useToast } from "@/hooks/use-toast"; -import { DataContext, BranchContext } from '../layout'; -import pdfService from '@/services/pdf.service'; -import { useAuth } from '@/hooks/use-auth'; - -export type Expense = { - id: string; - date: string; - branch: string; - category: string; - amount: number; - description: string; -}; - -export default function ExpensesPage() { - const { expenses, addExpense, deleteExpense } = useContext(DataContext); - const { user } = useAuth(); - const { currentBranch } = useContext(BranchContext); - const [filteredExpenses, setFilteredExpenses] = useState(expenses); - const [searchTerm, setSearchTerm] = useState(''); - const { toast } = useToast(); - - // Form state - const [date, setDate] = useState(new Date().toISOString().split('T')[0]); - const [branch, setBranch] = useState(currentBranch); // Set initial branch from context - const [category, setCategory] = useState(''); - const [amount, setAmount] = useState(''); - const [description, setDescription] = useState(''); - - useEffect(() => { - setFilteredExpenses(expenses); - }, [expenses]); - - // Keep form branch in sync with context - useEffect(() => { - setBranch(currentBranch); - }, [currentBranch]); - - - const handleSaveExpense = async (e: React.FormEvent) => { - e.preventDefault(); - if (!date || !branch || !category || !amount || !description) { - toast({ - variant: "destructive", - title: "خطأ", - description: "الرجاء تعبئة جميع الحقول المطلوبة.", - }); - return; - } - const newExpense: Omit = { - date, - branch: branch === 'laban' ? 'فرع لبن' : 'فرع طويق', - category, - amount: parseFloat(amount), - description, - }; - - const result = await addExpense(newExpense); - - if(result.success) { - // Reset form - setDate(new Date().toISOString().split('T')[0]); - // Keep branch set to current branch - setCategory(''); - setAmount(''); - setDescription(''); - - toast({ - title: "تم الحفظ بنجاح", - description: "تمت إضافة المصروف الجديد إلى السجل.", - className: "bg-primary text-primary-foreground", - }); - } else { - toast({ - variant: "destructive", - title: "فشل الحفظ", - description: "لم يتم حفظ المصروف بسبب خطأ في الشبكة أو الصلاحيات.", - }); - } - }; - - const handleSearch = (term: string) => { - setSearchTerm(term); - if (!term) { - setFilteredExpenses(expenses); - } else { - const results = expenses.filter(expense => - expense.description.toLowerCase().includes(term.toLowerCase()) || - expense.category.toLowerCase().includes(term.toLowerCase()) || - expense.branch.toLowerCase().includes(term.toLowerCase()) - ); - setFilteredExpenses(results); - } - } - - const handleDelete = (id: string) => { - deleteExpense(id); - toast({ - variant: "destructive", - title: "تم الحذف", - description: `تم حذف المصروف رقم ${id} من السجل.`, - }); - } - - const handleEdit = (id: string) => { - toast({ - title: "غير متاح حالياً", - description: `ميزة تعديل المصروف ${id} سيتم إضافتها قريباً.`, - }); - } - - const handlePrint = async () => { - if (filteredExpenses.length === 0) { - toast({ variant: 'destructive', title: 'لا توجد بيانات للطباعة' }); - return; - } - - const tableData = filteredExpenses.map(exp => [ - exp.date, - exp.branch, - exp.category, - exp.amount.toFixed(2) + ' ريال', - exp.description - ]); - - const supervisor = { name: 'المشرف المسؤول' }; // Placeholder - - await pdfService.generatePDF({ - title: 'تقرير المصروفات', - type: 'report', - content: { - table: { - headers: [['التاريخ', 'الفرع', 'البند', 'المبلغ', 'الوصف']], - data: tableData - } - }, - userData: user, - branchData: { - name: currentBranch === 'laban' ? 'فرع لبن' : 'فرع طويق', - supervisorName: supervisor.name - } - }); - - await pdfService.print(); - } - - - return ( - <> -
- - - - - إضافة مصروف - - - - عرض المصاريف - - - - - - - إضافة مصروف جديد - سجل المصروفات الجديدة للفروع للحفاظ على دقة السجلات المالية. - -
- -
-
- - setDate(e.target.value)} /> -
-
- - -
-
-
- - -
-
-
- - setAmount(e.target.value)} /> -
-
- -