# LIS Kanban Redesign Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Redesign the LIS kanban board to show all sections at once with date navigation, rich cards, multi-view completed column, and smart barcode scanner.

**Architecture:** Complete rewrite of the kanban component. The current single-section 3-column layout becomes a multi-section row layout where each section is a row with 3 status columns. A new kanban-card sub-component handles rich card rendering. The service gets a date parameter. All sections' data loads in parallel via forkJoin.

**Tech Stack:** Angular 21 standalone components, PrimeNG 21 (Tag, Button, Select, Dialog, Toast, InputText, Tooltip, DatePicker), signals, SCSS, ngx-translate.

---

### Task 1: Create Backend Ticket for Date Filter

**Files:**
- None (API call only)

**Step 1: Create support ticket for backend date filter**

Use curl to create a ticket on the support system (project LIS id=3, assigned to admin id=3):

```bash
# Login first
TOKEN=$(curl -s -X POST https://moon-erp.elbaset.com/api/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"hazem@gt4it.com","password":"123456789"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")

# Create ticket
curl -s -X POST https://moon-erp.elbaset.com/api/support/tasks \
  -H "X-Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "title": "Add date filter parameter to kanban API endpoint",
    "description": "## Overview\nAdd optional `date` query parameter to `GET /api/lis/sections/{sectionId}/kanban`.\n\n## Backend\n- Accept `?date=YYYY-MM-DD` parameter (default: today)\n- Filter kanban items by request creation date matching the given date\n- Return same response shape: `{ received: [], in_processing: [], completed: [] }`\n\n## Acceptance Criteria\n- `GET /sections/1/kanban` returns today data (backward compatible)\n- `GET /sections/1/kanban?date=2026-03-06` returns yesterday data\n- Date filtering works on the lab_request.created_at date",
    "project_id": 3,
    "assigned_to": 3,
    "priority": "medium",
    "status": "open"
  }'
```

**Step 2: Verify ticket was created**

Check the response has an id and status "open".

---

### Task 2: Update Kanban Service — Add Date Parameter

**Files:**
- Modify: `src/app/core/services/lis-kanban.service.ts`

**Step 1: Add date parameter to getKanban and add getAllSectionsKanban method**

Replace the entire file:

```typescript
import { Injectable, inject } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable, forkJoin } from 'rxjs';
import { map } from 'rxjs/operators';
import { environment } from '../../../environments/environment';

export interface KanbanSample {
  id: number;
  sample_number: string;
  barcode: string;
  patient?: { id: number; name: string; name_ar?: string; mrn: string };
  lab_request?: {
    id: number;
    request_number: string;
    priority?: string | { value: string; label: string };
    doctor?: { id: number; name: string; name_ar?: string };
    created_at?: string;
  };
  specimen_type?: string | { name: string; name_ar?: string };
}

export interface KanbanItem {
  id: number;
  sample_id: number;
  sample: KanbanSample;
  section_id: number;
  processing_status: { value: string; label: string };
  assigned_to: number | null;
  started_at: string | null;
  completed_at: string | null;
  notes: string | null;
  sort_order: number;
  created_at: string;
  updated_at: string;
}

export interface KanbanData {
  received: KanbanItem[];
  in_processing: KanbanItem[];
  completed: KanbanItem[];
}

export interface SectionKanban {
  sectionId: number;
  data: KanbanData;
}

@Injectable({ providedIn: 'root' })
export class LisKanbanService {
  private http = inject(HttpClient);
  private apiUrl = `${environment.apiUrl}/lis/sections`;

  getKanban(sectionId: number, date?: string): Observable<KanbanData> {
    let params = new HttpParams();
    if (date) params = params.set('date', date);
    return this.http.get<KanbanData>(`${this.apiUrl}/${sectionId}/kanban`, { params });
  }

  getAllSectionsKanban(sectionIds: number[], date?: string): Observable<SectionKanban[]> {
    const requests = sectionIds.map((id) =>
      this.getKanban(id, date).pipe(
        map((rawData) => {
          const data = (rawData as any).data || rawData;
          return { sectionId: id, data } as SectionKanban;
        })
      )
    );
    return forkJoin(requests);
  }

  startProcessing(sectionId: number, processingId: number): Observable<any> {
    return this.http.post(`${this.apiUrl}/${sectionId}/kanban/${processingId}/start`, {});
  }

  completeProcessing(sectionId: number, processingId: number): Observable<any> {
    return this.http.post(`${this.apiUrl}/${sectionId}/kanban/${processingId}/complete`, {});
  }

  assignProcessing(
    sectionId: number,
    processingId: number,
    data: { technician_id: number }
  ): Observable<any> {
    return this.http.post(`${this.apiUrl}/${sectionId}/kanban/${processingId}/assign`, data);
  }
}
```

---

### Task 3: Create Kanban Card Sub-Component

**Files:**
- Create: `src/app/features/lis/kanban/kanban-card/kanban-card.component.ts`
- Create: `src/app/features/lis/kanban/kanban-card/kanban-card.component.html`
- Create: `src/app/features/lis/kanban/kanban-card/kanban-card.component.scss`

**Step 1: Create the component TypeScript**

```typescript
import { Component, input, output, computed, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { ButtonModule } from 'primeng/button';
import { TagModule } from 'primeng/tag';
import { TooltipModule } from 'primeng/tooltip';
import { LanguageService } from '../../../../core/services/language.service';
import { KanbanItem } from '../../../../core/services/lis-kanban.service';

export type CardViewMode = 'full' | 'mini' | 'collapsed';
export type CardColumnType = 'received' | 'processing' | 'completed';

@Component({
  selector: 'app-kanban-card',
  standalone: true,
  imports: [CommonModule, TranslateModule, ButtonModule, TagModule, TooltipModule],
  templateUrl: './kanban-card.component.html',
  styleUrl: './kanban-card.component.scss',
})
export class KanbanCardComponent {
  lang = inject(LanguageService);

  item = input.required<KanbanItem>();
  viewMode = input<CardViewMode>('full');
  columnType = input<CardColumnType>('received');

  cardClicked = output<KanbanItem>();
  startClicked = output<KanbanItem>();
  completeClicked = output<KanbanItem>();

  barcode = computed(() => {
    const s = this.item().sample;
    return s?.barcode || s?.sample_number || `#${this.item().id}`;
  });

  patientName = computed(() => {
    const p = this.item().sample?.patient;
    if (!p) return '-';
    return this.lang.currentLang() === 'ar' ? (p.name_ar || p.name) : (p.name || p.name_ar || '-');
  });

  requestNumber = computed(() => this.item().sample?.lab_request?.request_number || '-');

  priority = computed(() => {
    const p = this.item().sample?.lab_request?.priority;
    if (!p) return 'routine';
    return typeof p === 'object' ? p.value : p;
  });

  prioritySeverity = computed((): 'danger' | 'warn' | 'info' => {
    switch (this.priority()) {
      case 'stat': return 'danger';
      case 'urgent': return 'warn';
      default: return 'info';
    }
  });

  doctorName = computed(() => {
    const d = this.item().sample?.lab_request?.doctor;
    if (!d) return '';
    return this.lang.currentLang() === 'ar' ? (d.name_ar || d.name) : (d.name || d.name_ar || '');
  });

  specimenType = computed(() => {
    const st = this.item().sample?.specimen_type;
    if (!st) return '';
    if (typeof st === 'string') return st;
    return this.lang.currentLang() === 'ar' ? (st.name_ar || st.name) : (st.name || st.name_ar || '');
  });

  investigations = computed(() => {
    const req = this.item().sample?.lab_request as any;
    if (!req?.investigations) return [];
    return req.investigations.map((inv: any) => {
      const i = inv.investigation || inv;
      return {
        code: i.code || '',
        name: this.lang.currentLang() === 'ar'
          ? (i.name_ar || i.name_en || '')
          : (i.name_en || i.name_ar || ''),
      };
    });
  });

  investigationCount = computed(() => this.investigations().length);

  investigationNames = computed(() =>
    this.investigations().map((i: any) => i.name).filter(Boolean).join(', ')
  );

  timeElapsed = computed(() => {
    const item = this.item();
    const col = this.columnType();
    let fromTime: string | null = null;

    if (col === 'processing') fromTime = item.started_at;
    else if (col === 'received') fromTime = item.created_at;
    else if (col === 'completed') fromTime = item.completed_at;

    if (!fromTime) return '';

    const diff = Date.now() - new Date(fromTime).getTime();
    const mins = Math.floor(diff / 60000);
    if (mins < 1) return '< 1m';
    if (mins < 60) return `${mins}m`;
    const hours = Math.floor(mins / 60);
    const remMins = mins % 60;
    return `${hours}h ${remMins}m`;
  });

  timeClass = computed(() => {
    const item = this.item();
    const col = this.columnType();
    let fromTime: string | null = null;

    if (col === 'processing') fromTime = item.started_at;
    else if (col === 'received') fromTime = item.created_at;

    if (!fromTime) return '';

    const mins = Math.floor((Date.now() - new Date(fromTime).getTime()) / 60000);
    if (mins < 30) return 'time-ok';
    if (mins < 60) return 'time-warn';
    return 'time-danger';
  });

  onCardClick(): void {
    this.cardClicked.emit(this.item());
  }

  onStart(event: Event): void {
    event.stopPropagation();
    this.startClicked.emit(this.item());
  }

  onComplete(event: Event): void {
    event.stopPropagation();
    this.completeClicked.emit(this.item());
  }
}
```

**Step 2: Create the component HTML template**

```html
@if (viewMode() === 'mini') {
  <!-- Mini view: single compact line -->
  <div class="kanban-card-mini" (click)="onCardClick()"
    [class.priority-stat]="priority() === 'stat'"
    [class.priority-urgent]="priority() === 'urgent'">
    <span class="mini-barcode"><i class="pi pi-barcode"></i> {{ barcode() }}</span>
    <span class="mini-patient">{{ patientName() }}</span>
    @if (investigationCount() > 0) {
      <span class="mini-count">{{ investigationCount() }}</span>
    }
    @if (timeElapsed()) {
      <span class="mini-time" [class]="timeClass()">{{ timeElapsed() }}</span>
    }
  </div>
} @else {
  <!-- Full view: rich card -->
  <div class="kanban-card-full" (click)="onCardClick()"
    [class.priority-stat]="priority() === 'stat'"
    [class.priority-urgent]="priority() === 'urgent'"
    [class.done]="columnType() === 'completed'">

    <div class="card-top">
      <span class="card-barcode"><i class="pi pi-barcode"></i> {{ barcode() }}</span>
      <div class="card-badges">
        @if (priority() !== 'routine') {
          <p-tag [value]="priority()" [severity]="prioritySeverity()" class="priority-tag" />
        }
        @if (investigationCount() > 0) {
          <span class="test-count" [pTooltip]="investigationNames()">{{ investigationCount() }} {{ 'LIS.KANBAN.TESTS' | translate }}</span>
        }
      </div>
    </div>

    <div class="card-body">
      <div class="card-row"><i class="pi pi-user"></i> <span>{{ patientName() }}</span></div>
      @if (investigationNames()) {
        <div class="card-row tests-row" [pTooltip]="investigationNames()">
          <i class="pi pi-list"></i> <span class="truncate">{{ investigationNames() }}</span>
        </div>
      }
      @if (specimenType()) {
        <div class="card-row muted"><i class="pi pi-tag"></i> <span>{{ specimenType() }}</span></div>
      }
      @if (doctorName()) {
        <div class="card-row muted"><i class="pi pi-id-card"></i> <span>{{ doctorName() }}</span></div>
      }
    </div>

    <div class="card-footer">
      @if (timeElapsed()) {
        <span class="card-time" [class]="timeClass()"><i class="pi pi-clock"></i> {{ timeElapsed() }}</span>
      }
      <div class="card-actions">
        @if (columnType() === 'received') {
          <p-button icon="pi pi-play" size="small" severity="info"
            [pTooltip]="'LIS.KANBAN.START' | translate" [rounded]="true" [text]="true"
            (onClick)="onStart($event)" />
        }
        @if (columnType() === 'processing') {
          <p-button icon="pi pi-check" size="small" severity="success"
            [pTooltip]="'LIS.KANBAN.COMPLETE' | translate" [rounded]="true" [text]="true"
            (onClick)="onComplete($event)" />
        }
      </div>
    </div>
  </div>
}
```

**Step 3: Create the component SCSS**

```scss
:host { display: block; }

$accent: #0d9488;
$text: #1e293b;
$text-muted: #64748b;
$border: #e2e8f0;

// ─── Priority border ───
.priority-stat { border-inline-start: 3px solid #ef4444 !important; }
.priority-urgent { border-inline-start: 3px solid #f59e0b !important; }

// ─── Mini Card ───
.kanban-card-mini {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.35rem 0.5rem;
  background: var(--surface-card);
  border: 1px solid $border;
  border-radius: 6px;
  cursor: pointer;
  font-size: 0.8rem;
  transition: border-color 0.15s;
  &:hover { border-color: $accent; }

  .mini-barcode {
    font-family: monospace;
    font-weight: 700;
    color: $accent;
    font-size: 0.78rem;
    white-space: nowrap;
    i { font-size: 0.75rem; }
  }
  .mini-patient {
    flex: 1;
    color: $text;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .mini-count {
    background: var(--surface-ground);
    border-radius: 50%;
    width: 20px;
    height: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.7rem;
    font-weight: 700;
    color: $text-muted;
  }
  .mini-time {
    font-size: 0.7rem;
    font-weight: 600;
    white-space: nowrap;
  }
}

// ─── Full Card ───
.kanban-card-full {
  background: var(--surface-card);
  border: 1px solid $border;
  border-radius: 8px;
  padding: 0.55rem 0.65rem;
  cursor: pointer;
  transition: border-color 0.15s, box-shadow 0.15s;
  &:hover { border-color: $accent; box-shadow: 0 2px 8px rgba(0,0,0,0.06); }
  &.done { opacity: 0.7; }
}

.card-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 0.35rem;
}

.card-barcode {
  font-family: monospace;
  font-weight: 700;
  font-size: 0.83rem;
  color: $accent;
  display: flex;
  align-items: center;
  gap: 0.3rem;
  i { font-size: 0.85rem; }
}

.card-badges {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

.test-count {
  background: var(--surface-ground);
  padding: 0.15rem 0.4rem;
  border-radius: 10px;
  font-size: 0.7rem;
  font-weight: 600;
  color: $text-muted;
  cursor: help;
}

.priority-tag { font-size: 0.65rem !important; }

.card-body {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  margin-bottom: 0.3rem;
}

.card-row {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.8rem;
  color: $text;
  i { color: $text-muted; font-size: 0.72rem; width: 14px; flex-shrink: 0; }
  &.muted { color: $text-muted; font-size: 0.72rem; }
  &.tests-row span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}

.truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 180px;
}

.card-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.card-time {
  font-size: 0.72rem;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 0.25rem;
  i { font-size: 0.7rem; }
}

.card-actions {
  display: flex;
  gap: 0.25rem;
  margin-inline-start: auto;
}

// ─── Time colors ───
.time-ok { color: #22c55e; }
.time-warn { color: #f59e0b; }
.time-danger { color: #ef4444; }
```

---

### Task 4: Rewrite Main Kanban Component — TypeScript

**Files:**
- Modify: `src/app/features/lis/kanban/lis-kanban.component.ts`

**Step 1: Complete rewrite of the component**

```typescript
import { Component, OnInit, OnDestroy, inject, signal, computed, ViewChild, ElementRef } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { ButtonModule } from 'primeng/button';
import { InputTextModule } from 'primeng/inputtext';
import { TagModule } from 'primeng/tag';
import { ToastModule } from 'primeng/toast';
import { TooltipModule } from 'primeng/tooltip';
import { DialogModule } from 'primeng/dialog';
import { DatePickerModule } from 'primeng/datepicker';
import { MessageService } from 'primeng/api';
import { LanguageService } from '../../../core/services/language.service';
import { LisSectionService } from '../../../core/services/lis-section.service';
import { LisSampleService } from '../../../core/services/lis-sample.service';
import {
  LisKanbanService,
  KanbanData,
  KanbanItem,
  SectionKanban,
} from '../../../core/services/lis-kanban.service';
import { LisSection } from '../../../core/models/lis-section.model';
import { KanbanCardComponent, CardViewMode } from './kanban-card/kanban-card.component';

interface SectionRow {
  section: LisSection;
  data: KanbanData;
  collapsed: boolean;
}

interface SampleDetail {
  barcode: string;
  sampleNumber: string;
  patientName: string;
  patientGender: string;
  patientAge: number | null;
  patientMrn: string;
  patientBloodGroup: string;
  requestNumber: string;
  priority: string;
  collectedAt: string | null;
  specimenType: string;
  investigations: { code: string; name: string }[];
  status: string;
}

@Component({
  selector: 'app-lis-kanban',
  standalone: true,
  imports: [
    CommonModule,
    FormsModule,
    TranslateModule,
    ButtonModule,
    InputTextModule,
    TagModule,
    ToastModule,
    TooltipModule,
    DialogModule,
    DatePickerModule,
    KanbanCardComponent,
  ],
  providers: [MessageService],
  templateUrl: './lis-kanban.component.html',
  styleUrl: './lis-kanban.component.scss',
})
export class LisKanbanComponent implements OnInit, OnDestroy {
  private sectionService = inject(LisSectionService);
  private kanbanService = inject(LisKanbanService);
  private sampleService = inject(LisSampleService);
  private messageService = inject(MessageService);
  private translate = inject(TranslateService);
  lang = inject(LanguageService);

  @ViewChild('barcodeInput') barcodeInput!: ElementRef<HTMLInputElement>;

  // ─── State ───
  sections = signal<LisSection[]>([]);
  sectionRows = signal<SectionRow[]>([]);
  loading = signal(false);
  selectedDate = signal<Date>(new Date());
  completedViewMode = signal<CardViewMode>('collapsed');

  // Barcode
  barcodeValue = '';
  barcodeLoading = signal(false);
  barcodeMessage = signal<{ type: 'success' | 'error'; text: string } | null>(null);

  // Detail dialog
  detailDialogVisible = signal(false);
  detailLoading = signal(false);
  sampleDetail = signal<SampleDetail | null>(null);

  // Auto-refresh
  private refreshInterval: any;

  isToday = computed(() => {
    const d = this.selectedDate();
    const today = new Date();
    return d.getFullYear() === today.getFullYear()
      && d.getMonth() === today.getMonth()
      && d.getDate() === today.getDate();
  });

  dateLabel = computed(() => {
    const d = this.selectedDate();
    return d.toISOString().split('T')[0];
  });

  ngOnInit(): void {
    this.loadSections();
    // Auto-refresh every 30 seconds
    this.refreshInterval = setInterval(() => this.loadAllKanban(), 30000);
  }

  ngOnDestroy(): void {
    if (this.refreshInterval) clearInterval(this.refreshInterval);
  }

  // ─── Date Navigation ───

  prevDay(): void {
    const d = new Date(this.selectedDate());
    d.setDate(d.getDate() - 1);
    this.selectedDate.set(d);
    this.loadAllKanban();
  }

  nextDay(): void {
    const d = new Date(this.selectedDate());
    d.setDate(d.getDate() + 1);
    this.selectedDate.set(d);
    this.loadAllKanban();
  }

  goToToday(): void {
    this.selectedDate.set(new Date());
    this.loadAllKanban();
  }

  onDateSelect(date: Date): void {
    if (date) {
      this.selectedDate.set(date);
      this.loadAllKanban();
    }
  }

  // ─── Completed View Toggle ───

  setCompletedView(mode: CardViewMode): void {
    this.completedViewMode.set(mode);
  }

  // ─── Section Toggle ───

  toggleSection(index: number): void {
    const rows = [...this.sectionRows()];
    rows[index] = { ...rows[index], collapsed: !rows[index].collapsed };
    this.sectionRows.set(rows);
  }

  // ─── Data Loading ───

  private loadSections(): void {
    this.sectionService.listAll().subscribe({
      next: (res) => {
        const active = res.data.filter((s) => s.is_active);
        this.sections.set(active);
        this.loadAllKanban();
      },
    });
  }

  loadAllKanban(): void {
    const secs = this.sections();
    if (secs.length === 0) return;
    this.loading.set(true);
    const date = this.dateLabel();
    const ids = secs.map((s) => s.id);

    this.kanbanService.getAllSectionsKanban(ids, date).subscribe({
      next: (results) => {
        const existingRows = this.sectionRows();
        const rows: SectionRow[] = results.map((r) => {
          const section = secs.find((s) => s.id === r.sectionId)!;
          const existing = existingRows.find((er) => er.section.id === r.sectionId);
          return {
            section,
            data: r.data,
            collapsed: existing ? existing.collapsed : false,
          };
        });
        this.sectionRows.set(rows);
        this.loading.set(false);
      },
      error: () => {
        this.loading.set(false);
      },
    });
  }

  // ─── Kanban Actions ───

  onStartItem(sectionId: number, item: KanbanItem): void {
    this.kanbanService.startProcessing(sectionId, item.id).subscribe({
      next: () => {
        this.loadAllKanban();
        this.messageService.add({
          severity: 'success',
          summary: this.translate.instant('COMMON.SUCCESS'),
          detail: this.translate.instant('LIS.KANBAN.STARTED'),
        });
      },
      error: (err) => this.showError(err),
    });
  }

  onCompleteItem(sectionId: number, item: KanbanItem): void {
    this.kanbanService.completeProcessing(sectionId, item.id).subscribe({
      next: () => {
        this.loadAllKanban();
        this.messageService.add({
          severity: 'success',
          summary: this.translate.instant('COMMON.SUCCESS'),
          detail: this.translate.instant('LIS.KANBAN.COMPLETED'),
        });
      },
      error: (err) => this.showError(err),
    });
  }

  // ─── Smart Barcode Scanner ───

  onBarcodeScan(): void {
    const barcode = this.barcodeValue.trim();
    if (!barcode) return;

    this.barcodeLoading.set(true);
    this.barcodeMessage.set(null);

    // Search across all loaded sections to find which section this sample belongs to
    const allRows = this.sectionRows();
    let foundItem: KanbanItem | null = null;
    let foundSection: LisSection | null = null;
    let foundStatus: 'received' | 'in_processing' | null = null;

    for (const row of allRows) {
      // Check received
      const inReceived = row.data.received.find(
        (item) => item.sample?.barcode === barcode || item.sample?.sample_number === barcode
      );
      if (inReceived) {
        foundItem = inReceived;
        foundSection = row.section;
        foundStatus = 'received';
        break;
      }
      // Check processing
      const inProcessing = row.data.in_processing.find(
        (item) => item.sample?.barcode === barcode || item.sample?.sample_number === barcode
      );
      if (inProcessing) {
        foundItem = inProcessing;
        foundSection = row.section;
        foundStatus = 'in_processing';
        break;
      }
    }

    if (foundItem && foundSection && foundStatus) {
      const sectionName = this.getSectionName(foundSection);
      if (foundStatus === 'received') {
        this.kanbanService.startProcessing(foundSection.id, foundItem.id).subscribe({
          next: () => {
            this.barcodeLoading.set(false);
            this.barcodeValue = '';
            this.barcodeMessage.set({
              type: 'success',
              text: `${barcode} → ${this.translate.instant('LIS.KANBAN.IN_PROCESSING')} (${sectionName})`,
            });
            this.loadAllKanban();
            this.playBeep(true);
            setTimeout(() => this.barcodeInput?.nativeElement?.focus(), 100);
          },
          error: (err) => {
            this.barcodeLoading.set(false);
            this.barcodeMessage.set({ type: 'error', text: err.error?.message || 'Failed' });
            this.playBeep(false);
          },
        });
      } else {
        // in_processing → complete
        this.kanbanService.completeProcessing(foundSection.id, foundItem.id).subscribe({
          next: () => {
            this.barcodeLoading.set(false);
            this.barcodeValue = '';
            this.barcodeMessage.set({
              type: 'success',
              text: `${barcode} → ${this.translate.instant('LIS.KANBAN.COMPLETED_COL')} (${sectionName})`,
            });
            this.loadAllKanban();
            this.playBeep(true);
            setTimeout(() => this.barcodeInput?.nativeElement?.focus(), 100);
          },
          error: (err) => {
            this.barcodeLoading.set(false);
            this.barcodeMessage.set({ type: 'error', text: err.error?.message || 'Failed' });
            this.playBeep(false);
          },
        });
      }
    } else {
      // Not found in kanban — try receiving the sample first
      this.sampleService.search(barcode, 5).subscribe({
        next: (res) => {
          const sample = res.data.find((s) => s.barcode === barcode) || res.data[0];
          if (!sample) {
            this.barcodeLoading.set(false);
            this.barcodeMessage.set({
              type: 'error',
              text: this.translate.instant('LIS.SAMPLES.BARCODE_NOT_FOUND'),
            });
            this.playBeep(false);
            return;
          }

          const status = typeof sample.status === 'object' ? (sample.status as any).value : sample.status;
          if (status === 'pending') {
            this.sampleService.collect(sample.id).subscribe({
              next: () => this.doReceiveSample(sample.id, barcode),
              error: (err) => {
                this.barcodeLoading.set(false);
                this.barcodeMessage.set({ type: 'error', text: err.error?.message || 'Failed to collect' });
                this.playBeep(false);
              },
            });
          } else if (status === 'collected') {
            this.doReceiveSample(sample.id, barcode);
          } else {
            this.barcodeLoading.set(false);
            this.barcodeMessage.set({
              type: 'success',
              text: this.translate.instant('LIS.KANBAN.ALREADY_RECEIVED') + ': ' + barcode,
            });
          }
        },
        error: () => {
          this.barcodeLoading.set(false);
          this.barcodeMessage.set({ type: 'error', text: this.translate.instant('COMMON.ERROR') });
          this.playBeep(false);
        },
      });
    }
  }

  private doReceiveSample(sampleId: number, barcode: string): void {
    this.sampleService.receive(sampleId).subscribe({
      next: () => {
        this.barcodeLoading.set(false);
        this.barcodeValue = '';
        this.barcodeMessage.set({
          type: 'success',
          text: this.translate.instant('LIS.KANBAN.RECEIVED_SUCCESS') + ': ' + barcode,
        });
        this.loadAllKanban();
        this.playBeep(true);
        setTimeout(() => this.barcodeInput?.nativeElement?.focus(), 100);
      },
      error: (err) => {
        this.barcodeLoading.set(false);
        this.barcodeMessage.set({ type: 'error', text: err.error?.message || 'Failed to receive' });
        this.playBeep(false);
      },
    });
  }

  private playBeep(success: boolean): void {
    try {
      const ctx = new AudioContext();
      const osc = ctx.createOscillator();
      const gain = ctx.createGain();
      osc.connect(gain);
      gain.connect(ctx.destination);
      osc.frequency.value = success ? 800 : 300;
      gain.gain.value = 0.1;
      osc.start();
      osc.stop(ctx.currentTime + (success ? 0.15 : 0.3));
    } catch {}
  }

  // ─── Detail Dialog ───

  openDetail(item: KanbanItem): void {
    this.detailDialogVisible.set(true);
    this.detailLoading.set(true);
    this.sampleDetail.set(null);

    this.sampleService.getById(item.sample_id).subscribe({
      next: (res) => {
        const s = res.data;
        const p = s.patient as any;
        const req = (s as any).lab_request;

        const detail: SampleDetail = {
          barcode: s.barcode || (s as any).sample_number || '',
          sampleNumber: (s as any).sample_number || '',
          patientName: p
            ? this.lang.currentLang() === 'ar'
              ? p.name_ar || p.name
              : p.name || p.name_ar || '-'
            : '-',
          patientGender: p?.gender
            ? typeof p.gender === 'object' ? p.gender.label || p.gender.value : p.gender
            : '-',
          patientAge: p?.date_of_birth ? this.calcAge(p.date_of_birth) : null,
          patientMrn: p?.mrn || '-',
          patientBloodGroup: p?.blood_group
            ? typeof p.blood_group === 'object' ? p.blood_group.label || p.blood_group.value : p.blood_group
            : '-',
          requestNumber: req?.request_number || item.sample?.lab_request?.request_number || '-',
          priority: req?.priority
            ? typeof req.priority === 'object' ? req.priority.label : req.priority
            : '-',
          collectedAt: (s as any).collected_at || s.created_at || null,
          specimenType: (s as any).specimen_type
            ? typeof (s as any).specimen_type === 'object'
              ? this.lang.currentLang() === 'ar'
                ? (s as any).specimen_type.name_ar
                : (s as any).specimen_type.name || (s as any).specimen_type.name_ar
              : (s as any).specimen_type
            : '-',
          investigations: ((req as any)?.investigations || []).map((inv: any) => ({
            code: inv.investigation?.code || '',
            name: this.lang.currentLang() === 'ar'
              ? inv.investigation?.name_ar || inv.investigation?.name_en || ''
              : inv.investigation?.name_en || inv.investigation?.name_ar || '',
          })),
          status: typeof s.status === 'object' ? (s.status as any).label : s.status || '',
        };

        this.sampleDetail.set(detail);
        this.detailLoading.set(false);
      },
      error: () => {
        this.detailLoading.set(false);
        this.sampleDetail.set({
          barcode: item.sample?.barcode || '',
          sampleNumber: item.sample?.sample_number || '',
          patientName: this.getPatientName(item),
          patientGender: '-',
          patientAge: null,
          patientMrn: item.sample?.patient?.mrn || '-',
          patientBloodGroup: '-',
          requestNumber: item.sample?.lab_request?.request_number || '-',
          priority: '-',
          collectedAt: item.created_at,
          specimenType: '-',
          investigations: [],
          status: '',
        });
      },
    });
  }

  private calcAge(dob: string): number {
    const birth = new Date(dob);
    const now = new Date();
    let age = now.getFullYear() - birth.getFullYear();
    const m = now.getMonth() - birth.getMonth();
    if (m < 0 || (m === 0 && now.getDate() < birth.getDate())) age--;
    return age;
  }

  // ─── Helpers ───

  getSectionName(section: LisSection): string {
    return this.lang.currentLang() === 'ar' ? section.name_ar : section.name_en || section.name_ar;
  }

  getPatientName(item: KanbanItem): string {
    const p = item.sample?.patient;
    if (!p) return '-';
    return this.lang.currentLang() === 'ar' ? (p.name_ar || p.name) : (p.name || p.name_ar || '-');
  }

  totalReceived = computed(() => this.sectionRows().reduce((sum, r) => sum + r.data.received.length, 0));
  totalProcessing = computed(() => this.sectionRows().reduce((sum, r) => sum + r.data.in_processing.length, 0));
  totalCompleted = computed(() => this.sectionRows().reduce((sum, r) => sum + r.data.completed.length, 0));

  private showError(err: any): void {
    this.messageService.add({
      severity: 'error',
      summary: this.translate.instant('COMMON.ERROR'),
      detail: err.error?.message || 'Operation failed',
    });
  }
}
```

---

### Task 5: Rewrite Main Kanban Component — HTML Template

**Files:**
- Modify: `src/app/features/lis/kanban/lis-kanban.component.html`

**Step 1: Complete rewrite of the template**

```html
<p-toast></p-toast>

<!-- Top Toolbar -->
<div class="kanban-toolbar">
  <!-- Date Navigation -->
  <div class="date-nav">
    <p-button icon="pi pi-chevron-left" [rounded]="true" [text]="true" severity="secondary" (onClick)="prevDay()" />
    <p-datepicker
      [ngModel]="selectedDate()"
      (ngModelChange)="onDateSelect($event)"
      dateFormat="yy-mm-dd"
      [showIcon]="true"
      [style]="{ width: '170px' }"
    />
    <p-button icon="pi pi-chevron-right" [rounded]="true" [text]="true" severity="secondary" (onClick)="nextDay()" />
    @if (!isToday()) {
      <p-button icon="pi pi-calendar" [label]="'LIS.RESULTS.TODAY' | translate"
        severity="secondary" size="small" (onClick)="goToToday()" />
    }
  </div>

  <!-- Barcode Scanner -->
  <div class="barcode-scanner">
    <div class="barcode-input-wrap">
      <i class="pi pi-barcode"></i>
      <input
        pInputText
        [(ngModel)]="barcodeValue"
        [placeholder]="'LIS.KANBAN.SCAN_BARCODE' | translate"
        (keydown.enter)="onBarcodeScan()"
        #barcodeInput
      />
      @if (barcodeLoading()) {
        <i class="pi pi-spinner pi-spin"></i>
      }
    </div>
    @if (barcodeMessage(); as msg) {
      <div class="barcode-msg" [class.success]="msg.type === 'success'" [class.error]="msg.type === 'error'">
        <i [class]="msg.type === 'success' ? 'pi pi-check-circle' : 'pi pi-exclamation-triangle'"></i>
        {{ msg.text }}
      </div>
    }
  </div>

  <!-- View Controls -->
  <div class="view-controls">
    <span class="view-label">{{ 'LIS.KANBAN.COMPLETED_COL' | translate }}:</span>
    <p-button icon="pi pi-minus" [rounded]="true" [text]="true" size="small"
      [severity]="completedViewMode() === 'collapsed' ? 'primary' : 'secondary'"
      [pTooltip]="'LIS.KANBAN.VIEW_COLLAPSED' | translate"
      (onClick)="setCompletedView('collapsed')" />
    <p-button icon="pi pi-bars" [rounded]="true" [text]="true" size="small"
      [severity]="completedViewMode() === 'mini' ? 'primary' : 'secondary'"
      [pTooltip]="'LIS.KANBAN.VIEW_MINI' | translate"
      (onClick)="setCompletedView('mini')" />
    <p-button icon="pi pi-th-large" [rounded]="true" [text]="true" size="small"
      [severity]="completedViewMode() === 'full' ? 'primary' : 'secondary'"
      [pTooltip]="'LIS.KANBAN.VIEW_FULL' | translate"
      (onClick)="setCompletedView('full')" />

    <span class="toolbar-divider"></span>
    <p-button icon="pi pi-refresh" [rounded]="true" [text]="true" severity="secondary" (onClick)="loadAllKanban()" />
  </div>
</div>

<!-- Summary Badges -->
<div class="kanban-summary">
  <span class="summary-badge received"><i class="pi pi-inbox"></i> {{ 'LIS.KANBAN.RECEIVED' | translate }}: {{ totalReceived() }}</span>
  <span class="summary-badge processing"><i class="pi pi-cog"></i> {{ 'LIS.KANBAN.IN_PROCESSING' | translate }}: {{ totalProcessing() }}</span>
  <span class="summary-badge completed"><i class="pi pi-check-circle"></i> {{ 'LIS.KANBAN.COMPLETED_COL' | translate }}: {{ totalCompleted() }}</span>
</div>

@if (loading()) {
  <div class="loading-state">
    <i class="pi pi-spin pi-spinner"></i>
  </div>
} @else {
  <!-- Section Rows -->
  <div class="kanban-sections">
    @for (row of sectionRows(); track row.section.id; let idx = $index) {
      <div class="section-row">
        <!-- Section Header -->
        <div class="section-header" (click)="toggleSection(idx)">
          <div class="section-header-start">
            <i class="pi" [class.pi-chevron-down]="!row.collapsed" [class.pi-chevron-right]="row.collapsed"></i>
            <span class="section-name">{{ getSectionName(row.section) }}</span>
          </div>
          <div class="section-counts">
            <span class="count-badge received-bg">{{ row.data.received.length }}</span>
            <span class="count-badge processing-bg">{{ row.data.in_processing.length }}</span>
            <span class="count-badge completed-bg">{{ row.data.completed.length }}</span>
          </div>
        </div>

        <!-- Section Columns (collapsible) -->
        @if (!row.collapsed) {
          <div class="section-columns">
            <!-- Received -->
            <div class="status-column">
              <div class="column-header received-bg">
                <span><i class="pi pi-inbox"></i> {{ 'LIS.KANBAN.RECEIVED' | translate }}</span>
                <span class="col-count">{{ row.data.received.length }}</span>
              </div>
              <div class="column-items">
                @for (item of row.data.received; track item.id) {
                  <app-kanban-card
                    [item]="item"
                    viewMode="full"
                    columnType="received"
                    (cardClicked)="openDetail($event)"
                    (startClicked)="onStartItem(row.section.id, $event)"
                  />
                } @empty {
                  <div class="column-empty">{{ 'COMMON.NO_DATA' | translate }}</div>
                }
              </div>
            </div>

            <!-- In Processing -->
            <div class="status-column">
              <div class="column-header processing-bg">
                <span><i class="pi pi-cog"></i> {{ 'LIS.KANBAN.IN_PROCESSING' | translate }}</span>
                <span class="col-count">{{ row.data.in_processing.length }}</span>
              </div>
              <div class="column-items">
                @for (item of row.data.in_processing; track item.id) {
                  <app-kanban-card
                    [item]="item"
                    viewMode="full"
                    columnType="processing"
                    (cardClicked)="openDetail($event)"
                    (completeClicked)="onCompleteItem(row.section.id, $event)"
                  />
                } @empty {
                  <div class="column-empty">{{ 'COMMON.NO_DATA' | translate }}</div>
                }
              </div>
            </div>

            <!-- Completed -->
            <div class="status-column">
              <div class="column-header completed-bg">
                <span><i class="pi pi-check-circle"></i> {{ 'LIS.KANBAN.COMPLETED_COL' | translate }}</span>
                <span class="col-count">{{ row.data.completed.length }}</span>
              </div>
              @if (completedViewMode() !== 'collapsed') {
                <div class="column-items">
                  @for (item of row.data.completed; track item.id) {
                    <app-kanban-card
                      [item]="item"
                      [viewMode]="completedViewMode()"
                      columnType="completed"
                      (cardClicked)="openDetail($event)"
                    />
                  } @empty {
                    <div class="column-empty">{{ 'COMMON.NO_DATA' | translate }}</div>
                  }
                </div>
              }
            </div>
          </div>
        }
      </div>
    } @empty {
      <div class="no-sections">{{ 'COMMON.NO_DATA' | translate }}</div>
    }
  </div>
}

<!-- Sample Detail Dialog -->
<p-dialog
  [header]="'LIS.SAMPLES.SAMPLE' | translate"
  [(visible)]="detailDialogVisible"
  [modal]="true"
  [style]="{ width: '500px' }"
>
  @if (detailLoading()) {
    <div class="detail-loading"><i class="pi pi-spinner pi-spin"></i></div>
  } @else if (sampleDetail(); as d) {
    <div class="detail-grid">
      <div class="detail-row">
        <span class="detail-label"><i class="pi pi-barcode"></i> {{ 'LIS.SAMPLES.SCAN_BARCODE' | translate }}</span>
        <span class="detail-value mono">{{ d.barcode }}</span>
      </div>
      <div class="detail-row">
        <span class="detail-label"><i class="pi pi-user"></i> {{ 'LIS.RESULTS.PATIENT' | translate }}</span>
        <span class="detail-value bold">{{ d.patientName }}</span>
      </div>
      <div class="detail-row half">
        <div>
          <span class="detail-label">{{ 'LIS.PATIENTS.GENDER' | translate }}</span>
          <span class="detail-value">{{ d.patientGender }}</span>
        </div>
        <div>
          <span class="detail-label">{{ 'LIS.PATIENTS.AGE' | translate }}</span>
          <span class="detail-value">{{ d.patientAge !== null ? d.patientAge + ' سنة' : '-' }}</span>
        </div>
      </div>
      <div class="detail-row half">
        <div>
          <span class="detail-label">MRN</span>
          <span class="detail-value mono">{{ d.patientMrn }}</span>
        </div>
        <div>
          <span class="detail-label">{{ 'LIS.PATIENTS.BLOOD_GROUP' | translate }}</span>
          <span class="detail-value">{{ d.patientBloodGroup }}</span>
        </div>
      </div>
      <div class="detail-divider"></div>
      <div class="detail-row">
        <span class="detail-label"><i class="pi pi-file"></i> {{ 'LIS.RESULTS.REQUEST' | translate }}</span>
        <span class="detail-value mono">{{ d.requestNumber }}</span>
      </div>
      <div class="detail-row half">
        <div>
          <span class="detail-label">{{ 'LIS.REQUESTS.PRIORITY' | translate }}</span>
          <span class="detail-value">{{ d.priority }}</span>
        </div>
        <div>
          <span class="detail-label">{{ 'LIS.SAMPLES.SPECIMEN_TYPE' | translate }}</span>
          <span class="detail-value">{{ d.specimenType }}</span>
        </div>
      </div>
      @if (d.collectedAt) {
        <div class="detail-row">
          <span class="detail-label"><i class="pi pi-clock"></i> {{ 'LIS.SAMPLES.COLLECTED_AT' | translate }}</span>
          <span class="detail-value">{{ d.collectedAt | date:'medium' }}</span>
        </div>
      }
      @if (d.investigations.length > 0) {
        <div class="detail-divider"></div>
        <div class="detail-label"><i class="pi pi-list"></i> {{ 'LIS.REQUESTS.INVESTIGATIONS' | translate }}</div>
        <div class="detail-tests">
          @for (inv of d.investigations; track inv.code) {
            <div class="detail-test-chip">
              <span class="code">{{ inv.code }}</span>
              <span class="name">{{ inv.name }}</span>
            </div>
          }
        </div>
      }
    </div>
  }
</p-dialog>
```

---

### Task 6: Rewrite Main Kanban Component — SCSS

**Files:**
- Modify: `src/app/features/lis/kanban/lis-kanban.component.scss`

**Step 1: Complete rewrite of styles**

```scss
:host { display: block; }

$accent: #0d9488;
$text: #1e293b;
$text-muted: #64748b;
$border: #e2e8f0;

$received-color: #f59e0b;
$processing-color: #3b82f6;
$completed-color: #22c55e;

// ─── Toolbar ───
.kanban-toolbar {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.5rem 0;
  flex-wrap: wrap;
}

.date-nav {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

.barcode-scanner {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 1;
  min-width: 250px;
}

.barcode-input-wrap {
  display: flex;
  align-items: center;
  background: var(--surface-card);
  border: 2px solid $border;
  border-radius: 10px;
  padding: 0 0.75rem;
  width: 350px;
  max-width: 100%;
  transition: border-color 0.2s;
  &:focus-within { border-color: $accent; }
  i.pi-barcode { color: $accent; font-size: 1.1rem; }
  i.pi-spinner { color: $accent; }
  input {
    flex: 1;
    border: none;
    background: transparent;
    padding: 0.55rem 0.5rem;
    font-size: 0.9rem;
    outline: none;
    color: $text;
    &::placeholder { color: #94a3b8; }
  }
}

.barcode-msg {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.3rem 0.6rem;
  border-radius: 8px;
  font-size: 0.8rem;
  font-weight: 500;
  animation: fadeIn 0.2s ease;
  white-space: nowrap;

  &.success { background: #ecfdf5; color: #065f46; border: 1px solid #a7f3d0; i { color: #10b981; } }
  &.error { background: #fef2f2; color: #991b1b; border: 1px solid #fecaca; i { color: #dc2626; } }
}

@keyframes fadeIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }

.view-controls {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

.view-label {
  font-size: 0.75rem;
  font-weight: 600;
  color: $text-muted;
  margin-inline-end: 0.25rem;
}

.toolbar-divider {
  width: 1px;
  height: 24px;
  background: $border;
  margin: 0 0.25rem;
}

// ─── Summary Badges ───
.kanban-summary {
  display: flex;
  gap: 0.5rem;
  padding: 0.25rem 0 0.5rem;
  flex-wrap: wrap;
}

.summary-badge {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.25rem 0.6rem;
  border-radius: 8px;
  font-size: 0.78rem;
  font-weight: 600;
  color: #fff;

  &.received { background: $received-color; }
  &.processing { background: $processing-color; }
  &.completed { background: $completed-color; }
}

// ─── Loading ───
.loading-state {
  text-align: center;
  padding: 3rem;
  i { font-size: 2rem; color: $accent; }
}

// ─── Section Rows ───
.kanban-sections {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.section-row {
  border: 1px solid $border;
  border-radius: 10px;
  overflow: hidden;
  background: var(--surface-card);
}

.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.5rem 0.75rem;
  cursor: pointer;
  user-select: none;
  background: var(--surface-ground);
  transition: background 0.15s;
  &:hover { background: var(--surface-hover); }
}

.section-header-start {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  i { font-size: 0.8rem; color: $text-muted; transition: transform 0.2s; }
}

.section-name {
  font-weight: 700;
  font-size: 0.9rem;
  color: $text;
}

.section-counts {
  display: flex;
  gap: 0.3rem;
}

.count-badge {
  border-radius: 50%;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.72rem;
  font-weight: 700;
  color: #fff;
}

.received-bg { background: $received-color; }
.processing-bg { background: $processing-color; }
.completed-bg { background: $completed-color; }

// ─── Section Columns ───
.section-columns {
  display: flex;
  gap: 0;
  border-top: 1px solid $border;
  min-height: 120px;
}

.status-column {
  flex: 1;
  display: flex;
  flex-direction: column;
  border-inline-end: 1px solid $border;
  &:last-child { border-inline-end: none; }
}

.column-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.4rem 0.6rem;
  font-size: 0.75rem;
  font-weight: 700;
  color: #fff;

  span { display: flex; align-items: center; gap: 0.3rem; }
  i { font-size: 0.75rem; }
}

.col-count {
  background: rgba(255, 255, 255, 0.3);
  border-radius: 50%;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.7rem;
}

.column-items {
  flex: 1;
  padding: 0.35rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  overflow-y: auto;
  max-height: 350px;
}

.column-empty {
  text-align: center;
  padding: 1.5rem 0.5rem;
  color: $text-muted;
  font-size: 0.8rem;
}

.no-sections {
  text-align: center;
  padding: 3rem;
  color: $text-muted;
}

// ─── Detail Dialog ───
.detail-loading {
  text-align: center;
  padding: 2rem;
  i { font-size: 1.5rem; color: $accent; }
}

.detail-grid {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.detail-row {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;

  &.half {
    flex-direction: row;
    gap: 1rem;
    > div { flex: 1; display: flex; flex-direction: column; gap: 0.15rem; }
  }
}

.detail-label {
  font-size: 0.7rem;
  color: $text-muted;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  display: flex;
  align-items: center;
  gap: 0.3rem;
  i { font-size: 0.75rem; }
}

.detail-value {
  font-size: 0.9rem;
  color: $text;
  font-weight: 500;
  &.mono { font-family: monospace; }
  &.bold { font-weight: 700; font-size: 1rem; }
}

.detail-divider {
  border-top: 1px solid $border;
  margin: 0.25rem 0;
}

.detail-tests {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-top: 0.25rem;
}

.detail-test-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.25rem 0.5rem;
  background: var(--surface-ground);
  border: 1px solid $border;
  border-radius: 6px;
  font-size: 0.8rem;

  .code { font-weight: 700; color: $accent; font-size: 0.75rem; font-family: monospace; }
  .name { color: $text; }
}
```

---

### Task 7: Add New Translation Keys

**Files:**
- Modify: `src/assets/i18n/en.json` — add keys inside `LIS.KANBAN`
- Modify: `src/assets/i18n/ar.json` — add keys inside `LIS.KANBAN`

**Step 1: Add new English translation keys to the KANBAN object**

Add these keys to the existing `LIS.KANBAN` object:
```json
"SCAN_BARCODE": "Scan barcode to process...",
"TESTS": "tests",
"VIEW_COLLAPSED": "Collapsed view",
"VIEW_MINI": "Compact view",
"VIEW_FULL": "Full view",
"ALL_SECTIONS": "All Sections"
```

Keep all existing keys (`TITLE`, `SUBTITLE`, `SECTION`, `SELECT_SECTION`, `RECEIVED`, `IN_PROCESSING`, `COMPLETED_COL`, `START`, `COMPLETE`, `STARTED`, `COMPLETED`, `PATIENT`, `INVESTIGATION`, `PRIORITY`, `SCAN_TO_RECEIVE`, `RECEIVED_SUCCESS`, `ALREADY_RECEIVED`, `CANNOT_RECEIVE_STATUS`).

**Step 2: Add Arabic translation keys**

Add these keys to the existing Arabic `LIS.KANBAN` object:
```json
"SCAN_BARCODE": "امسح الباركود للمعالجة...",
"TESTS": "تحاليل",
"VIEW_COLLAPSED": "عرض مطوي",
"VIEW_MINI": "عرض مختصر",
"VIEW_FULL": "عرض كامل",
"ALL_SECTIONS": "كل الأقسام"
```

---

### Task 8: Build, Deploy & Test

**Files:**
- None (build/deploy commands)

**Step 1: Build the application**

```bash
cd /home/moonui/public_html/moon-erp && npx ng build --base-href /app/
```

Expected: Build succeeds with no errors.

**Step 2: Deploy**

```bash
rm -f /home/moonui/public_html/app/*.js /home/moonui/public_html/app/*.css /home/moonui/public_html/app/*.html /home/moonui/public_html/app/*.ico
\cp -rf /home/moonui/public_html/moon-erp/dist/moon-erp/browser/* /home/moonui/public_html/app/
```

**Step 3: Verify**

Navigate to `https://moonui.elbaset.com/app/lab/kanban` and verify:
- All sections visible as rows
- Date navigation works (prev/next/today)
- Cards show barcode, patient, priority, time elapsed
- Completed view toggle works (collapsed/mini/full)
- Barcode scanner auto-detects section
- Click on card opens detail dialog

---

### Task 9: Create Frontend Tickets

**Files:**
- None (API calls only)

**Step 1: Create kanban frontend tickets on support system**

Create 3 tickets as described in the design doc:
1. "Kanban multi-section layout + date navigation" (FE, assigned to hazem id=6)
2. "Rich kanban cards + completed column multi-view" (FE, assigned to hazem id=6)
3. "Smart barcode scanner with section auto-detection" (FE, assigned to hazem id=6)

Mark all as `closed` since we're implementing them in this plan.
