112 lines
6.0 KiB
TypeScript
112 lines
6.0 KiB
TypeScript
/**
|
|
* Central enhetsdatabas för Recipe App (frontend-spegel av backend/src/common/utils/units.ts).
|
|
* Håll dessa filer i synk vid ändringar.
|
|
*/
|
|
|
|
export type UnitType = 'weight' | 'volume' | 'cooking' | 'piece' | 'other';
|
|
|
|
export interface UnitDefinition {
|
|
value: string;
|
|
labelSv: string;
|
|
type: UnitType;
|
|
toBaseFactor: number;
|
|
aliases: string[];
|
|
}
|
|
|
|
export const UNIT_DEFINITIONS: UnitDefinition[] = [
|
|
// ── Vikt ──────────────────────────────────────────────────
|
|
{ value: 'g', labelSv: 'g (gram)', type: 'weight', toBaseFactor: 1, aliases: ['gram'] },
|
|
{ value: 'hg', labelSv: 'hg (hektogram)', type: 'weight', toBaseFactor: 100, aliases: ['hektogram'] },
|
|
{ value: 'kg', labelSv: 'kg (kilogram)', type: 'weight', toBaseFactor: 1000, aliases: ['kilo', 'kilogram'] },
|
|
{ value: 'mg', labelSv: 'mg (milligram)', type: 'weight', toBaseFactor: 0.001, aliases: ['milligram'] },
|
|
// ── Volym ─────────────────────────────────────────────────
|
|
{ value: 'ml', labelSv: 'ml (milliliter)', type: 'volume', toBaseFactor: 1, aliases: ['milliliter'] },
|
|
{ value: 'cl', labelSv: 'cl (centiliter)', type: 'volume', toBaseFactor: 10, aliases: ['centiliter'] },
|
|
{ value: 'dl', labelSv: 'dl (deciliter)', type: 'volume', toBaseFactor: 100, aliases: ['deciliter'] },
|
|
{ value: 'l', labelSv: 'l (liter)', type: 'volume', toBaseFactor: 1000, aliases: ['liter'] },
|
|
// ── Matlagning ────────────────────────────────────────────
|
|
{ value: 'krm', labelSv: 'krm (kryddmått)', type: 'cooking', toBaseFactor: 1, aliases: ['kryddmatt', 'kryddmått'] },
|
|
{ value: 'tsk', labelSv: 'tsk (tesked)', type: 'cooking', toBaseFactor: 5, aliases: ['tesked', 'test'] },
|
|
{ value: 'msk', labelSv: 'msk (matsked)', type: 'cooking', toBaseFactor: 15, aliases: ['matsked', 'matsled'] },
|
|
// ── Styck ─────────────────────────────────────────────────
|
|
{ value: 'st', labelSv: 'st (styck)', type: 'piece', toBaseFactor: 1, aliases: ['stycke', 'styck', 'stk'] },
|
|
{ value: 'port', labelSv: 'port (portioner)', type: 'piece', toBaseFactor: 1, aliases: ['portion', 'portioner'] },
|
|
{ value: 'förp', labelSv: 'förp (förpackning)', type: 'piece', toBaseFactor: 1, aliases: ['forp', 'förpackning', 'forpackning'] },
|
|
{ value: 'klyfta', labelSv: 'klyfta', type: 'piece', toBaseFactor: 1, aliases: [] },
|
|
// ── Övrigt ────────────────────────────────────────────────
|
|
{ value: 'efter smak', labelSv: 'efter smak', type: 'other', toBaseFactor: 1, aliases: ['eftr smak'] },
|
|
];
|
|
|
|
/** Alla enheter som { value, label } — bakåtkompatibel ersättning för gamla UNIT_OPTIONS */
|
|
export const UNIT_OPTIONS = [
|
|
{ value: '', label: 'Välj enhet' },
|
|
...UNIT_DEFINITIONS.map((u) => ({ value: u.value, label: u.labelSv })),
|
|
];
|
|
|
|
/** Enheter grupperade per typ — för användning i dropdown med optgroup */
|
|
export const UNIT_OPTIONS_GROUPED: { group: string; options: { value: string; label: string }[] }[] = [
|
|
{
|
|
group: 'Vikt',
|
|
options: UNIT_DEFINITIONS.filter((u) => u.type === 'weight').map((u) => ({ value: u.value, label: u.labelSv })),
|
|
},
|
|
{
|
|
group: 'Volym',
|
|
options: UNIT_DEFINITIONS.filter((u) => u.type === 'volume').map((u) => ({ value: u.value, label: u.labelSv })),
|
|
},
|
|
{
|
|
group: 'Matlagning',
|
|
options: UNIT_DEFINITIONS.filter((u) => u.type === 'cooking').map((u) => ({ value: u.value, label: u.labelSv })),
|
|
},
|
|
{
|
|
group: 'Styck',
|
|
options: UNIT_DEFINITIONS.filter((u) => u.type === 'piece').map((u) => ({ value: u.value, label: u.labelSv })),
|
|
},
|
|
{
|
|
group: 'Övrigt',
|
|
options: UNIT_DEFINITIONS.filter((u) => u.type === 'other').map((u) => ({ value: u.value, label: u.labelSv })),
|
|
},
|
|
];
|
|
|
|
/** Normalisera en enhetssträng till kanonisk förkortning. */
|
|
export function normalizeUnit(unit: string): string {
|
|
const key = unit.trim().toLowerCase();
|
|
for (const def of UNIT_DEFINITIONS) {
|
|
if (def.value.toLowerCase() === key) return def.value;
|
|
if (def.aliases.some((a) => a.toLowerCase() === key)) return def.value;
|
|
}
|
|
return key;
|
|
}
|
|
|
|
/** Hämta UnitDefinition för en enhet. */
|
|
export function getUnitDefinition(unit: string): UnitDefinition | undefined {
|
|
const key = unit.trim().toLowerCase();
|
|
return UNIT_DEFINITIONS.find(
|
|
(d) => d.value.toLowerCase() === key || d.aliases.some((a) => a.toLowerCase() === key),
|
|
);
|
|
}
|
|
|
|
/** Hämta enhetstypen för en enhet. */
|
|
export function getUnitType(unit: string): UnitType | null {
|
|
return getUnitDefinition(unit)?.type ?? null;
|
|
}
|
|
|
|
/** Kontrollera om konvertering är möjlig mellan två enheter. */
|
|
export function canConvert(fromUnit: string, toUnit: string): boolean {
|
|
const from = getUnitDefinition(fromUnit);
|
|
const to = getUnitDefinition(toUnit);
|
|
if (!from || !to) return false;
|
|
if (from.type !== to.type) return false;
|
|
if (from.type === 'piece' || from.type === 'other') return false;
|
|
return true;
|
|
}
|
|
|
|
/** Konverterar en mängd från en enhet till en annan. */
|
|
export function convertUnit(quantity: number, fromUnit: string, toUnit: string): number {
|
|
const fromDef = getUnitDefinition(fromUnit);
|
|
const toDef = getUnitDefinition(toUnit);
|
|
if (!fromDef || !toDef || fromDef.type !== toDef.type) return quantity;
|
|
if (fromDef.type === 'piece' || fromDef.type === 'other') return quantity;
|
|
return (quantity * fromDef.toBaseFactor) / toDef.toBaseFactor;
|
|
}
|
|
|