diff --git a/frontend/build.ts b/frontend/build.ts index 6ceaf0a..1e7712f 100644 --- a/frontend/build.ts +++ b/frontend/build.ts @@ -7,6 +7,7 @@ import { renderFristenrechner } from "./src/fristenrechner"; import { renderDownloads } from "./src/downloads"; import { renderLinks } from "./src/links"; import { renderGlossar } from "./src/glossar"; +import { renderGebuehrentabellen } from "./src/gebuehrentabellen"; const DIST = join(import.meta.dir, "dist"); @@ -25,6 +26,7 @@ async function build() { join(import.meta.dir, "src/client/downloads.ts"), join(import.meta.dir, "src/client/links.ts"), join(import.meta.dir, "src/client/glossar.ts"), + join(import.meta.dir, "src/client/gebuehrentabellen.ts"), ], outdir: join(DIST, "assets"), naming: "[name].js", @@ -53,6 +55,7 @@ async function build() { await Bun.write(join(DIST, "downloads.html"), renderDownloads()); await Bun.write(join(DIST, "links.html"), renderLinks()); await Bun.write(join(DIST, "glossar.html"), renderGlossar()); + await Bun.write(join(DIST, "gebuehrentabellen.html"), renderGebuehrentabellen()); console.log("Build complete \u2192 dist/"); } diff --git a/frontend/src/client/gebuehrentabellen.ts b/frontend/src/client/gebuehrentabellen.ts new file mode 100644 index 0000000..8d9679d --- /dev/null +++ b/frontend/src/client/gebuehrentabellen.ts @@ -0,0 +1,578 @@ +import { initI18n, onLangChange, t, getLang } from "./i18n"; +import { initSidebar } from "./sidebar"; + +// --- Types matching API response --- + +interface FeeTableRow { + streitwert: number; + fee: number; +} + +interface FeeVersionTable { + version: string; + label: string; + validFrom: string; + isCurrent: boolean; + rows: FeeTableRow[]; +} + +interface UPCValueRow { + maxValue: number | null; + fee: number; +} + +interface UPCRecoverRow { + maxValue: number | null; + ceiling: number; +} + +interface UPCScheduleInfo { + version: string; + label: string; + fixedInfringement: number; + fixedRevocation: number; + smeReduction: number; + valueBased: UPCValueRow[]; + recoverableCosts: UPCRecoverRow[]; +} + +interface EPAFeeInfo { + key: string; + label: string; + labelEN: string; + fee: number; + smeFee: number; +} + +interface MultiplierInfo { + key: string; + label: string; + labelEN: string; + courtFeeFactor: number; + feeBasis: string; + raVGFactor: number; + raTGFactor: number; + paVGFactor: number; + paTGFactor: number; +} + +interface PatKostGCourtFee { + key: string; + label: string; + labelEN: string; + factor: number; + note: string; + noteEN: string; +} + +interface DPMAFee { + label: string; + labelEN: string; + fee: number; +} + +interface DPMAAnnualFee { + year: number; + fee: number; +} + +interface PatKostGInfo { + courtFees: PatKostGCourtFee[]; + dpmaFees: DPMAFee[]; + annualFees: DPMAAnnualFee[]; +} + +interface FeeTableData { + gkg: FeeVersionTable[]; + rvg: FeeVersionTable[]; + upc: UPCScheduleInfo[]; + epa: EPAFeeInfo[]; + multipliers: MultiplierInfo[]; + patkostg: PatKostGInfo; +} + +interface LookupResult { + streitwert: number; + gkg: Record; + rvg: Record; + upc: Record; +} + +// --- State --- + +let data: FeeTableData | null = null; +let activeTab = "gkg"; +let gkgVersion = "2025"; +let rvgVersion = "2025"; +let upcVersion = "2026"; +let lookupStreitwert: number | null = null; +let lookupResult: LookupResult | null = null; + +// --- Formatting --- + +function fmtEUR(v: number): string { + return v.toLocaleString("de-DE", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); +} + +function fmtInt(v: number): string { + return v.toLocaleString("de-DE", { maximumFractionDigits: 0 }); +} + +function esc(s: string): string { + const d = document.createElement("div"); + d.textContent = s; + return d.innerHTML; +} + +// --- Data loading --- + +async function loadData() { + const resp = await fetch("/api/tools/gebuehrentabellen"); + if (!resp.ok) return; + data = await resp.json(); + renderAll(); +} + +async function doLookup(streitwert: number) { + const resp = await fetch(`/api/tools/gebuehrentabellen/lookup?streitwert=${streitwert}`); + if (!resp.ok) return; + lookupResult = await resp.json(); + lookupStreitwert = streitwert; + showLookupResult(); + renderActivePanel(); +} + +// --- Render all --- + +function renderAll() { + if (!data) return; + renderVersionPills("gkg-versions", data.gkg, gkgVersion, (v) => { gkgVersion = v; renderGKG(); }); + renderVersionPills("rvg-versions", data.rvg, rvgVersion, (v) => { rvgVersion = v; renderRVG(); }); + renderUPCVersionPills(); + renderGKG(); + renderRVG(); + renderUPC(); + renderEPA(); + renderPatKostG(); + renderMultipliers(); +} + +function renderActivePanel() { + if (!data) return; + switch (activeTab) { + case "gkg": renderGKG(); break; + case "rvg": renderRVG(); break; + case "upc": renderUPC(); break; + } +} + +// --- Version pills --- + +function renderVersionPills( + containerId: string, + versions: FeeVersionTable[], + activeVersion: string, + onSelect: (v: string) => void +) { + const container = document.getElementById(containerId)!; + container.innerHTML = versions.map((v) => { + const label = v.isCurrent ? `${v.version} (${t("gebuehren.current")})` : v.version; + const cls = v.version === activeVersion ? "filter-pill active" : "filter-pill"; + return ``; + }).join(""); + + container.querySelectorAll(".filter-pill").forEach((btn) => { + btn.addEventListener("click", () => { + container.querySelectorAll(".filter-pill").forEach((p) => p.classList.remove("active")); + btn.classList.add("active"); + onSelect(btn.dataset.version!); + }); + }); +} + +function renderUPCVersionPills() { + if (!data) return; + const container = document.getElementById("upc-versions")!; + container.innerHTML = data.upc.map((s) => { + const cls = s.version === upcVersion ? "filter-pill active" : "filter-pill"; + return ``; + }).join(""); + + container.querySelectorAll(".filter-pill").forEach((btn) => { + btn.addEventListener("click", () => { + container.querySelectorAll(".filter-pill").forEach((p) => p.classList.remove("active")); + btn.classList.add("active"); + upcVersion = btn.dataset.version!; + renderUPC(); + }); + }); +} + +// --- GKG / RVG table rendering --- + +function renderFeeTable(tbodyId: string, versions: FeeVersionTable[], activeVersion: string, type: "gkg" | "rvg") { + const tbody = document.getElementById(tbodyId)!; + const table = versions.find((v) => v.version === activeVersion); + if (!table) { tbody.innerHTML = ""; return; } + + const lookupFee = lookupResult && lookupResult[type] ? lookupResult[type][activeVersion] : null; + const hasLookup = lookupStreitwert !== null && lookupFee !== null; + + let rows = table.rows; + let insertedLookup = false; + + const html: string[] = []; + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + + // Insert lookup row before the first row with streitwert >= lookupStreitwert + if (hasLookup && !insertedLookup && lookupStreitwert! <= row.streitwert) { + if (lookupStreitwert! === row.streitwert) { + // Exact match — highlight this row + html.push(`${fmtInt(row.streitwert)}${fmtEUR(row.fee)}`); + insertedLookup = true; + continue; + } + // Insert custom row + html.push(`${fmtInt(lookupStreitwert!)}${fmtEUR(lookupFee!)}`); + insertedLookup = true; + } + + html.push(`${fmtInt(row.streitwert)}${fmtEUR(row.fee)}`); + } + + // Lookup streitwert exceeds all rows + if (hasLookup && !insertedLookup) { + html.push(`${fmtInt(lookupStreitwert!)}${fmtEUR(lookupFee!)}`); + } + + tbody.innerHTML = html.join(""); + + // Scroll to highlighted row + const highlighted = tbody.querySelector("tr.highlight"); + if (highlighted) { + highlighted.scrollIntoView({ block: "center", behavior: "smooth" }); + } +} + +function renderGKG() { + if (!data) return; + renderFeeTable("gkg-body", data.gkg, gkgVersion, "gkg"); +} + +function renderRVG() { + if (!data) return; + renderFeeTable("rvg-body", data.rvg, rvgVersion, "rvg"); +} + +// --- UPC --- + +function renderUPC() { + if (!data) return; + const schedule = data.upc.find((s) => s.version === upcVersion); + if (!schedule) return; + + const isEN = getLang() === "en"; + const smePercent = Math.round(schedule.smeReduction * 100); + + // Summary + const summary = document.getElementById("upc-summary")!; + summary.innerHTML = `
+
+
${isEN ? "Fixed fee (infringement)" : "Festgebühr (Verletzung)"}
+
${fmtEUR(schedule.fixedInfringement)}
+
+
+
${isEN ? "Fixed fee (revocation)" : "Festgebühr (Nichtigkeit)"}
+
${fmtEUR(schedule.fixedRevocation)}
+
+
+
${isEN ? "SME reduction" : "KMU-Ermäßigung"}
+
${smePercent}%
+
+
`; + + // Value-based table + const valueBody = document.getElementById("upc-value-body")!; + const upcLookup = lookupResult?.upc?.[upcVersion]; + + valueBody.innerHTML = schedule.valueBased.map((row) => { + const maxLabel = row.maxValue === null + ? (isEN ? "unlimited" : "unbegrenzt") + : fmtInt(row.maxValue); + const isHighlighted = lookupStreitwert !== null && upcLookup && + ((row.maxValue === null && lookupStreitwert > (schedule.valueBased[schedule.valueBased.indexOf(row) - 1]?.maxValue ?? 0)) || + (row.maxValue !== null && lookupStreitwert <= row.maxValue && + (schedule.valueBased.indexOf(row) === 0 || lookupStreitwert > (schedule.valueBased[schedule.valueBased.indexOf(row) - 1]?.maxValue ?? 0)))); + return `${maxLabel}${fmtEUR(row.fee)}`; + }).join(""); + + // Recoverable costs table + const recoverBody = document.getElementById("upc-recover-body")!; + recoverBody.innerHTML = schedule.recoverableCosts.map((row) => { + const maxLabel = row.maxValue === null + ? (isEN ? "unlimited" : "unbegrenzt") + : fmtInt(row.maxValue); + const isHighlighted = lookupStreitwert !== null && upcLookup && + ((row.maxValue === null && lookupStreitwert > (schedule.recoverableCosts[schedule.recoverableCosts.indexOf(row) - 1]?.maxValue ?? 0)) || + (row.maxValue !== null && lookupStreitwert <= row.maxValue && + (schedule.recoverableCosts.indexOf(row) === 0 || lookupStreitwert > (schedule.recoverableCosts[schedule.recoverableCosts.indexOf(row) - 1]?.maxValue ?? 0)))); + return `${maxLabel}${fmtEUR(row.ceiling)}`; + }).join(""); + + // Scroll to highlighted + const highlighted = valueBody.querySelector("tr.highlight") || recoverBody.querySelector("tr.highlight"); + if (highlighted) { + highlighted.scrollIntoView({ block: "center", behavior: "smooth" }); + } +} + +// --- EPA --- + +function renderEPA() { + if (!data) return; + const isEN = getLang() === "en"; + const body = document.getElementById("epa-body")!; + body.innerHTML = data.epa.map((fee) => { + const label = isEN ? fee.labelEN : fee.label; + const smeCol = fee.smeFee !== fee.fee + ? fmtEUR(fee.smeFee) + : '\u2014'; + return `${esc(label)}${fmtEUR(fee.fee)}${smeCol}`; + }).join(""); +} + +// --- PatKostG --- + +function renderPatKostG() { + if (!data) return; + const isEN = getLang() === "en"; + const pk = data.patkostg; + + // Court fees + const courtBody = document.getElementById("patkostg-court-body")!; + courtBody.innerHTML = pk.courtFees.map((f) => { + const label = isEN ? f.labelEN : f.label; + const note = isEN ? f.noteEN : f.note; + return `${esc(label)}${f.factor.toFixed(1)}x${esc(note)}`; + }).join(""); + + // DPMA fees + const dpmaBody = document.getElementById("patkostg-dpma-body")!; + dpmaBody.innerHTML = pk.dpmaFees.map((f) => { + const label = isEN ? f.labelEN : f.label; + return `${esc(label)}${fmtEUR(f.fee)}`; + }).join(""); + + // Annual fees + const annualBody = document.getElementById("patkostg-annual-body")!; + annualBody.innerHTML = pk.annualFees.map((f) => { + const yearLabel = isEN ? `Year ${f.year}` : `${f.year}. Patentjahr`; + return `${yearLabel}${fmtEUR(f.fee)}`; + }).join(""); +} + +// --- Multipliers --- + +function renderMultipliers() { + if (!data) return; + const isEN = getLang() === "en"; + const body = document.getElementById("multipliers-body")!; + body.innerHTML = data.multipliers.map((m) => { + const label = isEN ? m.labelEN : m.label; + return ` + ${esc(label)} + ${m.courtFeeFactor.toFixed(1)}x + ${m.feeBasis} + ${m.raVGFactor.toFixed(1)} + ${m.raTGFactor.toFixed(1)} + ${m.paVGFactor.toFixed(1)} + ${m.paTGFactor.toFixed(1)} + `; + }).join(""); +} + +// --- Lookup result display --- + +function showLookupResult() { + const el = document.getElementById("lookup-result")!; + if (!lookupResult) { + el.style.display = "none"; + return; + } + + const isEN = getLang() === "en"; + const sw = lookupResult.streitwert; + const gkgFee = lookupResult.gkg["2025"] ?? 0; + const rvgFee = lookupResult.rvg["2025"] ?? 0; + + el.style.display = "block"; + el.innerHTML = `
+
+ ${isEN ? "Dispute value" : "Streitwert"} + ${fmtInt(sw)} EUR +
+
+ GKG 1,0 + ${fmtEUR(gkgFee)} EUR +
+
+ RVG 1,0 + ${fmtEUR(rvgFee)} EUR +
+
`; +} + +// --- Tab switching --- + +function initTabs() { + const container = document.getElementById("gebuehren-tabs")!; + container.addEventListener("click", (e) => { + const btn = (e.target as HTMLElement).closest(".gebuehren-tab"); + if (!btn) return; + container.querySelectorAll(".gebuehren-tab").forEach((t) => t.classList.remove("active")); + btn.classList.add("active"); + + const tab = btn.dataset.tab!; + activeTab = tab; + + // Show/hide panels + document.querySelectorAll(".gebuehren-panel").forEach((p) => { + p.style.display = "none"; + }); + document.getElementById(`tab-${tab}`)!.style.display = "block"; + + // Show multipliers only on GKG/RVG tabs + const multSection = document.getElementById("multipliers-section")!; + multSection.style.display = (tab === "gkg" || tab === "rvg") ? "block" : "none"; + }); + + // Initial multiplier visibility + document.getElementById("multipliers-section")!.style.display = "block"; +} + +// --- Streitwert lookup --- + +function parseStreitwert(input: string): number | null { + // Remove thousands separators (. or ,) and whitespace, then parse + let cleaned = input.replace(/\s/g, ""); + // Handle German format: 1.000.000,50 → 1000000.50 + if (cleaned.includes(",") && cleaned.includes(".")) { + cleaned = cleaned.replace(/\./g, "").replace(",", "."); + } else if (cleaned.includes(",")) { + // Could be 1000,50 (decimal) or 1,000,000 (thousands) + const parts = cleaned.split(","); + if (parts.length === 2 && parts[1].length <= 2) { + cleaned = cleaned.replace(",", "."); + } else { + cleaned = cleaned.replace(/,/g, ""); + } + } else { + cleaned = cleaned.replace(/\./g, ""); + } + const val = parseFloat(cleaned); + return isNaN(val) || val <= 0 ? null : val; +} + +function initLookup() { + const input = document.getElementById("streitwert-input") as HTMLInputElement; + const btn = document.getElementById("btn-lookup")!; + + const doIt = () => { + const val = parseStreitwert(input.value); + if (val === null) { + lookupStreitwert = null; + lookupResult = null; + document.getElementById("lookup-result")!.style.display = "none"; + renderActivePanel(); + return; + } + doLookup(val); + }; + + btn.addEventListener("click", doIt); + input.addEventListener("keydown", (e) => { + if (e.key === "Enter") { e.preventDefault(); doIt(); } + }); + + // Clear on empty input + input.addEventListener("input", () => { + if (input.value.trim() === "") { + lookupStreitwert = null; + lookupResult = null; + document.getElementById("lookup-result")!.style.display = "none"; + renderAll(); + } + }); +} + +// --- Feedback modal --- + +function initFeedback() { + const modal = document.getElementById("feedback-modal")!; + const form = document.getElementById("feedback-form")!; + const msg = document.getElementById("feedback-msg")!; + + document.getElementById("btn-feedback")!.addEventListener("click", () => { + msg.textContent = ""; + msg.className = "form-msg"; + modal.style.display = "flex"; + }); + + document.getElementById("modal-close")!.addEventListener("click", () => { modal.style.display = "none"; }); + document.getElementById("modal-cancel")!.addEventListener("click", () => { modal.style.display = "none"; }); + modal.addEventListener("click", (e) => { if (e.target === e.currentTarget) modal.style.display = "none"; }); + + form.addEventListener("submit", async (e) => { + e.preventDefault(); + const submitBtn = form.querySelector(".btn-submit") as HTMLButtonElement; + const payload = { + feedback_type: (document.getElementById("feedback-type") as HTMLSelectElement).value, + schedule: (document.getElementById("feedback-schedule") as HTMLSelectElement).value, + message: (document.getElementById("feedback-message") as HTMLTextAreaElement).value.trim(), + }; + + if (!payload.message) { + msg.textContent = t("gebuehren.feedback.error.required"); + msg.className = "form-msg form-msg-error"; + return; + } + + submitBtn.disabled = true; + try { + const resp = await fetch("/api/tools/gebuehrentabellen/feedback", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }); + if (!resp.ok) { + msg.textContent = t("gebuehren.feedback.error.generic"); + msg.className = "form-msg form-msg-error"; + return; + } + msg.textContent = t("gebuehren.feedback.success"); + msg.className = "form-msg form-msg-success"; + setTimeout(() => { modal.style.display = "none"; }, 1500); + } catch { + msg.textContent = t("gebuehren.feedback.error.generic"); + msg.className = "form-msg form-msg-error"; + } finally { + submitBtn.disabled = false; + } + }); +} + +// --- Init --- + +document.addEventListener("DOMContentLoaded", () => { + initI18n(); + initSidebar(); + initTabs(); + initLookup(); + initFeedback(); + onLangChange(() => { + renderAll(); + showLookupResult(); + }); + loadData(); +}); diff --git a/frontend/src/client/i18n.ts b/frontend/src/client/i18n.ts index 511ecd2..dd9d5a5 100644 --- a/frontend/src/client/i18n.ts +++ b/frontend/src/client/i18n.ts @@ -17,6 +17,7 @@ const translations: Record> = { "nav.downloads": "Downloads", "nav.links": "Links", "nav.glossar": "Glossar", + "nav.gebuehrentabellen": "Geb\u00fchrentabellen", "nav.logout": "Abmelden", // Footer @@ -211,6 +212,56 @@ const translations: Record> = { "glossar.suggest.error.generic": "Fehler beim Senden. Bitte versuchen Sie es erneut.", "glossar.feedback.title": "Korrektur vorschlagen", "glossar.feedback.tooltip": "Korrektur vorschlagen", + + // Geb\u00fchrentabellen + "gebuehren.title": "Geb\u00fchrentabellen \u2014 patHoLo", + "gebuehren.heading": "Geb\u00fchrentabellen", + "gebuehren.subtitle": "Interaktive Geb\u00fchrentabellen f\u00fcr GKG, RVG, UPC, EPA und PatKostG.", + "gebuehren.streitwert": "Streitwert (EUR)", + "gebuehren.streitwert.placeholder": "z.B. 1.000.000", + "gebuehren.lookup": "Nachschlagen", + "gebuehren.current": "Aktuell", + "gebuehren.col.streitwert": "Streitwert (EUR)", + "gebuehren.col.fee": "1,0 Geb\u00fchr (EUR)", + "gebuehren.col.maxvalue": "bis Streitwert (EUR)", + "gebuehren.col.courtfee": "Gerichtsgeb\u00fchr (EUR)", + "gebuehren.upc.valuebased": "Streitwertabh\u00e4ngige Geb\u00fchren", + "gebuehren.upc.recoverable": "Erstattungsf\u00e4hige Kosten (Obergrenze)", + "gebuehren.upc.ceiling": "Obergrenze (EUR)", + "gebuehren.epa.proceeding": "Verfahren", + "gebuehren.epa.fee": "Geb\u00fchr (EUR)", + "gebuehren.epa.smefee": "KMU-Geb\u00fchr (EUR)", + "gebuehren.multipliers.title": "Geb\u00fchren-Multiplikatoren", + "gebuehren.multipliers.desc": "Faktoren f\u00fcr die Berechnung von Gerichts- und Anwaltskosten je Instanz.", + "gebuehren.multipliers.instance": "Instanz", + "gebuehren.multipliers.courtfee": "Gericht", + "gebuehren.multipliers.factor": "Faktor", + "gebuehren.multipliers.basis": "Grundlage", + "gebuehren.patkostg.court": "Gerichtskosten (BPatG / BGH)", + "gebuehren.patkostg.dpma": "DPMA-Geb\u00fchren", + "gebuehren.patkostg.annual": "Jahresgeb\u00fchren (Patent)", + "gebuehren.patkostg.year": "Patentjahr", + "gebuehren.patkostg.note": "Hinweis", + "gebuehren.patkostg.item": "Geb\u00fchrentatbestand", + "gebuehren.feedback.btn": "Feedback", + "gebuehren.feedback.title": "Feedback zur Geb\u00fchrentabelle", + "gebuehren.feedback.type": "Art", + "gebuehren.feedback.error": "Fehler gefunden", + "gebuehren.feedback.missing": "Fehlende Daten", + "gebuehren.feedback.suggestion": "Verbesserungsvorschlag", + "gebuehren.feedback.other": "Sonstiges", + "gebuehren.feedback.schedule": "Betrifft", + "gebuehren.feedback.general": "Allgemein", + "gebuehren.feedback.message": "Nachricht", + "gebuehren.feedback.submit": "Absenden", + "gebuehren.feedback.cancel": "Abbrechen", + "gebuehren.feedback.success": "Danke f\u00fcr Ihr Feedback!", + "gebuehren.feedback.error.required": "Bitte geben Sie eine Nachricht ein.", + "gebuehren.feedback.error.generic": "Fehler beim Senden. Bitte versuchen Sie es erneut.", + + // Index — Geb\u00fchrentabellen card + "index.gebuehren.title": "Geb\u00fchrentabellen", + "index.gebuehren.desc": "Interaktive Geb\u00fchrentabellen f\u00fcr GKG, RVG, UPC, EPA und PatKostG. Streitwert eingeben, Geb\u00fchr ablesen.", }, en: { @@ -221,6 +272,7 @@ const translations: Record> = { "nav.downloads": "Downloads", "nav.links": "Links", "nav.glossar": "Glossary", + "nav.gebuehrentabellen": "Fee Schedules", "nav.logout": "Sign Out", // Footer @@ -415,6 +467,56 @@ const translations: Record> = { "glossar.suggest.error.generic": "Error submitting. Please try again.", "glossar.feedback.title": "Suggest a correction", "glossar.feedback.tooltip": "Suggest a correction", + + // Geb\u00fchrentabellen + "gebuehren.title": "Fee Schedules \u2014 patHoLo", + "gebuehren.heading": "Fee Schedules", + "gebuehren.subtitle": "Interactive fee schedules for GKG, RVG, UPC, EPO, and PatKostG.", + "gebuehren.streitwert": "Dispute Value (EUR)", + "gebuehren.streitwert.placeholder": "e.g. 1,000,000", + "gebuehren.lookup": "Look up", + "gebuehren.current": "Current", + "gebuehren.col.streitwert": "Dispute Value (EUR)", + "gebuehren.col.fee": "1.0 Fee (EUR)", + "gebuehren.col.maxvalue": "up to value (EUR)", + "gebuehren.col.courtfee": "Court Fee (EUR)", + "gebuehren.upc.valuebased": "Value-based Fees", + "gebuehren.upc.recoverable": "Recoverable Costs (Ceiling)", + "gebuehren.upc.ceiling": "Ceiling (EUR)", + "gebuehren.epa.proceeding": "Proceeding", + "gebuehren.epa.fee": "Fee (EUR)", + "gebuehren.epa.smefee": "SME Fee (EUR)", + "gebuehren.multipliers.title": "Fee Multipliers", + "gebuehren.multipliers.desc": "Factors for calculating court and attorney fees per instance.", + "gebuehren.multipliers.instance": "Instance", + "gebuehren.multipliers.courtfee": "Court", + "gebuehren.multipliers.factor": "Factor", + "gebuehren.multipliers.basis": "Basis", + "gebuehren.patkostg.court": "Court Fees (BPatG / BGH)", + "gebuehren.patkostg.dpma": "DPMA Fees", + "gebuehren.patkostg.annual": "Annual Renewal Fees (Patent)", + "gebuehren.patkostg.year": "Patent Year", + "gebuehren.patkostg.note": "Note", + "gebuehren.patkostg.item": "Fee Item", + "gebuehren.feedback.btn": "Feedback", + "gebuehren.feedback.title": "Fee Schedule Feedback", + "gebuehren.feedback.type": "Type", + "gebuehren.feedback.error": "Error found", + "gebuehren.feedback.missing": "Missing data", + "gebuehren.feedback.suggestion": "Improvement suggestion", + "gebuehren.feedback.other": "Other", + "gebuehren.feedback.schedule": "Regarding", + "gebuehren.feedback.general": "General", + "gebuehren.feedback.message": "Message", + "gebuehren.feedback.submit": "Submit", + "gebuehren.feedback.cancel": "Cancel", + "gebuehren.feedback.success": "Thank you for your feedback!", + "gebuehren.feedback.error.required": "Please enter a message.", + "gebuehren.feedback.error.generic": "Error submitting. Please try again.", + + // Index \u2014 Geb\u00fchrentabellen card + "index.gebuehren.title": "Fee Schedules", + "index.gebuehren.desc": "Interactive fee schedules for GKG, RVG, UPC, EPO, and PatKostG. Enter a dispute value, read the fee.", }, }; diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 687f081..5c8e1bb 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -6,6 +6,7 @@ const ICON_CLOCK = '" + ( + + + + + Gebührentabellen — patHoLo + + + + + +
+
+
+
+
+
+

Gebührentabellen

+

+ Interaktive Gebührentabellen für GKG, RVG, UPC, EPA und PatKostG. +

+
+ +
+
+ + {/* Streitwert lookup */} +
+ +
+ EUR + + +
+
+ + {/* Lookup result */} +
+
+ + {/* Feedback modal */} +