04/22 Upgraded code commit

This commit is contained in:
2026-04-22 12:22:26 -04:00
parent 7548dfc9bf
commit c0e965f10b
9 changed files with 714 additions and 19 deletions
+145 -1
View File
@@ -51,7 +51,10 @@ class AdminShiftsView(ttk.Frame):
command=self._open_edit).pack(side="right", padx=(4, 0))
ttk.Button(toolbar, text="✕ Delete",
style="Danger.TButton",
command=self._delete_selected).pack(side="right")
command=self._delete_selected).pack(side="right", padx=(4, 0))
ttk.Button(toolbar, text="⬇ Export PDF",
style="Ghost.TButton",
command=self._export_pdf).pack(side="right", padx=(0, 12))
cols = ("ID", "Shift Name", "Days", "Start", "End",
"Users", "Websites", "Active", "Note")
@@ -103,6 +106,147 @@ class AdminShiftsView(ttk.Frame):
sel = self.tree.selection()
return int(sel[0]) if sel else None
# ─── Export ───────────────────────────────────────────────────────────────
def _export_pdf(self):
"""Export the full shift schedule to a PDF file."""
from tkinter import filedialog
from models import get_all_shifts, get_shift_assigned_users, get_shift_assigned_websites
import datetime
save_path = filedialog.asksaveasfilename(
title="Save Shift Schedule PDF",
defaultextension=".pdf",
filetypes=[("PDF files", "*.pdf"), ("All files", "*.*")],
initialfile=f"shift_schedule_{datetime.date.today().isoformat()}.pdf",
)
if not save_path:
return
try:
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.lib import colors
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable
)
shifts = get_all_shifts()
doc = SimpleDocTemplate(save_path, pagesize=A4,
leftMargin=2*cm, rightMargin=2*cm,
topMargin=2*cm, bottomMargin=2*cm)
styles = getSampleStyleSheet()
ACCENT = colors.HexColor("#5b4de8")
LIGHT = colors.HexColor("#e0dff8")
DARK = colors.HexColor("#1a1a2e")
DIMGREY = colors.HexColor("#6b6b80")
title_style = ParagraphStyle("Title", parent=styles["Title"],
textColor=ACCENT, fontSize=20, spaceAfter=4)
sub_style = ParagraphStyle("Sub", parent=styles["Normal"],
textColor=DIMGREY, fontSize=10, spaceAfter=16)
h2_style = ParagraphStyle("H2", parent=styles["Heading2"],
textColor=ACCENT, fontSize=13, spaceBefore=14)
body_style = ParagraphStyle("Body", parent=styles["Normal"],
textColor=DARK, fontSize=10, leading=14)
dim_style = ParagraphStyle("Dim", parent=styles["Normal"],
textColor=DIMGREY, fontSize=9, leading=12)
story = [
Paragraph("Shift Schedule", title_style),
Paragraph(f"Generated {datetime.datetime.now().strftime('%d %B %Y, %H:%M')}",
sub_style),
HRFlowable(width="100%", thickness=1, color=ACCENT),
Spacer(1, 0.4*cm),
]
for s in shifts:
if not s["is_active"]:
continue
days_str = _days_label(s["days_of_week"])
start = str(s.get("start_time", ""))[:5]
end = str(s.get("end_time", ""))[:5]
story.append(Paragraph(s["name"], h2_style))
story.append(Paragraph(
f"<b>Days:</b> {days_str} &nbsp;&nbsp; "
f"<b>Time:</b> {start} {end} &nbsp;&nbsp; "
f"<b>Users:</b> {s['user_count']} &nbsp;&nbsp; "
f"<b>Websites:</b> {s['website_count']}",
body_style))
if s.get("note"):
story.append(Paragraph(f"<i>{s['note']}</i>", dim_style))
story.append(Spacer(1, 0.25*cm))
# Users table
users = get_shift_assigned_users(s["id"])
if users:
user_data = [["Assigned Users", ""]]
for u in users:
user_data.append([u["username"], u["full_name"] or ""])
t = Table(user_data, colWidths=[5*cm, 8*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), LIGHT),
("TEXTCOLOR", (0,0), (-1,0), ACCENT),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 9),
("ROWBACKGROUNDS", (0,1), (-1,-1), [colors.white, colors.HexColor("#f5f5fa")]),
("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#c5c5d8")),
("LEFTPADDING", (0,0), (-1,-1), 8),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
]))
story.append(t)
story.append(Spacer(1, 0.2*cm))
# Websites table
sites = get_shift_assigned_websites(s["id"])
if sites:
site_data = [["#", "Website", "URL"]]
for i, w in enumerate(sites, 1):
site_data.append([str(i), w["name"], w["url"]])
t = Table(site_data, colWidths=[1*cm, 5*cm, 10*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), LIGHT),
("TEXTCOLOR", (0,0), (-1,0), ACCENT),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 9),
("ROWBACKGROUNDS", (0,1), (-1,-1), [colors.white, colors.HexColor("#f5f5fa")]),
("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#c5c5d8")),
("LEFTPADDING", (0,0), (-1,-1), 6),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
]))
story.append(t)
story.append(Spacer(1, 0.5*cm))
story.append(HRFlowable(width="100%", thickness=0.5,
color=colors.HexColor("#c5c5d8")))
story.append(Spacer(1, 0.3*cm))
doc.build(story)
from models import log_action
log_action(self.current_user["id"], "EXPORT_SHIFT_PDF", "shifts",
None, f"Shift schedule exported to PDF: {save_path}")
logger.info(f"Shift schedule PDF exported: {save_path}")
from utils.ui_helpers import show_info
show_info(f"PDF exported successfully.\n\n{save_path}")
except ImportError:
from utils.ui_helpers import show_error
show_error("reportlab is required for PDF export.\n"
"Install it with: pip install reportlab")
except Exception as e:
from utils.ui_helpers import show_error
show_error(f"PDF export failed:\n{e}")
# ─── Actions ──────────────────────────────────────────────────────────────
def _open_add(self):