PrintLogTable.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import { useQuery } from '@tanstack/react-query';
  2. import { useTranslation } from 'react-i18next';
  3. import { Loader2 } from 'lucide-react';
  4. import { api } from '../api/client';
  5. import { parseUTCDate } from '../utils/date';
  6. interface PrintLogTableProps {
  7. archiveId: number;
  8. }
  9. function formatDuration(seconds: number | null): string {
  10. if (!seconds || seconds <= 0) return '—';
  11. const h = Math.floor(seconds / 3600);
  12. const m = Math.floor((seconds % 3600) / 60);
  13. if (h > 0) return `${h}h ${m}m`;
  14. return `${m}m`;
  15. }
  16. function formatDate(isoString: string | null): string {
  17. if (!isoString) return '—';
  18. const d = parseUTCDate(isoString);
  19. return d ? d.toLocaleString() : '—';
  20. }
  21. export function PrintLogTable({ archiveId }: PrintLogTableProps) {
  22. const { t } = useTranslation();
  23. const { data, isLoading } = useQuery({
  24. queryKey: ['archive-runs', archiveId],
  25. queryFn: () => api.getArchiveRuns(archiveId),
  26. });
  27. if (isLoading) {
  28. return (
  29. <div className="flex justify-center py-4">
  30. <Loader2 className="w-5 h-5 text-bambu-gray animate-spin" />
  31. </div>
  32. );
  33. }
  34. const runs = data?.items || [];
  35. // A file with one plate names plate 1 on every run: the column only shows
  36. // once a run printed another plate.
  37. const showPlate = runs.some((run) => run.plate_id != null && run.plate_id > 1);
  38. if (runs.length === 0) {
  39. return (
  40. <p className="text-sm text-bambu-gray italic py-2">
  41. {t('archives.runLog.empty')}
  42. </p>
  43. );
  44. }
  45. return (
  46. <div className="overflow-x-auto">
  47. <table className="w-full text-xs">
  48. <thead>
  49. <tr className="text-bambu-gray border-b border-bambu-dark-tertiary">
  50. <th className="text-left py-1.5 pr-2 font-medium">{t('archives.runLog.col.date')}</th>
  51. {showPlate && <th className="text-left py-1.5 pr-2 font-medium">{t('archives.runLog.col.plate')}</th>}
  52. <th className="text-left py-1.5 pr-2 font-medium">{t('archives.runLog.col.status')}</th>
  53. <th className="text-right py-1.5 pr-2 font-medium">{t('archives.runLog.col.duration')}</th>
  54. <th className="text-right py-1.5 pr-2 font-medium">{t('archives.runLog.col.filament')}</th>
  55. <th className="text-right py-1.5 font-medium">{t('archives.runLog.col.cost')}</th>
  56. </tr>
  57. </thead>
  58. <tbody>
  59. {runs.map((run) => {
  60. const statusClass =
  61. run.status === 'completed'
  62. ? 'text-bambu-green'
  63. : run.status === 'failed'
  64. ? 'text-red-700 dark:text-red-400'
  65. : 'text-bambu-gray';
  66. return (
  67. <tr
  68. key={run.id}
  69. className="border-b border-bambu-dark-tertiary/40 last:border-0"
  70. >
  71. <td className="py-1.5 pr-2 text-bambu-gray-light">
  72. {formatDate(run.started_at || run.created_at)}
  73. </td>
  74. {showPlate && (
  75. <td className="py-1.5 pr-2 text-bambu-gray-light">
  76. {run.plate_id != null ? t('archives.card.plateNumber', { index: run.plate_id }) : '—'}
  77. </td>
  78. )}
  79. <td className={`py-1.5 pr-2 font-medium ${statusClass}`}>
  80. {t(`archives.runLog.status.${run.status}`, { defaultValue: run.status })}
  81. {run.failure_reason && (
  82. <span className="block text-[10px] text-bambu-gray font-normal">
  83. {t(`editArchive.failureReasons.${run.failure_reason}`, { defaultValue: run.failure_reason })}
  84. </span>
  85. )}
  86. </td>
  87. <td className="py-1.5 pr-2 text-right text-bambu-gray-light">
  88. {formatDuration(run.duration_seconds)}
  89. </td>
  90. <td className="py-1.5 pr-2 text-right text-bambu-gray-light">
  91. {run.filament_used_grams != null
  92. ? `${run.filament_used_grams.toFixed(1)} g`
  93. : '—'}
  94. </td>
  95. <td className="py-1.5 text-right text-bambu-gray-light">
  96. {run.cost != null ? run.cost.toFixed(2) : '—'}
  97. </td>
  98. </tr>
  99. );
  100. })}
  101. </tbody>
  102. </table>
  103. </div>
  104. );
  105. }