SpoolUsageHistory.tsx 3.4 KB

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