SpoolUsageHistory.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
  2. import { useTranslation } from 'react-i18next';
  3. import { Loader2, Trash2, Clock } from 'lucide-react';
  4. import { api } from '../api/client';
  5. import type { SpoolUsageRecord } from '../api/client';
  6. import { Button } from './Button';
  7. import { useToast } from '../contexts/ToastContext';
  8. import { parseUTCDate } from '../utils/date';
  9. function formatDate(dateStr: string): string {
  10. const date = parseUTCDate(dateStr);
  11. if (!date) return '';
  12. return date.toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: '2-digit' }) +
  13. ' ' + date.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
  14. }
  15. interface SpoolUsageHistoryProps {
  16. spoolId: number;
  17. }
  18. const STATUS_COLORS: Record<string, string> = {
  19. completed: 'text-bambu-green',
  20. failed: 'text-red-700 dark:text-red-400',
  21. aborted: 'text-yellow-700 dark:text-yellow-400',
  22. };
  23. export function SpoolUsageHistory({ spoolId }: SpoolUsageHistoryProps) {
  24. const { t } = useTranslation();
  25. const queryClient = useQueryClient();
  26. const { showToast } = useToast();
  27. const { data: history, isLoading } = useQuery({
  28. queryKey: ['spool-usage', spoolId],
  29. queryFn: () => api.getSpoolUsageHistory(spoolId),
  30. });
  31. const clearMutation = useMutation({
  32. mutationFn: () => api.clearSpoolUsageHistory(spoolId),
  33. onSuccess: () => {
  34. queryClient.invalidateQueries({ queryKey: ['spool-usage', spoolId] });
  35. showToast(t('inventory.historyCleared'), 'success');
  36. },
  37. });
  38. if (isLoading) {
  39. return (
  40. <div className="flex justify-center py-4">
  41. <Loader2 className="w-5 h-5 animate-spin text-bambu-green" />
  42. </div>
  43. );
  44. }
  45. if (!history || history.length === 0) {
  46. return (
  47. <div className="text-center py-4 text-bambu-gray text-sm">
  48. <Clock className="w-5 h-5 mx-auto mb-2 opacity-50" />
  49. {t('inventory.noUsageHistory')}
  50. </div>
  51. );
  52. }
  53. return (
  54. <div className="space-y-2">
  55. <div className="flex items-center justify-between">
  56. <h4 className="text-sm font-medium text-white">{t('inventory.usageHistory')}</h4>
  57. <Button
  58. variant="ghost"
  59. size="sm"
  60. onClick={() => clearMutation.mutate()}
  61. disabled={clearMutation.isPending}
  62. className="text-xs text-bambu-gray hover:text-red-700 dark:hover:text-red-400"
  63. >
  64. <Trash2 className="w-3 h-3 mr-1" />
  65. {t('inventory.clearHistory')}
  66. </Button>
  67. </div>
  68. <div className="max-h-48 overflow-y-auto space-y-1">
  69. {history.map((record: SpoolUsageRecord) => (
  70. <div
  71. key={record.id}
  72. className="flex items-center justify-between p-2 rounded bg-bambu-dark/50 text-xs"
  73. >
  74. <div className="flex-1 min-w-0">
  75. <span className="text-bambu-gray">{formatDate(record.created_at)}</span>
  76. {record.print_name && (
  77. <span className="text-white ml-2 truncate" title={record.print_name}>
  78. {record.print_name}
  79. </span>
  80. )}
  81. </div>
  82. <div className="flex items-center gap-2 flex-shrink-0 ml-2">
  83. <span className="text-white font-medium">{record.weight_used.toFixed(1)}g</span>
  84. <span className="text-bambu-gray">({record.percent_used}%)</span>
  85. <span className={STATUS_COLORS[record.status] || 'text-bambu-gray'}>
  86. {record.status}
  87. </span>
  88. </div>
  89. </div>
  90. ))}
  91. </div>
  92. </div>
  93. );
  94. }