engine-size.ts 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. /**
  2. * Engine size, as the vehicle form writes it.
  3. *
  4. * The field is free text: people type "2.0", "1598 cc", "2.0 TDI" or an
  5. * engine family, and all of it is kept as typed. What a workshop chooses is
  6. * the unit a plate lookup writes in, because registries answer with the
  7. * exact displacement in cubic centimetres and the app used to round it to a
  8. * tenth of a litre, so a 649 cc motorcycle arrived as "0.6 L".
  9. */
  10. export type EngineSizeUnit = 'litres' | 'cc'
  11. export function engineSizeUnitOf(value: string | null | undefined): EngineSizeUnit {
  12. return value === 'cc' ? 'cc' : 'litres'
  13. }
  14. /**
  15. * Exact displacement in the workshop's unit. Litres keep a tenth for cars, as
  16. * the badge does, and every cubic centimetre below a litre, so a 49 cc moped
  17. * is 0.049 L and never rounds to zero.
  18. */
  19. export function formatEngineSize(cc: number | undefined, unit: EngineSizeUnit): string | undefined {
  20. if (cc === undefined || !Number.isFinite(cc) || cc < 30) return undefined
  21. if (unit === 'cc') return `${Math.round(cc)} cc`
  22. const litres = cc < 1000 ? Number((cc / 1000).toFixed(3)) : (cc / 1000).toFixed(1)
  23. return `${litres} L`
  24. }
  25. /**
  26. * The unit a bare number in the field stands for, or null when the field
  27. * holds anything else. Engines are under thirty litres and over thirty cubic
  28. * centimetres, so the size of the number says which one was meant.
  29. */
  30. export function bareEngineUnit(value: string): 'L' | 'cc' | null {
  31. const v = value.trim().replace(',', '.')
  32. if (!/^\d+(\.\d+)?$/.test(v)) return null
  33. const n = Number(v)
  34. if (!(n > 0)) return null
  35. return n < 30 ? 'L' : 'cc'
  36. }
  37. /** A bare number with its unit added; anything else exactly as typed. */
  38. export function withEngineUnit(value: string): string {
  39. const unit = bareEngineUnit(value)
  40. return unit ? `${value.trim().replace(',', '.')} ${unit}` : value
  41. }
  42. /**
  43. * What the form saves for the field: a value typed or changed here gets its
  44. * unit; one saved before is kept exactly as it was, so editing the mileage
  45. * never rewrites an old "649" as "649 cc".
  46. */
  47. export function engineSizeToSave(value: string, saved: string | null | undefined): string {
  48. return value.trim() === (saved ?? '').trim() ? value : withEngineUnit(value)
  49. }
  50. /** The unit the field shows beside an edited bare number, matching what will be saved. */
  51. export function engineSizeHint(draft: string, saved: string | null | undefined): 'L' | 'cc' | null {
  52. return draft.trim() === (saved ?? '').trim() ? null : bareEngineUnit(draft)
  53. }
  54. /** What the empty field shows, in the workshop's unit. */
  55. export function engineSizePlaceholder(unit: EngineSizeUnit): string {
  56. return unit === 'cc' ? '1598 cc' : '1.6 L'
  57. }