OemWhereUsed.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. 'use client'
  2. import { useState } from 'react'
  3. import { useTranslations } from 'next-intl'
  4. import { modelLines } from '../Lib/oem'
  5. import type { OemMeaningView } from '../Lib/types'
  6. const UNITS_SHOWN = 6
  7. /**
  8. * Where a make uses one meaning of its code: the models and years, the
  9. * engines and the control units that give it. On the code's page it fills a
  10. * card; beside a job it reads as one more part.
  11. */
  12. export function OemWhereUsed({
  13. meaning,
  14. layout,
  15. }: {
  16. meaning: OemMeaningView
  17. layout: 'page' | 'sheet'
  18. }) {
  19. const t = useTranslations('faultCodes.oem')
  20. const [allUnits, setAllUnits] = useState(false)
  21. const { lines, other } = modelLines(meaning.vehicles)
  22. const units = allUnits ? meaning.modules : meaning.modules.slice(0, UNITS_SHOWN)
  23. const page = layout === 'page'
  24. const heading = page
  25. ? 'text-xs font-semibold uppercase tracking-wide text-muted-foreground'
  26. : 'text-[11px] font-medium uppercase tracking-wide text-muted-foreground'
  27. return (
  28. <div className={page ? 'space-y-4' : 'space-y-3'}>
  29. {(lines.length > 0 || other.length > 0) && (
  30. <section className="space-y-1.5">
  31. <h4 className={heading}>{t('models')}</h4>
  32. <ul className="space-y-1 text-sm">
  33. {lines.map((line) => (
  34. <li key={line.model} className="flex flex-wrap items-baseline gap-x-2">
  35. <span className="font-medium">{line.model}</span>
  36. <span className="font-mono text-xs text-muted-foreground">
  37. {line.chassis.join(', ')}
  38. </span>
  39. {line.from !== null && (
  40. <span className="ml-auto text-xs tabular-nums text-muted-foreground">
  41. {line.to === null
  42. ? t('yearsFrom', { from: line.from })
  43. : t('years', { from: line.from, to: line.to })}
  44. </span>
  45. )}
  46. </li>
  47. ))}
  48. {other.length > 0 && (
  49. <li className="flex flex-wrap items-baseline gap-x-2">
  50. <span className="font-medium">{t('otherChassis')}</span>
  51. <span className="font-mono text-xs text-muted-foreground">{other.join(', ')}</span>
  52. </li>
  53. )}
  54. </ul>
  55. </section>
  56. )}
  57. {meaning.engines.length > 0 && (
  58. <section className="space-y-1.5">
  59. <h4 className={heading}>{t('engineList')}</h4>
  60. <div className="flex flex-wrap gap-1.5">
  61. {meaning.engines.map((engine) => (
  62. <span
  63. key={engine}
  64. className="rounded-md border border-card-edge bg-muted/40 px-1.5 py-0.5 font-mono text-xs"
  65. >
  66. {engine}
  67. </span>
  68. ))}
  69. </div>
  70. </section>
  71. )}
  72. <section className="space-y-1.5">
  73. <h4 className={heading}>{t('units')}</h4>
  74. <div className="flex flex-wrap items-center gap-1.5">
  75. {units.map((u) => (
  76. <span
  77. key={u.ref}
  78. title={u.name ?? undefined}
  79. className="rounded-md border border-card-edge bg-muted/40 px-1.5 py-0.5 font-mono text-[11px]"
  80. >
  81. {u.family ? `${u.family} · ${u.ref}` : u.ref}
  82. </span>
  83. ))}
  84. {meaning.modules.length > UNITS_SHOWN && (
  85. <button
  86. type="button"
  87. onClick={() => setAllUnits((v) => !v)}
  88. className="cursor-pointer text-xs font-medium text-primary underline-offset-2 hover:underline"
  89. >
  90. {allUnits
  91. ? t('fewerUnits')
  92. : t('moreUnits', { count: meaning.modules.length - UNITS_SHOWN })}
  93. </button>
  94. )}
  95. </div>
  96. </section>
  97. <p className="text-xs text-muted-foreground">{t('source')}</p>
  98. </div>
  99. )
  100. }