import flet as ft import requests from datetime import datetime from helpers.document_status import DocumentsStatus from helpers.emails import send_gmail from dataclasses import dataclass, field import asyncio @dataclass class State: file_picker: ft.FilePicker | None = None picked_files: list[ft.FilePickerFile] = field(default_factory=list) state = State() class Documents: def __init__(self, page: ft.Page, home): self.page = page self.home = home self.base_url = self.page.session.store.get('api_base_url') self.token = self.page.session.store.get('token') self.user = self.page.session.store.get('user') self.user_id = self.user['id'] if self.user else None self.documenet_title = None self.all_requests = [] self.current_selected_request = None # Elemente interfață: Căutare și Listă self.search_bar = ft.TextField( label="Căutare solicitări (Text solicitare sau comentariu)", on_submit=self._on_search_change, prefix_icon=ft.Icons.SEARCH, expand=True ) self.status_filter_dropdown = ft.Dropdown( label="Filtrează după status", options=[ ft.dropdown.Option("all", "Toate"), ft.dropdown.Option(DocumentsStatus.NEW, DocumentsStatus.get_label(DocumentsStatus.NEW)), ft.dropdown.Option(DocumentsStatus.ANALISE, DocumentsStatus.get_label(DocumentsStatus.ANALISE)), ft.dropdown.Option(DocumentsStatus.IN_PROGRESS, DocumentsStatus.get_label(DocumentsStatus.IN_PROGRESS)), ft.dropdown.Option(DocumentsStatus.WAITING_FOR_PAYMENT, DocumentsStatus.get_label(DocumentsStatus.WAITING_FOR_PAYMENT)), ft.dropdown.Option(DocumentsStatus.COMPLETED, DocumentsStatus.get_label(DocumentsStatus.COMPLETED)), ft.dropdown.Option(DocumentsStatus.CANCELED, DocumentsStatus.get_label(DocumentsStatus.CANCELED)), ], value="all", on_select=self._on_status_filter_change, expand=True ) self.requests_list_view = ft.ListView( expand=True, spacing=10, ) # Elemente panou detalii self.req_id_text = ft.Text("", size=18, weight=ft.FontWeight.BOLD) self.req_text_display = ft.Text("", selectable=True) self.price_field = ft.TextField( label="Preț stabilit (Lei)", width=200, keyboard_type=ft.KeyboardType.NUMBER, ) self.status_dropdown = ft.Dropdown( label="Schimbă Status", width=250, options=[ ft.dropdown.Option(DocumentsStatus.NEW, DocumentsStatus.get_label(DocumentsStatus.NEW)), ft.dropdown.Option(DocumentsStatus.ANALISE, DocumentsStatus.get_label(DocumentsStatus.ANALISE)), ft.dropdown.Option(DocumentsStatus.IN_PROGRESS, DocumentsStatus.get_label(DocumentsStatus.IN_PROGRESS)), ft.dropdown.Option(DocumentsStatus.WAITING_FOR_PAYMENT, DocumentsStatus.get_label(DocumentsStatus.WAITING_FOR_PAYMENT)), ft.dropdown.Option(DocumentsStatus.COMPLETED, DocumentsStatus.get_label(DocumentsStatus.COMPLETED)), ft.dropdown.Option(DocumentsStatus.CANCELED, DocumentsStatus.get_label(DocumentsStatus.CANCELED)), ] ) self.comment_field = ft.TextField( label="Adaugă răspuns/comentariu către client", multiline=True, min_lines=3, expand=True ) self.doc_id_info = ft.Text("Document Final: Niciunul", color=ft.Colors.GREY_700) self.download_button = ft.IconButton( icon=ft.Icons.DOWNLOAD, on_click=self._on_download_button_click, ) self.details_panel = ft.Column( [ self.req_id_text, ft.Divider(), ft.Text("Descriere Solicitare Client:", weight=ft.FontWeight.BOLD), ft.Container(content=self.req_text_display, padding=10, bgcolor=ft.Colors.GREY_50, border_radius=5), ft.Divider(), ft.Row([self.status_dropdown, self.price_field]), ft.FilledButton( "Salvează Modificări Status/Preț", icon=ft.Icons.SAVE, on_click=self._save_metadata ), ft.Divider(), ft.Text("Comunicare istoric:", weight=ft.FontWeight.BOLD), ft.Row([ self.comment_field, ft.IconButton(ft.Icons.SEND_ROUNDED, on_click=self._add_comment, tooltip="Trimite răspuns") ]), ft.Divider(), ft.Text("Finalizare și Încărcare Document:", weight=ft.FontWeight.BOLD), ft.Row( [ ft.Row([ ft.FilledButton( "Încarcă Document Final", icon=ft.Icons.UPLOAD_FILE, on_click=self._handle_file_upload, bgcolor=ft.Colors.GREEN_700 ), self.doc_id_info, self.download_button ]), ft.FilledButton("Salveaza si trimite", on_click=self._on_file_saved) ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN ) ], visible=False, expand=True, scroll=ft.ScrollMode.AUTO ) self._load_requests() def _load_requests(self): """Preia toate solicitările de documente custom de la server.""" try: response = requests.get( f"{self.base_url}/documents/customs/requests", headers={'Authorization': f'Bearer {self.token}'} ) if response.status_code == 200: self.all_requests = response.json()[::-1] self._populate_list(self.all_requests) except Exception as e: print(f"Error loading requests: {e}") def _populate_list(self, items): self.requests_list_view.controls = [] if not items: self.requests_list_view.controls.append(ft.Text("Nicio solicitare găsită.", italic=True)) else: for req in items: self.requests_list_view.controls.append( ft.Container( content=ft.Column([ ft.Text(f"Solicitare #{req['id']}", weight=ft.FontWeight.BOLD), ft.Text(f"Status: {DocumentsStatus.get_label(req['status'])}", size=12), ft.Text(f"Client ID: {req['client_id']}", size=11, color=ft.Colors.GREY_600), ], spacing=2), padding=15, border_radius=10, bgcolor=ft.Colors.WHITE, border=ft.Border.all(1, ft.Colors.GREY_300), ink=True, on_click=lambda e, r=req: self._show_details(r) ) ) self.page.update() def _show_details(self, req_summary): # Renamed from _show_details to _show_details_from_summary """Preia datele proaspete de la server pentru solicitarea selectată.""" try: response = requests.get( f"{self.base_url}/documents/customs/requests/{req_summary['id']}", headers={'Authorization': f'Bearer {self.token}'} ) if response.status_code == 200: req = response.json() self.current_selected_request = req self.req_id_text.value = f"Procesare Solicitare #{req['id']}" self.req_text_display.value = req['request_text'] self.status_dropdown.value = req['status'] self.price_field.value = str(req['price']) if req['price'] is not None else "" self.doc_id_info.value = f"ID Document asociat: {req['document_id']}" if req['document_id'] else "Document Final: Niciunul" self.details_panel.visible = True self.page.update() except Exception as e: print(f"Eroare la preluarea detaliilor solicitării: {e}") def _apply_filters(self): """Aplică filtrele de căutare și status și actualizează lista.""" query = self.search_bar.value.lower().strip() selected_status = self.status_filter_dropdown.value filtered = [] for r in self.all_requests: matches_query = query in r['request_text'].lower() or query in str(r['id']).lower() matches_status = (selected_status == "all" or r['status'] == selected_status) if matches_query and matches_status: filtered.append(r) self._populate_list(filtered) def _on_search_change(self, e): """Declanșează filtrarea la schimbarea textului de căutare.""" self._apply_filters() def _on_status_filter_change(self, e): """Declanșează filtrarea la schimbarea statusului selectat.""" self.current_selected_request = None # Clear details when filter changes self.details_panel.visible = False self._apply_filters() def _save_metadata(self, e): """Salvează prețul și statusul solicitării.""" if not self.current_selected_request: return price_val = self.price_field.value.strip() if self.price_field.value else "" price = None if price_val: try: price = float(price_val.replace(',', '.')) except ValueError: self.page.show_dialog(ft.SnackBar(ft.Text("Prețul trebuie să fie un număr!"))) return payload = { # Removed from here "status": self.status_dropdown.value, "price": price, "expert_id": self.user_id } # Actualizăm datele și, în caz de succes, trimitem notificarea if self._update_request_api(payload): try: # Preluăm email-ul clientului pentru a-l notifica client_id = self.current_selected_request.get('client_id') user_resp = requests.get( f"{self.base_url}/users/{client_id}", headers={'Authorization': f'Bearer {self.token}'} ) if user_resp.status_code == 200: client_email = user_resp.json().get('email') status_label = DocumentsStatus.get_label(payload['status']) price_info = f"Preț stabilit: {price} Lei." if price is not None else "Prețul va fi stabilit după analiză." subject = f"Actualizare status solicitare #{self.current_selected_request['id']}" body = ( f"Bună ziua,\n\n" f"Vă informăm că statusul solicitării dumneavoastră #{self.current_selected_request['id']} " f"a fost actualizat la: {status_label}.\n" f"{price_info}\n\n" f"Vă mulțumim,\nEchipa JuridicBloc" ) send_gmail(to_email=client_email, subject=subject, body=body) except Exception as mail_err: print(f"Eroare la trimiterea notificării email: {mail_err}") def _add_comment(self, e): """Adaugă un comentariu în istoricul conversației solicitării.""" if not self.current_selected_request or not self.comment_field.value.strip(): return timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment = self.comment_field.value.strip() updated_text = f"{self.current_selected_request['request_text']}\n\n--- Răspuns Expert ({timestamp}):\n{comment}" payload = { "request_text": updated_text, "expert_id": self.user_id } self._update_request_api(payload) self.comment_field.value = "" def _update_request_api(self, payload): try: req_id = self.current_selected_request['id'] response = requests.put( f"{self.base_url}/documents/customs/requests/update/{req_id}", json=payload, headers={'Authorization': f'Bearer {self.token}'} ) if response.status_code == 200: self.page.show_dialog(ft.SnackBar(ft.Text("Modificări salvate cu succes."))) self.current_selected_request.update(payload) self._load_requests() self._show_details(self.current_selected_request) client_id = self.current_selected_request.get('client_id') user_resp = requests.get( f"{self.base_url}/users/{client_id}", headers={'Authorization': f'Bearer {self.token}'} ) if user_resp.status_code == 200: subject = f"Actualizare status solicitare #{self.current_selected_request['id']}" client_email = user_resp.json().get('email') body = ( f"Bună ziua,\n\n" f"Vă informăm că ati primit un raspuns solicitării dumneavoastră #{self.current_selected_request['id']} " f"Vă mulțumim,\nEchipa JuridicBloc" ) send_gmail(to_email=client_email, subject=subject, body=body) return True except Exception as e: print(f"Update failed: {e}") return False async def _handle_file_upload(self, e: ft.Event[ft.Button]): print('File uploaded') try: state.file_picker = ft.FilePicker() files = await state.file_picker.pick_files(allow_multiple=False) print("Picked file:", files) state.picked_files = files uploaded_file_name = f"{datetime.now().strftime('%Y%m%d%H%M%S')}_{state.picked_files[0].name}" await state.file_picker.upload( files=[ ft.FilePickerUploadFile( name=file.name, upload_url=self.page.get_upload_url(uploaded_file_name, 60), ) for file in state.picked_files ] ) self.documenet_title = uploaded_file_name self.doc_id_info.value = f"ID Document asociat: {self.documenet_title}" return f'{uploaded_file_name}' except Exception as e: print(e) def _on_file_saved(self, e): reg_resp = requests.post( f"{self.base_url}/documents/customs/add", json={"name": f"Document Final Solicitare #{self.current_selected_request['id']}", "path": self.documenet_title}, headers={'Authorization': f'Bearer {self.token}'} ) if reg_resp.status_code == 201: doc_id = reg_resp.json().get('id') # 3. Legare document de solicitare și marcare ca finalizat self._update_request_api({ "document_id": doc_id, "status": DocumentsStatus.COMPLETED, "expert_id": self.user_id, }) async def _on_download_button_click(self, e): """Handles the download of the final document.""" if not self.current_selected_request or not self.current_selected_request.get('document_id'): self.page.show_dialog(ft.SnackBar(ft.Text("Nu există un document final asociat acestei solicitări."), open=True)) self.page.update() return document_id = self.current_selected_request['document_id'] try: # Fetch document details to get the path response = requests.get( f"{self.base_url}/documents/customs/{document_id}", headers={'Authorization': f'Bearer {self.token}'} ) if response.status_code == 200: document_data = response.json() document_path = document_data.get('path') if document_path: download_url = f"{self.base_url}/documents/download?path={document_path}&token={self.token}" await self.page.launch_url(download_url) else: self.page.show_dialog(ft.SnackBar(ft.Text("Calea documentului nu a putut fi găsită."), open=True)) else: self.page.show_dialog(ft.SnackBar(ft.Text(f"Eroare la preluarea detaliilor documentului: {response.status_code}"), open=True)) except requests.exceptions.RequestException as err: self.page.show_dialog(ft.SnackBar(ft.Text(f"Eroare de rețea la descărcarea documentului: {err}"), open=True)) except Exception as ex: self.page.show_dialog(ft.SnackBar(ft.Text(f"A apărut o eroare neașteptată: {ex}"), open=True)) self.page.update() def build(self): return ft.Container( content=ft.Row( [ ft.Column( [ ft.Row( [ #self.search_bar, self.status_filter_dropdown, ] ), self.requests_list_view ], width=400 ), ft.VerticalDivider(width=1), ft.Container( content=self.details_panel, expand=True, padding=20, bgcolor=ft.Colors.WHITE, border_radius=10 ) ], expand=True ), expand=True, bgcolor=ft.Colors.GREY_100, padding=10 )