includio-cms 0.28.0 → 0.33.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 +41 -13
- package/CHANGELOG.md +19 -0
- package/DOCS.md +1 -1
- package/ROADMAP.md +1 -0
- package/dist/admin/api/handler.js +4 -0
- package/dist/admin/api/integrations.d.ts +13 -0
- package/dist/admin/api/integrations.js +61 -0
- package/dist/admin/api/test-email.d.ts +9 -0
- package/dist/admin/api/test-email.js +39 -0
- package/dist/admin/auth-client.d.ts +543 -543
- package/dist/admin/client/index.d.ts +10 -0
- package/dist/admin/client/index.js +12 -0
- package/dist/admin/client/maintenance/maintenance-page.svelte +210 -0
- package/dist/admin/client/shop/coupon-schema.d.ts +1 -1
- package/dist/admin/client/shop/restore-order-cell.svelte +29 -0
- package/dist/admin/client/shop/restore-order-cell.svelte.d.ts +8 -0
- package/dist/admin/client/shop/shop-order-detail-page.svelte +71 -1
- package/dist/admin/client/shop/shop-orders-list-page.svelte +113 -53
- package/dist/admin/components/layout/app-sidebar.svelte +2 -0
- package/dist/admin/components/layout/nav-custom.svelte +26 -0
- package/dist/admin/components/layout/nav-custom.svelte.d.ts +3 -0
- package/dist/admin/components/layout/page-header.svelte +13 -3
- package/dist/admin/components/layout/page-header.svelte.d.ts +13 -3
- package/dist/admin/remote/admin.remote.d.ts +7 -0
- package/dist/admin/remote/admin.remote.js +10 -0
- package/dist/admin/remote/entry.remote.d.ts +2 -2
- package/dist/admin/remote/index.d.ts +1 -0
- package/dist/admin/remote/index.js +1 -0
- package/dist/admin/remote/invite.d.ts +1 -1
- package/dist/admin/remote/shop.remote.d.ts +71 -44
- package/dist/admin/remote/shop.remote.js +41 -10
- package/dist/admin/types.d.ts +15 -0
- package/dist/admin/utils/csv-export.d.ts +45 -0
- package/dist/admin/utils/csv-export.js +61 -0
- package/dist/cli/scaffold/admin.js +1 -1
- package/dist/components/ui/input/input.svelte.d.ts +1 -1
- package/dist/components/ui/input-group/input-group-input.svelte.d.ts +1 -1
- package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
- package/dist/core/cms.d.ts +44 -2
- package/dist/core/cms.js +64 -0
- package/dist/core/index.d.ts +2 -4
- package/dist/core/index.js +1 -4
- package/dist/core/server/index.d.ts +4 -1
- package/dist/core/server/index.js +4 -1
- package/dist/db-postgres/schema/shop/order.d.ts +34 -0
- package/dist/db-postgres/schema/shop/order.js +4 -0
- package/dist/shop/adapters/fakturownia/client.d.ts +5 -0
- package/dist/shop/adapters/fakturownia/client.js +20 -0
- package/dist/shop/adapters/fakturownia/index.js +11 -0
- package/dist/shop/adapters/payu/index.js +11 -0
- package/dist/shop/index.d.ts +1 -1
- package/dist/shop/server/coupons.d.ts +10 -0
- package/dist/shop/server/coupons.js +19 -0
- package/dist/shop/server/email.d.ts +7 -3
- package/dist/shop/server/email.js +86 -112
- package/dist/shop/server/emailTemplateRegistry.d.ts +47 -0
- package/dist/shop/server/emailTemplateRegistry.js +288 -0
- package/dist/shop/server/orders.d.ts +60 -1
- package/dist/shop/server/orders.js +145 -16
- package/dist/shop/templates/_partials/footer.en.html +4 -0
- package/dist/shop/templates/_partials/footer.pl.html +4 -0
- package/dist/shop/templates/_partials/header.en.html +4 -0
- package/dist/shop/templates/_partials/header.pl.html +4 -0
- package/dist/shop/templates/_partials/items.en.html +14 -0
- package/dist/shop/templates/_partials/items.pl.html +14 -0
- package/dist/shop/templates/_partials/tracking.en.html +7 -0
- package/dist/shop/templates/_partials/tracking.pl.html +7 -0
- package/dist/shop/templates/awaiting-payment.en.html +6 -0
- package/dist/shop/templates/awaiting-payment.pl.html +6 -0
- package/dist/shop/templates/cancelled.en.html +6 -0
- package/dist/shop/templates/cancelled.pl.html +6 -0
- package/dist/shop/templates/low-stock.en.html +14 -0
- package/dist/shop/templates/low-stock.pl.html +14 -0
- package/dist/shop/templates/order-completed.en.html +6 -0
- package/dist/shop/templates/order-completed.pl.html +6 -0
- package/dist/shop/templates/order-received.en.html +7 -0
- package/dist/shop/templates/order-received.pl.html +7 -0
- package/dist/shop/templates/payment-received.en.html +7 -0
- package/dist/shop/templates/payment-received.pl.html +7 -0
- package/dist/shop/templates/payment-rejected.en.html +6 -0
- package/dist/shop/templates/payment-rejected.pl.html +6 -0
- package/dist/shop/templates/preparing.en.html +7 -0
- package/dist/shop/templates/preparing.pl.html +7 -0
- package/dist/shop/templates/refunded.en.html +6 -0
- package/dist/shop/templates/refunded.pl.html +6 -0
- package/dist/shop/templates/shipped.en.html +7 -0
- package/dist/shop/templates/shipped.pl.html +7 -0
- package/dist/shop/types.d.ts +63 -0
- package/dist/sveltekit/index.d.ts +0 -1
- package/dist/sveltekit/index.js +0 -1
- package/dist/sveltekit/server/index.d.ts +1 -0
- package/dist/sveltekit/server/index.js +1 -0
- package/dist/types/adapters/email.d.ts +13 -0
- package/dist/types/cms.d.ts +30 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/updates/0.34.0/index.d.ts +2 -0
- package/dist/updates/0.34.0/index.js +17 -0
- package/dist/updates/index.js +3 -1
- package/package.json +7 -2
|
@@ -23,3 +23,13 @@ export { default as ShopCouponNewPage } from './shop/coupon-new-page.svelte';
|
|
|
23
23
|
export { default as ShopCouponEditPage } from './shop/coupon-edit-page.svelte';
|
|
24
24
|
export * from '../helpers/index.js';
|
|
25
25
|
export * from '../ui/index.js';
|
|
26
|
+
export { default as DataTable } from './collection/data-table.svelte';
|
|
27
|
+
export { default as TableToolbar } from './collection/table-toolbar.svelte';
|
|
28
|
+
export { default as TablePagination } from './collection/table-pagination.svelte';
|
|
29
|
+
export { default as StateDisplay } from './collection/state-display.svelte';
|
|
30
|
+
export { default as PageHeader } from '../components/layout/page-header.svelte';
|
|
31
|
+
export { buildCsv, downloadCsv } from '../utils/csv-export.js';
|
|
32
|
+
export type { ColumnDef, PaginationState, SortingState } from '@tanstack/table-core';
|
|
33
|
+
export { getBreadcrumbs, setBreadcrumbs, Breadcrumbs } from '../state/breadcrumbs.svelte.js';
|
|
34
|
+
export type { Breadcrumb, AdminNavItem } from '../types.js';
|
|
35
|
+
export type { AdminConfig } from '../../types/cms.js';
|
|
@@ -25,3 +25,15 @@ export { default as ShopCouponEditPage } from './shop/coupon-edit-page.svelte';
|
|
|
25
25
|
export * from '../helpers/index.js';
|
|
26
26
|
// Folded from `./admin/ui` (dropped as separate export in 0.20.0)
|
|
27
27
|
export * from '../ui/index.js';
|
|
28
|
+
// Public list-page primitives (since 0.32.0). Use these when building custom
|
|
29
|
+
// admin list views (e.g. `/admin/newsletter`) so they share visual style and
|
|
30
|
+
// behavior with the built-in `CollectionPage` / `ShopOrdersListPage`.
|
|
31
|
+
export { default as DataTable } from './collection/data-table.svelte';
|
|
32
|
+
export { default as TableToolbar } from './collection/table-toolbar.svelte';
|
|
33
|
+
export { default as TablePagination } from './collection/table-pagination.svelte';
|
|
34
|
+
export { default as StateDisplay } from './collection/state-display.svelte';
|
|
35
|
+
export { default as PageHeader } from '../components/layout/page-header.svelte';
|
|
36
|
+
export { buildCsv, downloadCsv } from '../utils/csv-export.js';
|
|
37
|
+
// Breadcrumbs API (since 0.33.0). Set the breadcrumb trail from a custom
|
|
38
|
+
// admin page with `getBreadcrumbs().state = [{ label, href? }, …]`.
|
|
39
|
+
export { getBreadcrumbs, setBreadcrumbs, Breadcrumbs } from '../state/breadcrumbs.svelte.js';
|
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
import Server from '@tabler/icons-svelte/icons/server';
|
|
15
15
|
import Clock from '@tabler/icons-svelte/icons/clock';
|
|
16
16
|
import Settings from '@tabler/icons-svelte/icons/automation';
|
|
17
|
+
import Plug from '@tabler/icons-svelte/icons/plug-connected';
|
|
18
|
+
import Mail from '@tabler/icons-svelte/icons/mail';
|
|
19
|
+
import CreditCard from '@tabler/icons-svelte/icons/credit-card';
|
|
20
|
+
import FileInvoice from '@tabler/icons-svelte/icons/file-invoice';
|
|
21
|
+
import Send from '@tabler/icons-svelte/icons/send';
|
|
17
22
|
import Button from '../../../components/ui/button/button.svelte';
|
|
18
23
|
import * as Card from '../../../components/ui/card/index.js';
|
|
19
24
|
import { toast } from 'svelte-sonner';
|
|
@@ -363,8 +368,80 @@
|
|
|
363
368
|
}
|
|
364
369
|
}
|
|
365
370
|
|
|
371
|
+
// --- Integrations health-check ---
|
|
372
|
+
interface IntegrationsInfo {
|
|
373
|
+
email: { configured: boolean; adminEmail: string | null };
|
|
374
|
+
payment: { id: string; label: Record<string, string>; canPing: boolean }[];
|
|
375
|
+
invoicing: { id: string; canPing: boolean } | null;
|
|
376
|
+
}
|
|
377
|
+
type PingState = { state: 'idle' | 'checking' | 'ok' | 'error'; message?: string };
|
|
378
|
+
|
|
379
|
+
let integrations = $state<IntegrationsInfo | null>(null);
|
|
380
|
+
let pingStates = $state<Record<string, PingState>>({});
|
|
381
|
+
let testEmailTo = $state('');
|
|
382
|
+
let sendingTestEmail = $state(false);
|
|
383
|
+
|
|
384
|
+
function i18n(label: Record<string, string>): string {
|
|
385
|
+
return label[interfaceLanguage.current] ?? label.pl ?? label.en ?? Object.values(label)[0] ?? '';
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async function loadIntegrations() {
|
|
389
|
+
try {
|
|
390
|
+
const res = await fetch('/admin/api/integrations');
|
|
391
|
+
if (res.ok) integrations = await res.json();
|
|
392
|
+
} catch {
|
|
393
|
+
// non-fatal — integrations section just stays hidden
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async function pingIntegration(kind: 'payment' | 'invoicing', id: string) {
|
|
398
|
+
const key = kind === 'payment' ? `payment:${id}` : 'invoicing';
|
|
399
|
+
pingStates[key] = { state: 'checking' };
|
|
400
|
+
try {
|
|
401
|
+
const res = await fetch('/admin/api/integrations', {
|
|
402
|
+
method: 'POST',
|
|
403
|
+
headers: { 'Content-Type': 'application/json' },
|
|
404
|
+
body: JSON.stringify({ kind, id })
|
|
405
|
+
});
|
|
406
|
+
const data = await res.json();
|
|
407
|
+
if (res.ok && data.ok) {
|
|
408
|
+
pingStates[key] = { state: 'ok' };
|
|
409
|
+
toast.success('Połączenie działa');
|
|
410
|
+
} else {
|
|
411
|
+
const message = data.message ?? data.error ?? 'Błąd połączenia';
|
|
412
|
+
pingStates[key] = { state: 'error', message };
|
|
413
|
+
toast.error(message);
|
|
414
|
+
}
|
|
415
|
+
} catch {
|
|
416
|
+
pingStates[key] = { state: 'error', message: 'Błąd sieci' };
|
|
417
|
+
toast.error('Błąd sieci');
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
async function sendTestEmail() {
|
|
422
|
+
sendingTestEmail = true;
|
|
423
|
+
try {
|
|
424
|
+
const res = await fetch('/admin/api/test-email', {
|
|
425
|
+
method: 'POST',
|
|
426
|
+
headers: { 'Content-Type': 'application/json' },
|
|
427
|
+
body: JSON.stringify({ to: testEmailTo.trim() || undefined })
|
|
428
|
+
});
|
|
429
|
+
const data = await res.json();
|
|
430
|
+
if (res.ok && data.ok) {
|
|
431
|
+
toast.success('Wysłano testową wiadomość');
|
|
432
|
+
} else {
|
|
433
|
+
toast.error(data.message ?? 'Nie udało się wysłać wiadomości');
|
|
434
|
+
}
|
|
435
|
+
} catch {
|
|
436
|
+
toast.error('Błąd sieci');
|
|
437
|
+
} finally {
|
|
438
|
+
sendingTestEmail = false;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
366
442
|
$effect(() => {
|
|
367
443
|
loadReport();
|
|
444
|
+
loadIntegrations();
|
|
368
445
|
});
|
|
369
446
|
</script>
|
|
370
447
|
|
|
@@ -814,4 +891,137 @@
|
|
|
814
891
|
</div>
|
|
815
892
|
{/if}
|
|
816
893
|
{/if}
|
|
894
|
+
|
|
895
|
+
<!-- Section: Integrations health-check -->
|
|
896
|
+
{#snippet statusBadge(ps: PingState | undefined)}
|
|
897
|
+
{#if ps?.state === 'ok'}
|
|
898
|
+
<span class="rounded-full px-2 py-0.5 text-xs font-medium" style="background: color-mix(in srgb, var(--success, #3A8A5C) 15%, transparent); color: var(--success, #3A8A5C);">Połączenie OK</span>
|
|
899
|
+
{:else if ps?.state === 'error'}
|
|
900
|
+
<span class="rounded-full px-2 py-0.5 text-xs font-medium" style="background: color-mix(in srgb, var(--error, #C44B4B) 15%, transparent); color: var(--error, #C44B4B);">Błąd</span>
|
|
901
|
+
{:else if ps?.state === 'checking'}
|
|
902
|
+
<span class="flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium" style="background: var(--muted, #e5e7eb); color: var(--muted-foreground);"><Loader2 class="size-3 animate-spin" /> Sprawdzanie…</span>
|
|
903
|
+
{:else}
|
|
904
|
+
<span class="rounded-full px-2 py-0.5 text-xs font-medium" style="background: var(--muted, #e5e7eb); color: var(--muted-foreground);">Niesprawdzone</span>
|
|
905
|
+
{/if}
|
|
906
|
+
{/snippet}
|
|
907
|
+
|
|
908
|
+
{#if integrations}
|
|
909
|
+
<div class="mt-8">
|
|
910
|
+
<h2 class="mb-3 flex items-center gap-2 text-sm font-semibold uppercase tracking-wider" style="color: var(--muted-foreground);">
|
|
911
|
+
<Plug class="size-4" /> Integracje
|
|
912
|
+
</h2>
|
|
913
|
+
<div class="grid gap-4 md:grid-cols-2">
|
|
914
|
+
<!-- Email -->
|
|
915
|
+
<Card.Root>
|
|
916
|
+
<Card.Header class="pb-3">
|
|
917
|
+
<div class="flex items-center justify-between">
|
|
918
|
+
<div class="flex items-center gap-2">
|
|
919
|
+
<Mail class="size-5" style="color: var(--primary);" />
|
|
920
|
+
<Card.Title class="text-base">E-mail</Card.Title>
|
|
921
|
+
</div>
|
|
922
|
+
{#if integrations.email.configured}
|
|
923
|
+
<span class="rounded-full px-2 py-0.5 text-xs font-medium" style="background: color-mix(in srgb, var(--success, #3A8A5C) 15%, transparent); color: var(--success, #3A8A5C);">Skonfigurowane</span>
|
|
924
|
+
{:else}
|
|
925
|
+
<span class="rounded-full px-2 py-0.5 text-xs font-medium" style="background: var(--muted, #e5e7eb); color: var(--muted-foreground);">Nieskonfigurowane</span>
|
|
926
|
+
{/if}
|
|
927
|
+
</div>
|
|
928
|
+
</Card.Header>
|
|
929
|
+
<Card.Content>
|
|
930
|
+
<p class="text-sm" style="color: var(--muted-foreground);">
|
|
931
|
+
Wyślij testową wiadomość, aby sprawdzić dostarczanie e-maili.
|
|
932
|
+
</p>
|
|
933
|
+
{#if integrations.email.configured}
|
|
934
|
+
<div class="mt-3 flex flex-col gap-2 sm:flex-row">
|
|
935
|
+
<input
|
|
936
|
+
type="email"
|
|
937
|
+
bind:value={testEmailTo}
|
|
938
|
+
placeholder={integrations.email.adminEmail ?? 'adres@przyklad.pl'}
|
|
939
|
+
class="flex-1 rounded-md border bg-transparent px-3 py-1.5 text-sm outline-none focus:ring-2"
|
|
940
|
+
style="border-color: var(--border);"
|
|
941
|
+
/>
|
|
942
|
+
<Button variant="default" size="sm" onclick={sendTestEmail} disabled={sendingTestEmail}>
|
|
943
|
+
{#if sendingTestEmail}<Loader2 class="size-3.5 animate-spin" />{:else}<Send class="size-3.5" />{/if}
|
|
944
|
+
Wyślij testowy e-mail
|
|
945
|
+
</Button>
|
|
946
|
+
</div>
|
|
947
|
+
{#if !testEmailTo.trim() && integrations.email.adminEmail}
|
|
948
|
+
<p class="mt-1.5 text-xs" style="color: var(--muted-foreground);">
|
|
949
|
+
Puste pole → wyślemy na {integrations.email.adminEmail}
|
|
950
|
+
</p>
|
|
951
|
+
{:else if !testEmailTo.trim() && !integrations.email.adminEmail}
|
|
952
|
+
<p class="mt-1.5 text-xs" style="color: var(--warning, #C4893A);">
|
|
953
|
+
Podaj adres odbiorcy (brak ustawionego ADMIN_EMAIL).
|
|
954
|
+
</p>
|
|
955
|
+
{/if}
|
|
956
|
+
{/if}
|
|
957
|
+
</Card.Content>
|
|
958
|
+
</Card.Root>
|
|
959
|
+
|
|
960
|
+
<!-- Payment adapters -->
|
|
961
|
+
{#each integrations.payment as p (p.id)}
|
|
962
|
+
{@const ps = pingStates[`payment:${p.id}`]}
|
|
963
|
+
<Card.Root>
|
|
964
|
+
<Card.Header class="pb-3">
|
|
965
|
+
<div class="flex items-center justify-between">
|
|
966
|
+
<div class="flex items-center gap-2">
|
|
967
|
+
<CreditCard class="size-5" style="color: var(--primary);" />
|
|
968
|
+
<Card.Title class="text-base">{i18n(p.label)}</Card.Title>
|
|
969
|
+
</div>
|
|
970
|
+
{@render statusBadge(ps)}
|
|
971
|
+
</div>
|
|
972
|
+
</Card.Header>
|
|
973
|
+
<Card.Content>
|
|
974
|
+
<p class="text-sm" style="color: var(--muted-foreground);">Płatności · {p.id}</p>
|
|
975
|
+
{#if ps?.state === 'error' && ps.message}
|
|
976
|
+
<p class="mt-2 break-words text-xs" style="color: var(--error, #C44B4B);">{ps.message}</p>
|
|
977
|
+
{/if}
|
|
978
|
+
<div class="mt-3">
|
|
979
|
+
{#if p.canPing}
|
|
980
|
+
<Button variant="outline" size="sm" onclick={() => pingIntegration('payment', p.id)} disabled={ps?.state === 'checking'}>
|
|
981
|
+
{#if ps?.state === 'checking'}<Loader2 class="size-3.5 animate-spin" />{:else}<Plug class="size-3.5" />{/if}
|
|
982
|
+
Sprawdź połączenie
|
|
983
|
+
</Button>
|
|
984
|
+
{:else}
|
|
985
|
+
<p class="text-xs" style="color: var(--muted-foreground);">Sprawdzanie połączenia niedostępne dla tego adaptera.</p>
|
|
986
|
+
{/if}
|
|
987
|
+
</div>
|
|
988
|
+
</Card.Content>
|
|
989
|
+
</Card.Root>
|
|
990
|
+
{/each}
|
|
991
|
+
|
|
992
|
+
<!-- Invoicing -->
|
|
993
|
+
{#if integrations.invoicing}
|
|
994
|
+
{@const inv = integrations.invoicing}
|
|
995
|
+
{@const ps = pingStates['invoicing']}
|
|
996
|
+
<Card.Root>
|
|
997
|
+
<Card.Header class="pb-3">
|
|
998
|
+
<div class="flex items-center justify-between">
|
|
999
|
+
<div class="flex items-center gap-2">
|
|
1000
|
+
<FileInvoice class="size-5" style="color: var(--primary);" />
|
|
1001
|
+
<Card.Title class="text-base">Faktury</Card.Title>
|
|
1002
|
+
</div>
|
|
1003
|
+
{@render statusBadge(ps)}
|
|
1004
|
+
</div>
|
|
1005
|
+
</Card.Header>
|
|
1006
|
+
<Card.Content>
|
|
1007
|
+
<p class="text-sm" style="color: var(--muted-foreground);">Wystawianie faktur · {inv.id}</p>
|
|
1008
|
+
{#if ps?.state === 'error' && ps.message}
|
|
1009
|
+
<p class="mt-2 break-words text-xs" style="color: var(--error, #C44B4B);">{ps.message}</p>
|
|
1010
|
+
{/if}
|
|
1011
|
+
<div class="mt-3">
|
|
1012
|
+
{#if inv.canPing}
|
|
1013
|
+
<Button variant="outline" size="sm" onclick={() => pingIntegration('invoicing', inv.id)} disabled={ps?.state === 'checking'}>
|
|
1014
|
+
{#if ps?.state === 'checking'}<Loader2 class="size-3.5 animate-spin" />{:else}<Plug class="size-3.5" />{/if}
|
|
1015
|
+
Sprawdź połączenie
|
|
1016
|
+
</Button>
|
|
1017
|
+
{:else}
|
|
1018
|
+
<p class="text-xs" style="color: var(--muted-foreground);">Sprawdzanie połączenia niedostępne dla tego adaptera.</p>
|
|
1019
|
+
{/if}
|
|
1020
|
+
</div>
|
|
1021
|
+
</Card.Content>
|
|
1022
|
+
</Card.Root>
|
|
1023
|
+
{/if}
|
|
1024
|
+
</div>
|
|
1025
|
+
</div>
|
|
1026
|
+
{/if}
|
|
817
1027
|
</div>
|
|
@@ -16,8 +16,8 @@ export type CouponInput = {
|
|
|
16
16
|
export declare function createCouponSchema(lang?: InterfaceLanguage): z.ZodObject<{
|
|
17
17
|
code: z.ZodString;
|
|
18
18
|
type: z.ZodEnum<{
|
|
19
|
-
percent: "percent";
|
|
20
19
|
fixed: "fixed";
|
|
20
|
+
percent: "percent";
|
|
21
21
|
}>;
|
|
22
22
|
value: z.ZodNumber;
|
|
23
23
|
minOrderAmount: z.ZodNullable<z.ZodNumber>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getRemotes } from '../../../sveltekit/index.js';
|
|
3
|
+
import { Button } from '../../../components/ui/button/index.js';
|
|
4
|
+
import RestoreIcon from '@tabler/icons-svelte/icons/restore';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
orderId,
|
|
8
|
+
label,
|
|
9
|
+
onRestored
|
|
10
|
+
}: { orderId: string; label: string; onRestored: () => void } = $props();
|
|
11
|
+
|
|
12
|
+
const remotes = getRemotes();
|
|
13
|
+
let busy = $state(false);
|
|
14
|
+
|
|
15
|
+
async function restore() {
|
|
16
|
+
busy = true;
|
|
17
|
+
try {
|
|
18
|
+
await remotes.restoreOrderCmd({ orderId });
|
|
19
|
+
onRestored();
|
|
20
|
+
} finally {
|
|
21
|
+
busy = false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<Button variant="outline" size="sm" disabled={busy} onclick={restore}>
|
|
27
|
+
<RestoreIcon class="size-4" />
|
|
28
|
+
{label}
|
|
29
|
+
</Button>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
orderId: string;
|
|
3
|
+
label: string;
|
|
4
|
+
onRestored: () => void;
|
|
5
|
+
};
|
|
6
|
+
declare const RestoreOrderCell: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type RestoreOrderCell = ReturnType<typeof RestoreOrderCell>;
|
|
8
|
+
export default RestoreOrderCell;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { page } from '$app/state';
|
|
3
|
+
import { goto } from '$app/navigation';
|
|
3
4
|
import { getRemotes } from '../../../sveltekit/index.js';
|
|
5
|
+
import { authClient } from '../../auth-client.js';
|
|
4
6
|
import { Button } from '../../../components/ui/button/index.js';
|
|
5
7
|
import MailIcon from '@tabler/icons-svelte/icons/mail';
|
|
8
|
+
import TrashIcon from '@tabler/icons-svelte/icons/trash';
|
|
6
9
|
import RefundDialog from './refund-dialog.svelte';
|
|
7
10
|
import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
|
|
8
11
|
import { getBreadcrumbs } from '../../state/breadcrumbs.svelte.js';
|
|
@@ -21,6 +24,16 @@
|
|
|
21
24
|
const interfaceLanguage = useInterfaceLanguage();
|
|
22
25
|
const breadcrumbs = getBreadcrumbs();
|
|
23
26
|
|
|
27
|
+
const session = authClient.useSession();
|
|
28
|
+
const isAdmin = $derived($session.data?.user?.role === 'admin');
|
|
29
|
+
// Mirror of DELETABLE_ORDER_STATUSES (server) — statuses an admin may hide.
|
|
30
|
+
const DELETABLE_STATUSES = new Set<OrderStatusT>([
|
|
31
|
+
'new',
|
|
32
|
+
'awaitingPayment',
|
|
33
|
+
'cancelled',
|
|
34
|
+
'paymentRejected'
|
|
35
|
+
]);
|
|
36
|
+
|
|
24
37
|
const orderId = $derived(page.params.id ?? '');
|
|
25
38
|
const query = $derived(remotes.getOrderForAdmin(orderId));
|
|
26
39
|
|
|
@@ -229,6 +242,29 @@
|
|
|
229
242
|
balanceLinkBusy = false;
|
|
230
243
|
}
|
|
231
244
|
}
|
|
245
|
+
|
|
246
|
+
let deleteDialogOpen = $state(false);
|
|
247
|
+
let deleting = $state(false);
|
|
248
|
+
|
|
249
|
+
async function performDelete() {
|
|
250
|
+
deleting = true;
|
|
251
|
+
errorMessage = null;
|
|
252
|
+
successMessage = null;
|
|
253
|
+
try {
|
|
254
|
+
const result = await remotes.deleteOrderCmd({ orderId });
|
|
255
|
+
if (!result.success) {
|
|
256
|
+
errorMessage = result.error;
|
|
257
|
+
deleteDialogOpen = false;
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
await goto('/admin/shop/orders');
|
|
261
|
+
} catch (err) {
|
|
262
|
+
errorMessage = err instanceof Error ? err.message : 'Nie udało się usunąć zamówienia';
|
|
263
|
+
deleteDialogOpen = false;
|
|
264
|
+
} finally {
|
|
265
|
+
deleting = false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
232
268
|
</script>
|
|
233
269
|
|
|
234
270
|
{#if !query.ready}
|
|
@@ -239,7 +275,7 @@
|
|
|
239
275
|
<Button href="/admin/shop/orders" variant="outline">← Wróć do listy</Button>
|
|
240
276
|
</div>
|
|
241
277
|
{:else}
|
|
242
|
-
{@const { order, items, history } = query.current}
|
|
278
|
+
{@const { order, items, history, coupon } = query.current}
|
|
243
279
|
{@const address = order.shippingAddress as Record<string, string> | null}
|
|
244
280
|
{@const consents = order.consents as Array<{
|
|
245
281
|
id: string;
|
|
@@ -324,6 +360,14 @@
|
|
|
324
360
|
>{formatCentsPrice(order.shippingGross, order.currency)}</td
|
|
325
361
|
>
|
|
326
362
|
</tr>
|
|
363
|
+
{#if coupon && coupon.discountAmount > 0}
|
|
364
|
+
<tr>
|
|
365
|
+
<td colspan="5" class="text-right text-sm">Rabat ({coupon.code}):</td>
|
|
366
|
+
<td class="text-primary text-right tabular-nums">
|
|
367
|
+
−{formatCentsPrice(coupon.discountAmount, order.currency)}
|
|
368
|
+
</td>
|
|
369
|
+
</tr>
|
|
370
|
+
{/if}
|
|
327
371
|
<tr>
|
|
328
372
|
<td colspan="5" class="text-right text-sm">Razem netto:</td>
|
|
329
373
|
<td class="text-muted-foreground text-right tabular-nums">
|
|
@@ -704,6 +748,24 @@
|
|
|
704
748
|
<p class="text-xs whitespace-pre-wrap">{order.notes}</p>
|
|
705
749
|
</section>
|
|
706
750
|
{/if}
|
|
751
|
+
|
|
752
|
+
{#if isAdmin && !order.deletedAt && DELETABLE_STATUSES.has(order.status as OrderStatusT)}
|
|
753
|
+
<section class="border-destructive/30 bg-card space-y-3 rounded-xl border p-3 text-sm sm:p-5">
|
|
754
|
+
<h2 class="text-base font-bold">Usuń zamówienie</h2>
|
|
755
|
+
<p class="text-muted-foreground text-xs">
|
|
756
|
+
Zamówienie zniknie z listy, ale zostanie zapisane — w razie potrzeby przywrócisz je
|
|
757
|
+
z kosza. Dostępne tylko dla zamówień bez płatności i faktury.
|
|
758
|
+
</p>
|
|
759
|
+
<Button
|
|
760
|
+
onclick={() => (deleteDialogOpen = true)}
|
|
761
|
+
variant="outline"
|
|
762
|
+
class="text-destructive hover:bg-destructive-bg w-full"
|
|
763
|
+
>
|
|
764
|
+
<TrashIcon class="size-4" />
|
|
765
|
+
Usuń zamówienie
|
|
766
|
+
</Button>
|
|
767
|
+
</section>
|
|
768
|
+
{/if}
|
|
707
769
|
</div>
|
|
708
770
|
</div>
|
|
709
771
|
</div>
|
|
@@ -732,3 +794,11 @@
|
|
|
732
794
|
loading={shipping}
|
|
733
795
|
onConfirm={performCancelShipment}
|
|
734
796
|
/>
|
|
797
|
+
|
|
798
|
+
<ConfirmationDialog
|
|
799
|
+
bind:open={deleteDialogOpen}
|
|
800
|
+
title="Usunąć zamówienie?"
|
|
801
|
+
description="Zniknie z listy zamówień. Zostanie zapisane i w razie potrzeby przywrócisz je z kosza."
|
|
802
|
+
loading={deleting}
|
|
803
|
+
onConfirm={performDelete}
|
|
804
|
+
/>
|