TradeFlowAI / app.py
simikkk's picture
Update app.py
0a45b43 verified
Raw
History Blame Contribute Delete
65.3 kB
import streamlit as st
import pandas as pd
import time
import urllib.parse
import stripe # Připraveno pro splnění požadavků v requirements.txt
import streamlit.components.v1 as components # Bezpečný import pro HTML komponenty na Hugging Face
# -----------------------------------------------------------------------------
# KOMPLETNÍ JAZYKOVÉ LOKALIZACE PRO VŠECHNY PRVKY NA STRÁNCE (CZ, SK, EN)
# -----------------------------------------------------------------------------
TRANSLATIONS = {
"CZ": {
# Obecné a záložky
"title": "🏆 TradeFlow AI",
"caption": "Chytrý operační systém pro novou generaci řemeslníků.",
"tab_dash": "📈 Přehled & Metriky",
"tab_gen": "📝 AI Generátor Nabídek",
"tab_team": "👥 Správa Týmu",
"tab_crm": "👥 Adresář (CRM)",
"tab_inv": "🧾 Fakturace jedním klikem",
"tab_pro": "🚀 PRO Exkluzivní Funkce",
# Sidebar & Admin Panel
"sb_settings": "🌐 Nastavení",
"sb_lang": "Jazyk aplikace",
"sb_curr": "Aktivní měna",
"sb_dev_panel": "🛡️ Dev Admin Panel",
"sb_pwd_label": "Zadejte administrátorské heslo",
"sb_pwd_hint": "Heslo pro testování je: admin123",
"sb_unlock_btn": "Odemknout Admin Panel",
"sb_auth_ok": "Přístup povolen!",
"sb_auth_err": "Nesprávné heslo!",
"sb_auth_status": "🔓 Vývojářská autorizace",
"sb_pro_toggle": "Uživatel má aktivní PRO tarif",
"sb_quotes_count": "Počet vytvořených nabídek",
"sb_demo_section": "🧪 Správa ukázkových dat",
"sb_import_demo": "📥 Importovat komplexní Demo Data",
"sb_clear_demo": "🗑️ Kompletní vyčištění databází",
"sb_logout": "Zamknout panel & Odhlásit se",
"toast_demo_ok": "Systém byl úspěšně naplněn cvičnými daty!",
"toast_clear_ok": "Aplikace byla kompletně vyčištěna.",
# Dashboard Metriky
"profit": "Skutečný zisk",
"pending": "Čekající peníze",
"potential": "Otevřené AI Nabídky",
"limit": "Využití Free limitu",
"dash_help_potential": "Počítá pouze Přesné fixní nabídky.",
"dash_limit_warn": "⚠️ LIMIT UKONČEN",
# Dashboard Analýza
"dash_analysis_title": "📊 Pokročilá analýza a finanční filtrování sekcí",
"dash_analysis_desc": "Sledujte finanční toky seskupené podle konkrétních nákladových sekcí napříč celou firmou.",
"dash_filter_cat": "Zvolte nákladovou kategorii k odfiltrování:",
"dash_filter_type": "Filtrovat typ zakázky:",
"dash_filter_all_cats": "Zobrazit vše",
"dash_type_all": "Všechny rozpočty",
"dash_type_fixed": "Pouze závazné přesné nabídky",
"dash_type_estimated": "Pouze orientační odhady",
"dash_aggregated_spend": "💰 Agregovaná útrata ve zvoleném filtru:",
"dash_no_records": "Pro zadaná kritéria a filtry nebyly nalezeny žádné záznamy.",
"dash_no_costs_yet": "V systému zatím nejsou evidovány žádné podrobné položky nákladů.",
"dash_book_title": "Kniha vytvořených kalkulací a nabídek",
"dash_book_empty": "Kniha nabídek je prázdná.",
# Sloupce tabulek v Dashboardu
"col_project": "Projekt / Klient",
"col_section": "Sekce nákladu",
"col_item_name": "Název položky / Popis",
"col_amount": "Částka",
"col_type": "Typ rozpočtu",
"col_date": "Datum",
# AI Generátor Nabídek
"gen_type_label": "Vyberte povahu výstupu a nacenění:",
"gen_type_est": "🔮 Orientační odhad od AI (Čistě orientační kalkulace, nepropisuje se do dlužníků)",
"gen_type_fix": "🎯 Přesná fixní nabídka (Závazný finanční planar, slouží jako podklad pro fakturu)",
"gen_client_section": "### 👤 Přiřazení klienta",
"gen_client_mode": "Způsob zadání zákazníka:",
"gen_client_crm": "Vybrat z adresáře CRM",
"gen_client_manual": "✍️ Zadat úplně nového klienta ručně",
"gen_select_client": "Zvolte klienta:",
"gen_input_client_name": "Zadejte jméno a příjmení nového klienta:",
"gen_client_placeholder": "Např. František Novotný",
"gen_spec_label": "Detailní specifikace projektu a zadání pro AI",
"gen_spec_default": "Kompletní rekonstrukce rodinného domu, rozvody vody a finální zapojení sanitární techniky.",
"gen_team_alloc": "### 🕒 Personální alokace (Odpracované hodiny týmu)",
"gen_no_team_warn": "ℹ️ V interní databázi zatím nejsou žádní pracovníci. Přidejte je v záložce 'Správa Týmu' nebo zapněte Demo Data v boční liště.",
"gen_ai_btn": "🪄 Spustit AI simulaci a předvyplnit položky i hodiny",
"gen_ai_toast": "AI úspěšně zanalyzovala text a navrhla optimální strukturu nákladů!",
"gen_direct_costs": "### 🚚 Přímé materiálové a vedlejší náklady",
"gen_table_hint": "💡 Nové řádky přidáte symbolem `+` vespod tabulky. Řádek smažete označením a stiskem klávesy `Delete` na klávesnici.",
"gen_csv_title": "### 📂 Hromadný import externích nákladů přes CSV",
"gen_csv_uploader": "Nahrajte soubor .csv (Struktura sloupců: Sekce nákladu, Název položky / Popis, Částka)",
"gen_csv_success": "CSV data byla úspěšně nahrána do tabulky výše!",
"gen_csv_err_headers": "Chyba: CSV neodpovídá požadované struktuře záhlaví.",
"gen_csv_err_read": "Soubor nelze přečíst:",
"gen_total_calc": "### 📈 Celková kalkulace rozpočtu:",
"gen_save_btn": "💾 Schválit a uložit rozpočet do systému",
"gen_err_no_client": "Chyba: Před uložením musíte uvést nebo vybrat jméno klienta!",
"gen_save_success": "Rozpočet byl úspěšně zanesen do firemní databáze.",
# Správa týmu
"team_title": "👥 Správa firemního týmu a mzdových sazeb",
"team_desc": "Editujte profily a interní hodinové sazby svých pracovníků.",
"team_sub_list": "Evidovaní pracovníci a specialisté",
"team_col_id": "Interní ID",
"team_col_role": "Jméno člena / Profesní role",
"team_col_rate": "Hodinová mzdová sazba",
"team_empty": "V týmu se aktuálně nenachází žádný pracovník. Použijte formulář vpravo nebo načtěte Demo data.",
"team_remove_section": "**❌ Odstranění pracovníka z týmu**",
"team_remove_select": "Vyberte osobu k odebrání:",
"team_remove_btn": "Definitivně odebrat člena",
"team_remove_toast": "Pracovník byl vyřazen z interní databáze.",
"team_add_section": "### ➕ Přidat / Upravit kartu pracovníka",
"team_input_name": "Jméno příjmení / Pracovní zařazení",
"team_input_placeholder": "Např. Kamil (Zedník)",
"team_input_rate": "Hodinová sazba v aktivní měně",
"team_submit_btn": "Uložit profil",
"team_update_toast": "Sazba u pracovníka byla zaktualizována.",
"team_add_toast": "Nový člen byl úspěšně zaevidován.",
# CRM Adresář
"crm_title": "👥 Adresář zákazníků (CRM)",
"crm_empty": "Adresář klientů je momentálně prázdný.",
"crm_add_section": "### ➕ Rychlé ruční založení klienta",
"cust_name": "Jméno a příjmení",
"company": "Firma (Volitelné)",
"phone": "Telefonní číslo",
"save_cust": "Uložit klienta",
"crm_err_name": "Jméno klienta je povinné pole.",
"crm_success_toast": "Klient byl bezpečně uložen do CRM.",
# Fakturace
"inv_title": "🧾 Fakturace jedním klikem",
"inv_select_quote": "Zvolte schválený pevný rozpočet k okamžité fakturaci:",
"invoice_btn": "🧾 VYSTAVIT FAKTURU JEDNÍM KLIKNUTÍM",
"inv_success_toast": "Faktura byla vystavena a uložena do knihy!",
"inv_no_fixed_quotes": "V systému se nenachází žádná schválená pevná nabídka (Orientační odhady nelze fakturovat).",
"inv_book_title": "Kompletní přehled vydaných faktur a stavu plateb",
"inv_status_paid": "🟢 Uhrazeno",
"inv_status_sent": "🔵 Odesláno (Po splatnosti)",
"inv_mark_paid": "Označit jako ZAPLACENO",
"inv_empty": "Kniha faktur je prázdná.",
# PRO Funkce
"paywall_msg": "🔒 Dosáhli jste limitu 3 bezplatných nabídek. Pro neomezený přístup aktivujte tarif PRO.",
"pro_router": "📍 AI Plánovač tras a logistiky",
"pro_sms": "📲 Inteligentní SMS Centrum upomínek",
"pro_locked_warn": "🔒 Tento panel obsahuje pokročilé moduly TradeFlow PRO. Aktivujte zkušební verzi v Dev Admin Panelu na boční liště.",
"pro_route_title": "📍 AI Geografický optimalizátor tras výjezdů",
"pro_route_label": "Seznam adres plánovaných zásahů",
"pro_route_btn": "🗺️ Spočítat matematicky nejkratší trasu",
"pro_route_result": "🚀 AI Výsledek kalkulace: Výjezd z Plzně -> Beroun -> Praha. Celková úspora paliva a logistických nákladů činí: 42 km.",
"pro_sms_title": "📱 Inteligentní manažerské SMS centrum pohledávek",
"pro_sms_step1": "### 🔍 1. Vyhledání dlužníka v systému",
"pro_sms_filter_name": "Filtrovat neplatiče dle jména:",
"pro_sms_sort_label": "Seřadit dlužníky dle kritéria:",
"pro_sms_sort_high": "Od nejvyšší dlužné částky",
"pro_sms_sort_low": "Od nejnižší dlužné částky",
"pro_sms_no_debts": "V systému aktuálně neevidujeme žádné neuhrazené faktury odpovídající filtrům.",
"pro_sms_select_debtor": "🎯 Vyberte cílového dlužníka k obeslání:",
"pro_sms_step2": "### 📋 2. Výběr textové šablony",
"pro_sms_template_label": "Zvolte aktivní komunikační šablonu:",
"pro_sms_expander": "⚙️ Pokročilý editor šablon & Generátor nových textů přes AI",
"pro_sms_ai_assistant": "🤖 **AI Asistent pro tvorbu tónu textu**",
"pro_sms_ai_prompt": "Zadejte zadání pro AI (např. 'Napiš velmi naléhavou předžalobní výzvu')",
"pro_sms_ai_btn": "🪄 Sestavit novou šablonu pomocí AI",
"pro_sms_ai_success": "AI text úspěšně vygenerován do pole níže!",
"pro_sms_new_title": "Název nové šablony:",
"pro_sms_new_body": "Text těla zprávy (používejte značky {jmeno}, {cislo}, {castka})",
"pro_sms_save_template_btn": "Uložit šablonu do trvalého seznamu",
"pro_sms_template_success": "Šablona úspěšně uložena!",
"pro_sms_step3": "### 📱 3. Finální vygenerovaná zpráva pro klienta",
"pro_sms_copy_btn": "📋 Kopírovat text zprávy",
"pro_sms_copy_ok": "✅ Zkopírováno do schránky!",
"pro_sms_select_info": "Vyberte dlužníka pro zobrazení a zkopírování SMS zprávy."
},
"SK": {
# Obecné a záložky
"title": "🏆 TradeFlow AI",
"caption": "Chytrý operačný systém pre novú generáciu remeselníkov.",
"tab_dash": "📈 Prehľad & Metriky",
"tab_gen": "📝 AI Generátor Ponúk",
"tab_team": "👥 Správa Tímu",
"tab_crm": "👥 Adresár (CRM)",
"tab_inv": "🧾 Fakturácia jedným klikom",
"tab_pro": "🚀 PRO Exkluzívne Funkcie",
# Sidebar & Admin Panel
"sb_settings": "🌐 Nastavenia",
"sb_lang": "Jazyk aplikácie",
"sb_curr": "Aktívna mena",
"sb_dev_panel": "🛡️ Dev Admin Panel",
"sb_pwd_label": "Zadajte administrátorské heslo",
"sb_pwd_hint": "Heslo pre testovanie je: admin123",
"sb_unlock_btn": "Odomknúť Admin Panel",
"sb_auth_ok": "Prístup povolený!",
"sb_auth_err": "Nesprávne heslo!",
"sb_auth_status": "🔓 Vývojárska autorizácia",
"sb_pro_toggle": "Užívateľ má aktívny PRO tarif",
"sb_quotes_count": "Počet vytvorených ponúk",
"sb_demo_section": "🧪 Správa ukážkových dát",
"sb_import_demo": "📥 Importovať komplexné Demo Dáta",
"sb_clear_demo": "🗑️ Kompletné vyčistenie databáz",
"sb_logout": "Zamknúť panel & Odhlásiť sa",
"toast_demo_ok": "Systém bol úspešne naplnený cvičnými dátami!",
"toast_clear_ok": "Aplikácia bola kompletne vyčistená.",
# Dashboard Metriky
"profit": "Skutočný zisk",
"pending": "Čakajúce peniaze",
"potential": "Otvorené AI Ponuky",
"limit": "Využitie Free limitu",
"dash_help_potential": "Počíta iba Presné fixné ponuky.",
"dash_limit_warn": "⚠️ LIMIT UKONČENÝ",
# Dashboard Analýza
"dash_analysis_title": "📊 Pokročilá analýza a finančné filtrovanie sekcií",
"dash_analysis_desc": "Sledujte finančné toky zoskupené podľa konkrétnych nákladových sekcií naprieč celou firmou.",
"dash_filter_cat": "Zvoľte nákladovú kategóriu na odfiltrovanie:",
"dash_filter_type": "Filtrovať typ zákazky:",
"dash_filter_all_cats": "Zobraziť všetko",
"dash_type_all": "Všetky rozpočty",
"dash_type_fixed": "Iba záväzné presné ponuky",
"dash_type_estimated": "Iba orientačné odhady",
"dash_aggregated_spend": "💰 Agregovaná útrata vo zvolenom filtri:",
"dash_no_records": "Pre zadané kritériá a filtre neboli nájdené žiadne záznamy.",
"dash_no_costs_yet": "V systéme zatiaľ nie sú evidované žiadne podrobné položky nákladov.",
"dash_book_title": "Kniha vytvorených kalkulácií a ponúk",
"dash_book_empty": "Kniha ponúk je prázdna.",
# Sloupce tabulek v Dashboardu
"col_project": "Projekt / Klient",
"col_section": "Sekcia nákladu",
"col_item_name": "Názov položky / Popis",
"col_amount": "Čiastka",
"col_type": "Typ rozpočtu",
"col_date": "Dátum",
# AI Generátor Nabídek
"gen_type_label": "Vyberte povahu výstupu a nacenenie:",
"gen_type_est": "🔮 Orientačný odhad od AI (Čisto orientačná kalkulácia, neprepisuje sa do dlžníkov)",
"gen_type_fix": "🎯 Presná fixná ponuka (Záväzný finančný plán, slúži ako podklad pre faktúru)",
"gen_client_section": "### 👤 Priradenie klienta",
"gen_client_mode": "Spôsob zadania zákazníka:",
"gen_client_crm": "Vybrať z adresára CRM",
"gen_client_manual": "✍️ Zadať úplne nového klienta ručne",
"gen_select_client": "Zvoľte klienta:",
"gen_input_client_name": "Zadajte meno a priezvisko nového klienta:",
"gen_client_placeholder": "Napr. František Novotný",
"gen_spec_label": "Detailná špecifikácia projektu a zadanie pre AI",
"gen_spec_default": "Kompletná rekonštrukcia rodinného domu, rozvody vody a finálne zapojenie sanitárnej techniky.",
"gen_team_alloc": "### 🕒 Personálna alokácia (Odpracované hodiny tímu)",
"gen_no_team_warn": "ℹ️ V internej databáze zatiaľ nie sú žiadni pracovníci. Pridajte ich v záložke 'Správa Tímu' alebo zapnite Demo Data v bočnej lište.",
"gen_ai_btn": "🪄 Spustiť AI simuláciu a predvyplniť položky aj hodiny",
"gen_ai_toast": "AI úspešne zanalyzovala text a navrhla optimálnu štruktúru nákladov!",
"gen_direct_costs": "### 🚚 Priame materiálové a vedľajšie náklady",
"gen_table_hint": "💡 Nové riadky pridáte symbolom `+` naspodku tabuľky. Riadok smažete označením a stlačením klávesy `Delete` na klávesnici.",
"gen_csv_title": "### 📂 Hromadný import externých nákladov cez CSV",
"gen_csv_uploader": "Nahrajte súbor .csv (Štruktúra stĺpcov: Sekcia nákladu, Název položky / Popis, Částka)",
"gen_csv_success": "CSV dáta boli úspešne nahrané do tabuľky vyššie!",
"gen_csv_err_headers": "Chyba: CSV neodpovedá požadovanej štruktúre záhlavia.",
"gen_csv_err_read": "Súbor nie je možné prečítať:",
"gen_total_calc": "### 📈 Celková kalkulácia rozpočtu:",
"gen_save_btn": "💾 Schváliť a uložiť rozpočet do systému",
"gen_err_no_client": "Chyba: Pred uložením musíte uviesť alebo vybrať meno klienta!",
"gen_save_success": "Rozpočet bol úspešne zanesený do firemnej databázy.",
# Správa týmu
"team_title": "👥 Správa firemného tímu a mzdových sadzieb",
"team_desc": "Editujte profily a interné hodinové sadzby svojich pracovníkov.",
"team_sub_list": "Evidovaní pracovníci a špecialisti",
"team_col_id": "Interné ID",
"team_col_role": "Meno člena / Profesijná rola",
"team_col_rate": "Hodinová mzdová sadzba",
"team_empty": "V tíme sa aktuálne nenachádza žiadny pracovník. Použite formulár vpravo alebo načítajte Demo dáta.",
"team_remove_section": "**❌ Odstránenie pracovníka z tímu**",
"team_remove_select": "Vyberte osobu na odobratie:",
"team_remove_btn": "Definitívne odobrať člena",
"team_remove_toast": "Pracovník bol vyradený z internej databázy.",
"team_add_section": "### ➕ Pridať / Upraviť kartu pracovníka",
"team_input_name": "Meno priezvisko / Pracovné zaradenie",
"team_input_placeholder": "Napr. Kamil (Murár)",
"team_input_rate": "Hodinová sadzba v aktívnej mene",
"team_submit_btn": "Uložiť profil",
"team_update_toast": "Sadzba u pracovníka bola zaktualizovaná.",
"team_add_toast": "Nový člen bol úspešne zaevidovaný.",
# CRM Adresář
"crm_title": "👥 Adresár zákazníkov (CRM)",
"crm_empty": "Adresár klientov je momentálne prázdny.",
"crm_add_section": "### ➕ Rýchle ručné založenie klienta",
"cust_name": "Meno a priezvisko",
"company": "Firma (Voliteľné)",
"phone": "Telefónne číslo",
"save_cust": "Uložiť klienta",
"crm_err_name": "Meno klienta je povinné pole.",
"crm_success_toast": "Klient bol bezpečne uložený do CRM.",
# Fakturace
"inv_title": "🧾 Fakturácia jedným klikom",
"inv_select_quote": "Zvoľte schválený pevný rozpočet k okamžitej fakturácii:",
"invoice_btn": "🧾 VYSTAVIŤ FAKTÚRU JEDNÝM KLIKNUTÍM",
"inv_success_toast": "Faktúra bola vystavená a uložená do knihy!",
"inv_no_fixed_quotes": "V systéme sa nenachádza žiadna schválená pevná ponuka (Orientačné odhady nie je možné fakturovať).",
"inv_book_title": "Kompletný prehľad vydaných faktúr a stavu platieb",
"inv_status_paid": "🟢 Uhradené",
"inv_status_sent": "🔵 Odoslané (Po splatnosti)",
"inv_mark_paid": "Označiť ako ZAPLATENÉ",
"inv_empty": "Kniha faktúr je prázdna.",
# PRO Funkce
"paywall_msg": "🔒 Dosiahli ste limitu 3 bezplatných ponúk. Pre neobmedzený prístup aktivujte tarif PRO.",
"pro_router": "📍 AI Plánovač trás a logistiky",
"pro_sms": "📲 Inteligentné SMS Centrum upomienok",
"pro_locked_warn": "🔒 Tento panel obsahuje pokročilé moduly TradeFlow PRO. Aktivujte skúšobnú verziu v Dev Admin Panelu na bočnej lište.",
"pro_route_title": "📍 AI Geografický optimalizátor trás výjazdov",
"pro_route_label": "Zoznam adries plánovaných zásahov",
"pro_route_btn": "🗺️ Spočítať matematicky najkratšiu trasu",
"pro_route_result": "🚀 AI Výsledok kalkulácie: Výjazd z Plzne -> Beroun -> Praha. Celková úspora paliva a logistických nákladov činí: 42 km.",
"pro_sms_title": "📱 Inteligentné manažérske SMS centrum pohľadávok",
"pro_sms_step1": "### 🔍 1. Vyhľadanie dlžníka v systéme",
"pro_sms_filter_name": "Filtrovať neplatičov podľa mena:",
"pro_sms_sort_label": "Zoradiť dlžníkov podľa kritéria:",
"pro_sms_sort_high": "Od najvyššej dlžnej sumy",
"pro_sms_sort_low": "Od najnižšej dlžnej sumy",
"pro_sms_no_debts": "V systéme aktuálne neevidujeme žiadne neuhradené faktúry zodpovedajúce filtrom.",
"pro_sms_select_debtor": "🎯 Vyberte cieľového dlžníka na obeslanie:",
"pro_sms_step2": "### 📋 2. Výber textovej šablóny",
"pro_sms_template_label": "Zvoľte aktívnu komunikačnú šablónu:",
"pro_sms_expander": "⚙️ Pokročilý editor šablón & Generátor nových textov cez AI",
"pro_sms_ai_assistant": "🤖 **AI Asistent pre tvorbu tónu textu**",
"pro_sms_ai_prompt": "Zadajte zadanie pre AI (napr. 'Napíš veľmi naliehavú predžalobnú výzvu')",
"pro_sms_ai_btn": "🪄 Zostaviť novú šablónu pomocou AI",
"pro_sms_ai_success": "AI text úspešne vygenerovaný do poľa nižšie!",
"pro_sms_new_title": "Názov novej šablóny:",
"pro_sms_new_body": "Text tela správy (používajte značky {jmeno}, {cislo}, {castka})",
"pro_sms_save_template_btn": "Uložiť šablónu do trvalého zoznamu",
"pro_sms_template_success": "Šablóna úspešne uložená!",
"pro_sms_step3": "### 📱 3. Finálna vygenerovaná správa pre klienta",
"pro_sms_copy_btn": "📋 Kopírovať text správy",
"pro_sms_copy_ok": "✅ Skopírované do schránky!",
"pro_sms_select_info": "Vyberte dlžníka pre zobrazenie a skopírovanie SMS správy."
},
"EN": {
# Obecné a záložky
"title": "🏆 TradeFlow AI",
"caption": "Smart operating system for the next-gen skilled trades.",
"tab_dash": "📈 Dashboard & Metrics",
"tab_gen": "📝 AI Quote Generator",
"tab_team": "👥 Team Management",
"tab_crm": "👥 Contacts (CRM)",
"tab_inv": "🧾 1-Click Invoicing",
"tab_pro": "🚀 PRO Features",
# Sidebar & Admin Panel
"sb_settings": "🌐 Settings",
"sb_lang": "App Language",
"sb_curr": "Active Currency",
"sb_dev_panel": "🛡️ Dev Admin Panel",
"sb_pwd_label": "Enter Admin Password",
"sb_pwd_hint": "Testing password is: admin123",
"sb_unlock_btn": "Unlock Admin Panel",
"sb_auth_ok": "Access Granted!",
"sb_auth_err": "Incorrect Password!",
"sb_auth_status": "🔓 Developer Authorization",
"sb_pro_toggle": "User has active PRO tier",
"sb_quotes_count": "Created quotes counter",
"sb_demo_section": "🧪 Sample Data Management",
"sb_import_demo": "📥 Import Complex Demo Data",
"sb_clear_demo": "🗑️ Complete Databases Wipe",
"sb_logout": "Lock Panel & Log Out",
"toast_demo_ok": "System filled with sample data successfully!",
"toast_clear_ok": "Application databases wiped completely.",
# Dashboard Metriky
"profit": "Actual Profit",
"pending": "Pending Revenue",
"potential": "Open AI Quotes",
"limit": "Free Tier Usage",
"dash_help_potential": "Counts strict Fixed Price quotes only.",
"dash_limit_warn": "⚠️ LIMIT EXCEEDED",
# Dashboard Analýza
"dash_analysis_title": "📊 Advanced Section Analysis & Financial Filtering",
"dash_analysis_desc": "Monitor financial flows grouped by specific cost sections across the entire company.",
"dash_filter_cat": "Select cost category to filter by:",
"dash_filter_type": "Filter project type:",
"dash_filter_all_cats": "Show all",
"dash_type_all": "All budgets",
"dash_type_fixed": "Binding fixed quotes only",
"dash_type_estimated": "Rough estimates only",
"dash_aggregated_spend": "💰 Aggregated spend in selected filter:",
"dash_no_records": "No records found matching the given criteria and filters.",
"dash_no_costs_yet": "No itemized cost entries are registered in the system yet.",
"dash_book_title": "Log of Created Calculations & Quotes",
"dash_book_empty": "The quotes logbook is currently empty.",
# Sloupce tabulek v Dashboardu
"col_project": "Project / Client",
"col_section": "Cost Section",
"col_item_name": "Item Name / Description",
"col_amount": "Amount",
"col_type": "Budget Type",
"col_date": "Date",
# AI Generátor Nabídek
"gen_type_label": "Select output scope and pricing strategy:",
"gen_type_est": "🔮 Rough AI Estimate (Purely indicative calculation, does not map into debtors ledger)",
"gen_type_fix": "🎯 Binding Fixed Quote (Strict financial plan, serves as an invoice baseline)",
"gen_client_section": "### 👤 Client Assignment",
"gen_client_mode": "Customer input method:",
"gen_client_crm": "Select from CRM address book",
"gen_client_manual": "✍️ Enter brand new client manually",
"gen_select_client": "Choose client:",
"gen_input_client_name": "Enter full name of the new client:",
"gen_client_placeholder": "E.g., John Smith",
"gen_spec_label": "Detailed project specifications and instructions for AI",
"gen_spec_default": "Complete residential house reconstruction, plumbing pipelines layout and final sanitary alignment.",
"gen_team_alloc": "### 🕒 Workforce Allocation (Team hours worked)",
"gen_no_team_warn": "ℹ️ There are no workers registered in the internal database yet. Add them in 'Team Management' or seed Demo Data via sidebar.",
"gen_ai_btn": "🪄 Execute AI Simulation and pre-fill rows & hours",
"gen_ai_toast": "AI successfully analyzed text and proposed optimal cost structure!",
"gen_direct_costs": "### 🚚 Direct Material & Secondary Expenses",
"gen_table_hint": "💡 Add new rows using the `+` icon at the bottom. Delete rows by selecting them and pressing `Delete` on your keyboard.",
"gen_csv_title": "### 📂 Bulk External Costs CSV Import",
"gen_csv_uploader": "Upload a .csv file (Required column layout: Sekce nákladu, Název položky / Popis, Částka)",
"gen_csv_success": "CSV data successfully mapped into the table above!",
"gen_csv_err_headers": "Error: CSV does not match the required headers layout.",
"gen_csv_err_read": "File cannot be parsed:",
"gen_total_calc": "### 📈 Total Budget Calculation:",
"gen_save_btn": "💾 Approve and record budget into the system",
"gen_err_no_client": "Error: You must state or select a client name before saving!",
"gen_save_success": "Budget was successfully recorded into company database.",
# Správa týmu
"team_title": "👥 Internal Team & Hourly Sazby Management",
"team_desc": "Edit profiles and corporate hourly rates of your specialists.",
"team_sub_list": "Registered Workers & Specialists",
"team_col_id": "Internal ID",
"team_col_role": "Member Name / Professional Role",
"team_col_rate": "Hourly Wage Rate",
"team_empty": "No workers inside the team database. Use the form on the right or turn on Demo Data.",
"team_remove_section": "**❌ Exclude worker from team**",
"team_remove_select": "Select person to remove:",
"team_remove_btn": "Permanently remove member",
"team_remove_toast": "Worker was removed from internal database.",
"team_add_section": "### ➕ Add / Modify Worker Card",
"team_input_name": "Full Name / Job Position",
"team_input_placeholder": "E.g., Carl (Mason)",
"team_input_rate": "Hourly rate in active currency",
"team_submit_btn": "Save Profile",
"team_update_toast": "Worker wage rate updated.",
"team_add_toast": "New member successfully registered.",
# CRM Adresář
"crm_title": "👥 Client Address Book (CRM)",
"crm_empty": "Client book is empty at the moment.",
"crm_add_section": "### ➕ Quick Manual Client Entry",
"cust_name": "Full Name",
"company": "Company (Optional)",
"phone": "Phone Number",
"save_cust": "Save Client",
"crm_err_name": "Client name is a mandatory field.",
"crm_success_toast": "Client records safely saved to CRM.",
# Fakturace
"inv_title": "🧾 1-Click Invoicing System",
"inv_select_quote": "Choose an approved fixed budget for immediate invoicing:",
"invoice_btn": "🧾 ISSUE 1-CLICK INVOICE NOW",
"inv_success_toast": "Invoice issued and stored into ledger successfully!",
"inv_no_fixed_quotes": "No approved fixed quotes available (Rough estimations cannot be converted into an invoice).",
"inv_book_title": "Complete Issued Invoices Overview & Payments Status",
"inv_status_paid": "🟢 Settled & Paid",
"inv_status_sent": "🔵 Sent (Overdue)",
"inv_mark_paid": "Mark as PAID / SETTLED",
"inv_empty": "The invoice ledger is empty.",
# PRO Funkce
"paywall_msg": "🔒 You have reached the 3 free quotes limit. Unlock PRO tier for unlimited access.",
"pro_router": "📍 AI Route & Logistics Planner",
"pro_sms": "📲 Intelligent SMS Reminder Center",
"pro_locked_warn": "🔒 This panel contains advanced TradeFlow PRO modules. Activate evaluation license in Dev Admin Panel via sidebar.",
"pro_route_title": "📍 AI Geographical Route Optimization Engine",
"pro_route_label": "Target list of planned on-site addresses",
"pro_route_btn": "🗺️ Compute Mathematical Shortest Path",
"pro_route_result": "🚀 AI Optimization outcome: Departure Plzeň -> Beroun -> Praha. Total fuel and logistics distance saved: 42 km.",
"pro_sms_title": "📱 Smart Corporate Debt Collection SMS Center",
"pro_sms_step1": "### 🔍 1. Locating Delinquent Accounts in System",
"pro_sms_filter_name": "Filter debtors by name lookup:",
"pro_sms_sort_label": "Sort debtors by criteria order:",
"pro_sms_sort_high": "Highest outstanding debt first",
"pro_sms_sort_low": "Lowest outstanding debt first",
"pro_sms_no_debts": "No outstanding overdue invoices matching current filters discovered.",
"pro_sms_select_debtor": "🎯 Choose target account to contact:",
"pro_sms_step2": "### 📋 2. Template Preset Assignment",
"pro_sms_template_label": "Choose active communications preset:",
"pro_sms_expander": "⚙️ Advanced Presets Editor & AI Content Generator Engine",
"pro_sms_ai_assistant": "🤖 **AI Tone Modulation Assistant**",
"pro_sms_ai_prompt": "State assignment for AI (e.g., 'Write highly urgent pre-legal warning letter')",
"pro_sms_ai_btn": "🪄 Compose New Template via AI",
"pro_sms_ai_success": "AI copy successfully synthesized into the area below!",
"pro_sms_new_title": "New template label name:",
"pro_sms_new_body": "Message body code (use placeholder tags {jmeno}, {cislo}, {castka})",
"pro_sms_save_template_btn": "Save Preset into permanent storage",
"pro_sms_template_success": "Template preset recorded!",
"pro_sms_step3": "### 📱 3. Final Compiled Client Text Copy",
"pro_sms_copy_btn": "📋 Copy Message Text",
"pro_sms_copy_ok": "✅ Copy to clipboard successful!",
"pro_sms_select_info": "Select an overdue account to preview and extract SMS dispatch copy."
}
}
CURRENCIES = {
"EUR": {"symbol": "EUR", "rate": 1.0},
"CZK": {"symbol": "Kč", "rate": 25.12},
"USD": {"symbol": "$", "rate": 1.09},
"GBP": {"symbol": "£", "rate": 0.84},
"CHF": {"symbol": "CHF", "rate": 0.96}
}
# -----------------------------------------------------------------------------
# INICIALIZACE STAVU
# -----------------------------------------------------------------------------
VERSION = "2.1.2-ENTERPRISE"
st.set_page_config(page_title="TradeFlow AI", page_icon="🏆", layout="wide", initial_sidebar_state="expanded")
if "lang" not in st.session_state: st.session_state.lang = "CZ"
if "curr" not in st.session_state: st.session_state.curr = "EUR"
if "admin_authenticated" not in st.session_state: st.session_state.admin_authenticated = False
if "user_subscribed" not in st.session_state: st.session_state.user_subscribed = False
if "quotes_count" not in st.session_state: st.session_state.quotes_count = 0
if "db_team" not in st.session_state: st.session_state.db_team = []
if "db_customers" not in st.session_state: st.session_state.db_customers = []
if "db_quotes" not in st.session_state: st.session_state.db_quotes = []
if "db_invoices" not in st.session_state: st.session_state.db_invoices = []
if "sms_presets" not in st.session_state:
st.session_state.sms_presets = [
{"název": "Standardní upomínka", "text": "Dobrý den, {jmeno}, evidujeme, že faktura číslo {cislo} na částku {castka} je po splatnosti. Prosíme o neprodlené uhrazení. Tým TradeFlow AI."},
{"název": "Důrazná výzva", "text": "Upozornění pro: {jmeno}. Faktura {cislo} ({castka}) je po splatnosti. Žádáme o okamžitou úhradu na náš bankovní účet, abychom předešli dalším krokům."},
{"název": "Kamarádská připomínka", "text": "Ahoj {jmeno}, jen rychlá připomínka, že faktura {cislo} na částku {castka} už čeká na proplacení. Díky moc!"}
]
t = TRANSLATIONS[st.session_state.lang if st.session_state.lang in TRANSLATIONS else "CZ"]
if "cost_items_df" not in st.session_state:
st.session_state.cost_items_df = pd.DataFrame([
{"Sekce nákladu": "Materiál", "Název položky / Popis": "", "Částka": 0.0}
])
def format_price(amount_in_eur):
c_info = CURRENCIES[st.session_state.curr]
converted = amount_in_eur * c_info["rate"]
if c_info["symbol"] in ["Kč", "CHF"]:
return f"{converted:,.2f} {c_info['symbol']}"
return f"{c_info['symbol']}{converted:,.2f}"
# -----------------------------------------------------------------------------
# SIDEBAR: PANEL S NASTAVENÍM & VÝVOJÁŘSKÝM PUSH SEEDINGEM
# -----------------------------------------------------------------------------
with st.sidebar:
st.header(t["sb_settings"])
st.session_state.lang = st.selectbox(t["sb_lang"], list(TRANSLATIONS.keys()), index=list(TRANSLATIONS.keys()).index(st.session_state.lang))
# Po změně jazyka musíme hned aktualizovat překladový slovník, aby zbytek kódu běžel v novém kontextu
t = TRANSLATIONS[st.session_state.lang]
st.session_state.curr = st.selectbox(t["sb_curr"], list(CURRENCIES.keys()), index=list(CURRENCIES.keys()).index(st.session_state.curr))
st.markdown("---")
st.header(t["sb_dev_panel"])
if not st.session_state.admin_authenticated:
pwd_input = st.text_input(t["sb_pwd_label"], type="password", help=t["sb_pwd_hint"])
if st.button(t["sb_unlock_btn"]):
if pwd_input == "admin123":
st.session_state.admin_authenticated = True
st.success(t["sb_auth_ok"])
st.rerun()
else:
st.error(t["sb_auth_err"])
else:
st.info(t["sb_auth_status"])
st.session_state.user_subscribed = st.toggle(t["sb_pro_toggle"], value=st.session_state.user_subscribed)
st.session_state.quotes_count = st.number_input(t["sb_quotes_count"], min_value=0, max_value=20, value=st.session_state.quotes_count)
st.markdown(f"### {t['sb_demo_section']}")
if st.button(t["sb_import_demo"], use_container_width=True):
st.session_state.db_team = [
{"id": 1, "jmeno": "Šimon (Director)", "sazba": 45.0},
{"id": 2, "jmeno": "Nikola (Prezentace & Podpora)", "sazba": 30.0},
{"id": 3, "jmeno": "Matěj (Technik)", "sazba": 25.0}
]
st.session_state.db_customers = [
{"id": 1, "jmeno": "Jan Novák", "firma": "Stavebniny s.r.o.", "telefon": "+420 777 123 456"},
{"id": 2, "jmeno": "Marie Podlahová", "firma": "Bytové družstvo", "telefon": "+420 602 987 654"},
{"id": 3, "jmeno": "Zdeněk Urban", "firma": "Instalatérství Urban", "telefon": "+420 731 555 999"}
]
st.session_state.db_quotes = [
{
"id": 1,
"zakaznik": "Jan Novák",
"popis": "Kompletní instalace rozvodů vody a topení v novostavbě",
"celkem": 1250.0,
"typ": "fixni",
"datum": "2026-06-05",
"labor_eur": 750.0,
"extra_costs": [
{"kategorie": "Materiál", "nazev": "Měděné potrubí a fitinky", "eur": 400.0},
{"kategorie": "Benzín / Doprava", "nazev": "Doprava techniky a závoz materiálu", "eur": 100.0}
]
}
]
st.session_state.db_invoices = [
{"id": 1, "cislo": "20260001", "zakaznik": "Jan Novák", "castka": 1250.00, "stav": "Zaplaceno", "datum": "2026-06-05"},
{"id": 2, "cislo": "20260002", "zakaznik": "Marie Podlahová", "castka": 1580.50, "stav": "Odesláno", "datum": "2026-06-10"},
{"id": 3, "cislo": "20260003", "zakaznik": "Zdeněk Urban", "castka": 290.00, "stav": "Zaplaceno", "datum": "2026-06-12"},
{"id": 4, "cislo": "20260004", "zakaznik": "Jan Novák", "castka": 5010.00, "stav": "Odesláno", "datum": "2026-06-15"}
]
st.session_state.quotes_count = 1
st.toast(t["toast_demo_ok"], icon="📥")
time.sleep(0.3)
st.rerun()
if st.button(t["sb_clear_demo"], use_container_width=True):
st.session_state.db_team = []
st.session_state.db_customers = []
st.session_state.db_quotes = []
st.session_state.db_invoices = []
st.session_state.quotes_count = 0
st.session_state.cost_items_df = pd.DataFrame([{"Sekce nákladu": "Materiál", "Název položky / Popis": "", "Částka": 0.0}])
st.toast(t["toast_clear_ok"], icon="🗑️")
time.sleep(0.3)
st.rerun()
st.markdown("---")
if st.button(t["sb_logout"], use_container_width=True):
st.session_state.admin_authenticated = False
st.rerun()
st.title(t["title"])
st.caption(f"{t['caption']} | Engine Verze: {VERSION}")
is_locked = st.session_state.quotes_count >= 3 and not st.session_state.user_subscribed
tab_dashboard, tab_generator, tab_team, tab_crm, tab_fakturace, tab_pro_features = st.tabs([
t["tab_dash"], t["tab_gen"], t["tab_team"], t["tab_crm"], t["tab_inv"], t["tab_pro"]
])
# -----------------------------------------------------------------------------
# TAB 1: DASHBOARD & METRIKY
# -----------------------------------------------------------------------------
with tab_dashboard:
st.header(t["tab_dash"])
total_revenue = sum(inv["castka"] for inv in st.session_state.db_invoices if inv["stav"] == "Zaplaceno")
pending_revenue = sum(inv["castka"] for inv in st.session_state.db_invoices if inv["stav"] == "Odesláno")
potencial = sum(q["celkem"] for q in st.session_state.db_quotes if q.get("typ") == "fixni")
c1, c2, c3, c4 = st.columns(4)
c1.metric(t["profit"], format_price(total_revenue))
c2.metric(t["pending"], format_price(pending_revenue))
c3.metric(t["potential"], format_price(potencial), help=t["dash_help_potential"])
c4.metric(t["limit"], f"{st.session_state.quotes_count} / 3", delta=t["dash_limit_warn"] if is_locked else None)
st.markdown("---")
st.subheader(t["dash_analysis_title"])
st.write(t["dash_analysis_desc"])
flat_costs = []
for q in st.session_state.db_quotes:
if q.get("labor_eur", 0) > 0:
flat_costs.append({
t["col_project"]: f"{q['zakaznik']} ({q['datum']})",
t["col_section"]: "Výplaty zaměstnancům" if st.session_state.lang != "EN" else "Employee Wages",
t["col_item_name"]: "Mzda týmu na projektu (odpracované hodiny)" if st.session_state.lang != "EN" else "Team payroll on project (hours worked)",
"Částka (Základ v EUR)": q["labor_eur"],
t["col_type"]: "🎯 Přesná fixní" if q["typ"] == "fixni" else "🔮 Orientační odhad"
})
for ec in q.get("extra_costs", []):
flat_costs.append({
t["col_project"]: f"{q['zakaznik']} ({q['datum']})",
t["col_section"]: ec["kategorie"],
t["col_item_name"]: ec["nazev"] if ec["nazev"] else "Přímý výdaj",
"Částka (Základ v EUR)": ec["eur"],
t["col_type"]: "🎯 Přesná fixní" if q["typ"] == "fixni" else "🔮 Orientační odhad"
})
if flat_costs:
df_costs = pd.DataFrame(flat_costs)
f_col1, f_col2 = st.columns(2)
with f_col1:
available_cats = [t["dash_filter_all_cats"]] + list(df_costs[t["col_section"]].unique())
selected_cat_filter = st.selectbox(t["dash_filter_cat"], available_cats)
with f_col2:
selected_type_filter = st.radio(t["dash_filter_type"], [t["dash_type_all"], t["dash_type_fixed"], t["dash_type_estimated"]], horizontal=True)
if selected_cat_filter != t["dash_filter_all_cats"]:
df_costs = df_costs[df_costs[t["col_section"]] == selected_cat_filter]
if selected_type_filter == t["dash_type_fixed"]:
df_costs = df_costs[df_costs[t["col_type"]] == "🎯 Přesná fixní"]
elif selected_type_filter == t["dash_type_estimated"]:
df_costs = df_costs[df_costs[t["col_type"]] == "🔮 Orientační odhad"]
if not df_costs.empty:
total_filtered_eur = df_costs["Částka (Základ v EUR)"].sum()
st.markdown(f"#### {t['dash_aggregated_spend']} **{format_price(total_filtered_eur)}**")
df_costs_display = df_costs.copy()
df_costs_display[t["col_amount"]] = df_costs_display["Částka (Základ v EUR)"].apply(format_price)
df_costs_display = df_costs_display.drop(columns=["Částka (Základ v EUR)"])
st.dataframe(df_costs_display, use_container_width=True, hide_index=True)
else:
st.info(t["dash_no_records"])
else:
st.info(t["dash_no_costs_yet"])
st.markdown("---")
st.subheader(t["dash_book_title"])
if st.session_state.db_quotes:
df_raw = pd.DataFrame(st.session_state.db_quotes)
df_display = df_raw.copy()
# Přejmenování klíčů a lokalizace pro tabulku
df_display[t["col_date"]] = df_display["datum"]
df_display[t["col_project"]] = df_display["zakaznik"]
df_display[t["col_item_name"]] = df_display["popis"]
df_display[t["col_amount"]] = df_display["celkem"].apply(format_price)
df_display[t["col_type"]] = df_display["typ"].map({"odhad": "🔮 " + t["dash_type_estimated"], "fixni": "🎯 " + t["dash_type_fixed"]})
cols_to_show = [t["col_date"], t["col_project"], t["col_type"], t["col_item_name"], t["col_amount"]]
st.dataframe(df_display[cols_to_show], use_container_width=True, hide_index=True)
else:
st.info(t["dash_book_empty"])
# -----------------------------------------------------------------------------
# TAB 2: AI GENERÁTOR NABÍDEK (OPRAVENÝ & S PŘELOŽENÝM SOUBOREM NÁKLADŮ)
# -----------------------------------------------------------------------------
with tab_generator:
st.header(t["tab_gen"])
if is_locked:
st.error(t["paywall_msg"])
else:
quote_type = st.radio(t["gen_type_label"], [t["gen_type_est"], t["gen_type_fix"]])
is_fixed = t["gen_type_fix"] in quote_type
st.markdown(t["gen_client_section"])
cust_names = [c["jmeno"] for c in st.session_state.db_customers]
client_mode = st.radio(t["gen_client_mode"], [t["gen_client_crm"], t["gen_client_manual"]], horizontal=True)
if client_mode == t["gen_client_crm"] and cust_names:
selected_cust = st.selectbox(t["gen_select_client"], cust_names)
else:
selected_cust = st.text_input(t["gen_input_client_name"], value="", placeholder=t["gen_client_placeholder"])
input_task = st.text_area(t["gen_spec_label"], value=t["gen_spec_default"])
st.markdown(t["gen_team_alloc"])
if not st.session_state.db_team:
st.info(t["gen_no_team_warn"])
team_hours = {}
else:
if st.button(t["gen_ai_btn"]):
st.session_state["ai_mock_active"] = True
st.session_state.cost_items_df = pd.DataFrame([
{"Sekce nákladu": "Materiál", "Název položky / Popis": "AI Odhad: Potrubí, ventily a fitinky", "Částka": 480.0},
{"Sekce nákladu": "Benzín / Doprava", "Název položky / Popis": "AI Odhad: Nákladní závoz materiálu", "Částka": 65.0},
{"Sekce nákladu": "Pronájem vybavení", "Název položky / Popis": "AI Odhad: Zapůjčení tlakové zkoušečky", "Částka": 110.0}
])
st.toast(t["gen_ai_toast"], icon="🪄")
team_hours = {}
cols_team = st.columns(len(st.session_state.db_team))
for idx, member in enumerate(st.session_state.db_team):
with cols_team[idx]:
default_val = 0
if st.session_state.get("ai_mock_active", False):
if "director" in member["jmeno"].lower(): default_val = 10
elif "prezentace" in member["jmeno"].lower(): default_val = 6
else: default_val = 24
hours_input = st.number_input(
f"{member['jmeno']} ({format_price(member['sazba'])}/h)",
min_value=0, max_value=500, value=default_val, key=f"gen_hours_{member['id']}"
)
team_hours[member["id"]] = hours_input
st.markdown(t["gen_direct_costs"])
st.caption(t["gen_table_hint"])
curr_rate = CURRENCIES[st.session_state.curr]["rate"]
# Úprava konfigurace tabulky - odstraněna měna ze záhlaví sloupce (pouze t["col_amount"])
column_configuration = {
"Sekce nákladu": st.column_config.SelectboxColumn(
t["col_section"],
options=["Materiál", "Benzín / Doprava", "Pronájem vybavení", "Ostatní režie"] if st.session_state.lang != "EN" else ["Material", "Logistics / Fuel", "Equipment Rental", "Other Overhead"],
required=True
),
"Název položky / Popis": st.column_config.TextColumn(t["col_item_name"], required=True),
"Částka": st.column_config.NumberColumn(t["col_amount"], min_value=0.0, required=True, format="%.2f")
}
edited_df = st.data_editor(
st.session_state.cost_items_df,
column_config=column_configuration,
num_rows="dynamic",
use_container_width=True,
key="main_cost_data_editor"
)
st.session_state.cost_items_df = edited_df
st.markdown(t["gen_csv_title"])
def zpracuj_csv_import():
if st.session_state.csv_uploader_key is not None:
try:
uploaded_df = pd.read_csv(st.session_state.csv_uploader_key)
needed_headers = ["Sekce nákladu", "Název položky / Popis", "Částka"]
if all(head in uploaded_df.columns for head in needed_headers):
sanitized_df = uploaded_df[needed_headers].copy()
sanitized_df["Částka"] = pd.to_numeric(sanitized_df["Částka"], errors='coerce').fillna(0.0)
st.session_state.cost_items_df = pd.concat([st.session_state.cost_items_df, sanitized_df], ignore_index=True)
st.toast(t["gen_csv_success"], icon="📥")
else:
st.error(t["gen_csv_err_headers"])
except Exception as e:
st.error(f"{t['gen_csv_err_read']} {e}")
st.file_uploader(
t["gen_csv_uploader"],
type=["csv"],
key="csv_uploader_key",
on_change=zprocessed_csv_import if 'zprocessed_csv_import' in locals() else zpracuj_csv_import
)
st.markdown("---")
total_labor_eur = 0.0
for member in st.session_state.db_team:
total_labor_eur += team_hours.get(member["id"], 0) * member["sazba"]
total_extra_local = edited_df["Částka"].sum() if not edited_df.empty else 0.0
total_extra_eur = total_extra_local / curr_rate
total_project_eur = total_labor_eur + total_extra_eur
st.markdown(f"{t['gen_total_calc']} **{format_price(total_project_eur)}**")
if st.button(t["gen_save_btn"], type="primary"):
if not selected_cust or selected_cust.strip() == "":
st.error(t["gen_err_no_client"])
else:
if selected_cust not in cust_names:
st.session_state.db_customers.append({
"id": len(st.session_state.db_customers) + 1,
"jmeno": selected_cust.strip(),
"firma": "Fyzická osoba" if st.session_state.lang != "EN" else "Individual Client",
"telefon": "Nezadáno" if st.session_state.lang != "EN" else "N/A"
})
saved_extra_costs = []
for _, row in edited_df.iterrows():
saved_extra_costs.append({
"kategorie": row["Sekce nákladu"],
"nazev": row["Název položky / Popis"],
"eur": float(row["Částka"]) / curr_rate
})
new_quote = {
"id": len(st.session_state.db_quotes) + 1,
"zakaznik": selected_cust.strip(),
"popis": input_task,
"celkem": total_project_eur,
"typ": "fixni" if is_fixed else "odhad",
"datum": time.strftime("%Y-%m-%d"),
"labor_eur": total_labor_eur,
"extra_costs": saved_extra_costs
}
st.session_state.db_quotes.append(new_quote)
st.session_state.quotes_count += 1
st.session_state["ai_mock_active"] = False
st.session_state.cost_items_df = pd.DataFrame([{"Sekce nákladu": "Materiál", "Název položky / Popis": "", "Částka": 0.0}])
st.success(t["gen_save_success"])
time.sleep(0.4)
st.rerun()
# -----------------------------------------------------------------------------
# TAB 3: SPRÁVA TÝMU
# -----------------------------------------------------------------------------
with tab_team:
st.header(t["team_title"])
st.write(t["team_desc"])
t_col1, t_col2 = st.columns([2, 1])
with t_col1:
st.markdown(f"### {t['team_sub_list']}")
team_display_list = []
for m in st.session_state.db_team:
team_display_list.append({
t["team_col_id"]: m["id"],
t["team_col_role"]: m["jmeno"],
t["team_col_rate"]: format_price(m["sazba"])
})
if team_display_list:
st.dataframe(pd.DataFrame(team_display_list), use_container_width=True, hide_index=True)
else:
st.info(t["team_empty"])
st.markdown(t["team_remove_section"])
if st.session_state.db_team:
member_to_remove = st.selectbox(t["team_remove_select"], [m["jmeno"] for m in st.session_state.db_team])
if st.button(t["team_remove_btn"]):
st.session_state.db_team = [m for m in st.session_state.db_team if m["jmeno"] != member_to_remove]
st.success(f"{t['team_remove_toast']} ({member_to_remove})")
time.sleep(0.3)
st.rerun()
with t_col2:
st.markdown(t["team_add_section"])
with st.form("team_management_form"):
new_tm_name = st.text_input(t["team_input_name"], placeholder=t["team_input_placeholder"])
new_tm_rate = st.number_input(t["team_input_rate"], min_value=1, value=400 if st.session_state.curr == "CZK" else 20)
if st.form_submit_button(t["team_submit_btn"]):
if new_tm_name:
rate_in_eur = new_tm_rate / CURRENCIES[st.session_state.curr]["rate"]
matched_member = next((m for m in st.session_state.db_team if m["jmeno"].lower() == new_tm_name.lower()), None)
if matched_member:
matched_member["sazba"] = rate_in_eur
st.success(t["team_update_toast"])
else:
next_id = max([m["id"] for m in st.session_state.db_team]) + 1 if st.session_state.db_team else 1
st.session_state.db_team.append({"id": next_id, "jmeno": new_tm_name, "sazba": rate_in_eur})
st.success(t["team_add_toast"])
time.sleep(0.3)
st.rerun()
# -----------------------------------------------------------------------------
# TAB 4: CRM ADRESÁŘ
# -----------------------------------------------------------------------------
with tab_crm:
st.header(t["crm_title"])
col_crm1, col_crm2 = st.columns([2, 1])
with col_crm1:
if st.session_state.db_customers:
df_crm = pd.DataFrame(st.session_state.db_customers)
df_crm_display = pd.DataFrame({
t["cust_name"]: df_crm["jmeno"],
t["company"]: df_crm["firma"],
t["phone"]: df_crm["telefon"]
})
st.dataframe(df_crm_display, use_container_width=True, hide_index=True)
else:
st.info(t["crm_empty"])
with col_crm2:
st.markdown(t["crm_add_section"])
with st.form("manual_crm_form"):
c_n = st.text_input(t["cust_name"], placeholder="Jan Novák")
c_f = st.text_input(t["company"], placeholder="Stavebniny s.r.o.")
c_p = st.text_input(t["phone"], placeholder="+420 777 888 999")
if st.form_submit_button(t["save_cust"]):
if c_n:
st.session_state.db_customers.append({
"id": len(st.session_state.db_customers) + 1,
"jmeno": c_n,
"firma": c_f if c_f else ("Fyzická osoba" if st.session_state.lang != "EN" else "Individual"),
"telefon": c_p
})
st.success(t["crm_success_toast"])
time.sleep(0.3)
st.rerun()
else:
st.error(t["crm_err_name"])
# -----------------------------------------------------------------------------
# TAB 5: FAKTURACE
# -----------------------------------------------------------------------------
with tab_fakturace:
st.header(t["inv_title"])
only_fixni_quotes = [q for q in st.session_state.db_quotes if q.get("typ") == "fixni"]
if only_fixni_quotes:
invoice_options = {f"ID: {q['id']} | {q['zakaznik']}{format_price(q['celkem'])}": q for q in only_fixni_quotes}
selected_quote_to_inv = st.selectbox(t["inv_select_quote"], list(invoice_options.keys()))
targeted_quote = invoice_options[selected_quote_to_inv]
if st.button(t["invoice_btn"], type="primary", use_container_width=True):
st.session_state.db_invoices.append({
"id": len(st.session_state.db_invoices) + 1,
"cislo": f"2026{len(st.session_state.db_invoices)+1:04d}",
"zakaznik": targeted_quote["zakaznik"],
"castka": targeted_quote["celkem"],
"stav": "Odesláno",
"datum": time.strftime("%Y-%m-%d")
})
st.success(t["inv_success_toast"])
time.sleep(0.3)
st.rerun()
else:
st.info(t["inv_no_fixed_quotes"])
st.markdown("---")
st.subheader(t["inv_book_title"])
if st.session_state.db_invoices:
for index_inv, invoice_obj in enumerate(st.session_state.db_invoices):
div_c1, div_c2, div_c3 = st.columns([2, 1, 1])
div_c1.write(f"🧾 **{t['col_amount']} č. {invoice_obj['cislo']}** — {invoice_obj['zakaznik']} [**{format_price(invoice_obj['castka'])}**]")
if invoice_obj["stav"] == "Zaplaceno":
div_c2.markdown(f"<span style='color:green; font-weight:bold;'>{t['inv_status_paid']}</span>", unsafe_allow_html=True)
div_c3.write("")
else:
div_c2.markdown(f"<span style='color:orange; font-weight:bold;'>{t['inv_status_sent']}</span>", unsafe_allow_html=True)
if div_c3.button(t["inv_mark_paid"], key=f"payment_trigger_key_{index_inv}", use_container_width=True):
invoice_obj["stav"] = "Zaplaceno"
st.rerun()
else:
st.info(t["inv_empty"])
# -----------------------------------------------------------------------------
# TAB 6: PRO BONUSY
# -----------------------------------------------------------------------------
with tab_pro_features:
st.header(t["tab_pro"])
if not st.session_state.user_subscribed:
st.warning(t["pro_locked_warn"])
else:
pro_sub1, pro_sub2 = st.tabs([t["pro_router"], t["pro_sms"]])
with pro_sub1:
st.subheader(t["pro_route_title"])
st.text_area(t["pro_route_label"], value="Plzeňská 15, Praha\nPrůmyslová 4, Beroun\nSukova 12, Plzeň")
if st.button(t["pro_route_btn"], use_container_width=True):
st.info(t["pro_route_result"])
with pro_sub2:
st.subheader(t["pro_sms_title"])
st.markdown(t["pro_sms_step1"])
open_debts = [invoice for invoice in st.session_state.db_invoices if invoice["stav"] == "Odesláno"]
search_col1, search_col2 = st.columns([1, 1])
with search_col1:
query_name = st.text_input(t["pro_sms_filter_name"], value="")
with search_col2:
sorting_order = st.selectbox(t["pro_sms_sort_label"], [t["pro_sms_sort_high"], t["pro_sms_sort_low"]])
if query_name:
open_debts = [d for d in open_debts if query_name.lower() in d["zakaznik"].lower()]
if sorting_order == t["pro_sms_sort_high"]:
open_debts.sort(key=lambda x: x["castka"], reverse=True)
else:
open_debts.sort(key=lambda x: x["castka"])
if not open_debts:
st.info(t["pro_sms_no_debts"])
active_debtor = None
else:
debtor_mapping = {f"👤 {d['zakaznik']} (Faktura č. {d['cislo']} | Dluh: {format_price(d['castka'])})": d for d in open_debts}
selected_debtor_string = st.selectbox(t["pro_sms_select_debtor"], list(debtor_mapping.keys()))
active_debtor = debtor_mapping[selected_debtor_string]
st.markdown("---")
st.markdown(t["pro_sms_step2"])
preset_labels = [p["název"] for p in st.session_state.sms_presets]
chosen_preset_label = st.selectbox(t["pro_sms_template_label"], preset_labels)
active_preset = next(p for p in st.session_state.sms_presets if p["název"] == chosen_preset_label)
with st.expander(t["pro_sms_expander"]):
st.markdown(t["pro_sms_ai_assistant"])
prompt_text = st.text_input(t["pro_sms_ai_prompt"], placeholder="...")
if st.button(t["pro_sms_ai_btn"]):
if prompt_text:
p_low = prompt_text.lower()
if "předžal" in p_low or "naléh" in p_low or "soud" in p_low:
result_sms = "PŘEDŽALOBNÍ VÝZVA: Vážený kliente {jmeno}. Upozorňujeme Vás, že faktura č. {cislo} na částku {castka} je dlouhodobě po splatnosti. Žádáme o okamžitou úhradu."
else:
result_sms = "Dobrý den {jmeno}, připomínáme zapomenutou fakturu č. {cislo} na částku {castka}. Prosíme o co nejrychlejší proplacení."
st.session_state["ai_sms_output_buffer"] = result_sms
st.success(t["pro_sms_ai_success"])
new_preset_title = st.text_input(t["pro_sms_new_title"], value="")
default_body = st.session_state.get("ai_sms_output_buffer", "Vážený pane/paní {jmeno}, ...")
new_preset_body = st.text_area(t["pro_sms_new_body"], value=default_body)
if st.button(t["pro_sms_save_template_btn"]):
if new_preset_title and new_preset_body:
st.session_state.sms_presets.append({"název": new_preset_title, "text": new_preset_body})
st.success(t["pro_sms_template_success"])
time.sleep(0.3)
st.rerun()
st.markdown("---")
st.markdown(t["pro_sms_step3"])
if active_debtor:
raw_template = active_preset["text"]
final_compiled_sms = raw_template.replace("{jmeno}", active_debtor["zakaznik"])\
.replace("{cislo}", active_debtor["cislo"])\
.replace("{castka}", format_price(active_debtor["castka"]))
st.markdown(f"""
<div style="
background-color: #f8f9fa;
border: 1px solid #dee2e6;
padding: 18px;
border-radius: 8px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 15px;
color: #212529;
white-space: pre-wrap;
word-wrap: break-word;
line-height: 1.6;
margin-bottom: 12px;
;">{final_compiled_sms}</div>
""", unsafe_allow_html=True)
safe_encoded_sms = urllib.parse.quote(final_compiled_sms)
copy_button_html = f"""
<button onclick="
navigator.clipboard.writeText(decodeURIComponent('{safe_encoded_sms}'));
let btn = document.getElementById('js_copy_btn');
btn.innerText = '{t['pro_sms_copy_ok']}';
btn.style.backgroundColor = '#28a745';
setTimeout(() => {{ btn.innerText = '{t['pro_sms_copy_btn']}'; btn.style.backgroundColor = '#007bff'; }}, 2000);
" id="js_copy_btn" style="
background-color: #007bff;
color: white;
border: none;
padding: 12px 20px;
font-size: 14px;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
width: 100%;
">{t['pro_sms_copy_btn']}</button>
"""
components.html(copy_button_html, height=50)
else:
st.info(t["pro_sms_select_info"])