fix: crash bugs in TeamInvoices + faster load
Bug fixes: - TeamInvoices: add useAuth import/currentUser (invoice-create crash) - TeamInvoices: setChartYear -> setExportYear (year-dropdown crash) - TeamDashboard: drop redundant setState-in-effect (cascading renders) - Companies: delete dead _UnusedClientCompanies (illegal hook calls) - annotate intentional empty PDF-fallback catches Load speed: - preconnect/dns-prefetch to Supabase origin - lazy-load heic-to in Converters: page chunk 2737KB -> 9KB - split recharts into its own 'charts' vendor chunk Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,362 @@
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.enums import TA_LEFT
|
||||
from reportlab.lib.pagesizes import landscape, legal
|
||||
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.platypus import (
|
||||
BaseDocTemplate,
|
||||
Frame,
|
||||
LongTable,
|
||||
PageBreak,
|
||||
PageTemplate,
|
||||
Paragraph,
|
||||
Spacer,
|
||||
Table,
|
||||
TableStyle,
|
||||
)
|
||||
|
||||
|
||||
DOCS_DIR = Path(__file__).resolve().parent
|
||||
INPUT_PATH = DOCS_DIR / "checked-sites-revision-audit-2026-06-04.json"
|
||||
OUTPUT_PATH = DOCS_DIR / "checked-sites-revision-audit-2026-06-04.pdf"
|
||||
|
||||
BG = colors.HexColor("#F5F1E8")
|
||||
TEXT = colors.HexColor("#231D14")
|
||||
MUTED = colors.HexColor("#6B6255")
|
||||
ACCENT = colors.HexColor("#B8831F")
|
||||
ACCENT_SOFT = colors.HexColor("#F1E3C2")
|
||||
OK = colors.HexColor("#1F8A4C")
|
||||
OK_SOFT = colors.HexColor("#E5F4EB")
|
||||
WARN = colors.HexColor("#B45309")
|
||||
WARN_SOFT = colors.HexColor("#F7E7D7")
|
||||
REVIEW = colors.HexColor("#2563EB")
|
||||
REVIEW_SOFT = colors.HexColor("#E6EEFF")
|
||||
TODO = colors.HexColor("#7C3AED")
|
||||
TODO_SOFT = colors.HexColor("#F0E8FF")
|
||||
LINE = colors.HexColor("#D7CFC1")
|
||||
|
||||
|
||||
def page_template(canvas, doc):
|
||||
canvas.saveState()
|
||||
canvas.setFillColor(BG)
|
||||
canvas.rect(0, 0, doc.pagesize[0], doc.pagesize[1], fill=1, stroke=0)
|
||||
canvas.setFillColor(MUTED)
|
||||
canvas.setFont("Helvetica", 9)
|
||||
canvas.drawRightString(doc.pagesize[0] - 36, 20, f"Page {doc.page}")
|
||||
canvas.restoreState()
|
||||
|
||||
|
||||
def pill(text, fg, bg):
|
||||
return Paragraph(
|
||||
(
|
||||
f'<para alignment="center" backColor="{bg}" borderColor="{fg}" '
|
||||
f'borderWidth="0.7" borderPadding="4" textColor="{fg}"><b>{escape(text)}</b></para>'
|
||||
),
|
||||
styles["pill"],
|
||||
)
|
||||
|
||||
|
||||
def escape(value):
|
||||
return (
|
||||
str(value or "")
|
||||
.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
)
|
||||
|
||||
|
||||
def status_pill(value):
|
||||
value = value or "—"
|
||||
if value == "client_review":
|
||||
return pill(value, REVIEW, REVIEW_SOFT)
|
||||
if value == "client_approved":
|
||||
return pill(value, OK, OK_SOFT)
|
||||
if value == "not_started":
|
||||
return pill(value, TODO, TODO_SOFT)
|
||||
if value == "in_progress":
|
||||
return pill(value, ACCENT, ACCENT_SOFT)
|
||||
if value == "Yes":
|
||||
return pill(value, OK, OK_SOFT)
|
||||
if value == "No":
|
||||
return pill(value, WARN, WARN_SOFT)
|
||||
return pill(value, MUTED, colors.white)
|
||||
|
||||
|
||||
def text_para(value, style_name="cell"):
|
||||
return Paragraph(escape(value), styles[style_name])
|
||||
|
||||
|
||||
def build_summary_cards(rows):
|
||||
existing_rows = [row for row in rows if row.get("exists", True) is not False]
|
||||
active_rows = [row for row in existing_rows if row.get("hasActiveRevision") == "Yes"]
|
||||
billed_rows = [row for row in existing_rows if row.get("r00Billed") != "No"]
|
||||
unbilled_rows = [row for row in existing_rows if row.get("r00Billed") == "No"]
|
||||
|
||||
cards = [
|
||||
("Checked Sites", str(len(existing_rows)), "Sites found in the database from your checked list."),
|
||||
("Active Revisions", str(len(active_rows)), "Checked sites that now have an active R01+."),
|
||||
("R00 Billed", str(len(billed_rows)), "Checked sites where the new-book unit is already billed."),
|
||||
("R00 Unbilled", str(len(unbilled_rows)), "Checked sites where R00 is done but not billed yet."),
|
||||
]
|
||||
|
||||
card_rows = []
|
||||
for label, value, subtext in cards:
|
||||
card_rows.append(
|
||||
Table(
|
||||
[
|
||||
[Paragraph(label.upper(), styles["card_label"])],
|
||||
[Paragraph(value, styles["card_value"])],
|
||||
[Paragraph(subtext, styles["card_sub"])],
|
||||
],
|
||||
colWidths=[3.0 * inch],
|
||||
style=TableStyle(
|
||||
[
|
||||
("BACKGROUND", (0, 0), (-1, -1), colors.white),
|
||||
("BOX", (0, 0), (-1, -1), 0.8, LINE),
|
||||
("INNERPADDING", (0, 0), (-1, -1), 10),
|
||||
("ROUNDEDCORNERS", [10, 10, 10, 10]),
|
||||
]
|
||||
),
|
||||
)
|
||||
)
|
||||
return Table([card_rows], colWidths=[3.1 * inch] * 4, hAlign="LEFT")
|
||||
|
||||
|
||||
def build_table(rows, title, intro):
|
||||
flow = [Paragraph(title, styles["section_title"]), Paragraph(intro, styles["small"]), Spacer(1, 10)]
|
||||
|
||||
header = [
|
||||
text_para("Site", "th"),
|
||||
text_para("DB Title", "th"),
|
||||
text_para("Internal #", "th"),
|
||||
text_para("R00 Done", "th"),
|
||||
text_para("R00 Billed", "th"),
|
||||
text_para("Active R#", "th"),
|
||||
text_para("Active Status", "th"),
|
||||
text_para("Completed Revisions", "th"),
|
||||
text_para("Billed Versions", "th"),
|
||||
text_para("Version Summary", "th"),
|
||||
]
|
||||
data = [header]
|
||||
|
||||
for row in rows:
|
||||
data.append(
|
||||
[
|
||||
text_para(row.get("site", ""), "cell_mono"),
|
||||
text_para(row.get("dbTitle", "")),
|
||||
text_para(row.get("internalTaskNumber", ""), "cell_mono"),
|
||||
status_pill(row.get("r00Completed", "No")),
|
||||
status_pill(row.get("r00Billed", "No")),
|
||||
text_para(row.get("activeRevision", "—"), "cell_mono"),
|
||||
status_pill(row.get("activeStatus", "—")),
|
||||
text_para(row.get("completedRevisions", "—")),
|
||||
text_para(row.get("billedVersions", "—")),
|
||||
text_para(row.get("versionSummary", "—")),
|
||||
]
|
||||
)
|
||||
|
||||
table = LongTable(
|
||||
data,
|
||||
colWidths=[
|
||||
0.78 * inch,
|
||||
1.7 * inch,
|
||||
0.72 * inch,
|
||||
0.75 * inch,
|
||||
0.85 * inch,
|
||||
0.6 * inch,
|
||||
1.0 * inch,
|
||||
1.45 * inch,
|
||||
1.2 * inch,
|
||||
2.15 * inch,
|
||||
],
|
||||
repeatRows=1,
|
||||
)
|
||||
table.setStyle(
|
||||
TableStyle(
|
||||
[
|
||||
("BACKGROUND", (0, 0), (-1, 0), colors.white),
|
||||
("BOX", (0, 0), (-1, -1), 0.8, LINE),
|
||||
("INNERGRID", (0, 0), (-1, -1), 0.4, LINE),
|
||||
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
||||
("TOPPADDING", (0, 0), (-1, -1), 6),
|
||||
("BOTTOMPADDING", (0, 0), (-1, -1), 6),
|
||||
("LEFTPADDING", (0, 0), (-1, -1), 6),
|
||||
("RIGHTPADDING", (0, 0), (-1, -1), 6),
|
||||
("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#FBF8F2")]),
|
||||
]
|
||||
)
|
||||
)
|
||||
flow.append(table)
|
||||
return flow
|
||||
|
||||
|
||||
styles = getSampleStyleSheet()
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="title_main",
|
||||
fontName="Helvetica-Bold",
|
||||
fontSize=24,
|
||||
leading=28,
|
||||
textColor=TEXT,
|
||||
alignment=TA_LEFT,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="eyebrow",
|
||||
fontName="Helvetica-Bold",
|
||||
fontSize=9,
|
||||
leading=11,
|
||||
textColor=MUTED,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="subtitle",
|
||||
fontName="Helvetica",
|
||||
fontSize=11,
|
||||
leading=15,
|
||||
textColor=MUTED,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="section_title",
|
||||
fontName="Helvetica-Bold",
|
||||
fontSize=15,
|
||||
leading=18,
|
||||
textColor=TEXT,
|
||||
spaceAfter=2,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="small",
|
||||
fontName="Helvetica",
|
||||
fontSize=9,
|
||||
leading=12,
|
||||
textColor=MUTED,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="card_label",
|
||||
fontName="Helvetica-Bold",
|
||||
fontSize=8,
|
||||
leading=10,
|
||||
textColor=MUTED,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="card_value",
|
||||
fontName="Helvetica-Bold",
|
||||
fontSize=24,
|
||||
leading=26,
|
||||
textColor=TEXT,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="card_sub",
|
||||
fontName="Helvetica",
|
||||
fontSize=8.5,
|
||||
leading=11,
|
||||
textColor=MUTED,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="th",
|
||||
fontName="Helvetica-Bold",
|
||||
fontSize=8,
|
||||
leading=10,
|
||||
textColor=MUTED,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="cell",
|
||||
fontName="Helvetica",
|
||||
fontSize=8.2,
|
||||
leading=10.5,
|
||||
textColor=TEXT,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="cell_mono",
|
||||
fontName="Courier",
|
||||
fontSize=8.1,
|
||||
leading=10.3,
|
||||
textColor=TEXT,
|
||||
)
|
||||
)
|
||||
styles.add(
|
||||
ParagraphStyle(
|
||||
name="pill",
|
||||
fontName="Helvetica-Bold",
|
||||
fontSize=7.5,
|
||||
leading=9,
|
||||
alignment=1,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
rows = json.loads(INPUT_PATH.read_text())
|
||||
existing_rows = [row for row in rows if row.get("exists", True) is not False]
|
||||
active_rows = [row for row in existing_rows if row.get("hasActiveRevision") == "Yes"]
|
||||
no_active_rows = [row for row in existing_rows if row.get("hasActiveRevision") != "Yes"]
|
||||
|
||||
doc = BaseDocTemplate(
|
||||
str(OUTPUT_PATH),
|
||||
pagesize=landscape(legal),
|
||||
leftMargin=36,
|
||||
rightMargin=36,
|
||||
topMargin=28,
|
||||
bottomMargin=28,
|
||||
)
|
||||
frame = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height, id="main")
|
||||
doc.addPageTemplates([PageTemplate(id="default", frames=[frame], onPage=page_template)])
|
||||
|
||||
story = [
|
||||
Paragraph("FOURGE PORTAL AUDIT", styles["eyebrow"]),
|
||||
Spacer(1, 4),
|
||||
Paragraph("Checked Sites Revision Audit", styles["title_main"]),
|
||||
Spacer(1, 6),
|
||||
Paragraph(
|
||||
"This report treats your checkmark as “R00 was completed,” then separates that from billing and from any newer active revisions. "
|
||||
"It is designed to make client-billing comparison easier at a glance.",
|
||||
styles["subtitle"],
|
||||
),
|
||||
Spacer(1, 18),
|
||||
build_summary_cards(rows),
|
||||
Spacer(1, 18),
|
||||
]
|
||||
|
||||
story.extend(
|
||||
build_table(
|
||||
active_rows,
|
||||
"Checked Sites With Active Revisions",
|
||||
"These are the sites where R00 is done, but there is still a newer revision active or awaiting billing.",
|
||||
)
|
||||
)
|
||||
story.append(PageBreak())
|
||||
story.extend(
|
||||
build_table(
|
||||
no_active_rows,
|
||||
"Checked Sites With No Active Revision",
|
||||
"These are the checked sites that currently do not have a newer active revision.",
|
||||
)
|
||||
)
|
||||
|
||||
doc.build(story)
|
||||
print(OUTPUT_PATH)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user