SpoolUsageHistory.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  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. import { ownRecords } from '../utils/usageHistory';
  10. function formatDate(dateStr: string): string {
  11. const date = parseUTCDate(dateStr);
  12. if (!date) return '';
  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. interface SpoolUsageHistoryProps {
  17. spoolId: number;
  18. // A Spoolman spool: the rows Bambuddy wrote for the prints it charged (#3407).
  19. spoolman?: boolean;
  20. // When the spool was created: older rows are an earlier spool's that had its id.
  21. createdAt?: string | null;
  22. }
  23. const STATUS_COLORS: Record<string, string> = {
  24. completed: 'text-bambu-green',
  25. failed: 'text-red-700 dark:text-red-400',
  26. aborted: 'text-yellow-700 dark:text-yellow-400',
  27. };
  28. export function SpoolUsageHistory({ spoolId, spoolman = false, createdAt = null }: SpoolUsageHistoryProps) {
  29. const { t } = useTranslation();
  30. const queryClient = useQueryClient();
  31. const { showToast } = useToast();
  32. const { data: fetched, isLoading } = useQuery({
  33. queryKey: ['spool-usage', spoolman, spoolId],
  34. queryFn: () => (spoolman ? api.getSpoolmanSpoolUsageHistory(spoolId) : api.getSpoolUsageHistory(spoolId)),
  35. });
  36. const history = fetched && spoolman ? ownRecords({ created_at: createdAt }, fetched) : fetched;
  37. const clearMutation = useMutation({
  38. mutationFn: () => (spoolman ? api.clearSpoolmanSpoolUsageHistory(spoolId) : api.clearSpoolUsageHistory(spoolId)),
  39. onSuccess: () => {
  40. queryClient.invalidateQueries({ queryKey: ['spool-usage', spoolman, spoolId] });
  41. showToast(t('inventory.historyCleared'), 'success');
  42. },
  43. });
  44. if (isLoading) {
  45. return (
  46. <div className="flex justify-center py-4">
  47. <Loader2 className="w-5 h-5 animate-spin text-bambu-green" />
  48. </div>
  49. );
  50. }
  51. if (!history || history.length === 0) {
  52. return (
  53. <div className="text-center py-4 text-bambu-gray text-sm">
  54. <Clock className="w-5 h-5 mx-auto mb-2 opacity-50" />
  55. {t('inventory.noUsageHistory')}
  56. </div>
  57. );
  58. }
  59. return (
  60. <div className="space-y-2">
  61. <div className="flex items-center justify-between">
  62. <h4 className="text-sm font-medium text-white">{t('inventory.usageHistory')}</h4>
  63. <Button
  64. variant="ghost"
  65. size="sm"
  66. onClick={() => clearMutation.mutate()}
  67. disabled={clearMutation.isPending}
  68. className="text-xs text-bambu-gray hover:text-red-700 dark:hover:text-red-400"
  69. >
  70. <Trash2 className="w-3 h-3 mr-1" />
  71. {t('inventory.clearHistory')}
  72. </Button>
  73. </div>
  74. <div className="max-h-48 overflow-y-auto space-y-1">
  75. {history.map((record: SpoolUsageRecord) => (
  76. <div
  77. key={record.id}
  78. className="flex items-center justify-between p-2 rounded bg-bambu-dark/50 text-xs"
  79. >
  80. <div className="flex-1 min-w-0">
  81. <span className="text-bambu-gray">{formatDate(record.created_at)}</span>
  82. {record.print_name && (
  83. <span className="text-white ml-2 truncate" title={record.print_name}>
  84. {record.print_name}
  85. </span>
  86. )}
  87. </div>
  88. <div className="flex items-center gap-2 flex-shrink-0 ml-2">
  89. <span className="text-white font-medium">{record.weight_used.toFixed(1)}g</span>
  90. <span className="text-bambu-gray">({record.percent_used}%)</span>
  91. <span className={STATUS_COLORS[record.status] || 'text-bambu-gray'}>
  92. {record.status}
  93. </span>
  94. </div>
  95. </div>
  96. ))}
  97. </div>
  98. </div>
  99. );
  100. }