From 02e3958bc8f6cbc9938c3c0d51cd0e5f7e710758 Mon Sep 17 00:00:00 2001 From: Kasun Vithanage Date: Tue, 27 Jan 2026 19:53:26 +0530 Subject: [PATCH] feat: add optional subtitle field for print header Add a new optional subtitle field that appears as a second line below the Hospital Name in printed reports. The subtitle can be configured in Settings and toggled on/off in the template builder's Header block. Print header structure: - Line 1: Hospital Name - Line 2: Subtitle (new, optional) - Line 3: Unit/Department --- src/main/db/default-templates.ts | 2 + src/main/db/migrations.ts | 4 +- .../db/migrations/018_subtitle_setting.ts | 53 +++++++++++++++++++ .../components/settings/GeneralSettings.tsx | 29 ++++++++++ .../template-builder/SampleDataContext.tsx | 3 ++ .../properties/HeaderProperties.tsx | 11 ++++ .../sample-data/SettingsFields.tsx | 33 +++++++----- src/renderer/src/lib/print.ts | 3 ++ src/renderer/src/lib/template-context.ts | 3 ++ src/renderer/src/lib/template-renderer.ts | 4 ++ src/shared/constants/template-fields.ts | 10 +++- src/shared/types/template-blocks.d.ts | 2 + 12 files changed, 142 insertions(+), 15 deletions(-) create mode 100644 src/main/db/migrations/018_subtitle_setting.ts diff --git a/src/main/db/default-templates.ts b/src/main/db/default-templates.ts index 3c1b606..5228253 100644 --- a/src/main/db/default-templates.ts +++ b/src/main/db/default-templates.ts @@ -15,6 +15,7 @@ export const defaultSurgeryTemplate = { type: 'header', props: { showHospital: true, + showSubtitle: true, showUnit: true, showTelephone: true, showLogo: false, @@ -193,6 +194,7 @@ export const defaultFollowupTemplate = { type: 'header', props: { showHospital: true, + showSubtitle: true, showUnit: true, showTelephone: true, showLogo: false, diff --git a/src/main/db/migrations.ts b/src/main/db/migrations.ts index ed6d1b5..3010b44 100644 --- a/src/main/db/migrations.ts +++ b/src/main/db/migrations.ts @@ -16,6 +16,7 @@ import * as m_014_sync_default_templates from './migrations/014_sync_default_tem import * as m_015_add_discharge_plan from './migrations/015_add_discharge_plan' import * as m_016_update_default_templates_discharge_plan from './migrations/016_update_default_templates_discharge_plan' import * as m_017_sync_print_templates_with_defaults from './migrations/017_sync_print_templates_with_defaults' +import * as m_018_subtitle_setting from './migrations/018_subtitle_setting' export default { '000_init': m_000_init, @@ -35,5 +36,6 @@ export default { '014_sync_default_templates': m_014_sync_default_templates, '015_add_discharge_plan': m_015_add_discharge_plan, '016_update_default_templates_discharge_plan': m_016_update_default_templates_discharge_plan, - '017_sync_print_templates_with_defaults': m_017_sync_print_templates_with_defaults + '017_sync_print_templates_with_defaults': m_017_sync_print_templates_with_defaults, + '018_subtitle_setting': m_018_subtitle_setting } diff --git a/src/main/db/migrations/018_subtitle_setting.ts b/src/main/db/migrations/018_subtitle_setting.ts new file mode 100644 index 0000000..7637fef --- /dev/null +++ b/src/main/db/migrations/018_subtitle_setting.ts @@ -0,0 +1,53 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Kysely } from 'kysely' +import { defaultSurgeryTemplate, defaultFollowupTemplate } from '../default-templates' + +/** + * Add subtitle support to print templates. + * + * This migration: + * 1. Updates default_print_templates with the new template structure (includes showSubtitle prop) + * 2. Syncs print_templates with the updated defaults + */ +export async function up(db: Kysely): Promise { + const now = Date.now() + + // Update default_print_templates with new template structures + await db + .updateTable('default_print_templates') + .set({ + structure: JSON.stringify(defaultSurgeryTemplate) + }) + .where('key', '=', 'surgery-standard') + .execute() + + await db + .updateTable('default_print_templates') + .set({ + structure: JSON.stringify(defaultFollowupTemplate) + }) + .where('key', '=', 'followup-standard') + .execute() + + // Sync print_templates with updated defaults + const defaults = await db + .selectFrom('default_print_templates') + .select(['key', 'type', 'structure']) + .execute() + + for (const defaultTemplate of defaults) { + await db + .updateTable('print_templates') + .set({ + structure: defaultTemplate.structure, + updated_at: now + }) + .where('type', '=', defaultTemplate.type) + .where('is_default', '=', 1) + .execute() + } +} + +export async function down(_db: Kysely): Promise { + // No-op: This is a data sync migration, not reversible without storing old data +} diff --git a/src/renderer/src/components/settings/GeneralSettings.tsx b/src/renderer/src/components/settings/GeneralSettings.tsx index e2a0e18..0d350f3 100644 --- a/src/renderer/src/components/settings/GeneralSettings.tsx +++ b/src/renderer/src/components/settings/GeneralSettings.tsx @@ -31,6 +31,7 @@ type UpdateChannel = 'stable' | 'beta' | 'alpha' const formSchema = z.object({ hospital: z.string(), + subtitle: z.string().optional(), unit: z.string(), telephone: z.string() }) @@ -44,6 +45,7 @@ export const GeneralSettings = () => { resolver: zodResolver(formSchema), defaultValues: { hospital: '', + subtitle: '', unit: '', telephone: '' } @@ -69,6 +71,7 @@ export const GeneralSettings = () => { if (settings) { form.reset({ hospital: settings['hospital'] || '', + subtitle: settings['subtitle'] || '', unit: settings['unit'] || '', telephone: settings['telephone'] || '' }) @@ -151,6 +154,32 @@ export const GeneralSettings = () => { )} /> + ( + +
+
+ +
+ Subtitle +
+ + + + + Optional second line shown below Hospital Name in printed reports + + +
+ )} + /> +
{ const hospital = settings.hospital + const subtitle = settings.subtitle const unit = settings.unit const telephone = settings.telephone return { hospital: hospital || null, + subtitle: subtitle || null, unit: unit || null, telephone: telephone || null, hasAny: Boolean(hospital || unit || telephone) @@ -116,6 +118,7 @@ export const SampleDataProvider = ({ templateType, children }: SampleDataProvide ...base, settings: { hospital: realSettings.hospital || base.settings.hospital, + subtitle: realSettings.subtitle || base.settings.subtitle, unit: realSettings.unit || base.settings.unit, telephone: realSettings.telephone || base.settings.telephone } diff --git a/src/renderer/src/components/template-builder/properties/HeaderProperties.tsx b/src/renderer/src/components/template-builder/properties/HeaderProperties.tsx index d3feaec..6d451d5 100644 --- a/src/renderer/src/components/template-builder/properties/HeaderProperties.tsx +++ b/src/renderer/src/components/template-builder/properties/HeaderProperties.tsx @@ -36,6 +36,17 @@ export const HeaderProperties = ({ block, onUpdate }: HeaderPropertiesProps) => />
+
+ + updateProps({ showSubtitle: checked })} + /> +
+
)} - {/* Hospital, Unit */} + {/* Hospital Name */} + updateField('settings.hospital', v)} + /> + + {/* Subtitle */} + updateField('settings.subtitle', v)} + /> + + {/* Unit, Telephone */}
- updateField('settings.hospital', v)} - /> updateField('settings.unit', v)} /> + updateField('settings.telephone', v)} + />
- {/* Telephone */} - updateField('settings.telephone', v)} - /> - {/* Note */} {!usingRealSettings && (

diff --git a/src/renderer/src/lib/print.ts b/src/renderer/src/lib/print.ts index 3872e75..62d5d3c 100644 --- a/src/renderer/src/lib/print.ts +++ b/src/renderer/src/lib/print.ts @@ -40,6 +40,7 @@ export const surgeryPrintData = ( }, settings: { hospital: settings?.hospital || '', + subtitle: settings?.subtitle || '', unit: settings?.unit || '', telephone: settings?.telephone || '' } @@ -70,6 +71,7 @@ export const followupPrintData = ( }, settings: { hospital: settings?.hospital || '', + subtitle: settings?.subtitle || '', unit: settings?.unit || '', telephone: settings?.telephone || '' } @@ -128,6 +130,7 @@ export const createSurgeryContext = ( }, settings: { hospital: settings?.hospital || 'Hospital Name', + subtitle: settings?.subtitle || '', unit: settings?.unit || 'Unit Name', telephone: settings?.telephone || null } diff --git a/src/renderer/src/lib/template-context.ts b/src/renderer/src/lib/template-context.ts index dc3cc1c..7fdb555 100644 --- a/src/renderer/src/lib/template-context.ts +++ b/src/renderer/src/lib/template-context.ts @@ -69,6 +69,7 @@ export const getSampleContext = (type: TemplateType): TemplateContext => { }, settings: { hospital: 'General Hospital Colombo', + subtitle: 'Teaching Hospital', unit: 'Surgical Unit A', telephone: '+94 11 234 5678' } @@ -135,6 +136,7 @@ export interface CreateContextParams { } settings: { hospital?: string + subtitle?: string unit?: string telephone?: string | null } @@ -211,6 +213,7 @@ export const createTemplateContext = (params: CreateContextParams): TemplateCont : undefined, settings: { hospital: params.settings.hospital || 'Hospital Name', + subtitle: params.settings.subtitle || '', unit: params.settings.unit || 'Unit Name', telephone: params.settings.telephone || null } diff --git a/src/renderer/src/lib/template-renderer.ts b/src/renderer/src/lib/template-renderer.ts index 9ca64ab..8a548d1 100644 --- a/src/renderer/src/lib/template-renderer.ts +++ b/src/renderer/src/lib/template-renderer.ts @@ -91,6 +91,10 @@ const renderHeader = (block: HeaderBlock, context: TemplateContext): string => { html += `

${escapeHtml(context.settings.hospital)}

` } + if (props.showSubtitle && context.settings.subtitle) { + html += `

${escapeHtml(context.settings.subtitle)}

` + } + if (props.showUnit && context.settings.unit) { html += `

${escapeHtml(context.settings.unit)}

` } diff --git a/src/shared/constants/template-fields.ts b/src/shared/constants/template-fields.ts index e8b7e70..7f125e3 100644 --- a/src/shared/constants/template-fields.ts +++ b/src/shared/constants/template-fields.ts @@ -223,7 +223,14 @@ export const TEMPLATE_FIELDS: FieldDefinition[] = [ label: 'Hospital Name', category: 'settings', description: 'Name of the hospital', - example: 'General Hospital Colombo' + example: 'National Cancer Institute' + }, + { + path: 'settings.subtitle', + label: 'Subtitle', + category: 'settings', + description: 'Optional second line below hospital name', + example: 'Teaching Hospital' }, { path: 'settings.unit', @@ -263,6 +270,7 @@ export const BLOCK_DEFINITIONS: BlockDefinition[] = [ description: 'Hospital header with name, unit, and contact info', defaultProps: { showHospital: true, + showSubtitle: true, showUnit: true, showTelephone: true, showLogo: false, diff --git a/src/shared/types/template-blocks.d.ts b/src/shared/types/template-blocks.d.ts index 27795d5..90c0cfd 100644 --- a/src/shared/types/template-blocks.d.ts +++ b/src/shared/types/template-blocks.d.ts @@ -12,6 +12,7 @@ export interface BaseBlock { export interface HeaderBlockProps { showHospital: boolean + showSubtitle: boolean showUnit: boolean showTelephone: boolean showLogo: boolean @@ -301,6 +302,7 @@ export interface TemplateContext { } settings: { hospital: string + subtitle: string unit: string telephone: string | null }