commit c22ee0af3a5a1529e7bec5e012e172aa0434522d Author: Charlie Root Date: Mon Nov 10 17:05:43 2025 +0100 init diff --git a/cgi-bin/gio-querymodules.core b/cgi-bin/gio-querymodules.core new file mode 100644 index 0000000..a79e943 Binary files /dev/null and b/cgi-bin/gio-querymodules.core differ diff --git a/cgi-bin/glib-compile-schemas.core b/cgi-bin/glib-compile-schemas.core new file mode 100644 index 0000000..6d0bf42 Binary files /dev/null and b/cgi-bin/glib-compile-schemas.core differ diff --git a/cgi-bin/python3.10.core b/cgi-bin/python3.10.core new file mode 100644 index 0000000..421fe7a Binary files /dev/null and b/cgi-bin/python3.10.core differ diff --git a/cgi-bin/python3.9.core b/cgi-bin/python3.9.core new file mode 100644 index 0000000..3997175 Binary files /dev/null and b/cgi-bin/python3.9.core differ diff --git a/cgi-bin/scenar.py b/cgi-bin/scenar.py new file mode 100755 index 0000000..3ebb96b --- /dev/null +++ b/cgi-bin/scenar.py @@ -0,0 +1,614 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import cgi +import cgitb +import pandas as pd +from openpyxl import Workbook +from openpyxl.styles import Alignment, Border, Font, PatternFill, Side +from openpyxl.utils import get_column_letter +from datetime import datetime, time +from io import BytesIO +import base64 +import os + +cgitb.enable() + +print("Content-Type: text/html; charset=utf-8") +print() + +form = cgi.FieldStorage() + +title = form.getvalue('title') +detail = form.getvalue('detail') +show_debug = form.getvalue('debug') == 'on' +step = form.getvalue('step', '1') + +file_item = None +if 'file' in form: + file_item = form['file'] + +def get_program_types(form): + program_descriptions = {} + program_colors = {} + for key in form.keys(): + if key.startswith('type_code_'): + index = key.split('_')[-1] + type_code = form.getvalue(f'type_code_{index}') + description = form.getvalue(f'desc_{index}', '') + color = 'FF' + form.getvalue(f'color_{index}', 'FFFFFF').lstrip('#') + program_descriptions[type_code] = description + program_colors[type_code] = color + return program_descriptions, program_colors + +program_descriptions, program_colors = get_program_types(form) + +def normalize_time(time_str): + for fmt in ('%H:%M', '%H:%M:%S'): + try: + return datetime.strptime(time_str, fmt).time() + except ValueError: + continue + return None + +def read_excel(file_content): + excel_data = pd.read_excel(BytesIO(file_content), skiprows=0) # Do not skip the header row + excel_data.columns = ["Datum", "Zacatek", "Konec", "Program", "Typ", "Garant", "Poznamka"] + + if show_debug: + print("
")
+        print("Raw data:")
+        print(excel_data.head())
+        print("
") + + error_rows = [] + valid_data = [] + for index, row in excel_data.iterrows(): + try: + datum = pd.to_datetime(row["Datum"], errors='coerce').date() + zacatek = normalize_time(str(row["Zacatek"])) + konec = normalize_time(str(row["Konec"])) + + if pd.isna(datum) or zacatek is None or konec is None: + raise ValueError("Invalid date or time format") + + valid_data.append({ + "index": index, + "Datum": datum, + "Zacatek": zacatek, + "Konec": row["Konec"], # Changed from `konec` to `row["Konec"]` to avoid ValueError in future calculations + "Program": row["Program"], + "Typ": row["Typ"], + "Garant": row["Garant"], + "Poznamka": row["Poznamka"], + "row_data": row + }) + except Exception as e: + error_rows.append({ + "index": index, + "row": row, + "error": str(e) + }) + + valid_data = pd.DataFrame(valid_data) + + if show_debug: + print("
")
+        print("Cleaned data:")
+        print(valid_data.head())
+        print("
") + print("
")
+        print("Error rows:")
+        for error_row in error_rows:
+            print(f"Index: {error_row['index']}, Error: {error_row['error']}")
+            print(error_row['row'])
+        print("
") + + overlap_errors = [] + for date, group in valid_data.groupby('Datum'): + sorted_group = group.sort_values(by='Zacatek') + previous_end_time = None + + for idx, row in sorted_group.iterrows(): + if previous_end_time and row['Zacatek'] < previous_end_time: + overlap_errors.append({ + "index": row["index"], + "Datum": row["Datum"], + "Zacatek": row["Zacatek"], + "Konec": row["Konec"], + "Program": row["Program"], + "Typ": row["Typ"], + "Garant": row["Garant"], + "Poznamka": row["Poznamka"], + "Error": f"Overlapping time block with previous block ending at {previous_end_time}", + "row_data": row["row_data"] + }) + previous_end_time = row['Konec'] + + if overlap_errors: + if show_debug: + print("
")
+            print("Overlap errors:")
+            for error in overlap_errors:
+                print(error)
+            print("
") + valid_data = valid_data[~valid_data.index.isin([error['index'] for error in overlap_errors])] + error_rows.extend(overlap_errors) + + return valid_data.drop(columns='index'), error_rows + +def calculate_row_height(cell_value, column_width): + if not cell_value: + return 15 + + max_line_length = column_width * 1.2 + lines = cell_value.split('\n') + line_count = 0 + for line in lines: + line_count += len(line) // max_line_length + 1 + + return line_count * 15 + +def create_timetable(data, title, detail, program_descriptions, program_colors): + if data.empty: + print("

Chyba: Načtená data jsou prázdná nebo obsahují neplatné hodnoty. Zkontrolujte vstupní soubor.

") + return None + + missing_types = [typ for typ in data["Typ"].unique() if typ not in program_colors] + if missing_types: + print(f"

Chyba: Následující typy programu nejsou specifikovány: {', '.join(missing_types)}. Zkontrolujte vstupní soubor a formulář.

") + return None + + wb = Workbook() + ws = wb.active + + thick_border = Border(left=Side(style='thick', color='000000'), + right=Side(style='thick', color='000000'), + top=Side(style='thick', color='000000'), + bottom=Side(style='thick', color='000000')) + + ws['A1'] = title + ws['A1'].alignment = Alignment(horizontal="center", vertical="center") + ws['A1'].font = Font(size=24, bold=True) + ws['A1'].border = thick_border + + ws['A2'] = detail + ws['A2'].alignment = Alignment(horizontal="center", vertical="center") + ws['A2'].font = Font(size=16, italic=True) + ws['A2'].border = thick_border + + title_row_height = calculate_row_height(title, ws.column_dimensions[get_column_letter(1)].width) + detail_row_height = calculate_row_height(detail, ws.column_dimensions[get_column_letter(1)].width) + ws.row_dimensions[1].height = title_row_height + ws.row_dimensions[2].height = detail_row_height + + data = data.sort_values(by=["Datum", "Zacatek"]) + + start_times = data["Zacatek"] + end_times = data["Konec"] + + if start_times.isnull().any() or end_times.isnull().any(): + print("

Chyba: Načtená data obsahují neplatné hodnoty času. Zkontrolujte vstupní soubor.

") + if show_debug: + print("
")
+            print("Start times:")
+            print(start_times)
+            print("End times:")
+            print(end_times)
+            print("
") + return None + + try: + min_time = min(start_times) + max_time = max(end_times) + except ValueError as e: + print("

Chyba při zjišťování minimálního a maximálního času: {}

".format(e)) + if show_debug: + print("
")
+            print("Start times:")
+            print(start_times)
+            print("End times:")
+            print(end_times)
+            print("
") + return None + + time_slots = pd.date_range(datetime.combine(datetime.today(), min_time), datetime.combine(datetime.today(), max_time), freq='15min').time + + total_columns = len(time_slots) + 1 + ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=total_columns) + ws.merge_cells(start_row=2, start_column=1, end_row=2, end_column=total_columns) + + row_offset = 3 + col_offset = 1 + cell = ws.cell(row=row_offset, column=col_offset, value="Datum") + cell.fill = PatternFill(start_color="D3D3D3", end_color="D3D3D3", fill_type="solid") + cell.alignment = Alignment(horizontal="center", vertical="center") + cell.font = Font(bold=True) + cell.border = thick_border + + for i, time_slot in enumerate(time_slots, start=col_offset + 1): + cell = ws.cell(row=row_offset, column=i, value=time_slot.strftime("%H:%M")) + cell.fill = PatternFill(start_color="D3D3D3", end_color="D3D3D3", fill_type="solid") + cell.alignment = Alignment(horizontal="center", vertical="center") + cell.font = Font(bold=True) + cell.border = thick_border + + current_row = row_offset + 1 + grouped_data = data.groupby(data['Datum']) + + for date, group in grouped_data: + day_name = date.strftime("%A") + date_str = date.strftime(f"%d.%m {day_name}") + + cell = ws.cell(row=current_row, column=col_offset, value=date_str) + cell.alignment = Alignment(horizontal="center", vertical="center") + cell.fill = PatternFill(start_color="D3D3D3", end_color="D3D3D3", fill_type="solid") + cell.font = Font(bold=True, size=14) + cell.border = thick_border + + for _, row in group.iterrows(): + start_time = row["Zacatek"] + end_time = row["Konec"] + try: + start_index = list(time_slots).index(start_time) + col_offset + 1 + end_index = list(time_slots).index(end_time) + col_offset + 1 + except ValueError as e: + print("

Chyba při hledání indexu časového slotu: {}

".format(e)) + if show_debug: + print("
")
+                    print("Start time: {}".format(start_time))
+                    print("End time: {}".format(end_time))
+                    print("
") + continue + + cell_value = f"{row['Program']}" + if pd.notna(row['Garant']): + cell_value += f"\n{row['Garant']}" + if pd.notna(row['Poznamka']): + cell_value += f"\n\n{row['Poznamka']}" + + try: + ws.merge_cells(start_row=current_row, start_column=start_index, end_row=current_row, end_column=end_index - 1) + cell = ws.cell(row=current_row, column=start_index) + cell.value = cell_value + except AttributeError as e: + print(f"

Chyba: {str(e)}. Zkontrolujte vstupní data, která způsobují překrývající se časy bloků.

") + if show_debug: + print("
")
+                    print(f"Overlapping block:\n{row}")
+                    print("
") + return None + + cell.alignment = Alignment(wrap_text=True, horizontal="center", vertical="center") + lines = cell_value.split("\n") + for idx, line in enumerate(lines): + if idx == 0: + cell.font = Font(bold=True) + elif idx == 1: + cell.font = Font(bold=False) + elif idx > 1 and pd.notna(row['Poznamka']): + cell.font = Font(italic=True) + + cell.fill = PatternFill(start_color=program_colors[row["Typ"]], end_color=program_colors[row["Typ"]], fill_type="solid") + cell.border = thick_border + + current_row += 1 + + legend_row = current_row + 2 + legend_max_length = 0 + ws.cell(row=legend_row, column=1, value="Legenda:").font = Font(bold=True) + legend_row += 1 + for typ, desc in program_descriptions.items(): + legend_text = f"{desc} ({typ})" + legend_cell = ws.cell(row=legend_row, column=1, value=legend_text) + legend_cell.fill = PatternFill(start_color=program_colors[typ], fill_type="solid") + legend_max_length = max(legend_max_length, calculate_column_width(legend_text)) + legend_row += 1 + + ws.column_dimensions[get_column_letter(col_offset)].width = legend_max_length + + for col in range(2, total_columns + 1): + ws.column_dimensions[get_column_letter(col)].width = 15 + + for row in ws.iter_rows(min_row=1, max_row=current_row - 1, min_col=1, max_col=total_columns): + for cell in row: + cell.alignment = Alignment(wrap_text=True, horizontal="center", vertical="center") + cell.border = thick_border + + for row in ws.iter_rows(min_row=1, max_row=current_row - 1): + max_height = 0 + for cell in row: + if cell.value: + height = calculate_row_height(cell.value, ws.column_dimensions[get_column_letter(cell.column)].width) + if height > max_height: + max_height = height + ws.row_dimensions[row[0].row].height = max_height + + return wb + +def calculate_column_width(text): + max_length = max(len(line) for line in text.split('\n')) + return max_length * 1.2 + +if step == '1': + print(''' + + + + + Vytvoření Scénáře - Krok 1 + + + +
+

Vytvoření Scénáře - Krok 1

+

Vyplňte níže uvedený formulář k vytvoření nového scénáře. Prosím, vyplňte všechny povinné položky a přiložte požadovaný Excel soubor. Po vyplnění formuláře klikněte na tlačítko "Načíst typy programu".

+

STAHNOUT SABLONU SCENARE ZDE

+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ + + + + ''') + +elif step == '2' and file_item is not None and file_item.filename: + file_content = file_item.file.read() + file_content_base64 = base64.b64encode(file_content).decode('utf-8') + data, error_rows = read_excel(file_content) + if data.empty: + print("

Chyba: Načtená data jsou prázdná nebo obsahují neplatné hodnoty. Zkontrolujte vstupní soubor.

") + else: + program_types = data["Typ"].dropna().unique() + program_types = [typ.strip() for typ in program_types] # Remove any whitespace characters + + print(''' + + + + + Vytvoření Scénáře - Krok 2 + + + +
+

Vytvoření Scénáře - Krok 2

+

Prosím, vyplňte tituly a barvy pro nalezené typy programu. Po vyplnění formuláře klikněte na tlačítko "Vygenerovat scénář".

+
+ + + + + '''.format(title, detail, file_content_base64)) + + for i, typ in enumerate(program_types, start=1): + print(''' +
+ + + + +
+ '''.format(i, i, i, i, typ, i, i, i, i)) + + print(''' +
+ + +
+
+ +
+
+
+ + + + '''.format(' checked' if show_debug else '')) + +elif step == '3' and title and detail: + file_content_base64 = form.getvalue('file_content_base64') + if file_content_base64: + file_content = base64.b64decode(file_content_base64) + + data, error_rows = read_excel(file_content) + if data.empty: + print("

Chyba: Načtená data jsou prázdná nebo obsahují neplatné hodnoty. Zkontrolujte vstupní soubor.

") + else: + wb = create_timetable(data, title, detail, program_descriptions, program_colors) + if wb: + filename = f"{title}.xlsx" + file_path = os.path.join("/var/www/htdocs/scripts/tmp", filename) + wb.save(file_path) + + print(f''' +
+

Výsledky zpracování

+

Stáhnout scénář pro {title} ZDE

+

Název akce: {title}

+

Detail akce: {detail}

+

Data ze souboru:

+ {data.to_html(index=False)} +

Stáhnout scénář pro {title} ZDE

+
+ ''') + + if error_rows: + print(f''' +
+

Ignorované řádky:

+ + + + + + + ''') + for error_row in error_rows: + print(f''' + + + + + + ''') + print(''' +
IndexŘádekDůvod
{error_row["index"]}{error_row["row"].to_dict()}{error_row["error"]}
+
+ ''') + else: + print("

Chyba: Soubor nebyl nalezen. Zkontrolujte vstupní data.

") +else: + print("

Chyba: Není vybrán žádný soubor nebo došlo k chybě ve formuláři.

") + diff --git a/cgi-bin/update-desktop-database.core b/cgi-bin/update-desktop-database.core new file mode 100644 index 0000000..90a7925 Binary files /dev/null and b/cgi-bin/update-desktop-database.core differ diff --git a/templates/scenar_template.xlsx b/templates/scenar_template.xlsx new file mode 100755 index 0000000..68d30bc Binary files /dev/null and b/templates/scenar_template.xlsx differ diff --git a/tmp/F.A.K.A.P.xlsx b/tmp/F.A.K.A.P.xlsx new file mode 100644 index 0000000..7bfaabb Binary files /dev/null and b/tmp/F.A.K.A.P.xlsx differ diff --git a/tmp/FAKAP.xlsx b/tmp/FAKAP.xlsx new file mode 100644 index 0000000..3305759 Binary files /dev/null and b/tmp/FAKAP.xlsx differ diff --git a/tmp/FAKAP2.xlsx b/tmp/FAKAP2.xlsx new file mode 100644 index 0000000..8759658 Binary files /dev/null and b/tmp/FAKAP2.xlsx differ diff --git a/tmp/FAKAP3.xlsx b/tmp/FAKAP3.xlsx new file mode 100644 index 0000000..6a87886 Binary files /dev/null and b/tmp/FAKAP3.xlsx differ diff --git a/tmp/TRABANT.xlsx b/tmp/TRABANT.xlsx new file mode 100644 index 0000000..b7b39c2 Binary files /dev/null and b/tmp/TRABANT.xlsx differ diff --git a/tmp/Zazitkovy kurz TRABANT.xlsx b/tmp/Zazitkovy kurz TRABANT.xlsx new file mode 100644 index 0000000..a9505e7 Binary files /dev/null and b/tmp/Zazitkovy kurz TRABANT.xlsx differ diff --git a/tmp/Zazitkovy kurz trabant.xlsx b/tmp/Zazitkovy kurz trabant.xlsx new file mode 100644 index 0000000..0f2e009 Binary files /dev/null and b/tmp/Zazitkovy kurz trabant.xlsx differ diff --git a/tmp/Zážitkový kurz TRABANT.xlsx b/tmp/Zážitkový kurz TRABANT.xlsx new file mode 100644 index 0000000..017c3ba Binary files /dev/null and b/tmp/Zážitkový kurz TRABANT.xlsx differ diff --git a/tmp/a.xlsx b/tmp/a.xlsx new file mode 100644 index 0000000..04dba36 Binary files /dev/null and b/tmp/a.xlsx differ diff --git a/tmp/test.xlsx b/tmp/test.xlsx new file mode 100644 index 0000000..71abe22 Binary files /dev/null and b/tmp/test.xlsx differ diff --git a/tmp/x.xlsx b/tmp/x.xlsx new file mode 100644 index 0000000..48586c0 Binary files /dev/null and b/tmp/x.xlsx differ