mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-20 13:51:45 +00:00
47671d52e0
FE6: split the oversized page and panel components into thin layout shells plus colocated use<Component> hooks, .constants.ts, .helpers.ts (with tests) and presentational sub-components, following the established 'logic in a hook, render in slices' pattern. Behaviour, markup, classes and effect order are unchanged. Largest reductions: PackingListPanel 1598->42, FileManager 1055->36, AdminPage 1525->167, BudgetPanel 1266->146, JourneyDetailPage 2822->547, PlacesSidebar 945->66, CollabChat 861->106, CollabNotes 1417->532. DayPlanSidebar's drag-and-drop render body was left intact (ref-identity sensitive) and only its toolbar/modals/constants were extracted.
56 lines
2.2 KiB
TypeScript
56 lines
2.2 KiB
TypeScript
import { KAT_COLORS } from './packingListPanel.constants'
|
|
|
|
// Stable color assignment: category name → index via simple hash
|
|
export function katColor(kat: string, allCategories?: string[]): string {
|
|
const idx = allCategories ? allCategories.indexOf(kat) : -1
|
|
if (idx >= 0) return KAT_COLORS[idx % KAT_COLORS.length]
|
|
// Fallback: hash-based
|
|
let h = 0
|
|
for (let i = 0; i < kat.length; i++) h = ((h << 5) - h + kat.charCodeAt(i)) | 0
|
|
return KAT_COLORS[Math.abs(h) % KAT_COLORS.length]
|
|
}
|
|
|
|
/** Weight an item contributes to a total: unit weight times quantity (defaults: 0 g, qty 1). */
|
|
export const itemWeight = (i: { weight_grams?: number | null; quantity?: number | null }): number =>
|
|
(i.weight_grams || 0) * (i.quantity || 1)
|
|
|
|
// Parse CSV line respecting quoted values (e.g. "Shirt, blue" stays as one field)
|
|
export const parseCsvLine = (line: string): string[] => {
|
|
const parts: string[] = []
|
|
let current = ''
|
|
let inQuotes = false
|
|
for (let i = 0; i < line.length; i++) {
|
|
const ch = line[i]
|
|
if (ch === '"') { inQuotes = !inQuotes; continue }
|
|
if (!inQuotes && (ch === ',' || ch === ';' || ch === '\t')) { parts.push(current.trim()); current = ''; continue }
|
|
current += ch
|
|
}
|
|
parts.push(current.trim())
|
|
return parts
|
|
}
|
|
|
|
export interface ParsedImportItem {
|
|
name: string
|
|
category: string | undefined
|
|
weight_grams: string | undefined
|
|
bag: string | undefined
|
|
checked: boolean
|
|
}
|
|
|
|
export const parseImportLines = (text: string): ParsedImportItem[] => {
|
|
return text.split('\n').map(line => line.trim()).filter(Boolean).map(line => {
|
|
// Format: Category, Name, Weight (optional), Bag (optional), checked/unchecked (optional)
|
|
const parts = parseCsvLine(line)
|
|
if (parts.length >= 2) {
|
|
const category = parts[0]
|
|
const name = parts[1]
|
|
const weight_grams = parts[2] || undefined
|
|
const bag = parts[3] || undefined
|
|
const checked = parts[4]?.toLowerCase() === 'checked' || parts[4] === '1'
|
|
return { name, category, weight_grams, bag, checked }
|
|
}
|
|
// Single value = just a name
|
|
return { name: parts[0], category: undefined, weight_grams: undefined, bag: undefined, checked: false }
|
|
}).filter(i => i.name)
|
|
}
|