@nice2dev/ui-printing 1.1.6 → 1.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/index.cjs +2 -2
  2. package/dist/index.d.ts +5 -5
  3. package/dist/index.mjs +1894 -1728
  4. package/dist/ui-printing.css +1 -1
  5. package/package.json +3 -2
  6. package/src/index.ts +5 -24
  7. package/src/print-button/NicePrintButton.tsx +44 -18
  8. package/src/print-button/PrintButton.module.css +147 -43
  9. package/src/print-preview/NicePrintPreview.tsx +19 -17
  10. package/src/print-preview/PrintPreview.module.css +60 -18
  11. package/src/print-queue/NicePrintQueue.tsx +76 -42
  12. package/src/print-queue/PrintQueue.module.css +207 -57
  13. package/src/template-browser/NiceTemplateBrowser.tsx +44 -19
  14. package/src/template-browser/TemplateBrowser.module.css +70 -25
  15. package/src/template-editor/NiceTemplateEditor.tsx +307 -182
  16. package/src/template-editor/TemplateEditor.module.css +184 -128
  17. package/src/types/printingTypes.ts +1 -1
  18. package/dist/index.d.ts.map +0 -1
  19. package/dist/print-button/NicePrintButton.d.ts.map +0 -1
  20. package/dist/print-button/index.d.ts.map +0 -1
  21. package/dist/print-preview/NicePrintPreview.d.ts.map +0 -1
  22. package/dist/print-preview/index.d.ts.map +0 -1
  23. package/dist/print-queue/NicePrintQueue.d.ts.map +0 -1
  24. package/dist/print-queue/index.d.ts.map +0 -1
  25. package/dist/style.css +0 -1
  26. package/dist/template-browser/NiceTemplateBrowser.d.ts.map +0 -1
  27. package/dist/template-browser/index.d.ts.map +0 -1
  28. package/dist/template-editor/NiceTemplateEditor.d.ts.map +0 -1
  29. package/dist/template-editor/index.d.ts.map +0 -1
  30. package/dist/types/printingTypes.d.ts.map +0 -1
  31. package/src/template-editor/NiceTemplateEditor.tsx.first +0 -1011
  32. package/src/template-editor/NiceTemplateEditor.tsx.tmp +0 -392
@@ -1,3 +1,5 @@
1
+ import { NiceIcon, useNiceTranslation } from '@nice2dev/ui';
2
+ import type { NiceTranslateFn } from '@nice2dev/ui';
1
3
  import React, { useState, useCallback } from 'react';
2
4
 
3
5
  import type { PrintJob, PrinterInfo, NicePrintQueueProps } from '../types/printingTypes';
@@ -5,32 +7,46 @@ import type { PrintJob, PrinterInfo, NicePrintQueueProps } from '../types/printi
5
7
  import styles from './PrintQueue.module.css';
6
8
 
7
9
  // ── Helpers ───────────────────────────────────────────────────────────────────
8
- const STATUS_LABELS: Record<string, string> = {
9
- queued: 'Kolejka',
10
- printing: 'Drukowanie',
11
- done: 'Wydrukowano',
12
- error: 'Błąd',
13
- paused: 'Wstrzymano',
14
- cancelled: 'Anulowano',
10
+ const STATUS_DEFAULTS: Record<string, string> = {
11
+ queued: 'Queued',
12
+ printing: 'Printing',
13
+ done: 'Printed',
14
+ error: 'Error',
15
+ paused: 'Paused',
16
+ cancelled: 'Cancelled',
15
17
  };
16
18
 
17
- function formatTime(iso: string): string {
19
+ const statusLabel = (t: NiceTranslateFn, status: string): string =>
20
+ STATUS_DEFAULTS[status] ? t(`printing.status.${status}`, STATUS_DEFAULTS[status]) : status;
21
+
22
+ function formatTime(lang: string, iso: string): string {
18
23
  try {
19
24
  const d = new Date(iso);
20
- return d.toLocaleTimeString('pl-PL', { hour: '2-digit', minute: '2-digit' });
25
+ return d.toLocaleTimeString(lang, { hour: '2-digit', minute: '2-digit' });
21
26
  } catch {
22
27
  return iso;
23
28
  }
24
29
  }
25
30
 
31
+ /**
32
+ * How long a job has been running. Rolls over into hours and days — without
33
+ * that, a job stuck since yesterday rendered as `1680m 12s`, which reads as a
34
+ * bug rather than as a stuck job.
35
+ */
26
36
  function formatDuration(startIso: string): string {
27
- const diff = Date.now() - new Date(startIso).getTime();
28
- const s = Math.floor(diff / 1000);
37
+ const s = Math.floor((Date.now() - new Date(startIso).getTime()) / 1000);
29
38
  if (s < 60) {
30
39
  return `${s}s`;
31
40
  }
32
41
  const m = Math.floor(s / 60);
33
- return `${m}m ${s % 60}s`;
42
+ if (m < 60) {
43
+ return `${m}m ${s % 60}s`;
44
+ }
45
+ const h = Math.floor(m / 60);
46
+ if (h < 24) {
47
+ return `${h}h ${m % 60}m`;
48
+ }
49
+ return `${Math.floor(h / 24)}d ${h % 24}h`;
34
50
  }
35
51
 
36
52
  // ── Stats bar ─────────────────────────────────────────────────────────────────
@@ -38,6 +54,7 @@ interface StatsBarProps {
38
54
  jobs: PrintJob[];
39
55
  }
40
56
  const StatsBar: React.FC<StatsBarProps> = ({ jobs }) => {
57
+ const { t } = useNiceTranslation();
41
58
  const printing = jobs.filter((j) => j.status === 'printing').length;
42
59
  const queued = jobs.filter((j) => j.status === 'queued').length;
43
60
  const done = jobs.filter((j) => j.status === 'done').length;
@@ -46,19 +63,20 @@ const StatsBar: React.FC<StatsBarProps> = ({ jobs }) => {
46
63
  <div className={styles.statsBar}>
47
64
  <span className={`${styles.stat} ${styles.statPrinting}`}>
48
65
  <span className={styles.dot} />
49
- Drukuje: <strong>{printing}</strong>
66
+ {t('printing.stats.active', 'Printing')}: <strong>{printing}</strong>
50
67
  </span>
51
68
  <span className={`${styles.stat} ${styles.statQueued}`}>
52
- <span className={styles.dot} />W kolejce: <strong>{queued}</strong>
69
+ <span className={styles.dot} />
70
+ {t('printing.stats.queued', 'In queue')}: <strong>{queued}</strong>
53
71
  </span>
54
72
  <span className={`${styles.stat} ${styles.statDone}`}>
55
73
  <span className={styles.dot} />
56
- Gotowe: <strong>{done}</strong>
74
+ {t('printing.stats.done', 'Done')}: <strong>{done}</strong>
57
75
  </span>
58
76
  {errored > 0 && (
59
77
  <span className={`${styles.stat} ${styles.statError}`}>
60
78
  <span className={styles.dot} />
61
- Błędy: <strong>{errored}</strong>
79
+ {t('printing.stats.errors', 'Errors')}: <strong>{errored}</strong>
62
80
  </span>
63
81
  )}
64
82
  </div>
@@ -90,6 +108,7 @@ const JobRow: React.FC<JobRowProps> = ({
90
108
  onPrint,
91
109
  onReorder,
92
110
  }) => {
111
+ const { t, lang } = useNiceTranslation();
93
112
  const printer = printers.find((p) => p.id === job.options.printerId);
94
113
  const canMoveUp = index > 0 && job.status === 'queued';
95
114
  const canMoveDown = index < total - 1 && job.status === 'queued';
@@ -108,7 +127,7 @@ const JobRow: React.FC<JobRowProps> = ({
108
127
 
109
128
  {/* Status badge */}
110
129
  <span className={`${styles.statusBadge} ${styles[`badge-${job.status}`]}`}>
111
- {STATUS_LABELS[job.status] ?? job.status}
130
+ {statusLabel(t, job.status)}
112
131
  </span>
113
132
 
114
133
  {/* Job info */}
@@ -116,9 +135,15 @@ const JobRow: React.FC<JobRowProps> = ({
116
135
  <div className={styles.jobName}>{job.templateName ?? job.templateId}</div>
117
136
  <div className={styles.jobMeta}>
118
137
  {job.entityLabel && <span>{job.entityLabel}</span>}
119
- {printer && <span>🖨 {printer.name}</span>}
138
+ {printer && (
139
+ <span>
140
+ <NiceIcon name="printer" size={12} /> {printer.name}
141
+ </span>
142
+ )}
120
143
  <span>×{job.options.copies}</span>
121
- {job.createdAt && <span className={styles.jobTime}>{formatTime(job.createdAt)}</span>}
144
+ {job.createdAt && (
145
+ <span className={styles.jobTime}>{formatTime(lang, job.createdAt)}</span>
146
+ )}
122
147
  {job.startedAt && job.status !== 'done' && (
123
148
  <span className={styles.jobDuration}>{formatDuration(job.startedAt)}</span>
124
149
  )}
@@ -140,20 +165,22 @@ const JobRow: React.FC<JobRowProps> = ({
140
165
  <button
141
166
  className={styles.copiesBtn}
142
167
  disabled={job.options.copies <= 1}
168
+ aria-label={t('printing.copies.decrease', 'One copy fewer')}
143
169
  onClick={() =>
144
170
  onUpdate(job.id, { options: { ...job.options, copies: job.options.copies - 1 } })
145
171
  }
146
172
  >
147
- −
173
+ <NiceIcon name="minus" size={12} />
148
174
  </button>
149
175
  <span className={styles.copiesValue}>{job.options.copies}</span>
150
176
  <button
151
177
  className={styles.copiesBtn}
178
+ aria-label={t('printing.copies.increase', 'One copy more')}
152
179
  onClick={() =>
153
180
  onUpdate(job.id, { options: { ...job.options, copies: job.options.copies + 1 } })
154
181
  }
155
182
  >
156
- +
183
+ <NiceIcon name="plus" size={12} />
157
184
  </button>
158
185
  </div>
159
186
 
@@ -162,50 +189,54 @@ const JobRow: React.FC<JobRowProps> = ({
162
189
  {canMoveUp && (
163
190
  <button
164
191
  className={styles.actionBtn}
165
- title="Przesuń wyżej"
192
+ title={t('printing.action.moveUp', 'Move up')}
166
193
  onClick={() => onReorder(index, index - 1)}
167
194
  >
168
- ↑
195
+ <NiceIcon name="arrow-up" size={14} />
169
196
  </button>
170
197
  )}
171
198
  {canMoveDown && (
172
199
  <button
173
200
  className={styles.actionBtn}
174
- title="Przesuń niżej"
201
+ title={t('printing.action.moveDown', 'Move down')}
175
202
  onClick={() => onReorder(index, index + 1)}
176
203
  >
177
- ↓
204
+ <NiceIcon name="arrow-down" size={14} />
178
205
  </button>
179
206
  )}
180
207
  {job.status === 'queued' && (
181
- <button className={styles.actionBtn} title="Drukuj teraz" onClick={() => onPrint(job.id)}>
182
- ▶
208
+ <button
209
+ className={styles.actionBtn}
210
+ title={t('printing.action.printNow', 'Print now')}
211
+ onClick={() => onPrint(job.id)}
212
+ >
213
+ <NiceIcon name="play" size={14} />
183
214
  </button>
184
215
  )}
185
216
  {job.status === 'printing' && (
186
217
  <button
187
218
  className={styles.actionBtn}
188
- title="Wstrzymaj"
219
+ title={t('printing.action.pause', 'Pause')}
189
220
  onClick={() => onUpdate(job.id, { status: 'paused' })}
190
221
  >
191
- ⏸
222
+ <NiceIcon name="pause" size={14} />
192
223
  </button>
193
224
  )}
194
225
  {job.status === 'paused' && (
195
226
  <button
196
227
  className={styles.actionBtn}
197
- title="Wznów"
228
+ title={t('printing.action.resume', 'Resume')}
198
229
  onClick={() => onUpdate(job.id, { status: 'queued' })}
199
230
  >
200
- ▶
231
+ <NiceIcon name="play" size={14} />
201
232
  </button>
202
233
  )}
203
234
  <button
204
235
  className={`${styles.actionBtn} ${styles.removeBtn}`}
205
- title="Usuń"
236
+ title={t('printing.action.remove', 'Remove')}
206
237
  onClick={() => onRemove(job.id)}
207
238
  >
208
- ✕
239
+ <NiceIcon name="close" size={14} />
209
240
  </button>
210
241
  </div>
211
242
  </div>
@@ -227,6 +258,7 @@ export const NicePrintQueue: React.FC<NicePrintQueueProps> = ({
227
258
  style,
228
259
  theme = 'light',
229
260
  }) => {
261
+ const { t } = useNiceTranslation();
230
262
  const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
231
263
  const [filter, setFilter] = useState<'all' | 'queued' | 'printing' | 'done' | 'error'>('all');
232
264
 
@@ -280,7 +312,7 @@ export const NicePrintQueue: React.FC<NicePrintQueueProps> = ({
280
312
  className={`${styles.filterTab} ${filter === f ? styles.filterTabActive : ''}`}
281
313
  onClick={() => setFilter(f)}
282
314
  >
283
- {f === 'all' ? 'Wszystkie' : STATUS_LABELS[f]}
315
+ {f === 'all' ? t('printing.filter.all', 'All') : statusLabel(t, f)}
284
316
  <span className={styles.filterCount}>
285
317
  {f === 'all' ? jobs.length : jobs.filter((j) => j.status === f).length}
286
318
  </span>
@@ -291,15 +323,17 @@ export const NicePrintQueue: React.FC<NicePrintQueueProps> = ({
291
323
  {/* Bulk actions */}
292
324
  {selectedIds.size > 0 && (
293
325
  <div className={styles.bulkActions}>
294
- <span className={styles.bulkCount}>{selectedIds.size} zaznaczonych</span>
326
+ <span className={styles.bulkCount}>
327
+ {selectedIds.size} {t('printing.bulk.selected', 'selected')}
328
+ </span>
295
329
  {onBulkPrint && (
296
330
  <button className={styles.bulkBtn} onClick={bulkPrint}>
297
- ▶ Drukuj wybrane
331
+ <NiceIcon name="printer" size={14} /> {t('printing.bulk.print', 'Print selected')}
298
332
  </button>
299
333
  )}
300
334
  {onBulkRemove && (
301
335
  <button className={`${styles.bulkBtn} ${styles.bulkBtnRemove}`} onClick={bulkRemove}>
302
- ✕ Usuń wybrane
336
+ <NiceIcon name="trash" size={14} /> {t('printing.bulk.remove', 'Remove selected')}
303
337
  </button>
304
338
  )}
305
339
  </div>
@@ -311,8 +345,8 @@ export const NicePrintQueue: React.FC<NicePrintQueueProps> = ({
311
345
  {filteredJobs.length === 0 ? (
312
346
  <div className={styles.emptyState}>
313
347
  {filter === 'all'
314
- ? 'Brak zadań w kolejce'
315
- : `Brak zadań ze statusem "${STATUS_LABELS[filter]}"`}
348
+ ? t('printing.empty.queue', 'No jobs in the queue')
349
+ : `${t('printing.empty.filtered', 'No jobs with status')} "${statusLabel(t, filter)}"`}
316
350
  </div>
317
351
  ) : (
318
352
  <>
@@ -323,9 +357,9 @@ export const NicePrintQueue: React.FC<NicePrintQueueProps> = ({
323
357
  onChange={selectAll}
324
358
  checked={selectedIds.size === filteredJobs.length && filteredJobs.length > 0}
325
359
  />
326
- <span className={styles.headerLabel}>Zadanie</span>
327
- <span className={styles.headerLabel}>Kopie</span>
328
- <span className={styles.headerLabel}>Akcje</span>
360
+ <span className={styles.headerLabel}>{t('printing.col.job', 'Job')}</span>
361
+ <span className={styles.headerLabel}>{t('printing.col.copies', 'Copies')}</span>
362
+ <span className={styles.headerLabel}>{t('printing.col.actions', 'Actions')}</span>
329
363
  </div>
330
364
  {filteredJobs.map((job, _index) => (
331
365
  <JobRow
@@ -1,7 +1,7 @@
1
1
  .root {
2
2
  display: flex;
3
3
  flex-direction: column;
4
- background: var(--nice-bg, #FFF);
4
+ background: var(--nice-bg, #fff);
5
5
  border: 1px solid var(--nice-border, #e2e8f0);
6
6
  border-radius: var(--nice-radius-xl, 12px);
7
7
  overflow: hidden;
@@ -9,7 +9,11 @@
9
9
  font-size: 14px;
10
10
  color: var(--nice-text, #1e293b);
11
11
  }
12
- .dark { background: var(--nice-text, #1e293b); border-color: var(--nice-text, #334155); color: var(--nice-bg-secondary, #f1f5f9); }
12
+ .dark {
13
+ background: var(--nice-bg-secondary, #1e293b);
14
+ border-color: var(--nice-border-color, #334155);
15
+ color: var(--nice-text-primary, #f1f5f9);
16
+ }
13
17
 
14
18
  /* Stats bar */
15
19
  .statsBar {
@@ -20,14 +24,35 @@
20
24
  background: var(--nice-bg-secondary, #f8fafc);
21
25
  border-bottom: 1px solid var(--nice-border, #e2e8f0);
22
26
  }
23
- .dark .statsBar { background: var(--nice-text, #0f172a); border-color: var(--nice-text, #334155); }
27
+ .dark .statsBar {
28
+ background: var(--nice-bg-primary, #0f172a);
29
+ border-color: var(--nice-border-color, #334155);
30
+ }
24
31
 
25
- .stat { display: flex; align-items: center; gap: var(--nice-space-1-5, 6px); font-size: 13px; }
26
- .dot { width: 8px; height: 8px; border-radius: var(--nice-radius-full, 50%); display: inline-block; }
27
- .statPrinting .dot { background: var(--nice-primary-hover, #2563eb); }
28
- .statQueued .dot { background: var(--nice-text-secondary, #64748b); }
29
- .statDone .dot { background: var(--nice-success-dark, #16a34a); }
30
- .statError .dot { background: var(--nice-danger-dark, #dc2626); }
32
+ .stat {
33
+ display: flex;
34
+ align-items: center;
35
+ gap: var(--nice-space-1-5, 6px);
36
+ font-size: 13px;
37
+ }
38
+ .dot {
39
+ width: 8px;
40
+ height: 8px;
41
+ border-radius: var(--nice-radius-full, 50%);
42
+ display: inline-block;
43
+ }
44
+ .statPrinting .dot {
45
+ background: var(--nice-primary-hover, #2563eb);
46
+ }
47
+ .statQueued .dot {
48
+ background: var(--nice-text-secondary, #64748b);
49
+ }
50
+ .statDone .dot {
51
+ background: var(--nice-success-dark, #16a34a);
52
+ }
53
+ .statError .dot {
54
+ background: var(--nice-danger-dark, #dc2626);
55
+ }
31
56
 
32
57
  /* Toolbar */
33
58
  .toolbar {
@@ -38,11 +63,18 @@
38
63
  border-bottom: 1px solid var(--nice-border, #e2e8f0);
39
64
  flex-wrap: wrap;
40
65
  }
41
- .dark .toolbar { border-color: var(--nice-text, #334155); }
66
+ .dark .toolbar {
67
+ border-color: var(--nice-border-color, #334155);
68
+ }
42
69
 
43
- .filterTabs { display: flex; gap: var(--nice-space-1, 4px); }
70
+ .filterTabs {
71
+ display: flex;
72
+ gap: var(--nice-space-1, 4px);
73
+ }
44
74
  .filterTab {
45
- display: flex; align-items: center; gap: var(--nice-space-1-25, 5px);
75
+ display: flex;
76
+ align-items: center;
77
+ gap: var(--nice-space-1-25, 5px);
46
78
  padding: var(--nice-space-1, 4px) var(--nice-space-2-5, 10px);
47
79
  background: none;
48
80
  border: 1px solid var(--nice-border, #e2e8f0);
@@ -51,31 +83,60 @@
51
83
  font-size: 12px;
52
84
  color: var(--nice-text-secondary, #475569);
53
85
  }
54
- .filterTab:hover { background: var(--nice-bg-secondary, #f1f5f9); }
55
- .filterTabActive { background: var(--nice-primary-bg, #eff6ff); border-color: var(--nice-primary-hover, #2563eb); color: var(--nice-primary-hover, #2563eb); }
86
+ .filterTab:hover {
87
+ background: var(--nice-bg-secondary, #f1f5f9);
88
+ }
89
+ .filterTabActive {
90
+ background: var(--nice-primary-bg, #eff6ff);
91
+ border-color: var(--nice-primary-hover, #2563eb);
92
+ color: var(--nice-primary-hover, #2563eb);
93
+ }
56
94
  .filterCount {
57
- background: var(--nice-bg-secondary, #f1f5f9); padding: var(--nice-space-px, 1px) var(--nice-space-1-25, 5px); border-radius: var(--nice-radius-lg, 8px);
58
- font-size: 11px; color: var(--nice-text-secondary, #64748b);
95
+ background: var(--nice-bg-secondary, #f1f5f9);
96
+ padding: var(--nice-space-px, 1px) var(--nice-space-1-25, 5px);
97
+ border-radius: var(--nice-radius-lg, 8px);
98
+ font-size: 11px;
99
+ color: var(--nice-text-secondary, #64748b);
100
+ }
101
+ .filterTabActive .filterCount {
102
+ background: var(--nice-primary-bg, #dbeafe);
103
+ color: var(--nice-primary-hover, #2563eb);
59
104
  }
60
- .filterTabActive .filterCount { background: var(--nice-primary-bg, #dbeafe); color: var(--nice-primary-hover, #2563eb); }
61
105
 
62
- .bulkActions { display: flex; align-items: center; gap: var(--nice-space-2, 8px); margin-left: auto; }
63
- .bulkCount { font-size: 13px; color: var(--nice-text-secondary, #64748b); }
106
+ .bulkActions {
107
+ display: flex;
108
+ align-items: center;
109
+ gap: var(--nice-space-2, 8px);
110
+ margin-left: auto;
111
+ }
112
+ .bulkCount {
113
+ font-size: 13px;
114
+ color: var(--nice-text-secondary, #64748b);
115
+ }
64
116
  .bulkBtn {
65
117
  padding: var(--nice-space-1, 4px) var(--nice-space-2-5, 10px);
66
118
  border-radius: var(--nice-radius-md, 6px);
67
119
  border: 1px solid var(--nice-border, #e2e8f0);
68
120
  cursor: pointer;
69
121
  font-size: 12px;
70
- background: var(--nice-bg, #FFF);
122
+ background: var(--nice-bg, #fff);
71
123
  color: var(--nice-text, #1e293b);
72
124
  }
73
- .bulkBtn:hover { background: var(--nice-bg-secondary, #f1f5f9); }
74
- .bulkBtnRemove { color: var(--nice-danger-dark, #dc2626); border-color: var(--nice-danger-bg, #fecaca); }
75
- .bulkBtnRemove:hover { background: var(--nice-danger-bg, #fef2f2); }
125
+ .bulkBtn:hover {
126
+ background: var(--nice-bg-secondary, #f1f5f9);
127
+ }
128
+ .bulkBtnRemove {
129
+ color: var(--nice-danger-dark, #dc2626);
130
+ border-color: var(--nice-danger-bg, #fecaca);
131
+ }
132
+ .bulkBtnRemove:hover {
133
+ background: var(--nice-danger-bg, #fef2f2);
134
+ }
76
135
 
77
136
  /* Job list */
78
- .jobList { overflow-y: auto; }
137
+ .jobList {
138
+ overflow-y: auto;
139
+ }
79
140
 
80
141
  .listHeader {
81
142
  display: grid;
@@ -90,8 +151,12 @@
90
151
  letter-spacing: 0.05em;
91
152
  border-bottom: 1px solid var(--nice-bg-secondary, #f1f5f9);
92
153
  }
93
- .dark .listHeader { border-color: var(--nice-text, #1e293b); }
94
- .headerLabel { white-space: nowrap; }
154
+ .dark .listHeader {
155
+ border-color: var(--nice-border-color, #1e293b);
156
+ }
157
+ .headerLabel {
158
+ white-space: nowrap;
159
+ }
95
160
 
96
161
  /* Job row */
97
162
  .jobRow {
@@ -103,13 +168,27 @@
103
168
  border-bottom: 1px solid var(--nice-bg-secondary, #f1f5f9);
104
169
  transition: background 0.1s;
105
170
  }
106
- .dark .jobRow { border-color: var(--nice-text, #334155); }
107
- .jobRow:hover { background: var(--nice-bg-secondary, #f8fafc); }
108
- .dark .jobRow:hover { background: var(--nice-text, #0f172a); }
109
- .jobRowSelected { background: var(--nice-primary-bg, #eff6ff) !important; }
110
- .dark .jobRowSelected { background: #172554 !important; }
171
+ .dark .jobRow {
172
+ border-color: var(--nice-border-color, #334155);
173
+ }
174
+ .jobRow:hover {
175
+ background: var(--nice-bg-secondary, #f8fafc);
176
+ }
177
+ .dark .jobRow:hover {
178
+ background: var(--nice-bg-primary, #0f172a);
179
+ }
180
+ .jobRowSelected {
181
+ background: var(--nice-primary-bg, #eff6ff) !important;
182
+ }
183
+ .dark .jobRowSelected {
184
+ background: #172554 !important;
185
+ }
111
186
 
112
- .jobCheckbox { cursor: pointer; width: 16px; height: 16px; }
187
+ .jobCheckbox {
188
+ cursor: pointer;
189
+ width: 16px;
190
+ height: 16px;
191
+ }
113
192
 
114
193
  /* Status badges */
115
194
  .statusBadge {
@@ -121,26 +200,71 @@
121
200
  font-weight: 500;
122
201
  white-space: nowrap;
123
202
  }
124
- .badge-queued { background: var(--nice-bg-secondary, #f1f5f9); color: var(--nice-text-secondary, #64748b); }
125
- .badge-printing { background: var(--nice-primary-bg, #dbeafe); color: var(--nice-primary-dark, #1d4ed8); animation: pulse 1.5s ease-in-out infinite; }
126
- .badge-done { background: var(--nice-success-bg, #dcfce7); color: var(--nice-success-dark, #16a34a); }
127
- .badge-error { background: var(--nice-danger-bg, #fee2e2); color: var(--nice-danger-dark, #dc2626); }
128
- .badge-paused { background: var(--nice-warning-bg, #fef9c3); color: var(--nice-warning-dark, #854d0e); }
129
- .badge-cancelled { background: var(--nice-bg-secondary, #f1f5f9); color: var(--nice-text-muted, #94a3b8); }
203
+ .badge-queued {
204
+ background: var(--nice-bg-secondary, #f1f5f9);
205
+ color: var(--nice-text-secondary, #64748b);
206
+ }
207
+ .badge-printing {
208
+ background: var(--nice-primary-bg, #dbeafe);
209
+ color: var(--nice-primary-dark, #1d4ed8);
210
+ animation: pulse 1.5s ease-in-out infinite;
211
+ }
212
+ .badge-done {
213
+ background: var(--nice-success-bg, #dcfce7);
214
+ color: var(--nice-success-dark, #16a34a);
215
+ }
216
+ .badge-error {
217
+ background: var(--nice-danger-bg, #fee2e2);
218
+ color: var(--nice-danger-dark, #dc2626);
219
+ }
220
+ .badge-paused {
221
+ background: var(--nice-warning-bg, #fef9c3);
222
+ color: var(--nice-warning-dark, #854d0e);
223
+ }
224
+ .badge-cancelled {
225
+ background: var(--nice-bg-secondary, #f1f5f9);
226
+ color: var(--nice-text-muted, #94a3b8);
227
+ }
130
228
 
131
- @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } }
229
+ @keyframes pulse {
230
+ 0%,
231
+ 100% {
232
+ opacity: 1;
233
+ }
234
+ 50% {
235
+ opacity: 0.7;
236
+ }
237
+ }
132
238
 
133
239
  /* Job info */
134
- .jobInfo { min-width: 0; }
135
- .jobName { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
240
+ .jobInfo {
241
+ min-width: 0;
242
+ }
243
+ .jobName {
244
+ font-weight: 500;
245
+ white-space: nowrap;
246
+ overflow: hidden;
247
+ text-overflow: ellipsis;
248
+ }
136
249
  .jobMeta {
137
- display: flex; align-items: center; gap: var(--nice-space-2, 8px);
138
- font-size: 12px; color: var(--nice-text-muted, #94a3b8);
250
+ display: flex;
251
+ align-items: center;
252
+ gap: var(--nice-space-2, 8px);
253
+ font-size: 12px;
254
+ color: var(--nice-text-muted, #94a3b8);
139
255
  margin-top: var(--nice-space-0-5, 2px);
140
256
  }
141
- .jobTime { color: var(--nice-text-secondary, #64748b); }
142
- .jobDuration { color: var(--nice-warning, #f59e0b); }
143
- .jobError { font-size: 12px; color: var(--nice-danger-dark, #dc2626); margin-top: var(--nice-space-1, 4px); }
257
+ .jobTime {
258
+ color: var(--nice-text-secondary, #64748b);
259
+ }
260
+ .jobDuration {
261
+ color: var(--nice-warning, #f59e0b);
262
+ }
263
+ .jobError {
264
+ font-size: 12px;
265
+ color: var(--nice-danger-dark, #dc2626);
266
+ margin-top: var(--nice-space-1, 4px);
267
+ }
144
268
 
145
269
  /* Progress bar */
146
270
  .progressBar {
@@ -165,8 +289,11 @@
165
289
  white-space: nowrap;
166
290
  }
167
291
  .copiesBtn {
168
- width: 22px; height: 22px;
169
- display: flex; align-items: center; justify-content: center;
292
+ width: 22px;
293
+ height: 22px;
294
+ display: flex;
295
+ align-items: center;
296
+ justify-content: center;
170
297
  background: var(--nice-bg-secondary, #f8fafc);
171
298
  border: 1px solid var(--nice-border, #e2e8f0);
172
299
  border-radius: var(--nice-radius-sm, 4px);
@@ -174,15 +301,31 @@
174
301
  font-size: 14px;
175
302
  color: var(--nice-text-secondary, #475569);
176
303
  }
177
- .copiesBtn:disabled { opacity: 0.4; cursor: default; }
178
- .copiesBtn:not(:disabled):hover { background: var(--nice-border, #e2e8f0); }
179
- .copiesValue { min-width: 20px; text-align: center; font-size: 13px; }
304
+ .copiesBtn:disabled {
305
+ opacity: 0.4;
306
+ cursor: default;
307
+ }
308
+ .copiesBtn:not(:disabled):hover {
309
+ background: var(--nice-border, #e2e8f0);
310
+ }
311
+ .copiesValue {
312
+ min-width: 20px;
313
+ text-align: center;
314
+ font-size: 13px;
315
+ }
180
316
 
181
317
  /* Actions */
182
- .jobActions { display: flex; align-items: center; gap: var(--nice-space-1, 4px); }
318
+ .jobActions {
319
+ display: flex;
320
+ align-items: center;
321
+ gap: var(--nice-space-1, 4px);
322
+ }
183
323
  .actionBtn {
184
- width: 28px; height: 28px;
185
- display: flex; align-items: center; justify-content: center;
324
+ width: 28px;
325
+ height: 28px;
326
+ display: flex;
327
+ align-items: center;
328
+ justify-content: center;
186
329
  background: none;
187
330
  border: 1px solid var(--nice-border, #e2e8f0);
188
331
  border-radius: var(--nice-radius-md, 6px);
@@ -190,9 +333,16 @@
190
333
  font-size: 13px;
191
334
  color: var(--nice-text-secondary, #475569);
192
335
  }
193
- .actionBtn:hover { background: var(--nice-bg-secondary, #f1f5f9); }
194
- .removeBtn { color: var(--nice-danger, #ef4444); border-color: var(--nice-danger-bg, #fecaca); }
195
- .removeBtn:hover { background: var(--nice-danger-bg, #fef2f2); }
336
+ .actionBtn:hover {
337
+ background: var(--nice-bg-secondary, #f1f5f9);
338
+ }
339
+ .removeBtn {
340
+ color: var(--nice-danger, #ef4444);
341
+ border-color: var(--nice-danger-bg, #fecaca);
342
+ }
343
+ .removeBtn:hover {
344
+ background: var(--nice-danger-bg, #fef2f2);
345
+ }
196
346
 
197
347
  /* Empty state */
198
348
  .emptyState {