@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.
- package/dist/index.cjs +2 -2
- package/dist/index.d.ts +5 -5
- package/dist/index.mjs +1894 -1728
- package/dist/ui-printing.css +1 -1
- package/package.json +3 -2
- package/src/index.ts +5 -24
- package/src/print-button/NicePrintButton.tsx +44 -18
- package/src/print-button/PrintButton.module.css +147 -43
- package/src/print-preview/NicePrintPreview.tsx +19 -17
- package/src/print-preview/PrintPreview.module.css +60 -18
- package/src/print-queue/NicePrintQueue.tsx +76 -42
- package/src/print-queue/PrintQueue.module.css +207 -57
- package/src/template-browser/NiceTemplateBrowser.tsx +44 -19
- package/src/template-browser/TemplateBrowser.module.css +70 -25
- package/src/template-editor/NiceTemplateEditor.tsx +307 -182
- package/src/template-editor/TemplateEditor.module.css +184 -128
- package/src/types/printingTypes.ts +1 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/print-button/NicePrintButton.d.ts.map +0 -1
- package/dist/print-button/index.d.ts.map +0 -1
- package/dist/print-preview/NicePrintPreview.d.ts.map +0 -1
- package/dist/print-preview/index.d.ts.map +0 -1
- package/dist/print-queue/NicePrintQueue.d.ts.map +0 -1
- package/dist/print-queue/index.d.ts.map +0 -1
- package/dist/style.css +0 -1
- package/dist/template-browser/NiceTemplateBrowser.d.ts.map +0 -1
- package/dist/template-browser/index.d.ts.map +0 -1
- package/dist/template-editor/NiceTemplateEditor.d.ts.map +0 -1
- package/dist/template-editor/index.d.ts.map +0 -1
- package/dist/types/printingTypes.d.ts.map +0 -1
- package/src/template-editor/NiceTemplateEditor.tsx.first +0 -1011
- 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
|
|
9
|
-
queued: '
|
|
10
|
-
printing: '
|
|
11
|
-
done: '
|
|
12
|
-
error: '
|
|
13
|
-
paused: '
|
|
14
|
-
cancelled: '
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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} />
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
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 &&
|
|
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 &&
|
|
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=
|
|
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=
|
|
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
|
|
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=
|
|
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=
|
|
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=
|
|
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' ? '
|
|
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}>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
? '
|
|
315
|
-
:
|
|
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}>
|
|
327
|
-
<span className={styles.headerLabel}>
|
|
328
|
-
<span className={styles.headerLabel}>
|
|
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, #
|
|
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 {
|
|
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 {
|
|
27
|
+
.dark .statsBar {
|
|
28
|
+
background: var(--nice-bg-primary, #0f172a);
|
|
29
|
+
border-color: var(--nice-border-color, #334155);
|
|
30
|
+
}
|
|
24
31
|
|
|
25
|
-
.stat {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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 {
|
|
66
|
+
.dark .toolbar {
|
|
67
|
+
border-color: var(--nice-border-color, #334155);
|
|
68
|
+
}
|
|
42
69
|
|
|
43
|
-
.filterTabs {
|
|
70
|
+
.filterTabs {
|
|
71
|
+
display: flex;
|
|
72
|
+
gap: var(--nice-space-1, 4px);
|
|
73
|
+
}
|
|
44
74
|
.filterTab {
|
|
45
|
-
display: flex;
|
|
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 {
|
|
55
|
-
|
|
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);
|
|
58
|
-
|
|
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 {
|
|
63
|
-
|
|
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, #
|
|
122
|
+
background: var(--nice-bg, #fff);
|
|
71
123
|
color: var(--nice-text, #1e293b);
|
|
72
124
|
}
|
|
73
|
-
.bulkBtn:hover {
|
|
74
|
-
|
|
75
|
-
|
|
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 {
|
|
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 {
|
|
94
|
-
|
|
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 {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
.
|
|
110
|
-
|
|
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 {
|
|
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 {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
.badge-
|
|
129
|
-
|
|
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 {
|
|
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 {
|
|
135
|
-
|
|
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;
|
|
138
|
-
|
|
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 {
|
|
142
|
-
|
|
143
|
-
|
|
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;
|
|
169
|
-
|
|
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 {
|
|
178
|
-
|
|
179
|
-
|
|
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 {
|
|
318
|
+
.jobActions {
|
|
319
|
+
display: flex;
|
|
320
|
+
align-items: center;
|
|
321
|
+
gap: var(--nice-space-1, 4px);
|
|
322
|
+
}
|
|
183
323
|
.actionBtn {
|
|
184
|
-
width: 28px;
|
|
185
|
-
|
|
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 {
|
|
194
|
-
|
|
195
|
-
|
|
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 {
|