includio-cms 0.24.0 → 0.25.0
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/API.md +29 -6
- package/CHANGELOG.md +95 -0
- package/DOCS.md +80 -5
- package/ROADMAP.md +1 -0
- package/dist/admin/client/index.d.ts +3 -0
- package/dist/admin/client/index.js +3 -0
- package/dist/admin/client/shop/coupon-edit-page.svelte +44 -0
- package/dist/admin/client/shop/coupon-edit-page.svelte.d.ts +3 -0
- package/dist/admin/client/shop/coupon-form.svelte +170 -0
- package/dist/admin/client/shop/coupon-form.svelte.d.ts +18 -0
- package/dist/admin/client/shop/coupon-new-page.svelte +25 -0
- package/dist/admin/client/shop/coupon-new-page.svelte.d.ts +18 -0
- package/dist/admin/client/shop/coupons-list-page.svelte +135 -0
- package/dist/admin/client/shop/coupons-list-page.svelte.d.ts +3 -0
- package/dist/admin/client/shop/refund-dialog.svelte +161 -0
- package/dist/admin/client/shop/refund-dialog.svelte.d.ts +11 -0
- package/dist/admin/client/shop/shipping-method-edit-page.svelte +3 -6
- package/dist/admin/client/shop/shipping-method-form.svelte +15 -21
- package/dist/admin/client/shop/shipping-method-new-page.svelte +3 -6
- package/dist/admin/client/shop/shipping-methods-list-page.svelte +6 -6
- package/dist/admin/client/shop/shop-order-detail-page.svelte +107 -27
- package/dist/admin/client/shop/shop-orders-list-page.svelte +49 -11
- package/dist/admin/client/shop/shop-products-list-page.svelte +12 -11
- package/dist/admin/components/layout/lang.d.ts +1 -0
- package/dist/admin/components/layout/lang.js +4 -2
- package/dist/admin/components/layout/layout-renderer.svelte +12 -11
- package/dist/admin/components/layout/nav-breadcrumbs.svelte +3 -5
- package/dist/admin/components/layout/nav-shop.svelte +3 -1
- package/dist/admin/components/layout/nav-user.svelte +6 -4
- package/dist/admin/components/layout/site-header.svelte +11 -5
- package/dist/admin/remote/shop.remote.d.ts +122 -3
- package/dist/admin/remote/shop.remote.js +161 -5
- package/dist/db-postgres/schema/shop/couponRedemptions.d.ts +97 -0
- package/dist/db-postgres/schema/shop/couponRedemptions.js +21 -0
- package/dist/db-postgres/schema/shop/coupons.d.ts +197 -0
- package/dist/db-postgres/schema/shop/coupons.js +18 -0
- package/dist/db-postgres/schema/shop/index.d.ts +4 -0
- package/dist/db-postgres/schema/shop/index.js +4 -0
- package/dist/db-postgres/schema/shop/product.d.ts +17 -0
- package/dist/db-postgres/schema/shop/product.js +2 -0
- package/dist/db-postgres/schema/shop/refunds.d.ts +214 -0
- package/dist/db-postgres/schema/shop/refunds.js +21 -0
- package/dist/db-postgres/schema/shop/webhookEvents.d.ts +183 -0
- package/dist/db-postgres/schema/shop/webhookEvents.js +22 -0
- package/dist/shop/adapters/payu/client.d.ts +9 -0
- package/dist/shop/adapters/payu/client.js +29 -0
- package/dist/shop/adapters/payu/index.js +17 -1
- package/dist/shop/adapters/stripe/index.d.ts +64 -0
- package/dist/shop/adapters/stripe/index.js +169 -0
- package/dist/shop/adapters/stripe/payload.d.ts +38 -0
- package/dist/shop/adapters/stripe/payload.js +90 -0
- package/dist/shop/adapters/stripe/status-map.d.ts +11 -0
- package/dist/shop/adapters/stripe/status-map.js +31 -0
- package/dist/shop/cart/coupon-cookie.d.ts +7 -0
- package/dist/shop/cart/coupon-cookie.js +32 -0
- package/dist/shop/cart/types.d.ts +12 -0
- package/dist/shop/client/index.d.ts +118 -0
- package/dist/shop/client/index.js +39 -1
- package/dist/shop/http/cart-handler.d.ts +8 -0
- package/dist/shop/http/cart-handler.js +60 -1
- package/dist/shop/http/checkout-handler.js +7 -3
- package/dist/shop/http/index.d.ts +1 -1
- package/dist/shop/http/index.js +1 -1
- package/dist/shop/http/retry-payment-handler.js +1 -1
- package/dist/shop/http/webhook-handler.js +19 -1
- package/dist/shop/http/webhook-idempotency.d.ts +16 -0
- package/dist/shop/http/webhook-idempotency.js +51 -0
- package/dist/shop/http/webhook-logic.js +2 -1
- package/dist/shop/index.d.ts +3 -1
- package/dist/shop/index.js +3 -1
- package/dist/shop/pricing.d.ts +15 -0
- package/dist/shop/pricing.js +22 -0
- package/dist/shop/server/cart-hydrate.d.ts +1 -0
- package/dist/shop/server/cart-hydrate.js +58 -10
- package/dist/shop/server/coupons.d.ts +53 -0
- package/dist/shop/server/coupons.js +117 -0
- package/dist/shop/server/email.d.ts +15 -0
- package/dist/shop/server/email.js +46 -3
- package/dist/shop/server/orders.d.ts +1 -0
- package/dist/shop/server/orders.js +120 -54
- package/dist/shop/server/refund.d.ts +32 -0
- package/dist/shop/server/refund.js +140 -0
- package/dist/shop/svelte/InpostPicker.svelte +4 -7
- package/dist/shop/svelte/OrderStatus.svelte +6 -10
- package/dist/shop/svelte/labels.js +4 -2
- package/dist/shop/types.d.ts +41 -1
- package/dist/updates/0.25.0/index.d.ts +2 -0
- package/dist/updates/0.25.0/index.js +89 -0
- package/dist/updates/index.js +64 -1
- package/package.json +6 -1
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
import { getRemotes } from '../../../sveltekit/index.js';
|
|
4
4
|
import { Button } from '../../../components/ui/button/index.js';
|
|
5
5
|
import MailIcon from '@tabler/icons-svelte/icons/mail';
|
|
6
|
+
import RefundDialog from './refund-dialog.svelte';
|
|
6
7
|
|
|
7
8
|
const remotes = getRemotes();
|
|
8
9
|
|
|
9
10
|
const orderId = $derived(page.params.id ?? '');
|
|
10
11
|
const query = $derived(remotes.getOrderForAdmin(orderId));
|
|
12
|
+
const refundsQuery = $derived(remotes.getOrderRefundsAdmin(orderId));
|
|
13
|
+
let refundDialogOpen = $state(false);
|
|
11
14
|
|
|
12
15
|
type OrderStatus =
|
|
13
16
|
| 'new'
|
|
@@ -17,7 +20,8 @@
|
|
|
17
20
|
| 'sent'
|
|
18
21
|
| 'done'
|
|
19
22
|
| 'cancelled'
|
|
20
|
-
| 'paymentRejected'
|
|
23
|
+
| 'paymentRejected'
|
|
24
|
+
| 'refunded';
|
|
21
25
|
|
|
22
26
|
const STATUSES: Array<{ value: OrderStatus; label: string }> = [
|
|
23
27
|
{ value: 'new', label: 'Nowe' },
|
|
@@ -27,7 +31,8 @@
|
|
|
27
31
|
{ value: 'sent', label: 'Wysłane' },
|
|
28
32
|
{ value: 'done', label: 'Zrealizowane' },
|
|
29
33
|
{ value: 'cancelled', label: 'Anulowane' },
|
|
30
|
-
{ value: 'paymentRejected', label: 'Płatność odrzucona' }
|
|
34
|
+
{ value: 'paymentRejected', label: 'Płatność odrzucona' },
|
|
35
|
+
{ value: 'refunded', label: 'Zwrócone' }
|
|
31
36
|
];
|
|
32
37
|
|
|
33
38
|
const STATUS_STYLES: Record<string, string> = {
|
|
@@ -38,7 +43,8 @@
|
|
|
38
43
|
sent: 'bg-indigo-100 text-indigo-800',
|
|
39
44
|
done: 'bg-gray-100 text-gray-800',
|
|
40
45
|
cancelled: 'bg-red-100 text-red-800',
|
|
41
|
-
paymentRejected: 'bg-red-100 text-red-800'
|
|
46
|
+
paymentRejected: 'bg-red-100 text-red-800',
|
|
47
|
+
refunded: 'bg-orange-100 text-orange-800'
|
|
42
48
|
};
|
|
43
49
|
|
|
44
50
|
let newStatus = $state<OrderStatus | ''>('');
|
|
@@ -119,8 +125,7 @@
|
|
|
119
125
|
await query.refresh();
|
|
120
126
|
}
|
|
121
127
|
} catch (err) {
|
|
122
|
-
errorMessage =
|
|
123
|
-
err instanceof Error ? err.message : 'Błąd przy tworzeniu przesyłki';
|
|
128
|
+
errorMessage = err instanceof Error ? err.message : 'Błąd przy tworzeniu przesyłki';
|
|
124
129
|
} finally {
|
|
125
130
|
shipping = false;
|
|
126
131
|
}
|
|
@@ -140,8 +145,7 @@
|
|
|
140
145
|
await query.refresh();
|
|
141
146
|
}
|
|
142
147
|
} catch (err) {
|
|
143
|
-
errorMessage =
|
|
144
|
-
err instanceof Error ? err.message : 'Błąd przy anulowaniu przesyłki';
|
|
148
|
+
errorMessage = err instanceof Error ? err.message : 'Błąd przy anulowaniu przesyłki';
|
|
145
149
|
} finally {
|
|
146
150
|
shipping = false;
|
|
147
151
|
}
|
|
@@ -158,7 +162,11 @@
|
|
|
158
162
|
{:else}
|
|
159
163
|
{@const { order, items, history } = query.current}
|
|
160
164
|
{@const address = order.shippingAddress as Record<string, string> | null}
|
|
161
|
-
{@const consents = order.consents as Array<{
|
|
165
|
+
{@const consents = order.consents as Array<{
|
|
166
|
+
id: string;
|
|
167
|
+
accepted: boolean;
|
|
168
|
+
label: string;
|
|
169
|
+
}> | null}
|
|
162
170
|
|
|
163
171
|
<div class="space-y-6 p-6">
|
|
164
172
|
<div class="flex items-start justify-between gap-4">
|
|
@@ -167,14 +175,14 @@
|
|
|
167
175
|
<span class="font-mono">{order.number}</span>
|
|
168
176
|
</h1>
|
|
169
177
|
<p class="text-muted-foreground text-sm">
|
|
170
|
-
{formatDate(order.createdAt)} ·
|
|
171
|
-
|
|
172
|
-
class="hover:underline">← Lista</a
|
|
173
|
-
>
|
|
178
|
+
{formatDate(order.createdAt)} ·
|
|
179
|
+
<a href="/admin/shop/orders" class="hover:underline">← Lista</a>
|
|
174
180
|
</p>
|
|
175
181
|
</div>
|
|
176
182
|
<span
|
|
177
|
-
class="inline-flex rounded-full px-3 py-1 text-xs font-semibold {STATUS_STYLES[
|
|
183
|
+
class="inline-flex rounded-full px-3 py-1 text-xs font-semibold {STATUS_STYLES[
|
|
184
|
+
order.status
|
|
185
|
+
] ?? 'bg-gray-100 text-gray-800'}"
|
|
178
186
|
>
|
|
179
187
|
{statusLabel(order.status)}
|
|
180
188
|
</span>
|
|
@@ -212,9 +220,13 @@
|
|
|
212
220
|
<div class="text-muted-foreground text-xs">{name.variant}</div>
|
|
213
221
|
{/if}
|
|
214
222
|
</td>
|
|
215
|
-
<td class="text-muted-foreground py-2 font-mono text-xs"
|
|
223
|
+
<td class="text-muted-foreground py-2 font-mono text-xs"
|
|
224
|
+
>{item.skuSnapshot ?? '—'}</td
|
|
225
|
+
>
|
|
216
226
|
<td class="py-2 text-center">{item.qty}</td>
|
|
217
|
-
<td class="py-2 text-right tabular-nums"
|
|
227
|
+
<td class="py-2 text-right tabular-nums"
|
|
228
|
+
>{formatPrice(item.priceGrossSnapshot, order.currency)}</td
|
|
229
|
+
>
|
|
218
230
|
<td class="text-muted-foreground py-2 text-right">{item.vatRate}%</td>
|
|
219
231
|
<td class="py-2 text-right font-semibold tabular-nums">
|
|
220
232
|
{formatPrice(item.priceGrossSnapshot * item.qty, order.currency)}
|
|
@@ -225,7 +237,9 @@
|
|
|
225
237
|
<tfoot class="border-border border-t-2">
|
|
226
238
|
<tr>
|
|
227
239
|
<td colspan="5" class="pt-3 text-right text-sm">Wysyłka (brutto):</td>
|
|
228
|
-
<td class="pt-3 text-right tabular-nums"
|
|
240
|
+
<td class="pt-3 text-right tabular-nums"
|
|
241
|
+
>{formatPrice(order.shippingGross, order.currency)}</td
|
|
242
|
+
>
|
|
229
243
|
</tr>
|
|
230
244
|
<tr>
|
|
231
245
|
<td colspan="5" class="text-right text-sm">Razem netto:</td>
|
|
@@ -255,7 +269,9 @@
|
|
|
255
269
|
{#each history as h (h.id)}
|
|
256
270
|
<li class="flex items-start gap-3">
|
|
257
271
|
<span
|
|
258
|
-
class="mt-0.5 inline-flex shrink-0 rounded-full px-2 py-0.5 text-xs {STATUS_STYLES[
|
|
272
|
+
class="mt-0.5 inline-flex shrink-0 rounded-full px-2 py-0.5 text-xs {STATUS_STYLES[
|
|
273
|
+
h.status
|
|
274
|
+
] ?? 'bg-gray-100 text-gray-800'}"
|
|
259
275
|
>
|
|
260
276
|
{statusLabel(h.status)}
|
|
261
277
|
</span>
|
|
@@ -299,7 +315,9 @@
|
|
|
299
315
|
</select>
|
|
300
316
|
</label>
|
|
301
317
|
<label class="block">
|
|
302
|
-
<span class="text-muted-foreground mb-1 block text-xs font-semibold"
|
|
318
|
+
<span class="text-muted-foreground mb-1 block text-xs font-semibold"
|
|
319
|
+
>Notatka (opcjonalna)</span
|
|
320
|
+
>
|
|
303
321
|
<textarea
|
|
304
322
|
bind:value={note}
|
|
305
323
|
rows="2"
|
|
@@ -312,10 +330,57 @@
|
|
|
312
330
|
</Button>
|
|
313
331
|
</section>
|
|
314
332
|
|
|
333
|
+
{#if refundsQuery.ready && refundsQuery.current}
|
|
334
|
+
{@const r = refundsQuery.current}
|
|
335
|
+
<section class="border-border bg-card space-y-3 rounded-xl border p-5 text-sm">
|
|
336
|
+
<h2 class="text-base font-bold">Zwroty</h2>
|
|
337
|
+
{#if r.refunds.length > 0}
|
|
338
|
+
<ul class="space-y-2">
|
|
339
|
+
{#each r.refunds as refund (refund.id)}
|
|
340
|
+
<li class="border-border flex items-start justify-between gap-2 border-t pt-2">
|
|
341
|
+
<div>
|
|
342
|
+
<div class="font-medium tabular-nums">
|
|
343
|
+
{formatPrice(refund.amount, r.currency)}
|
|
344
|
+
</div>
|
|
345
|
+
<div class="text-muted-foreground text-xs">
|
|
346
|
+
{formatDate(refund.createdAt)} · {refund.status}
|
|
347
|
+
</div>
|
|
348
|
+
{#if refund.reason}
|
|
349
|
+
<div class="text-muted-foreground text-xs">{refund.reason}</div>
|
|
350
|
+
{/if}
|
|
351
|
+
</div>
|
|
352
|
+
</li>
|
|
353
|
+
{/each}
|
|
354
|
+
</ul>
|
|
355
|
+
<div class="border-border border-t pt-2 text-xs">
|
|
356
|
+
<div class="text-muted-foreground">
|
|
357
|
+
Zwrócono: <strong>{formatPrice(r.refundedAmount, r.currency)}</strong>
|
|
358
|
+
</div>
|
|
359
|
+
<div class="text-muted-foreground">
|
|
360
|
+
Pozostało: <strong>{formatPrice(r.remainingRefundable, r.currency)}</strong>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
{:else}
|
|
364
|
+
<p class="text-muted-foreground text-xs">Brak zwrotów dla tego zamówienia.</p>
|
|
365
|
+
{/if}
|
|
366
|
+
{#if r.refundSupported && r.remainingRefundable > 0 && (order.status === 'paid' || order.status === 'preparing' || order.status === 'sent' || order.status === 'done')}
|
|
367
|
+
<Button onclick={() => (refundDialogOpen = true)} variant="outline" class="w-full">
|
|
368
|
+
Wykonaj zwrot
|
|
369
|
+
</Button>
|
|
370
|
+
{:else if !r.refundSupported && r.remainingRefundable > 0}
|
|
371
|
+
<p class="text-muted-foreground text-xs">
|
|
372
|
+
Adapter „{r.paymentMethod}” nie obsługuje zwrotów programowych.
|
|
373
|
+
</p>
|
|
374
|
+
{/if}
|
|
375
|
+
</section>
|
|
376
|
+
{/if}
|
|
377
|
+
|
|
315
378
|
{#if order.carrierType && order.carrierType !== 'none'}
|
|
316
379
|
<section class="border-border bg-card space-y-3 rounded-xl border p-5 text-sm">
|
|
317
380
|
<h2 class="text-base font-bold">
|
|
318
|
-
Przesyłka <span class="text-muted-foreground font-mono text-xs"
|
|
381
|
+
Przesyłka <span class="text-muted-foreground font-mono text-xs"
|
|
382
|
+
>{order.carrierType}</span
|
|
383
|
+
>
|
|
319
384
|
</h2>
|
|
320
385
|
{#if order.shipmentId}
|
|
321
386
|
<div class="space-y-1">
|
|
@@ -354,11 +419,7 @@
|
|
|
354
419
|
<p class="text-muted-foreground text-xs">
|
|
355
420
|
Wygeneruj etykietę i nadaj paczkę przez ShipX.
|
|
356
421
|
</p>
|
|
357
|
-
<Button
|
|
358
|
-
onclick={handleCreateShipment}
|
|
359
|
-
disabled={shipping}
|
|
360
|
-
class="w-full"
|
|
361
|
-
>
|
|
422
|
+
<Button onclick={handleCreateShipment} disabled={shipping} class="w-full">
|
|
362
423
|
{shipping ? 'Tworzenie…' : 'Utwórz przesyłkę InPost'}
|
|
363
424
|
</Button>
|
|
364
425
|
{:else}
|
|
@@ -394,10 +455,15 @@
|
|
|
394
455
|
<div class="border-border mt-2 border-t pt-2">
|
|
395
456
|
<div class="text-muted-foreground text-xs font-semibold">Faktura</div>
|
|
396
457
|
<div>{address.invoiceCompany}</div>
|
|
397
|
-
{#if address.invoiceNip}<div class="text-muted-foreground text-xs">
|
|
458
|
+
{#if address.invoiceNip}<div class="text-muted-foreground text-xs">
|
|
459
|
+
NIP: {address.invoiceNip}
|
|
460
|
+
</div>{/if}
|
|
398
461
|
{#if address.invoiceStreet}<div class="text-xs">{address.invoiceStreet}</div>{/if}
|
|
399
462
|
{#if address.invoicePostcode || address.invoiceCity}
|
|
400
|
-
<div class="text-xs">
|
|
463
|
+
<div class="text-xs">
|
|
464
|
+
{address.invoicePostcode ?? ''}
|
|
465
|
+
{address.invoiceCity ?? ''}
|
|
466
|
+
</div>
|
|
401
467
|
{/if}
|
|
402
468
|
</div>
|
|
403
469
|
{/if}
|
|
@@ -424,10 +490,24 @@
|
|
|
424
490
|
{#if order.notes}
|
|
425
491
|
<section class="border-border bg-card space-y-1 rounded-xl border p-5 text-sm">
|
|
426
492
|
<h2 class="text-base font-bold">Uwagi klienta</h2>
|
|
427
|
-
<p class="whitespace-pre-wrap
|
|
493
|
+
<p class="text-xs whitespace-pre-wrap">{order.notes}</p>
|
|
428
494
|
</section>
|
|
429
495
|
{/if}
|
|
430
496
|
</div>
|
|
431
497
|
</div>
|
|
432
498
|
</div>
|
|
499
|
+
|
|
500
|
+
{#if refundsQuery.ready && refundsQuery.current && refundsQuery.current.refundSupported}
|
|
501
|
+
<RefundDialog
|
|
502
|
+
bind:open={refundDialogOpen}
|
|
503
|
+
{orderId}
|
|
504
|
+
currency={refundsQuery.current.currency}
|
|
505
|
+
remainingRefundable={refundsQuery.current.remainingRefundable}
|
|
506
|
+
onOpenChange={(v) => (refundDialogOpen = v)}
|
|
507
|
+
onRefunded={async () => {
|
|
508
|
+
successMessage = 'Zwrot wykonany.';
|
|
509
|
+
await Promise.all([query.refresh(), refundsQuery.refresh()]);
|
|
510
|
+
}}
|
|
511
|
+
/>
|
|
512
|
+
{/if}
|
|
433
513
|
{/if}
|
|
@@ -12,9 +12,41 @@
|
|
|
12
12
|
{ value: 'sent', label: 'Wysłane' },
|
|
13
13
|
{ value: 'done', label: 'Zrealizowane' },
|
|
14
14
|
{ value: 'cancelled', label: 'Anulowane' },
|
|
15
|
-
{ value: 'paymentRejected', label: 'Płatność odrzucona' }
|
|
15
|
+
{ value: 'paymentRejected', label: 'Płatność odrzucona' },
|
|
16
|
+
{ value: 'refunded', label: 'Zwrócone' }
|
|
16
17
|
] as const;
|
|
17
18
|
|
|
19
|
+
let exporting = $state(false);
|
|
20
|
+
|
|
21
|
+
async function handleExportCsv() {
|
|
22
|
+
exporting = true;
|
|
23
|
+
try {
|
|
24
|
+
const result = await remotes.exportOrdersCsv(
|
|
25
|
+
statusFilter === 'all'
|
|
26
|
+
? appliedEmail
|
|
27
|
+
? { email: appliedEmail }
|
|
28
|
+
: undefined
|
|
29
|
+
: appliedEmail
|
|
30
|
+
? { status: statusFilter, email: appliedEmail }
|
|
31
|
+
: { status: statusFilter }
|
|
32
|
+
);
|
|
33
|
+
const blob = new Blob(['' + result.csv], { type: 'text/csv;charset=utf-8' });
|
|
34
|
+
const url = URL.createObjectURL(blob);
|
|
35
|
+
const a = document.createElement('a');
|
|
36
|
+
const ts = new Date().toISOString().slice(0, 10);
|
|
37
|
+
a.href = url;
|
|
38
|
+
a.download = `zamowienia-${ts}.csv`;
|
|
39
|
+
document.body.appendChild(a);
|
|
40
|
+
a.click();
|
|
41
|
+
a.remove();
|
|
42
|
+
URL.revokeObjectURL(url);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
alert(err instanceof Error ? err.message : 'Eksport nie powiódł się.');
|
|
45
|
+
} finally {
|
|
46
|
+
exporting = false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
18
50
|
let statusFilter = $state<(typeof STATUSES)[number]['value']>('all');
|
|
19
51
|
let emailFilter = $state('');
|
|
20
52
|
let appliedEmail = $state('');
|
|
@@ -35,7 +67,8 @@
|
|
|
35
67
|
sent: 'bg-indigo-100 text-indigo-800',
|
|
36
68
|
done: 'bg-gray-100 text-gray-800',
|
|
37
69
|
cancelled: 'bg-red-100 text-red-800',
|
|
38
|
-
paymentRejected: 'bg-red-100 text-red-800'
|
|
70
|
+
paymentRejected: 'bg-red-100 text-red-800',
|
|
71
|
+
refunded: 'bg-orange-100 text-orange-800'
|
|
39
72
|
};
|
|
40
73
|
|
|
41
74
|
function formatPrice(smallest: number, currency: string) {
|
|
@@ -73,10 +106,7 @@
|
|
|
73
106
|
<div class="flex flex-wrap items-end gap-3">
|
|
74
107
|
<label class="block">
|
|
75
108
|
<span class="text-muted-foreground mb-1 block text-xs font-semibold">Status</span>
|
|
76
|
-
<select
|
|
77
|
-
bind:value={statusFilter}
|
|
78
|
-
class="border-border h-9 rounded-lg border px-3 text-sm"
|
|
79
|
-
>
|
|
109
|
+
<select bind:value={statusFilter} class="border-border h-9 rounded-lg border px-3 text-sm">
|
|
80
110
|
{#each STATUSES as s (s.value)}
|
|
81
111
|
<option value={s.value}>{s.label}</option>
|
|
82
112
|
{/each}
|
|
@@ -93,6 +123,15 @@
|
|
|
93
123
|
class="border-border h-9 rounded-lg border px-3 text-sm"
|
|
94
124
|
/>
|
|
95
125
|
</label>
|
|
126
|
+
<button
|
|
127
|
+
type="button"
|
|
128
|
+
onclick={handleExportCsv}
|
|
129
|
+
disabled={exporting}
|
|
130
|
+
class="border-border hover:bg-muted h-9 rounded-lg border px-3 text-sm disabled:opacity-50"
|
|
131
|
+
title="Pobierz CSV z bieżącymi filtrami"
|
|
132
|
+
>
|
|
133
|
+
{exporting ? 'Eksportuję…' : 'Eksport CSV'}
|
|
134
|
+
</button>
|
|
96
135
|
</div>
|
|
97
136
|
</div>
|
|
98
137
|
|
|
@@ -102,14 +141,12 @@
|
|
|
102
141
|
{:else if (query.current?.length ?? 0) === 0}
|
|
103
142
|
<div class="bg-lavender-lighter/40 border-border rounded-xl border p-8 text-center text-sm">
|
|
104
143
|
<p class="mb-2 font-semibold">Brak zamówień</p>
|
|
105
|
-
<p class="text-muted-foreground">
|
|
106
|
-
Gdy klient złoży zamówienie, pojawi się tutaj.
|
|
107
|
-
</p>
|
|
144
|
+
<p class="text-muted-foreground">Gdy klient złoży zamówienie, pojawi się tutaj.</p>
|
|
108
145
|
</div>
|
|
109
146
|
{:else}
|
|
110
147
|
<div class="border-border overflow-hidden rounded-xl border">
|
|
111
148
|
<table class="w-full text-sm">
|
|
112
|
-
<thead class="bg-muted/50 text-left text-xs
|
|
149
|
+
<thead class="bg-muted/50 text-left text-xs tracking-wide uppercase">
|
|
113
150
|
<tr>
|
|
114
151
|
<th class="px-4 py-3 font-semibold">Numer</th>
|
|
115
152
|
<th class="px-4 py-3 font-semibold">Data</th>
|
|
@@ -136,7 +173,8 @@
|
|
|
136
173
|
<td class="text-muted-foreground px-4 py-3 text-xs">{o.paymentMethod ?? '—'}</td>
|
|
137
174
|
<td class="px-4 py-3">
|
|
138
175
|
<span
|
|
139
|
-
class="inline-flex rounded-full px-2 py-0.5 text-xs {STATUS_STYLES[o.status] ??
|
|
176
|
+
class="inline-flex rounded-full px-2 py-0.5 text-xs {STATUS_STYLES[o.status] ??
|
|
177
|
+
'bg-gray-100 text-gray-800'}"
|
|
140
178
|
>
|
|
141
179
|
{STATUSES.find((s) => s.value === o.status)?.label ?? o.status}
|
|
142
180
|
</span>
|
|
@@ -89,14 +89,14 @@
|
|
|
89
89
|
<div class="bg-lavender-lighter/40 border-border rounded-xl border p-8 text-center text-sm">
|
|
90
90
|
<p class="mb-2 font-semibold">Brak produktów</p>
|
|
91
91
|
<p class="text-muted-foreground">
|
|
92
|
-
Dodaj pole <code class="text-primary">{ type: 'shop' }</code> do kolekcji,
|
|
93
|
-
|
|
92
|
+
Dodaj pole <code class="text-primary">{ type: 'shop' }</code> do kolekcji, a następnie
|
|
93
|
+
utwórz wpis i wypełnij sekcję „Sklep".
|
|
94
94
|
</p>
|
|
95
95
|
</div>
|
|
96
96
|
{:else}
|
|
97
97
|
<div class="border-border overflow-hidden rounded-xl border">
|
|
98
98
|
<table class="w-full text-sm">
|
|
99
|
-
<thead class="bg-muted/50 text-left text-xs
|
|
99
|
+
<thead class="bg-muted/50 text-left text-xs tracking-wide uppercase">
|
|
100
100
|
<tr>
|
|
101
101
|
<th class="px-4 py-3 font-semibold">Nazwa</th>
|
|
102
102
|
<th class="px-4 py-3 font-semibold">Kolekcja</th>
|
|
@@ -112,14 +112,12 @@
|
|
|
112
112
|
{@const title = resolveTitle(row.publishedData ?? row.draftData, row.collectionSlug)}
|
|
113
113
|
<tr class="border-border hover:bg-muted/30 border-t">
|
|
114
114
|
<td class="px-4 py-3">
|
|
115
|
-
<a
|
|
116
|
-
href={`/admin/entries/${row.entryId}`}
|
|
117
|
-
class="text-primary hover:underline"
|
|
118
|
-
>
|
|
115
|
+
<a href={`/admin/entries/${row.entryId}`} class="text-primary hover:underline">
|
|
119
116
|
{title}
|
|
120
117
|
</a>
|
|
121
118
|
</td>
|
|
122
|
-
<td class="text-muted-foreground px-4 py-3 font-mono text-xs">{row.collectionSlug}</td
|
|
119
|
+
<td class="text-muted-foreground px-4 py-3 font-mono text-xs">{row.collectionSlug}</td
|
|
120
|
+
>
|
|
123
121
|
<td class="px-4 py-3">{formatPrice(row.basePrice)}</td>
|
|
124
122
|
<td class="px-4 py-3">{row.vatRate}%</td>
|
|
125
123
|
<td class="px-4 py-3">{row.variantCount}</td>
|
|
@@ -133,16 +131,19 @@
|
|
|
133
131
|
<td class="px-4 py-3">
|
|
134
132
|
<div class="flex gap-1.5">
|
|
135
133
|
{#if row.published}
|
|
136
|
-
<span
|
|
134
|
+
<span
|
|
135
|
+
class="inline-flex rounded-full bg-green-100 px-2 py-0.5 text-xs text-green-800"
|
|
137
136
|
>Opublikowany</span
|
|
138
137
|
>
|
|
139
138
|
{:else}
|
|
140
|
-
<span
|
|
139
|
+
<span
|
|
140
|
+
class="inline-flex rounded-full bg-yellow-100 px-2 py-0.5 text-xs text-yellow-800"
|
|
141
141
|
>Szkic</span
|
|
142
142
|
>
|
|
143
143
|
{/if}
|
|
144
144
|
{#if !row.isActive}
|
|
145
|
-
<span
|
|
145
|
+
<span
|
|
146
|
+
class="inline-flex rounded-full bg-gray-100 px-2 py-0.5 text-xs text-gray-800"
|
|
146
147
|
>Nieaktywny</span
|
|
147
148
|
>
|
|
148
149
|
{/if}
|
|
@@ -24,7 +24,8 @@ export const sidebarLang = {
|
|
|
24
24
|
title: 'Sklep',
|
|
25
25
|
products: 'Produkty',
|
|
26
26
|
orders: 'Zamówienia',
|
|
27
|
-
shipping: 'Metody wysyłki'
|
|
27
|
+
shipping: 'Metody wysyłki',
|
|
28
|
+
coupons: 'Kody rabatowe'
|
|
28
29
|
},
|
|
29
30
|
footer: {
|
|
30
31
|
help: 'Pomoc'
|
|
@@ -63,7 +64,8 @@ export const sidebarLang = {
|
|
|
63
64
|
title: 'Shop',
|
|
64
65
|
products: 'Products',
|
|
65
66
|
orders: 'Orders',
|
|
66
|
-
shipping: 'Shipping methods'
|
|
67
|
+
shipping: 'Shipping methods',
|
|
68
|
+
coupons: 'Coupon codes'
|
|
67
69
|
},
|
|
68
70
|
footer: {
|
|
69
71
|
help: 'Help'
|
|
@@ -42,7 +42,9 @@
|
|
|
42
42
|
const fieldMap = $derived(new Map(fields.map((f) => [f.slug, f])));
|
|
43
43
|
|
|
44
44
|
// Compute distributed object slugs once at top level
|
|
45
|
-
const distributedSlugs = $derived(
|
|
45
|
+
const distributedSlugs = $derived(
|
|
46
|
+
parentDistributedSlugs ?? getDistributedObjectSlugs(nodes, fields)
|
|
47
|
+
);
|
|
46
48
|
|
|
47
49
|
/**
|
|
48
50
|
* Resolve a field reference (slug or dot-notation path) to its Field definition and form path.
|
|
@@ -90,7 +92,8 @@
|
|
|
90
92
|
class={cn(
|
|
91
93
|
'rounded-lg transition-all duration-500',
|
|
92
94
|
autoGrid && !isCompactField(field) && 'auto-grid-full',
|
|
93
|
-
isFlashing(formPath) &&
|
|
95
|
+
isFlashing(formPath) &&
|
|
96
|
+
'ring-primary bg-primary/5 animate-in fade-in ring-2 ring-offset-2'
|
|
94
97
|
)}
|
|
95
98
|
>
|
|
96
99
|
<FieldRenderer
|
|
@@ -135,12 +138,8 @@
|
|
|
135
138
|
{@render recurse(node.children)}
|
|
136
139
|
{/if}
|
|
137
140
|
</section>
|
|
138
|
-
|
|
139
141
|
{:else if node.type === 'columns'}
|
|
140
|
-
<div
|
|
141
|
-
class="layout-columns"
|
|
142
|
-
style="grid-template-columns: {node.ratio};"
|
|
143
|
-
>
|
|
142
|
+
<div class="layout-columns" style="grid-template-columns: {node.ratio};">
|
|
144
143
|
{#if isLayoutBranch(node)}
|
|
145
144
|
{#each node.children as child (child)}
|
|
146
145
|
<div class="layout-column">
|
|
@@ -149,9 +148,13 @@
|
|
|
149
148
|
{/each}
|
|
150
149
|
{/if}
|
|
151
150
|
</div>
|
|
152
|
-
|
|
153
151
|
{:else if node.type === 'card'}
|
|
154
|
-
<div
|
|
152
|
+
<div
|
|
153
|
+
role="group"
|
|
154
|
+
aria-label={getLabel(node) || undefined}
|
|
155
|
+
class="layout-card"
|
|
156
|
+
class:no-header={!getLabel(node)}
|
|
157
|
+
>
|
|
155
158
|
{#if getLabel(node)}
|
|
156
159
|
<div class="layout-card-header">
|
|
157
160
|
{getLabel(node)}
|
|
@@ -176,7 +179,6 @@
|
|
|
176
179
|
{/if}
|
|
177
180
|
</div>
|
|
178
181
|
</div>
|
|
179
|
-
|
|
180
182
|
{:else if node.type === 'accordion'}
|
|
181
183
|
<div class="layout-accordion-wrapper">
|
|
182
184
|
<Accordion.Root type="single" value={node.defaultOpen ? 'item' : ''}>
|
|
@@ -199,7 +201,6 @@
|
|
|
199
201
|
</Accordion.Item>
|
|
200
202
|
</Accordion.Root>
|
|
201
203
|
</div>
|
|
202
|
-
|
|
203
204
|
{:else if node.type === 'stack'}
|
|
204
205
|
<div class="layout-stack">
|
|
205
206
|
{#if isLayoutLeaf(node)}
|
|
@@ -6,15 +6,13 @@
|
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
8
|
<Breadcrumb.Root>
|
|
9
|
-
<Breadcrumb.List class="text-[13px]
|
|
9
|
+
<Breadcrumb.List class="gap-2 text-[13px]">
|
|
10
10
|
{#each breadcrumbs.state as crumb, i}
|
|
11
11
|
<Breadcrumb.Item>
|
|
12
12
|
{#if crumb.href && i < breadcrumbs.state.length - 1}
|
|
13
|
-
<Breadcrumb.Link class="font-medium" href={crumb.href}
|
|
14
|
-
>{crumb.label}</Breadcrumb.Link
|
|
15
|
-
>
|
|
13
|
+
<Breadcrumb.Link class="font-medium" href={crumb.href}>{crumb.label}</Breadcrumb.Link>
|
|
16
14
|
{:else}
|
|
17
|
-
<Breadcrumb.Page class="
|
|
15
|
+
<Breadcrumb.Page class="max-w-60 truncate font-semibold">{crumb.label}</Breadcrumb.Page>
|
|
18
16
|
{/if}
|
|
19
17
|
</Breadcrumb.Item>
|
|
20
18
|
{#if i < breadcrumbs.state.length - 1}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import PackageIcon from '@tabler/icons-svelte/icons/package';
|
|
7
7
|
import ShoppingCartIcon from '@tabler/icons-svelte/icons/shopping-cart';
|
|
8
8
|
import TruckIcon from '@tabler/icons-svelte/icons/truck';
|
|
9
|
+
import TicketIcon from '@tabler/icons-svelte/icons/ticket';
|
|
9
10
|
import BuildingStoreIcon from '@tabler/icons-svelte/icons/building-store';
|
|
10
11
|
import { page } from '$app/state';
|
|
11
12
|
|
|
@@ -22,7 +23,8 @@
|
|
|
22
23
|
const items = $derived([
|
|
23
24
|
{ title: lang.products, url: '/admin/shop/products', icon: PackageIcon },
|
|
24
25
|
{ title: lang.orders, url: '/admin/shop/orders', icon: ShoppingCartIcon },
|
|
25
|
-
{ title: lang.shipping, url: '/admin/shop/shipping-methods', icon: TruckIcon }
|
|
26
|
+
{ title: lang.shipping, url: '/admin/shop/shipping-methods', icon: TruckIcon },
|
|
27
|
+
{ title: lang.coupons, url: '/admin/shop/coupons', icon: TicketIcon }
|
|
26
28
|
]);
|
|
27
29
|
</script>
|
|
28
30
|
|
|
@@ -29,18 +29,20 @@
|
|
|
29
29
|
<Sidebar.MenuItem>
|
|
30
30
|
{#if $session.data}
|
|
31
31
|
<div class="flex items-center gap-2 px-2 py-1.5">
|
|
32
|
-
<a href="/admin/account" class="flex
|
|
33
|
-
<Avatar.Root class="size-7 rounded-lg
|
|
32
|
+
<a href="/admin/account" class="group flex min-w-0 flex-1 items-center gap-2">
|
|
33
|
+
<Avatar.Root class="size-7 shrink-0 rounded-lg">
|
|
34
34
|
<Avatar.Image src={$session.data.user.image} alt={$session.data.user.name} />
|
|
35
35
|
<Avatar.Fallback class="rounded-lg text-xs">
|
|
36
36
|
{$session.data.user.name[0].toUpperCase()}
|
|
37
37
|
</Avatar.Fallback>
|
|
38
38
|
</Avatar.Root>
|
|
39
|
-
<div class="min-w-0 flex
|
|
39
|
+
<div class="flex min-w-0 flex-col">
|
|
40
40
|
<span class="truncate text-sm font-medium group-hover:underline">
|
|
41
41
|
{$session.data.user.name}
|
|
42
42
|
</span>
|
|
43
|
-
<span class="text-muted-foreground text-[10px] leading-tight"
|
|
43
|
+
<span class="text-muted-foreground text-[10px] leading-tight"
|
|
44
|
+
>v{updates[0].version}</span
|
|
45
|
+
>
|
|
44
46
|
</div>
|
|
45
47
|
</a>
|
|
46
48
|
<button
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
</script>
|
|
41
41
|
|
|
42
42
|
<header
|
|
43
|
-
class="flex h-(--header-height) shrink-0 items-center gap-2 border-b
|
|
43
|
+
class="border-sidebar-border bg-background/60 flex h-(--header-height) shrink-0 items-center gap-2 border-b backdrop-blur-xl transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)"
|
|
44
44
|
>
|
|
45
45
|
<div class="flex w-full items-center px-5">
|
|
46
46
|
<div class="flex items-center gap-3">
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
href="/"
|
|
54
54
|
variant="outline"
|
|
55
55
|
size="sm"
|
|
56
|
-
class="text-[13px] font-medium
|
|
56
|
+
class="text-primary text-[13px] font-medium hover:border-[var(--lavender)] hover:bg-[var(--lavender-lighter)]"
|
|
57
57
|
target="_blank"
|
|
58
58
|
rel="noopener noreferrer"
|
|
59
59
|
>
|
|
@@ -62,10 +62,14 @@
|
|
|
62
62
|
</Button>
|
|
63
63
|
{#if $session.data}
|
|
64
64
|
<DropdownMenu.Root>
|
|
65
|
-
<DropdownMenu.Trigger
|
|
65
|
+
<DropdownMenu.Trigger
|
|
66
|
+
class="focus-visible:ring-ring cursor-pointer rounded-full outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
|
|
67
|
+
>
|
|
66
68
|
<Avatar.Root class="size-[30px]">
|
|
67
69
|
<Avatar.Image src={$session.data.user.image} alt={$session.data.user.name} />
|
|
68
|
-
<Avatar.Fallback
|
|
70
|
+
<Avatar.Fallback
|
|
71
|
+
class="to-primary bg-gradient-to-br from-[var(--lavender)] text-[12px] font-bold text-white"
|
|
72
|
+
>
|
|
69
73
|
{$session.data.user.name
|
|
70
74
|
.split(' ')
|
|
71
75
|
.map((n) => n[0])
|
|
@@ -78,7 +82,9 @@
|
|
|
78
82
|
<DropdownMenu.Content align="end" class="w-56">
|
|
79
83
|
<DropdownMenu.Label class="flex flex-col gap-0.5">
|
|
80
84
|
<span class="text-sm font-medium">{$session.data.user.name}</span>
|
|
81
|
-
<span class="text-xs font-normal
|
|
85
|
+
<span class="text-muted-foreground text-xs font-normal"
|
|
86
|
+
>{$session.data.user.email}</span
|
|
87
|
+
>
|
|
82
88
|
</DropdownMenu.Label>
|
|
83
89
|
<DropdownMenu.Separator />
|
|
84
90
|
<DropdownMenu.Item onSelect={() => goto('/admin/account')}>
|