Skip to content

feat: export substitutions#231

Open
KoZsombat wants to merge 6 commits into
mainfrom
admin-subs-export
Open

feat: export substitutions#231
KoZsombat wants to merge 6 commits into
mainfrom
admin-subs-export

Conversation

@KoZsombat
Copy link
Copy Markdown
Contributor

@KoZsombat KoZsombat commented May 31, 2026

Summary by CodeRabbit

  • New Features

    • Export substitutions as PDF or CSV from the substitutions page with date filtering, progress feedback, and error handling.
    • Export dialog with format selection, download/open behavior, disabled controls while exporting, and export status indicator.
  • Chores

    • Added English and Hungarian translation strings for the export UI.
    • Added Noto Sans font support for generated PDFs.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a substitution export feature to Iris admin: a dialog to export filtered substitutions as PDF or CSV, a PDF renderer with Noto Sans font registration, EN/HU translation strings, a package dependency for the font, and integration into the substitutions admin route.

Changes

Substitution Export Feature

Layer / File(s) Summary
Dependencies and i18n strings
apps/iris/package.json, apps/iris/public/locales/en/translation.json, apps/iris/public/locales/hu/translation.json
NotoSans font dependency added and English/Hungarian translation keys for export UI labels, format, progress state, and fields (absent teacher, class) added.
PDF export component
apps/iris/src/components/admin/substitution-pdf.tsx
SubstitutionPDF registers NotoSans (weights), disables hyphenation, defines styles and types, and renders an A4 table of SubstitutionExportRow items with fallbacks and an empty-state row.
Export dialog component
apps/iris/src/components/admin/substitution-export-dialog.tsx
SubstitutionExportDialog manages date/format/loading state, buildRows() filters lessons by date and builds rows, downloadCsv() emits semicolon-delimited CSV with UTF-8 BOM and escaping, and PDF export renders SubstitutionPDF to a blob opened in a new tab.
Route integration
apps/iris/src/routes/_private/admin/timetable/substitutions.tsx
Adds exportDialogOpen state, imports the dialog, an "Export" button with FileDown icon, and mounts SubstitutionExportDialog with current substitutions data.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Dialog as SubstitutionExportDialog
  participant RowBuilder as buildRows
  participant PDF as SubstitutionPDF
  participant CSV as downloadCsv
  participant Browser
  
  User->>Dialog: Select date & format
  activate Dialog
  Dialog->>RowBuilder: buildRows(substitutions, date)
  activate RowBuilder
  RowBuilder->>RowBuilder: Filter lessons by date
  RowBuilder->>RowBuilder: Extract teacher/cohort/period
  RowBuilder-->>Dialog: Export rows
  deactivate RowBuilder
  
  alt PDF format
    Dialog->>PDF: Render PDF with rows
    PDF-->>Dialog: PDF blob
    Dialog->>Browser: Open blob in new tab
  else CSV format
    Dialog->>CSV: downloadCsv(rows)
    CSV->>Browser: Trigger download with BOM
  end
  Dialog->>User: Close dialog
  deactivate Dialog
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • nemvince
  • busyLambda
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: export substitutions' directly and clearly summarizes the main change: adding an export feature for substitutions with dialog, PDF/CSV formats, and UI integration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@KoZsombat KoZsombat linked an issue May 31, 2026 that may be closed by this pull request
@coderabbitai coderabbitai Bot requested a review from busyLambda May 31, 2026 13:30
@coderabbitai coderabbitai Bot added feature New feature or request iris - web Work to be done on iris, the web labels May 31, 2026
@KoZsombat
Copy link
Copy Markdown
Contributor Author

@coderabbitai work

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

@KoZsombat Sure, I'll review the changes in this PR!

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/iris/src/components/admin/substitution-export-dialog.tsx`:
- Around line 28-41: The SubstitutionItem type is duplicated between this file
and substitutions.tsx; extract it to a shared type (for example export type
SubstitutionItem from a new module like `@/types/substitution.ts` or reuse the
existing type from substitutions.tsx) and replace the local declaration with an
import of SubstitutionItem in substitution-export-dialog.tsx; ensure the
exported type includes the same properties (substitution, teacher, lessons) and
update any imports/usages in both substitution-export-dialog.tsx and
substitutions.tsx to reference the single shared type.
- Line 57: The condition in substitution-export-dialog.tsx uses
dayjs(sub.substitution.date).format('YYYY-MM-DD').startsWith(target), which is
misleading because both sides are full YYYY-MM-DD strings; change the check to
use strict equality (compare the formatted date string to target with ===) so
replace the startsWith usage on the expression
dayjs(sub.substitution.date).format('YYYY-MM-DD') with a === comparison to
target, keeping the same operands (sub.substitution.date formatting and the
target variable) to make the intent exact and robust.
- Around line 141-164: The handleExport function lacks a catch block so failures
during PDF generation (pdf / SubstitutionPDF) or CSV download (downloadCsv)
silently close the dialog; add a catch that imports toast from 'sonner' and on
error calls toast.error with a user-friendly message (including optional
error.message), do not call onOpenChange(false) on failure, and keep the
existing finally to call setLoading(false); update handleExport to wrap the
existing try body in try { ... } catch (error) { toast.error('Export failed: ' +
(error?.message || 'Please try again')); } finally { setLoading(false); } so
export errors are surfaced to the user.

In `@apps/iris/src/components/admin/substitution-pdf.tsx`:
- Around line 128-135: Replace the four em-dashes shown when rows.length === 0
with a clear single-cell message like "No data for selected date" so users know
the export succeeded but contains no rows; modify the empty-state branch inside
the component that renders rows (the block referencing rows, styles.tableRow,
styles.cell and styles.periodCell) to render one centered Text element (use a
single row/view with one Text styled to span/center across the table) or, if
`@react-pdf/renderer` doesn't support colSpan, render the Text in a full-width row
using the tableRow style and a centered cell style instead and remove the
em-dash Text elements.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1d446b8b-2c9a-41f3-bb1e-1d134335e09e

📥 Commits

Reviewing files that changed from the base of the PR and between d9bc1c5 and 5c6fe1a.

⛔ Files ignored due to path filters (3)
  • apps/iris/public/fonts/NotoSans-Bold.ttf is excluded by !**/*.ttf
  • apps/iris/public/fonts/NotoSans-Regular.ttf is excluded by !**/*.ttf
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • apps/iris/package.json
  • apps/iris/public/locales/en/translation.json
  • apps/iris/public/locales/hu/translation.json
  • apps/iris/src/components/admin/substitution-export-dialog.tsx
  • apps/iris/src/components/admin/substitution-pdf.tsx
  • apps/iris/src/routes/_private/admin/timetable/substitutions.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: typecheck
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (6)
apps/{chronos,iris}/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/instructions/filc-reuse.instructions.md)

apps/{chronos,iris}/src/**/*.{ts,tsx}: Reuse existing helpers, types, schemas, and hooks before adding new ones. Check nearby feature folders first, then shared files such as apps/chronos/src/database/helpers.ts, apps/iris/src/utils/query-keys.ts, apps/iris/src/hooks/use-has-permission.ts, apps/iris/src/components/admin/admin.types.ts, and apps/iris/src/components/doorlock/doorlock.types.ts
When a second call site needs the same logic, prefer extracting or extending the existing abstraction instead of creating a parallel helper with a slightly different name
Keep abstractions local to the narrowest shared boundary that already exists. Do not create cross-app utilities for one feature-specific use
Extend existing dialog props, response shapes, and query key families instead of re-declaring near-identical types in each file
Prefer the smallest root-cause fix that matches neighboring code over broad rewrites or speculative cleanup
Keep imports on the app alias boundary: #... for Chronos and @/... for Iris

Files:

  • apps/iris/src/routes/_private/admin/timetable/substitutions.tsx
  • apps/iris/src/components/admin/substitution-export-dialog.tsx
  • apps/iris/src/components/admin/substitution-pdf.tsx
apps/iris/src/routes/**/*.tsx

📄 CodeRabbit inference engine (.github/instructions/iris-data-flow.instructions.md)

apps/iris/src/routes/**/*.tsx: Follow route composition patterns from apps/iris/src/routes/_private/admin/news/system-messages.tsx: use createFileRoute(...) at the top, then permission gating, then queries and mutations grouped near the component that owns them
Keep search-param-driven page state in TanStack Router when the page already uses it for filters or selection. Do not fork that state into unrelated local state

Files:

  • apps/iris/src/routes/_private/admin/timetable/substitutions.tsx
apps/iris/src/{routes,components}/**/*.tsx

📄 CodeRabbit inference engine (.github/instructions/iris-data-flow.instructions.md)

apps/iris/src/{routes,components}/**/*.tsx: Always use centralized keys from apps/iris/src/utils/query-keys.ts for React Query. Do not introduce inline array query keys for existing domains
Use parseResponse(...) and the generated Hono client from apps/iris/src/utils/hc.ts for API requests when that is the local pattern
When a mutation changes server state, invalidate every affected query family, not just the page-local list. Follow the multi-invalidation pattern already used in admin news and doorlock screens
Reuse apps/iris/src/hooks/use-has-permission.ts and existing permission guard components instead of duplicating permission logic in the view
New user-facing error and success messages should go through t(...) and the locale files, even when surfaced through toasts

Files:

  • apps/iris/src/routes/_private/admin/timetable/substitutions.tsx
  • apps/iris/src/components/admin/substitution-export-dialog.tsx
  • apps/iris/src/components/admin/substitution-pdf.tsx
apps/iris/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/instructions/iris.instructions.md)

apps/iris/src/**/*.{ts,tsx}: Keep user-facing text in t(...) and update both locale trees under apps/iris/public/locales/en and apps/iris/public/locales/hu
TanStack Form is the default form pattern; follow examples with useForm, useStore(form.store, selector), and <form.Field>{(field) => ...}</form.Field>
form.reset(values) takes raw values, not { values }. form.reset and form.setFieldValue are not stable useEffect dependencies, so omit them from dependency arrays when needed
Base UI dropdown wrappers use onClick, not Radix-style onSelect, unless the local component explicitly exposes a different API
apps/iris/src/components/ui/chart.tsx already owns ResponsiveContainer; do not wrap chart children in another one
Keep public timetable filter state in TanStack Router search params instead of duplicating it in unrelated local state

Files:

  • apps/iris/src/routes/_private/admin/timetable/substitutions.tsx
  • apps/iris/src/components/admin/substitution-export-dialog.tsx
  • apps/iris/src/components/admin/substitution-pdf.tsx
apps/iris/src/routes/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/instructions/iris.instructions.md)

Treat apps/iris/src/route-tree.gen.ts as generated; change source files under apps/iris/src/routes instead

Files:

  • apps/iris/src/routes/_private/admin/timetable/substitutions.tsx
apps/iris/src/components/**/*dialog.tsx

📄 CodeRabbit inference engine (.github/instructions/iris-dialog-form.instructions.md)

apps/iris/src/components/**/*dialog.tsx: Follow the dialog structure used in files like card-dialog.tsx and user-dialog.tsx: create the form near the top of the component, derive reactive slices with useStore(form.store, selector), and render fields with <form.Field>{(field) => ...}</form.Field>
Reuse validation schemas from apps/iris/src/utils/form-schemas.ts when available. If a schema becomes shared by multiple dialogs, move it there instead of copying validation logic
form.reset takes raw values, not { values: ... }. Because form.reset and form.setFieldValue are not stable dependencies, do not add them to useEffect arrays when synchronizing dialog state
Reuse or extend shared dialog prop types such as admin.types.ts and doorlock.types.ts instead of defining near-duplicate props in each dialog
Keep submit side effects together: mutation success should close the dialog, invalidate the relevant query keys, and surface translated success or failure feedback
New labels, button text, placeholders, and empty states belong in t(...) and the locale files, even if older dialogs still have hardcoded strings

Files:

  • apps/iris/src/components/admin/substitution-export-dialog.tsx
🔇 Additional comments (7)
apps/iris/package.json (1)

14-14: LGTM!

apps/iris/public/locales/en/translation.json (1)

246-252: LGTM!

apps/iris/public/locales/hu/translation.json (1)

246-252: LGTM!

apps/iris/src/components/admin/substitution-pdf.tsx (2)

1-8: LGTM!

Also applies to: 28-46, 48-101, 103-127


17-23: ⚡ Quick win

NotoSans PDF font assets are present

apps/iris/public/fonts/ contains NotoSans-Regular.ttf and NotoSans-Bold.ttf, so the Font.register(...) in apps/iris/src/components/admin/substitution-pdf.tsx can load /fonts/NotoSans-*.ttf as-is.

apps/iris/src/components/admin/substitution-export-dialog.tsx (1)

1-26: LGTM!

Also applies to: 49-78, 80-122, 124-140, 166-217

apps/iris/src/routes/_private/admin/timetable/substitutions.tsx (1)

9-9: LGTM!

Also applies to: 14-14, 68-68, 319-322, 512-516

Comment on lines +28 to +41
type SubstitutionItem = {
substitution: { id: string; date: string };
teacher: { firstName: string; lastName: string } | null;
lessons: Array<
| {
id: string;
teachers?: Array<{ id: string; name: string; short?: string }>;
period?: { period: number } | null;
cohorts?: string[];
}
| null
| undefined
>;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Extract shared type instead of duplicating SubstitutionItem.

The SubstitutionItem type is duplicated here and in substitutions.tsx. Per coding guidelines, prefer reusing existing types over re-declaring near-identical definitions.

Consider extracting the type to a shared location (e.g., @/types/substitution.ts) or deriving it from the API response type in both files.

As per coding guidelines: "Reuse existing helpers, types, schemas, and hooks before adding new ones" and "Extend existing dialog props, response shapes, and query key families instead of re-declaring near-identical types in each file".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/iris/src/components/admin/substitution-export-dialog.tsx` around lines
28 - 41, The SubstitutionItem type is duplicated between this file and
substitutions.tsx; extract it to a shared type (for example export type
SubstitutionItem from a new module like `@/types/substitution.ts` or reuse the
existing type from substitutions.tsx) and replace the local declaration with an
import of SubstitutionItem in substitution-export-dialog.tsx; ensure the
exported type includes the same properties (substitution, teacher, lessons) and
update any imports/usages in both substitution-export-dialog.tsx and
substitutions.tsx to reference the single shared type.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The finding is not valid — the two SubstitutionItem types only share a name; their lesson shapes differ:

Dialog type (substitution-export-dialog.tsx:29-42): lessons have id, teachers, period, cohorts — no subject
Route type (substitutions.tsx:48): NonNullable<SubstitutionApiResponse['data']>[number] — the inferred API type which includes subject (accessed at lines 207 and 440 via l.subject?.name and l.subject?.short)
Extracting a shared type would either:

Use the dialog's narrower type → breaks l.subject access in the route (TypeScript error)
Use the route's full API type → couples the dialog component to the API response shape
These are structurally different types that happen to share a name. No change needed — skipping this finding.

Comment thread apps/iris/src/components/admin/substitution-export-dialog.tsx Outdated
Comment thread apps/iris/src/components/admin/substitution-export-dialog.tsx
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/iris/src/components/admin/substitution-export-dialog.tsx (2)

151-159: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

PDF tab may be blocked by popup blockers; object URL is never revoked.

window.open(url, '_blank') runs after await pdf(...).toBlob(), so it is outside the original click gesture and many browsers will silently block it — the export then appears to do nothing. The created object URL is also never revoked, leaking memory across exports. Prefer the same anchor-download approach used by downloadCsv, which both avoids the popup blocker and lets you revoke the URL.

🔧 Proposed fix using an anchor download
       if (format === 'pdf') {
         const blob = await pdf(
           <SubstitutionPDF date={dateLabel} labels={labels} rows={rows} />
         ).toBlob();
         const url = URL.createObjectURL(blob);
-        window.open(url, '_blank');
+        const a = document.createElement('a');
+        a.href = url;
+        a.download = `substitutions-${isoDate}.pdf`;
+        a.click();
+        URL.revokeObjectURL(url);
       } else {
         downloadCsv(rows, `substitutions-${isoDate}.csv`, labels);
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/iris/src/components/admin/substitution-export-dialog.tsx` around lines
151 - 159, The PDF export currently calls window.open(url, '_blank') after
awaiting pdf(...).toBlob(), which can be blocked and never revokes the object
URL; instead create an <a> element, set its href to the object URL, set download
to `substitutions-${isoDate}.pdf`, append it (or not), programmatically call
a.click() within the original click handler to avoid popup blockers, then revoke
the object URL (URL.revokeObjectURL(url)) after the click (e.g., via setTimeout
0 or onfocus/onload) and remove the anchor; update the branch handling `format
=== 'pdf'` where `pdf(<SubstitutionPDF .../>).toBlob()` is used and mirror the
`downloadCsv` anchor-download behavior.

91-94: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

CSV escaping misses \r and offers no formula-injection guard.

escapeS quotes on ;, ", and \n, but a lone \r (or \r\n embedded in a value) is not quoted, which can break row parsing. Additionally, exported values that begin with =, +, -, or @ are interpreted as formulas by spreadsheet apps (CSV injection); teacher names/cohorts are attacker-influenceable in principle. Consider quoting on \r and prefixing risky leading characters.

🛡️ Proposed hardening
   const escapeS = (v: string) =>
-    v.includes(';') || v.includes('"') || v.includes('\n')
-      ? `"${v.replace(/"/g, '""')}"`
-      : v;
+    /[;"\r\n]/.test(v) || /^[=+\-@]/.test(v)
+      ? `"${(/^[=+\-@]/.test(v) ? `'${v}` : v).replace(/"/g, '""')}"`
+      : v;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/iris/src/components/admin/substitution-export-dialog.tsx` around lines
91 - 94, escapeS currently only quotes on ';', '"' and '\n' and doesn't protect
against CSV-injection; update the escapeS function to also detect '\r' (and any
embedded CRLF) when deciding to quote, and if the resulting field begins with
any of the risky leading characters '=','+','-','@' prefix the field with a safe
character (e.g. a leading single quote) before quoting to prevent formula
execution; ensure you still escape internal double quotes by doubling them and
apply the prefix/check against the raw value so names/cohorts that start with
those chars are neutralized.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/iris/src/components/admin/substitution-export-dialog.tsx`:
- Around line 162-167: The catch block in substitution-export-dialog.tsx
currently passes a hardcoded 'Export failed' string as the fallback message to
t('error.generic'); update it to use a localized fallback by calling t(...) for
the fallback (e.g. use t('error.exportFailed') as the fallback message) so the
toast.error uses t('error.generic', { message: error instanceof Error ?
error.message : t('error.exportFailed') }); if the key 'error.exportFailed' (or
a similarly named key) doesn't exist, add it to the locale files for both en and
hu.

In `@apps/iris/src/components/admin/substitution-pdf.tsx`:
- Around line 128-134: The empty-state string in the PDF table is hardcoded;
replace it to use the provided translated label (e.g., use labels.empty or
similar) instead of the literal "No substitutions found for this date" in the
component rendering (where rows.length === 0, View/ Text with
styles.tableRow/styles.cell/styles.dash). Update the dialog that renders this
component to pass empty: t('substitution.exportNoData') via the labels prop, and
add the substitution.exportNoData key and translations to both
apps/iris/public/locales/en and apps/iris/public/locales/hu locale files.

---

Outside diff comments:
In `@apps/iris/src/components/admin/substitution-export-dialog.tsx`:
- Around line 151-159: The PDF export currently calls window.open(url, '_blank')
after awaiting pdf(...).toBlob(), which can be blocked and never revokes the
object URL; instead create an <a> element, set its href to the object URL, set
download to `substitutions-${isoDate}.pdf`, append it (or not), programmatically
call a.click() within the original click handler to avoid popup blockers, then
revoke the object URL (URL.revokeObjectURL(url)) after the click (e.g., via
setTimeout 0 or onfocus/onload) and remove the anchor; update the branch
handling `format === 'pdf'` where `pdf(<SubstitutionPDF .../>).toBlob()` is used
and mirror the `downloadCsv` anchor-download behavior.
- Around line 91-94: escapeS currently only quotes on ';', '"' and '\n' and
doesn't protect against CSV-injection; update the escapeS function to also
detect '\r' (and any embedded CRLF) when deciding to quote, and if the resulting
field begins with any of the risky leading characters '=','+','-','@' prefix the
field with a safe character (e.g. a leading single quote) before quoting to
prevent formula execution; ensure you still escape internal double quotes by
doubling them and apply the prefix/check against the raw value so names/cohorts
that start with those chars are neutralized.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7636b01c-cddb-43fe-ab5b-ddac525e2cab

📥 Commits

Reviewing files that changed from the base of the PR and between 5c6fe1a and b8789ca.

📒 Files selected for processing (2)
  • apps/iris/src/components/admin/substitution-export-dialog.tsx
  • apps/iris/src/components/admin/substitution-pdf.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (4)
apps/{chronos,iris}/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/instructions/filc-reuse.instructions.md)

apps/{chronos,iris}/src/**/*.{ts,tsx}: Reuse existing helpers, types, schemas, and hooks before adding new ones. Check nearby feature folders first, then shared files such as apps/chronos/src/database/helpers.ts, apps/iris/src/utils/query-keys.ts, apps/iris/src/hooks/use-has-permission.ts, apps/iris/src/components/admin/admin.types.ts, and apps/iris/src/components/doorlock/doorlock.types.ts
When a second call site needs the same logic, prefer extracting or extending the existing abstraction instead of creating a parallel helper with a slightly different name
Keep abstractions local to the narrowest shared boundary that already exists. Do not create cross-app utilities for one feature-specific use
Extend existing dialog props, response shapes, and query key families instead of re-declaring near-identical types in each file
Prefer the smallest root-cause fix that matches neighboring code over broad rewrites or speculative cleanup
Keep imports on the app alias boundary: #... for Chronos and @/... for Iris

Files:

  • apps/iris/src/components/admin/substitution-pdf.tsx
  • apps/iris/src/components/admin/substitution-export-dialog.tsx
apps/iris/src/{routes,components}/**/*.tsx

📄 CodeRabbit inference engine (.github/instructions/iris-data-flow.instructions.md)

apps/iris/src/{routes,components}/**/*.tsx: Always use centralized keys from apps/iris/src/utils/query-keys.ts for React Query. Do not introduce inline array query keys for existing domains
Use parseResponse(...) and the generated Hono client from apps/iris/src/utils/hc.ts for API requests when that is the local pattern
When a mutation changes server state, invalidate every affected query family, not just the page-local list. Follow the multi-invalidation pattern already used in admin news and doorlock screens
Reuse apps/iris/src/hooks/use-has-permission.ts and existing permission guard components instead of duplicating permission logic in the view
New user-facing error and success messages should go through t(...) and the locale files, even when surfaced through toasts

Files:

  • apps/iris/src/components/admin/substitution-pdf.tsx
  • apps/iris/src/components/admin/substitution-export-dialog.tsx
apps/iris/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/instructions/iris.instructions.md)

apps/iris/src/**/*.{ts,tsx}: Keep user-facing text in t(...) and update both locale trees under apps/iris/public/locales/en and apps/iris/public/locales/hu
TanStack Form is the default form pattern; follow examples with useForm, useStore(form.store, selector), and <form.Field>{(field) => ...}</form.Field>
form.reset(values) takes raw values, not { values }. form.reset and form.setFieldValue are not stable useEffect dependencies, so omit them from dependency arrays when needed
Base UI dropdown wrappers use onClick, not Radix-style onSelect, unless the local component explicitly exposes a different API
apps/iris/src/components/ui/chart.tsx already owns ResponsiveContainer; do not wrap chart children in another one
Keep public timetable filter state in TanStack Router search params instead of duplicating it in unrelated local state

Files:

  • apps/iris/src/components/admin/substitution-pdf.tsx
  • apps/iris/src/components/admin/substitution-export-dialog.tsx
apps/iris/src/components/**/*dialog.tsx

📄 CodeRabbit inference engine (.github/instructions/iris-dialog-form.instructions.md)

apps/iris/src/components/**/*dialog.tsx: Follow the dialog structure used in files like card-dialog.tsx and user-dialog.tsx: create the form near the top of the component, derive reactive slices with useStore(form.store, selector), and render fields with <form.Field>{(field) => ...}</form.Field>
Reuse validation schemas from apps/iris/src/utils/form-schemas.ts when available. If a schema becomes shared by multiple dialogs, move it there instead of copying validation logic
form.reset takes raw values, not { values: ... }. Because form.reset and form.setFieldValue are not stable dependencies, do not add them to useEffect arrays when synchronizing dialog state
Reuse or extend shared dialog prop types such as admin.types.ts and doorlock.types.ts instead of defining near-duplicate props in each dialog
Keep submit side effects together: mutation success should close the dialog, invalidate the relevant query keys, and surface translated success or failure feedback
New labels, button text, placeholders, and empty states belong in t(...) and the locale files, even if older dialogs still have hardcoded strings

Files:

  • apps/iris/src/components/admin/substitution-export-dialog.tsx
🔇 Additional comments (2)
apps/iris/src/components/admin/substitution-pdf.tsx (1)

130-130: 💤 Low value

colSpan is not honored by @react-pdf/renderer.

This was already flagged on a prior commit: @react-pdf/renderer does not support colSpan on <Text>, so the cell will not span the table. The text still renders inside a single flex: 1 cell, so the layout works but the prop is a no-op (and may surface a TS prop error). Consider removing it or widening the row style instead.

apps/iris/src/components/admin/substitution-export-dialog.tsx (1)

57-60: LGTM!

Comment on lines +162 to +167
} catch (error) {
toast.error(
t('error.generic', {
message: error instanceof Error ? error.message : 'Export failed',
})
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm translation keys used by the export flow exist in en/hu locales.
fd -t f translation.json apps/iris/public/locales | while read -r f; do
  echo "== $f =="
  jq '{ "error.generic": (.error.generic // "MISSING"),
        "substitution.exportTitle": (.substitution.exportTitle // "MISSING"),
        "substitution.exportNoData": (.substitution.exportNoData // "MISSING"),
        "substitution.exportFormat": (.substitution.exportFormat // "MISSING"),
        "substitution.exportClass": (.substitution.exportClass // "MISSING") }' "$f"
done

Repository: filcdev/filc

Length of output: 638


apps/iris/src/components/admin/substitution-export-dialog.tsx (162-167): use a localized fallback for non-Error export failures

  • error.generic already exists in both en/hu and supports message interpolation ({{message}}).
  • Remove the hardcoded 'Export failed' fallback and supply a t(...)-based localized message instead (add a locale key if needed).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/iris/src/components/admin/substitution-export-dialog.tsx` around lines
162 - 167, The catch block in substitution-export-dialog.tsx currently passes a
hardcoded 'Export failed' string as the fallback message to t('error.generic');
update it to use a localized fallback by calling t(...) for the fallback (e.g.
use t('error.exportFailed') as the fallback message) so the toast.error uses
t('error.generic', { message: error instanceof Error ? error.message :
t('error.exportFailed') }); if the key 'error.exportFailed' (or a similarly
named key) doesn't exist, add it to the locale files for both en and hu.

Comment thread apps/iris/src/components/admin/substitution-pdf.tsx
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/iris/src/components/admin/substitution-export-dialog.tsx (1)

91-94: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard against CSV formula injection.

escapeS handles quoting for ;, ", and \n, but does not neutralize cells that begin with =, +, -, @ (or tab/CR). Teacher names and cohort values flow unmodified into the CSV, so a crafted value could be interpreted as a formula when the file is opened in Excel/Sheets.

🛡️ Proposed fix to prefix risky values
-  const escapeS = (v: string) =>
-    v.includes(';') || v.includes('"') || v.includes('\n')
-      ? `"${v.replace(/"/g, '""')}"`
-      : v;
+  const escapeS = (v: string) => {
+    // Neutralize formula injection (=, +, -, @, tab, CR) by prefixing with a single quote
+    const guarded = /^[=+\-@\t\r]/.test(v) ? `'${v}` : v;
+    return guarded.includes(';') ||
+      guarded.includes('"') ||
+      guarded.includes('\n')
+      ? `"${guarded.replace(/"/g, '""')}"`
+      : guarded;
+  };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/iris/src/components/admin/substitution-export-dialog.tsx` around lines
91 - 94, escapeS currently only quotes values containing ;, ", or newline but
doesn't neutralize CSV formula injection vectors (cells starting with =, +, -,
@, tab, or CR); update escapeS to detect if the raw string begins with any of
the dangerous characters (characters set: '=', '+', '-', '@', '\t', '\r') and,
before applying the existing quoting/quote-escaping logic, prefix the value with
a single quote (') to neutralize formulas, then continue to double internal
quotes and wrap in quotes as needed; refer to the escapeS function to implement
this guard so teacher names and cohort values are safe in exported CSVs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@apps/iris/src/components/admin/substitution-export-dialog.tsx`:
- Around line 91-94: escapeS currently only quotes values containing ;, ", or
newline but doesn't neutralize CSV formula injection vectors (cells starting
with =, +, -, @, tab, or CR); update escapeS to detect if the raw string begins
with any of the dangerous characters (characters set: '=', '+', '-', '@', '\t',
'\r') and, before applying the existing quoting/quote-escaping logic, prefix the
value with a single quote (') to neutralize formulas, then continue to double
internal quotes and wrap in quotes as needed; refer to the escapeS function to
implement this guard so teacher names and cohort values are safe in exported
CSVs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3d8d88df-2f71-4b78-bf42-cd03fbc743a2

📥 Commits

Reviewing files that changed from the base of the PR and between b8789ca and 385dd4b.

📒 Files selected for processing (2)
  • apps/iris/src/components/admin/substitution-export-dialog.tsx
  • apps/iris/src/components/admin/substitution-pdf.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (4)
apps/{chronos,iris}/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/instructions/filc-reuse.instructions.md)

apps/{chronos,iris}/src/**/*.{ts,tsx}: Reuse existing helpers, types, schemas, and hooks before adding new ones. Check nearby feature folders first, then shared files such as apps/chronos/src/database/helpers.ts, apps/iris/src/utils/query-keys.ts, apps/iris/src/hooks/use-has-permission.ts, apps/iris/src/components/admin/admin.types.ts, and apps/iris/src/components/doorlock/doorlock.types.ts
When a second call site needs the same logic, prefer extracting or extending the existing abstraction instead of creating a parallel helper with a slightly different name
Keep abstractions local to the narrowest shared boundary that already exists. Do not create cross-app utilities for one feature-specific use
Extend existing dialog props, response shapes, and query key families instead of re-declaring near-identical types in each file
Prefer the smallest root-cause fix that matches neighboring code over broad rewrites or speculative cleanup
Keep imports on the app alias boundary: #... for Chronos and @/... for Iris

Files:

  • apps/iris/src/components/admin/substitution-pdf.tsx
  • apps/iris/src/components/admin/substitution-export-dialog.tsx
apps/iris/src/{routes,components}/**/*.tsx

📄 CodeRabbit inference engine (.github/instructions/iris-data-flow.instructions.md)

apps/iris/src/{routes,components}/**/*.tsx: Always use centralized keys from apps/iris/src/utils/query-keys.ts for React Query. Do not introduce inline array query keys for existing domains
Use parseResponse(...) and the generated Hono client from apps/iris/src/utils/hc.ts for API requests when that is the local pattern
When a mutation changes server state, invalidate every affected query family, not just the page-local list. Follow the multi-invalidation pattern already used in admin news and doorlock screens
Reuse apps/iris/src/hooks/use-has-permission.ts and existing permission guard components instead of duplicating permission logic in the view
New user-facing error and success messages should go through t(...) and the locale files, even when surfaced through toasts

Files:

  • apps/iris/src/components/admin/substitution-pdf.tsx
  • apps/iris/src/components/admin/substitution-export-dialog.tsx
apps/iris/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/instructions/iris.instructions.md)

apps/iris/src/**/*.{ts,tsx}: Keep user-facing text in t(...) and update both locale trees under apps/iris/public/locales/en and apps/iris/public/locales/hu
TanStack Form is the default form pattern; follow examples with useForm, useStore(form.store, selector), and <form.Field>{(field) => ...}</form.Field>
form.reset(values) takes raw values, not { values }. form.reset and form.setFieldValue are not stable useEffect dependencies, so omit them from dependency arrays when needed
Base UI dropdown wrappers use onClick, not Radix-style onSelect, unless the local component explicitly exposes a different API
apps/iris/src/components/ui/chart.tsx already owns ResponsiveContainer; do not wrap chart children in another one
Keep public timetable filter state in TanStack Router search params instead of duplicating it in unrelated local state

Files:

  • apps/iris/src/components/admin/substitution-pdf.tsx
  • apps/iris/src/components/admin/substitution-export-dialog.tsx
apps/iris/src/components/**/*dialog.tsx

📄 CodeRabbit inference engine (.github/instructions/iris-dialog-form.instructions.md)

apps/iris/src/components/**/*dialog.tsx: Follow the dialog structure used in files like card-dialog.tsx and user-dialog.tsx: create the form near the top of the component, derive reactive slices with useStore(form.store, selector), and render fields with <form.Field>{(field) => ...}</form.Field>
Reuse validation schemas from apps/iris/src/utils/form-schemas.ts when available. If a schema becomes shared by multiple dialogs, move it there instead of copying validation logic
form.reset takes raw values, not { values: ... }. Because form.reset and form.setFieldValue are not stable dependencies, do not add them to useEffect arrays when synchronizing dialog state
Reuse or extend shared dialog prop types such as admin.types.ts and doorlock.types.ts instead of defining near-duplicate props in each dialog
Keep submit side effects together: mutation success should close the dialog, invalidate the relevant query keys, and surface translated success or failure feedback
New labels, button text, placeholders, and empty states belong in t(...) and the locale files, even if older dialogs still have hardcoded strings

Files:

  • apps/iris/src/components/admin/substitution-export-dialog.tsx
🔇 Additional comments (3)
apps/iris/src/components/admin/substitution-export-dialog.tsx (2)

163-168: ⚡ Quick win

Hardcoded 'Export failed' fallback bypasses i18n.

The non-Error fallback message passed to t('error.generic', ...) is still the hardcoded English string 'Export failed'. Route it through a translated key (e.g. t('error.exportFailed')) and add it to both en and hu locale trees.

As per coding guidelines: "New user-facing error and success messages should go through t(...) and the locale files, even when surfaced through toasts".


50-79: LGTM!

apps/iris/src/components/admin/substitution-pdf.tsx (1)

40-40: LGTM!

Also applies to: 129-135

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/iris/src/components/admin/substitution-export-dialog.tsx (3)

134-136: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use the standard TanStack Form dialog pattern here.

This dialog keeps date/format in ad-hoc useState and wires the inputs manually instead of following the repo’s useForm + useStore(form.store, selector) + <form.Field> pattern for dialogs. Converting it now will keep validation/reset/submission behavior aligned with the rest of Iris dialogs.

As per coding guidelines, "TanStack Form is the default form pattern; follow examples with useForm, useStore(form.store, selector), and <form.Field>{(field) => ...}</form.Field>" and "apps/iris/src/components/**/*dialog.tsx: Follow the dialog structure used in files like card-dialog.tsx and user-dialog.tsx: create the form near the top of the component, derive reactive slices with useStore(form.store, selector), and render fields with <form.Field>{(field) => ...}</form.Field>".

Also applies to: 187-200

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/iris/src/components/admin/substitution-export-dialog.tsx` around lines
134 - 136, The component currently manages date and format with ad-hoc useState
hooks (date, setDate; format, setFormat) and wires inputs manually; replace this
with the TanStack Form dialog pattern by creating a form via useForm near the
top, derive reactive values using useStore(form.store, selector) for
date/format, and render inputs using <form.Field>{(field) => ...}</form.Field>
so validation/reset/submission follow the repo standard (also remove the manual
useState hooks and the manual submit handler in favor of form.handleSubmit).

206-207: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize the format option labels.

PDF and CSV are new user-facing strings but are still hardcoded here. Please move them behind t(...) and add matching keys in both EN/HU locale trees.

As per coding guidelines, "Keep user-facing text in t(...) and update both locale trees under apps/iris/public/locales/en and apps/iris/public/locales/hu".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/iris/src/components/admin/substitution-export-dialog.tsx` around lines
206 - 207, Replace the hardcoded "PDF" and "CSV" labels in the SelectItem nodes
with localized strings via t(...) (e.g., t('substitutionExport.format.pdf') and
t('substitutionExport.format.csv')) inside the substitution-export-dialog
component so the UI uses translations instead of raw text; then add matching
keys and English/Hungarian translations under the locale trees
(apps/iris/public/locales/en and apps/iris/public/locales/hu) such as
"substitutionExport": { "format": { "pdf": "PDF", "csv": "CSV" } } (with HU
equivalents) so both languages are covered.

155-160: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Revoke the PDF blob URL after opening it.

This branch creates an object URL and never releases it, so repeated exports keep those blobs alive until the page unloads.

Possible fix
         const blob = await pdf(
           <SubstitutionPDF date={dateLabel} labels={labels} rows={rows} />
         ).toBlob();
         const url = URL.createObjectURL(blob);
         window.open(url, '_blank');
+        setTimeout(() => URL.revokeObjectURL(url), 60_000);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/iris/src/components/admin/substitution-export-dialog.tsx` around lines
155 - 160, The code creates a PDF blob URL via URL.createObjectURL in the format
=== 'pdf' branch and never releases it; after calling window.open(url, '_blank')
you should revoke the object URL to avoid memory leaks — locate the
pdf(...).toBlob() call that renders <SubstitutionPDF .../> and the url variable
created by URL.createObjectURL, then call URL.revokeObjectURL(url) (optionally
wrapped in a short setTimeout or after confirming the window was opened) to
release the blob reference.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@apps/iris/src/components/admin/substitution-export-dialog.tsx`:
- Around line 134-136: The component currently manages date and format with
ad-hoc useState hooks (date, setDate; format, setFormat) and wires inputs
manually; replace this with the TanStack Form dialog pattern by creating a form
via useForm near the top, derive reactive values using useStore(form.store,
selector) for date/format, and render inputs using <form.Field>{(field) =>
...}</form.Field> so validation/reset/submission follow the repo standard (also
remove the manual useState hooks and the manual submit handler in favor of
form.handleSubmit).
- Around line 206-207: Replace the hardcoded "PDF" and "CSV" labels in the
SelectItem nodes with localized strings via t(...) (e.g.,
t('substitutionExport.format.pdf') and t('substitutionExport.format.csv'))
inside the substitution-export-dialog component so the UI uses translations
instead of raw text; then add matching keys and English/Hungarian translations
under the locale trees (apps/iris/public/locales/en and
apps/iris/public/locales/hu) such as "substitutionExport": { "format": { "pdf":
"PDF", "csv": "CSV" } } (with HU equivalents) so both languages are covered.
- Around line 155-160: The code creates a PDF blob URL via URL.createObjectURL
in the format === 'pdf' branch and never releases it; after calling
window.open(url, '_blank') you should revoke the object URL to avoid memory
leaks — locate the pdf(...).toBlob() call that renders <SubstitutionPDF .../>
and the url variable created by URL.createObjectURL, then call
URL.revokeObjectURL(url) (optionally wrapped in a short setTimeout or after
confirming the window was opened) to release the blob reference.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9ae76e2a-68d5-4016-89b4-183de0f1f23b

📥 Commits

Reviewing files that changed from the base of the PR and between 385dd4b and d45ae9b.

📒 Files selected for processing (1)
  • apps/iris/src/components/admin/substitution-export-dialog.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
apps/{chronos,iris}/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/instructions/filc-reuse.instructions.md)

apps/{chronos,iris}/src/**/*.{ts,tsx}: Reuse existing helpers, types, schemas, and hooks before adding new ones. Check nearby feature folders first, then shared files such as apps/chronos/src/database/helpers.ts, apps/iris/src/utils/query-keys.ts, apps/iris/src/hooks/use-has-permission.ts, apps/iris/src/components/admin/admin.types.ts, and apps/iris/src/components/doorlock/doorlock.types.ts
When a second call site needs the same logic, prefer extracting or extending the existing abstraction instead of creating a parallel helper with a slightly different name
Keep abstractions local to the narrowest shared boundary that already exists. Do not create cross-app utilities for one feature-specific use
Extend existing dialog props, response shapes, and query key families instead of re-declaring near-identical types in each file
Prefer the smallest root-cause fix that matches neighboring code over broad rewrites or speculative cleanup
Keep imports on the app alias boundary: #... for Chronos and @/... for Iris

Files:

  • apps/iris/src/components/admin/substitution-export-dialog.tsx
apps/iris/src/{routes,components}/**/*.tsx

📄 CodeRabbit inference engine (.github/instructions/iris-data-flow.instructions.md)

apps/iris/src/{routes,components}/**/*.tsx: Always use centralized keys from apps/iris/src/utils/query-keys.ts for React Query. Do not introduce inline array query keys for existing domains
Use parseResponse(...) and the generated Hono client from apps/iris/src/utils/hc.ts for API requests when that is the local pattern
When a mutation changes server state, invalidate every affected query family, not just the page-local list. Follow the multi-invalidation pattern already used in admin news and doorlock screens
Reuse apps/iris/src/hooks/use-has-permission.ts and existing permission guard components instead of duplicating permission logic in the view
New user-facing error and success messages should go through t(...) and the locale files, even when surfaced through toasts

Files:

  • apps/iris/src/components/admin/substitution-export-dialog.tsx
apps/iris/src/components/**/*dialog.tsx

📄 CodeRabbit inference engine (.github/instructions/iris-dialog-form.instructions.md)

apps/iris/src/components/**/*dialog.tsx: Follow the dialog structure used in files like card-dialog.tsx and user-dialog.tsx: create the form near the top of the component, derive reactive slices with useStore(form.store, selector), and render fields with <form.Field>{(field) => ...}</form.Field>
Reuse validation schemas from apps/iris/src/utils/form-schemas.ts when available. If a schema becomes shared by multiple dialogs, move it there instead of copying validation logic
form.reset takes raw values, not { values: ... }. Because form.reset and form.setFieldValue are not stable dependencies, do not add them to useEffect arrays when synchronizing dialog state
Reuse or extend shared dialog prop types such as admin.types.ts and doorlock.types.ts instead of defining near-duplicate props in each dialog
Keep submit side effects together: mutation success should close the dialog, invalidate the relevant query keys, and surface translated success or failure feedback
New labels, button text, placeholders, and empty states belong in t(...) and the locale files, even if older dialogs still have hardcoded strings

Files:

  • apps/iris/src/components/admin/substitution-export-dialog.tsx
apps/iris/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/instructions/iris.instructions.md)

apps/iris/src/**/*.{ts,tsx}: Keep user-facing text in t(...) and update both locale trees under apps/iris/public/locales/en and apps/iris/public/locales/hu
TanStack Form is the default form pattern; follow examples with useForm, useStore(form.store, selector), and <form.Field>{(field) => ...}</form.Field>
form.reset(values) takes raw values, not { values }. form.reset and form.setFieldValue are not stable useEffect dependencies, so omit them from dependency arrays when needed
Base UI dropdown wrappers use onClick, not Radix-style onSelect, unless the local component explicitly exposes a different API
apps/iris/src/components/ui/chart.tsx already owns ResponsiveContainer; do not wrap chart children in another one
Keep public timetable filter state in TanStack Router search params instead of duplicating it in unrelated local state

Files:

  • apps/iris/src/components/admin/substitution-export-dialog.tsx
🔇 Additional comments (1)
apps/iris/src/components/admin/substitution-export-dialog.tsx (1)

91-96: LGTM!

Copy link
Copy Markdown
Member

@nemvince nemvince left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ember mi a fasz ez a PR

  • Fontot telepited is es be is rakod fajlkent a projektbe (amugy minek kulon font??? hasznald az OS stacket vagy legalabb hasznald a packaget, nem akarok kulon fajlt)
  • Helyettesites export az backend is lesz, itt is lesz egy date range selector es azt az idovonalat exportalja
  • Type mar megint ujra defineolva

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request iris - web Work to be done on iris, the web

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

Admin panel - Exportálás

2 participants