add article and pubications

This commit is contained in:
2026-06-25 10:30:24 +03:00
parent 7fa8a9b7fc
commit 7206a0a0c5
25 changed files with 1180 additions and 86 deletions

View File

@@ -15,7 +15,7 @@ class DocumentsStatus:
mapping = {
DocumentsStatus.NEW: 'Nou',
DocumentsStatus.ANALISE: 'Analiza',
DocumentsStatus.IN_PROGRESS: 'In progres',
DocumentsStatus.IN_PROGRESS: 'In lucru',
DocumentsStatus.WAITING_FOR_PAYMENT: 'Asteptam plata',
DocumentsStatus.COMPLETED: 'Complet',
DocumentsStatus.CANCELED: 'Anulat'

View File

@@ -1,5 +1,6 @@
import flet as ft
from pages.documents.ba import Documents
from pages.publications.articles import Articles as ExpertArticlesPage
from pages.settings.settings import Settings
class NavigationBA:
@@ -13,8 +14,8 @@ class NavigationBA:
label="Documente Custom",
)
self.articole_si_publicatii = ft.NavigationRailDestination(
icon=ft.Icons.ARTICLE_OUTLINED,
selected_icon=ft.Icons.ARTICLE,
icon=ft.Icons.MENU_BOOK_OUTLINED, # Icon mai potrivit pentru articole/blog
selected_icon=ft.Icons.MENU_BOOK,
label="Articole si Publicatii",
)
self.comunicare = ft.NavigationRailDestination(
@@ -61,12 +62,7 @@ class NavigationBA:
def build(self):
return [
self.documente_juridice,
# self.articole_si_publicatii,
# self.comunicare,
# self.consultanta,
# self.convocator,
# self.licitatii_si_lucrari,
# self.profil,
self.articole_si_publicatii,
self.abonamente,
self.setari,
self.logout
@@ -76,21 +72,31 @@ class NavigationBA:
self.docs = Documents(self.page, self.home)
return self.docs.build()
def build_articole_si_publicatii_expert(self):
self.articles_page = ExpertArticlesPage(self.page, self.home)
return self.articles_page.build()
def build_subscriptions(self):
from pages.subscriptions.subscriptions import SubscriptionsPage
self.subs_page = SubscriptionsPage(self.page)
return self.subs_page.build()
async def on_nav_change(self, e):
print( "Selected destination:", e.control.selected_index)
if e.control.selected_index == 0:
self.home.placeholder.content = self.build_documente_juridice()
self.page.update()
if e.control.selected_index == 1:
pass
if e.control.selected_index == 2:
elif e.control.selected_index == 1: # Index for Articole si Publicatii
self.home.placeholder.content = self.build_articole_si_publicatii_expert()
self.page.update()
elif e.control.selected_index == 2: # Abonamente
self.home.placeholder.content = self.build_subscriptions()
self.page.update()
elif e.control.selected_index == 3: # Setari
self.settings = Settings(self.page, self)
self.home.placeholder.content = self.settings.build()
self.page.update() # Actualizează întreaga pagină inclusiv overlay-ul
if e.control.selected_index == 3:
elif e.control.selected_index == 4: # Logout
await ft.SharedPreferences().clear()
self.page.session.store.clear()
self.page.go('/auth')

View File

@@ -1,5 +1,6 @@
import flet as ft
from pages.documents.home import DocumentsHome
from pages.publications.articles import Articles as ClientArticlesPage
class NavigationUser:
def __init__(self, page: ft.Page, home):
@@ -12,8 +13,8 @@ class NavigationUser:
label="Documente Juridice",
)
self.articole_si_publicatii = ft.NavigationRailDestination(
icon=ft.Icons.ARTICLE_OUTLINED,
selected_icon=ft.Icons.ARTICLE,
icon=ft.Icons.MENU_BOOK_OUTLINED, # Icon mai potrivit pentru articole/blog
selected_icon=ft.Icons.MENU_BOOK,
label="Articole si Publicatii",
)
self.comunicare = ft.NavigationRailDestination(
@@ -75,19 +76,23 @@ class NavigationUser:
self.docs = DocumentsHome(self.page, self.home)
return self.docs.build()
def build_articole_si_publicatii_client(self):
self.articles_page = ClientArticlesPage(self.page, self.home)
return self.articles_page.build()
async def on_nav_change(self, e):
print( "Selected destination:", e.control.selected_index)
if e.control.selected_index == 0:
self.home.placeholder.content = self.build_documente_juridice()
self.page.update()
if e.control.selected_index == 1:
pass
elif e.control.selected_index == 1: # Index for Articole si Publicatii
self.home.placeholder.content = self.build_articole_si_publicatii_client()
self.page.update()
if e.control.selected_index == 2:
pass
if e.control.selected_index == 3:
if e.control.selected_index == 9:
await ft.SharedPreferences().clear()
self.page.session.store.clear()
self.page.go('/auth')

View File

@@ -47,6 +47,7 @@ class ForgotPassword:
self.go_to_login
]
)
self.verifica_btn = ft.Button("Verifica", width=150, on_click=self.verfy_code)
def _generate_numeric_code(self) -> str:
digits = string.digits
@@ -58,7 +59,7 @@ class ForgotPassword:
self.main_column.controls.append(self.title)
self.main_column.controls.append(self.inserted_code)
self.main_column.controls.append(self.error_message)
self.main_column.controls.append("Verifica", width=150, on_click=self.verfy_code)
self.main_column.controls.append(self.verifica_btn)
self.main_column.update()
#print(self.otp_code)
send_gmail(

View File

@@ -1,7 +1,5 @@
import flet as ft
import requests
from navigation.ba import NavigationBA
from navigation.user import NavigationUser

View File

@@ -4,6 +4,7 @@ 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:
@@ -11,6 +12,7 @@ class State:
file_picker: ft.FilePicker | None = None
picked_files: list[ft.FilePickerFile] = field(default_factory=list)
state = State()
class Documents:
@@ -21,11 +23,11 @@ class Documents:
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)",
@@ -87,6 +89,11 @@ class Documents:
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,
@@ -108,15 +115,23 @@ class Documents:
]),
ft.Divider(),
ft.Text("Finalizare și Încărcare Document:", weight=ft.FontWeight.BOLD),
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
])
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,
@@ -301,8 +316,8 @@ class Documents:
print(f"Update failed: {e}")
return False
async def _handle_file_upload(self, e):
if not self.current_selected_request: return
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)
@@ -310,36 +325,69 @@ class Documents:
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
]
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)
# 2. Înregistrare în tabela documents_custom
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}'}
)
reg_resp = requests.post(
f"{self.base_url}/documents/customs/add",
json={"name": f"Document Final Solicitare #{self.current_selected_request['id']}", "path": uploaded_file_name},
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 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,
})
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:
print(f"Error during file upload or registration: {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(

View File

@@ -73,6 +73,13 @@ class Documents:
disabled=True, # Will be enabled based on status
visible=False # Initially hidden
)
self.download_button = ft.FilledButton(
"Descarcă Document Final",
icon=ft.Icons.DOWNLOAD,
on_click=self._on_download_button_click,
disabled=True, # Will be enabled based on status and document_id
visible=False # Initially hidden
)
self.comment_text_field = ft.TextField(
label="Adauga un comentariu",
multiline=True,
@@ -96,11 +103,13 @@ class Documents:
ft.Text("Pret:", weight=ft.FontWeight.BOLD),
self.request_price_text,
self.pay_button,
self.download_button,
ft.Divider(),
ft.Text("Adauga Comentariu:", weight=ft.FontWeight.BOLD),
ft.Row([self.comment_text_field, self.add_comment_button]),
],
expand=True,
scroll=ft.ScrollMode.ADAPTIVE,
visible=False # Initially hidden
)
@@ -186,6 +195,14 @@ class Documents:
self.pay_button.visible = False
self.pay_button.disabled = True
# Handle Download button visibility and state
if request_data.get('status') == DocumentsStatus.COMPLETED and request_data.get('document_id'):
self.download_button.visible = True
self.download_button.disabled = False
else:
self.download_button.visible = False
self.download_button.disabled = True
# Enable comment section
self.add_comment_button.disabled = False
self.comment_text_field.disabled = False
@@ -308,6 +325,36 @@ class Documents:
))
self.page.update()
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 _close_dialog(self, e):
self.page.pop_dialog()
self.page.update()

View File

@@ -0,0 +1,334 @@
import flet as ft
import requests
from helpers.roles import Roles
class Articles:
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') or {}
self.all_articles = []
self.selected_article_id = None
# UI Elements for Search
self.search_bar = ft.TextField(
label="Caută articole după titlu...",
prefix_icon=ft.Icons.SEARCH,
on_change=self.on_search_change,
expand=True,
hint_text="Introdu titlul sau fragmente din titlu..."
)
# UI list container
self.articles_container = ft.Column(
scroll=ft.ScrollMode.AUTO,
spacing=15,
expand=True
)
# Delete confirmation dialog
self.article_id_to_delete = None
self.delete_confirm_dialog = ft.AlertDialog(
title=ft.Text("Confirmare Ștergere"),
content=ft.Text("Sigur doriți să ștergeți acest articol? Această acțiune este ireversibilă."),
actions=[
ft.FilledButton("Șterge", on_click=self.confirm_delete_article, bgcolor=ft.Colors.RED),
ft.FilledButton("Anulează", on_click=self.close_dialog, bgcolor=ft.Colors.GREY)
],
actions_alignment=ft.MainAxisAlignment.END
)
# Read Article Modal Dialog
self.read_title = ft.Text(size=22, weight=ft.FontWeight.BOLD)
self.read_meta = ft.Text(size=12, italic=True, color=ft.Colors.GREY_600)
self.read_content = ft.Markdown(selectable=True, expand=True)
self.read_article_dialog = ft.AlertDialog(
content=ft.Container(
content=ft.Column(
[
self.read_title,
self.read_meta,
ft.Divider(height=10),
ft.Column(
[self.read_content],
scroll=ft.ScrollMode.AUTO,
expand=True
)
],
spacing=10,
),
width=650,
height=500,
),
actions=[
ft.TextButton("Închide", on_click=self.close_dialog)
]
)
def load_articles(self):
try:
response = requests.get(
f"{self.base_url}/articles/",
headers={"Authorization": f"Bearer {self.token}"}
)
if response.status_code == 200:
self.all_articles = response.json()
else:
self.all_articles = []
except Exception as e:
print(f"Error fetching articles: {e}")
self.all_articles = []
def populate_articles_list(self, query=""):
self.articles_container.controls.clear()
query = query.strip().lower()
filtered = [
art for art in self.all_articles
if query in art.get('title', '').lower()
]
if not filtered:
self.articles_container.controls.append(
ft.Container(
content=ft.Text("Nu s-au găsit articole publicate.", size=16, color=ft.Colors.GREY_600),
alignment=ft.Alignment.CENTER,
padding=40
)
)
else:
can_publish = self.user.get('can_create_articles') == 1
user_id = self.user.get('id')
for art in filtered:
# Curățăm formatarea markdown pentru previzualizare
preview = self.strip_markdown(art.get('content', ''))
if len(preview) > 180:
preview = preview[:180] + "..."
# Check if logged-in user is the author of this post
is_author = art.get('author_id') == user_id
# Formatează data
raw_date = art.get('created_at', '')
formatted_date = raw_date.replace("T", " ").split(".")[0] if raw_date else "Dată necunoscută"
# Row of actions
actions_row = ft.Row(
[
ft.TextButton(
"Citește Articolul",
icon=ft.Icons.ARROW_FORWARD,
on_click=lambda e, a=art: self.open_read_dialog(a)
)
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN
)
# Daca utilizatorul este autorul, adaugam butoane de Edit/Delete
if can_publish and is_author:
actions_row.controls.append(
ft.Row(
[
ft.IconButton(
icon=ft.Icons.EDIT_OUTLINED,
tooltip="Editează articolul",
icon_color=ft.Colors.BLUE_700,
on_click=lambda e, a=art: self.page.run_task(self.open_edit_dialog, a)
),
ft.IconButton(
icon=ft.Icons.DELETE_OUTLINE,
tooltip="Șterge articolul",
icon_color=ft.Colors.RED_600,
on_click=lambda e, a_id=art['id']: self.open_delete_confirm(a_id)
)
],
spacing=5
)
)
self.articles_container.controls.append(
ft.Container(
content=ft.Column(
[
ft.Text(art.get('title', 'Fără Titlu'), weight=ft.FontWeight.BOLD, size=18, color=ft.Colors.BLUE_GREY_900),
ft.Row(
[
ft.Icon(ft.Icons.PERSON_OUTLINE, size=14, color=ft.Colors.GREY_500),
ft.Text(f"Autor: {art.get('author_name', 'Necunoscut')}", size=12, color=ft.Colors.GREY_600),
ft.VerticalDivider(width=1, color=ft.Colors.GREY_300),
ft.Icon(ft.Icons.ACCESS_TIME, size=14, color=ft.Colors.GREY_500),
ft.Text(f"Publicat: {formatted_date}", size=12, color=ft.Colors.GREY_600),
],
spacing=10
),
ft.Divider(height=5, color=ft.Colors.TRANSPARENT),
ft.Text(preview, size=14, color=ft.Colors.GREY_700),
ft.Divider(height=10),
actions_row
],
spacing=10
),
bgcolor=ft.Colors.WHITE,
padding=20,
border_radius=12,
border=ft.Border.all(1, ft.Colors.GREY_200),
shadow=ft.BoxShadow(
blur_radius=8,
color=ft.Colors.with_opacity(0.04, ft.Colors.BLACK),
offset=ft.Offset(0, 3)
)
)
)
#self.articles_container.update()
def on_search_change(self, e):
self.populate_articles_list(self.search_bar.value)
def strip_markdown(self, text):
if not text:
return ""
import re
# Elimină antetele (ex: # Titlu 1)
text = re.sub(r'(?m)^#{1,6}\s+', '', text)
# Elimină marcatorii de listă (ex: - element)
text = re.sub(r'(?m)^[\-\*\+]\s+', '', text)
# Elimină caracterele pentru bold, italic și inline-code
text = re.sub(r'\*\*|__|\*|_|`', '', text)
# Înlocuiește multiplele spații sau linii noi cu un singur spațiu
text = re.sub(r'\s+', ' ', text)
return text.strip()
# Creation/Editing Dialog (deschise în tab separat)
async def open_add_dialog(self, e):
editor_url = f"{self.base_url}/articles/editor?token={self.token}"
await self.page.launch_url(editor_url)
async def open_edit_dialog(self, article):
print('edit')
editor_url = f"{self.base_url}/articles/editor?token={self.token}&article_id={article['id']}"
await self.page.launch_url(editor_url)
# Delete Dialog
def open_delete_confirm(self, article_id):
self.article_id_to_delete = article_id
self.page.show_dialog(self.delete_confirm_dialog)
self.page.update()
def confirm_delete_article(self, e):
if not self.article_id_to_delete:
return
try:
response = requests.delete(
f"{self.base_url}/articles/delete/{self.article_id_to_delete}",
headers={"Authorization": f"Bearer {self.token}"}
)
if response.status_code == 200:
self.page.pop_dialog()
self.load_articles()
self.populate_articles_list(self.search_bar.value)
self.show_snack_bar("Articolul a fost șters cu succes.", ft.Colors.GREEN)
else:
self.page.pop_dialog()
err = response.json().get('error', 'Eroare la ștergerea articolului.')
self.show_snack_bar(err, ft.Colors.RED)
except Exception as ex:
print(f"Error deleting article: {ex}")
self.page.pop_dialog()
self.show_snack_bar("Eroare de conexiune la server.", ft.Colors.RED)
# Read modal dialog
def open_read_dialog(self, article):
self.read_title.value = article.get('title', 'Fără Titlu')
raw_date = article.get('created_at', '')
formatted_date = raw_date.replace("T", " ").split(".")[0] if raw_date else "Dată necunoscută"
self.read_meta.value = f"Scris de {article.get('author_name', 'Necunoscut')} la data de {formatted_date}"
self.read_content.value = article.get('content', '')
self.page.show_dialog(self.read_article_dialog)
self.page.update()
def close_dialog(self, e):
self.page.pop_dialog()
def show_snack_bar(self, message, color):
self.page.snack_bar = ft.SnackBar(
content=ft.Text(message, color=ft.Colors.WHITE),
bgcolor=color,
duration=3000
)
self.page.snack_bar.open = True
self.page.update()
def on_refresh_click(self, e):
self.load_articles()
self.populate_articles_list(self.search_bar.value)
self.show_snack_bar("Lista de articole a fost actualizată.", ft.Colors.GREEN)
def build(self):
self.load_articles()
self.populate_articles_list()
# Check if the user is an expert with posting privileges
can_publish = self.user.get('can_create_articles') == 1
header_row = ft.Row(
[
ft.Column(
[
ft.Text("Articole și Publicații", size=24, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_GREY_900),
ft.Text("Răsfoiți noutățile, ghidurile și publicațiile juridice recente.", size=14, color=ft.Colors.GREY_600)
],
spacing=2
)
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN
)
# Dacă utilizatorul are bifa de publicare, adăugăm butonul de Adăugare
if can_publish:
header_row.controls.append(
ft.FilledButton(
"Adaugă Articol",
icon=ft.Icons.ADD,
#on_click=lambda e: self.page.run_task(self.open_add_dialog(e)),
on_click=self.open_add_dialog,
style=ft.ButtonStyle(
shape=ft.RoundedRectangleBorder(radius=8)
)
)
)
refresh_btn = ft.IconButton(
icon=ft.Icons.REFRESH,
tooltip="Reîncarcă articolele",
icon_color=ft.Colors.BLUE_700,
on_click=self.on_refresh_click
)
return ft.Container(
content=ft.Column(
[
header_row,
ft.Divider(height=10, color=ft.Colors.TRANSPARENT),
ft.Row(
[
self.search_bar,
refresh_btn
],
spacing=10
),
ft.Divider(height=10, color=ft.Colors.GREY_100),
self.articles_container
],
expand=True
),
expand=True,
padding=20,
bgcolor=ft.Colors.GREY_50
)

View File

@@ -0,0 +1,188 @@
import flet as ft
import requests
from helpers.roles import Roles
class ArticlesSettings:
def __init__(self, page: ft.Page):
self.page = page
self.base_url = self.page.session.store.get('api_base_url')
self.token = self.page.session.store.get('token')
self.search_bar = ft.TextField(
label="Caută expert...",
prefix_icon=ft.Icons.SEARCH,
on_change=self.on_search_change,
expand=True
)
self.experts_list = ft.ListView(
spacing=10,
expand=True,
padding=10
)
self.all_experts = []
def get_experts(self):
try:
response = requests.get(
f"{self.base_url}/users/",
headers={'Authorization': f'Bearer {self.token}'}
)
if response.status_code == 200:
users = response.json()
# Filtrăm doar utilizatorii cu rol de expert sau ba
self.all_experts = [u for u in users if u.get('role') in [Roles.EXPERT, Roles.BA]]
return self.all_experts
except Exception as e:
print(f"Error fetching experts: {e}")
return []
def load_experts_list(self, query=""):
self.experts_list.controls.clear()
query = query.strip().lower()
filtered = [
exp for exp in self.all_experts
if query in f"{exp.get('first_name', '')} {exp.get('last_name', '')} {exp.get('email', '')}".lower()
]
if not filtered:
self.experts_list.controls.append(
ft.Container(
content=ft.Text("Nu s-au găsit experți.", size=16, color=ft.Colors.GREY_600),
alignment=ft.Alignment.CENTER,
padding=20
)
)
else:
for exp in filtered:
expert_id = exp['id']
name = f"{exp.get('first_name') or ''} {exp.get('last_name') or ''}".strip() or "Nume nespecificat"
email = exp.get('email', 'Email nespecificat')
role_label = exp.get('role', '').upper()
can_create = exp.get('can_create_articles') == 1
self.experts_list.controls.append(
ft.Container(
content=ft.Row(
[
ft.Row(
[
ft.CircleAvatar(
content=ft.Icon(ft.Icons.PERSON, color=ft.Colors.BLUE_800),
bgcolor=ft.Colors.BLUE_50,
radius=20
),
ft.Column(
[
ft.Text(name, weight=ft.FontWeight.BOLD, size=15),
ft.Row(
[
ft.Text(email, size=13, color=ft.Colors.GREY_600),
ft.Container(
content=ft.Text(role_label, size=10, color=ft.Colors.BLUE_800, weight=ft.FontWeight.BOLD),
bgcolor=ft.Colors.BLUE_50,
padding=ft.Padding(5, 2, 5, 2),
border_radius=4
)
],
spacing=10
)
],
alignment=ft.MainAxisAlignment.CENTER,
spacing=2
)
],
spacing=15
),
ft.Switch(
value=can_create,
on_change=lambda e, uid=expert_id, uemail=email: self.toggle_permission(e, uid, uemail)
)
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN
),
bgcolor=ft.Colors.WHITE,
padding=15,
border_radius=10,
border=ft.Border.all(1, ft.Colors.GREY_200),
shadow=ft.BoxShadow(
blur_radius=4,
color=ft.Colors.with_opacity(0.05, ft.Colors.BLACK),
offset=ft.Offset(0, 2)
)
)
)
def toggle_permission(self, e, user_id, user_email):
new_val = 1 if e.control.value else 0
try:
payload = {"can_create_articles": new_val}
response = requests.put(
f"{self.base_url}/users/update/{user_id}",
json=payload,
headers={'Authorization': f'Bearer {self.token}'}
)
if response.status_code == 200:
# Update local data model
for exp in self.all_experts:
if exp['id'] == user_id:
exp['can_create_articles'] = new_val
break
status_msg = "permisiune acordată" if new_val == 1 else "permisiune retrasă"
self.show_snack_bar(f"Drepturi actualizate pentru {user_email}: {status_msg}.", ft.Colors.GREEN)
else:
e.control.value = not e.control.value
e.control.update()
self.show_snack_bar("Eroare la salvarea permisiunilor.", ft.Colors.RED)
except Exception as ex:
print(f"Error toggling permission: {ex}")
e.control.value = not e.control.value
e.control.update()
self.show_snack_bar("Eroare de conexiune la server.", ft.Colors.RED)
def on_search_change(self, e):
self.load_experts_list(self.search_bar.value)
def show_snack_bar(self, message, color):
self.page.snack_bar = ft.SnackBar(
content=ft.Text(message, color=ft.Colors.WHITE),
bgcolor=color,
duration=3000
)
self.page.snack_bar.open = True
self.page.update()
def build(self):
self.get_experts()
self.load_experts_list()
return ft.Container(
content=ft.Column(
[
ft.Container(
content=ft.Column(
[
ft.Text("Configurare Drepturi Publicare", size=20, weight=ft.FontWeight.BOLD),
ft.Text(
"Permiteți sau blocați dreptul experților de a crea, edita și șterge publicații de pe blog.",
size=14,
color=ft.Colors.GREY_600
),
],
spacing=5
),
margin=ft.Margin(0, 0, 0, 10)
),
ft.Row([self.search_bar]),
ft.Divider(height=10, color=ft.Colors.GREY_100),
self.experts_list
],
expand=True
),
expand=True,
padding=20,
bgcolor=ft.Colors.GREY_50
)

View File

@@ -2,6 +2,7 @@ import flet as ft
from pages.settings.documente_juridice import DocumenteJuridice
from pages.settings.users import UsersSettings
from pages.settings.payment_and_subscription import PaymentAndSubscription
from pages.settings.articles import ArticlesSettings
class Settings:
def __init__(self, page: ft.Page, home):
@@ -11,6 +12,7 @@ class Settings:
self.doc_juridice = DocumenteJuridice(self.page)
self.users_settings = UsersSettings(self.page)
self.payment_and_subscription = PaymentAndSubscription(self.page)
self.articles_settings = ArticlesSettings(self.page)
def build(self):
return ft.Tabs(
@@ -42,8 +44,9 @@ class Settings:
expand=True
),
ft.Container(
content=ft.Text("This is Tab 2"),
content=self.articles_settings.build(),
alignment=ft.Alignment.CENTER,
expand=True
),
ft.Container(
content=ft.Text("This is Tab 3"),