diff --git a/server/src/db/repositories/base-repository.ts b/server/src/db/repositories/base-repository.ts index d07bc28..65f3950 100644 --- a/server/src/db/repositories/base-repository.ts +++ b/server/src/db/repositories/base-repository.ts @@ -3,6 +3,7 @@ import { Knex } from 'knex'; export abstract class BaseRepository { protected tableName: string; protected db: Knex; + protected items: T[] = []; constructor(db: Knex, tableName: string) { this.db = db; diff --git a/server/src/db/repositories/in-memory-repository.ts b/server/src/db/repositories/in-memory-repository.ts index 0ad2a2c..38a2175 100644 --- a/server/src/db/repositories/in-memory-repository.ts +++ b/server/src/db/repositories/in-memory-repository.ts @@ -1,9 +1,9 @@ -import { PrintHistory, Step } from '@shared/index'; +import { PrintHistory, Step, Task } from '@shared/index'; import { Knex } from 'knex'; import { BaseRepository } from './base-repository'; export class InMemoryRepository extends BaseRepository { - private items: T[] = []; + protected items: T[] = []; private nextId = 1; constructor(db: Knex, tableName: string) { @@ -76,4 +76,15 @@ export class InMemoryStepRepository extends InMemoryRepository { last_printed_at: new Date(), }); } + + async findTaskById(stepId: number): Promise { + const step = this.items.find(s => s.id === stepId); + return step ? { id: step.task_id, name: 'Test Task', group_id: 1, print_count: 0, created_at: new Date(), updated_at: new Date() } : undefined; + } + + async findStepNumber(stepId: number): Promise { + const step = this.items.find(s => s.id === stepId); + if (!step) return 0; + return this.items.filter(s => s.task_id === step.task_id).findIndex(s => s.id === stepId) + 1; + } } \ No newline at end of file diff --git a/server/src/db/repositories/step-repository.ts b/server/src/db/repositories/step-repository.ts index 1e750fd..8210138 100644 --- a/server/src/db/repositories/step-repository.ts +++ b/server/src/db/repositories/step-repository.ts @@ -1,6 +1,6 @@ import { Knex } from 'knex'; import { BaseRepository } from './base-repository'; -import { Step } from '@shared/index'; +import { Step, Task } from '@shared/index'; export class StepRepository extends BaseRepository { constructor(db: Knex) { @@ -23,4 +23,35 @@ export class StepRepository extends BaseRepository { last_printed_at: new Date() }); } + + async findTaskById(stepId: number): Promise { + const step = await this.db('steps') + .where({ id: stepId }) + .first(); + + if (!step) { + return undefined; + } + + return await this.db('tasks') + .where({ id: step.task_id }) + .first(); + } + + async findStepNumber(stepId: number): Promise { + const step = await this.db('steps') + .where({ id: stepId }) + .first(); + + if (!step) { + return 0; + } + + const steps = await this.db('steps') + .where({ task_id: step.task_id }) + .orderBy('order', 'asc') + .select('id'); + + return steps.findIndex(s => s.id === stepId) + 1; + } } \ No newline at end of file diff --git a/server/src/printer/__tests__/format-utils.test.ts b/server/src/printer/__tests__/format-utils.test.ts index 2d61ac7..053038c 100644 --- a/server/src/printer/__tests__/format-utils.test.ts +++ b/server/src/printer/__tests__/format-utils.test.ts @@ -83,9 +83,7 @@ describe('formatUtils', () => { expect(section).toEqual([ '[ ] Header', '='.repeat(40), - '', 'Content', - '' ]); }); @@ -94,9 +92,7 @@ describe('formatUtils', () => { expect(section).toEqual([ '[ ] Header', '-'.repeat(40), - '', 'Content', - '' ]); }); @@ -106,8 +102,6 @@ describe('formatUtils', () => { '[ ] Header', '='.repeat(40), '', - '', - '' ]); }); @@ -116,10 +110,35 @@ describe('formatUtils', () => { expect(section).toEqual([ '[ ] ', '='.repeat(40), - '', 'Content', - '' ]); }); }); + + describe('formatStepHeader', () => { + it('should format step header with just step name and number', () => { + const header = formatUtils.formatStepHeader('Test Step', 1); + expect(header).toBe('Step 1: Test Step'); + }); + + it('should format step header with step number and task name in single step view', () => { + const header = formatUtils.formatStepHeader('Test Step', 1, 'Test Task'); + expect(header).toBe('Step 1 of Test Task: Test Step'); + }); + + it('should format step header with step number but no task name in task view', () => { + const header = formatUtils.formatStepHeader('Test Step', 1, 'Test Task', true); + expect(header).toBe('Step 1: Test Step'); + }); + + it('should handle empty step name', () => { + const header = formatUtils.formatStepHeader('', 1); + expect(header).toBe('Step 1: '); + }); + + it('should handle zero step number', () => { + const header = formatUtils.formatStepHeader('Test Step', 0); + expect(header).toBe('Step 0: Test Step'); + }); + }); }); \ No newline at end of file diff --git a/server/src/printer/format-utils.ts b/server/src/printer/format-utils.ts index 56981c7..a24d2ad 100644 --- a/server/src/printer/format-utils.ts +++ b/server/src/printer/format-utils.ts @@ -31,6 +31,28 @@ export const formatUtils = { }); }, + /** + * Formats a step header with task context + * @param stepName Name of the step + * @param stepNumber Step number (1-based) + * @param taskName Name of the parent task (only used in single step view) + * @param isTaskView Whether this is being used in a task view + * @returns Formatted step header + */ + formatStepHeader(stepName: string, stepNumber: number, taskName?: string, isTaskView: boolean = false): string { + const parts = ['Step']; + if (stepNumber !== undefined) { + parts.push(' '); + parts.push(stepNumber.toString()); + } + if (!isTaskView && taskName) { + parts.push(` of ${taskName}`); + } + parts.push(': '); + parts.push(stepName); + return parts.join(''); + }, + /** * Formats a section with a header and content * @param header Section header @@ -42,9 +64,7 @@ export const formatUtils = { return [ this.formatCheckbox(header), this.createBanner(bannerChar), - '', content, - '', ]; } }; \ No newline at end of file diff --git a/server/src/printer/serial-printer.ts b/server/src/printer/serial-printer.ts index d79628d..7df2e59 100644 --- a/server/src/printer/serial-printer.ts +++ b/server/src/printer/serial-printer.ts @@ -72,7 +72,11 @@ export class SerialPrinter implements PrinterInterface { // Print steps for (let i = 0; i < taskSteps.length; i++) { const step = taskSteps[i]; - const stepSection = formatUtils.formatSection(`Step ${i + 1}: ${step.name}`, step.instructions, '-'); + const stepSection = formatUtils.formatSection( + formatUtils.formatStepHeader(step.name, i + 1, task.name, true), + step.instructions, + '-' + ); await this.printer .size(1, 1) // Normal size for step header @@ -89,9 +93,7 @@ export class SerialPrinter implements PrinterInterface { } await this.printer - .text('') - .text('') - .cut() + .cut(true, 2) .close(); logger.info(`Printed task ${task.id}`); @@ -107,13 +109,20 @@ export class SerialPrinter implements PrinterInterface { } } - async printStep(step: Step, _db: Knex): Promise { + async printStep(step: Step, db: Knex): Promise { if (!this.printer || !this.device) { throw new Error('Printer not initialized'); } try { - const stepSection = formatUtils.formatSection(`Step: ${step.name}`, step.instructions); + // Get the task name for context + const task = await this.stepRepository.findTaskById(step.id); + const stepNumber = await this.stepRepository.findStepNumber(step.id); + + const stepSection = formatUtils.formatSection( + formatUtils.formatStepHeader(step.name, stepNumber, task?.name), + step.instructions + ); await this.printer .font('a') @@ -132,9 +141,7 @@ export class SerialPrinter implements PrinterInterface { // Print step ID as barcode await this.printer .barcode(step.id.toString(), 'CODE128', { width: 2, height: 50 }) - .text('') - .text('') - .cut() + .cut(true, 2) .close(); logger.info(`Printed step ${step.id}`); diff --git a/server/src/printer/test-printer.ts b/server/src/printer/test-printer.ts index 10eba46..1f87a36 100644 --- a/server/src/printer/test-printer.ts +++ b/server/src/printer/test-printer.ts @@ -38,7 +38,11 @@ export class TestPrinter implements Printer { const content = [ ...formatUtils.formatSection(`Task: ${task.name}`, ''), ...taskSteps.map((step, index) => - formatUtils.formatSection(`Step ${index + 1}: ${step.name}`, step.instructions, '-') + formatUtils.formatSection( + formatUtils.formatStepHeader(step.name, index + 1, task.name, true), + step.instructions, + '-' + ) ).flat(), ].join('\n'); @@ -57,7 +61,14 @@ export class TestPrinter implements Printer { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const filename = path.join(this.outputDir, `step-${step.id}-${timestamp}.txt`); - const content = formatUtils.formatSection(`Step: ${step.name}`, step.instructions).join('\n'); + // Get the task name for context + const task = await this.stepRepository.findTaskById(step.id); + const stepNumber = await this.stepRepository.findStepNumber(step.id); + + const content = formatUtils.formatSection( + formatUtils.formatStepHeader(step.name, stepNumber, task?.name), + step.instructions + ).join('\n'); await fs.writeFile(filename, content); logger.info(`Printed step ${step.id} to ${filename}`);