@zexsoft/fix-core 1.14.15 → 1.14.16-staging
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/README.md
CHANGED
|
@@ -239,6 +239,7 @@ the route of its list (records link to `${route}/{id}`), defaulting to the FIX a
|
|
|
239
239
|
|---|---|---|
|
|
240
240
|
| `AccountPage` | — | `docsUrl` (FIX API docs, default `/docs`), `loginRoute` (default `/login`) |
|
|
241
241
|
| `CompanyPage`, `TeamPage` | — | |
|
|
242
|
+
| `PreferencesPage` | — | `fixApps` (FIX Fiscal app updates/downloads and version, default on) |
|
|
242
243
|
| `InvoicePage` | `/invoicing/invoices`, `/invoicing/expenses` | `isReceived` (expenses) |
|
|
243
244
|
| `ItemPage`, `PricingRulePage`, `DocumentSeriesPage`, `ItemTagPage`, `ItemUnitPage` | `/catalog/…` | |
|
|
244
245
|
| `FiscalEntityPage`, `BankAccountPage`, `EntityBranchPage` | `/partners/…` | |
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
<script lang="ts">import { Icon } from "iconify-static";
|
|
2
|
+
import { mode, setMode } from "mode-watcher";
|
|
3
|
+
import AppDownloadCard from "../components/domain/AppDownload/AppDownloadCard.svelte";
|
|
4
|
+
import { afterOverlaysSettle } from "../components/ui/back-dismiss/BackDismiss.svelte";
|
|
5
|
+
import { Button } from "../components/ui/button/index.js";
|
|
6
|
+
import * as Card from "../components/ui/card/index.js";
|
|
7
|
+
import * as Field from "../components/ui/field/index.js";
|
|
8
|
+
import { PageHeading } from "../components/ui/page-heading";
|
|
9
|
+
import * as Select from "../components/ui/select/index.js";
|
|
10
|
+
import { toast } from "../components/ui/sonner/index.js";
|
|
11
|
+
import * as Switch from "../components/ui/switch/index.js";
|
|
12
|
+
import ZexsoftLogo from "../components/ui/wordmark/zexsoftLogo.svelte";
|
|
13
|
+
import { t } from "../core/fix-core.svelte.js";
|
|
14
|
+
import { getLocale, isLocale, setLocale } from "../paraglide/runtime";
|
|
15
|
+
import { appUpdate } from "../stores/app-update.svelte.js";
|
|
16
|
+
import { deviceStore } from "../stores/device-store.svelte.js";
|
|
17
|
+
import { IsMobile } from "../utils/is-mobile.svelte.js";
|
|
18
|
+
import { uiPreferences } from "../utils/ui-preferences.svelte.js";
|
|
19
|
+
let {
|
|
20
|
+
fixApps = true
|
|
21
|
+
} = $props();
|
|
22
|
+
const isMobile = new IsMobile();
|
|
23
|
+
const isDark = $derived(mode.current === "dark");
|
|
24
|
+
function changeLocale(value) {
|
|
25
|
+
if (value && isLocale(value)) afterOverlaysSettle(() => setLocale(value));
|
|
26
|
+
}
|
|
27
|
+
function delay(ms) {
|
|
28
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
29
|
+
}
|
|
30
|
+
async function checkNow() {
|
|
31
|
+
appUpdate.checkForUpdates();
|
|
32
|
+
const started = Date.now();
|
|
33
|
+
while (appUpdate.status !== "checking" && Date.now() - started < 2e3) await delay(40);
|
|
34
|
+
while (appUpdate.status === "checking" && Date.now() - started < 25e3) await delay(40);
|
|
35
|
+
const status = appUpdate.status;
|
|
36
|
+
if (status === "readyToApply") {
|
|
37
|
+
if (appUpdate.latestVersion && appUpdate.latestVersion === appUpdate.currentVersion) {
|
|
38
|
+
toast.info(t("update_none_found"));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
toast.info(
|
|
42
|
+
t("update_ready_title", {
|
|
43
|
+
from: appUpdate.currentVersion ?? "\u2014",
|
|
44
|
+
to: appUpdate.latestVersion ?? ""
|
|
45
|
+
}),
|
|
46
|
+
{
|
|
47
|
+
action: {
|
|
48
|
+
label: t("update_install_now"),
|
|
49
|
+
onClick: () => appUpdate.applyUpdate()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
} else if (status === "available") {
|
|
54
|
+
toast.info(t("update_available"), {
|
|
55
|
+
action: {
|
|
56
|
+
label: t("update_download"),
|
|
57
|
+
onClick: () => appUpdate.startUpdate()
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
} else if (status === "upToDate") {
|
|
61
|
+
toast.info(t("update_none_found"));
|
|
62
|
+
} else if (status === "failed") {
|
|
63
|
+
toast.error(t("update_failed"), {
|
|
64
|
+
description: appUpdate.error ?? void 0,
|
|
65
|
+
action: {
|
|
66
|
+
label: t("update_retry"),
|
|
67
|
+
onClick: () => appUpdate.checkForUpdates()
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
} else if (status === "paused") {
|
|
71
|
+
toast.info(t("update_paused"), {
|
|
72
|
+
action: { label: t("update_resume"), onClick: () => appUpdate.startUpdate() }
|
|
73
|
+
});
|
|
74
|
+
} else if (status === "unavailable") {
|
|
75
|
+
toast.error(t("update_unavailable"));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<PageHeading icon="fluent:options-24-filled">{t('preferences')}</PageHeading>
|
|
81
|
+
|
|
82
|
+
<div class="flex min-w-0 flex-col gap-6">
|
|
83
|
+
<Card.Root class="@container/card w-full max-w-3xl">
|
|
84
|
+
<Card.Header>
|
|
85
|
+
<Card.Title>{t('interface')}</Card.Title>
|
|
86
|
+
</Card.Header>
|
|
87
|
+
<Card.Content class="space-y-6">
|
|
88
|
+
<Field.Field orientation={isMobile.current ? "vertical" : "horizontal"}>
|
|
89
|
+
<Field.Content>
|
|
90
|
+
<Field.Label for="language">
|
|
91
|
+
<Icon icon="fluent:local-language-20-filled" />
|
|
92
|
+
{t('language')}
|
|
93
|
+
</Field.Label>
|
|
94
|
+
<Field.Description>
|
|
95
|
+
{t('languageDescription')}
|
|
96
|
+
</Field.Description>
|
|
97
|
+
</Field.Content>
|
|
98
|
+
<Select.Root
|
|
99
|
+
type="single"
|
|
100
|
+
value={getLocale()}
|
|
101
|
+
onValueChange={changeLocale}
|
|
102
|
+
>
|
|
103
|
+
<Select.Trigger>
|
|
104
|
+
<span data-slot="select-value">
|
|
105
|
+
<span class="font-bold">
|
|
106
|
+
{getLocale().toLocaleUpperCase()}
|
|
107
|
+
</span>
|
|
108
|
+
{getLocale() === "en" ? t('en') : t('ro')}
|
|
109
|
+
</span>
|
|
110
|
+
</Select.Trigger>
|
|
111
|
+
<Select.Content>
|
|
112
|
+
<Select.Item value="en">
|
|
113
|
+
<span class="font-bold">
|
|
114
|
+
EN
|
|
115
|
+
</span>{t('en')}
|
|
116
|
+
</Select.Item>
|
|
117
|
+
<Select.Item value="ro">
|
|
118
|
+
<span class="font-bold">
|
|
119
|
+
RO
|
|
120
|
+
</span>
|
|
121
|
+
{t('ro')}
|
|
122
|
+
</Select.Item>
|
|
123
|
+
</Select.Content>
|
|
124
|
+
</Select.Root>
|
|
125
|
+
</Field.Field>
|
|
126
|
+
|
|
127
|
+
<Field.Field orientation={isMobile.current ? "vertical" : "horizontal"}>
|
|
128
|
+
<Field.Content>
|
|
129
|
+
<Field.Label for="theme" class="relative">
|
|
130
|
+
<span class="relative inline-flex shrink-0">
|
|
131
|
+
<Icon
|
|
132
|
+
icon="fluent:weather-sunny-24-filled"
|
|
133
|
+
class="size-5 scale-100 rotate-0 !transition-all dark:scale-0 dark:-rotate-90"
|
|
134
|
+
/>
|
|
135
|
+
<Icon
|
|
136
|
+
icon="fluent:weather-moon-24-filled"
|
|
137
|
+
class="absolute inset-0 size-5 scale-0 rotate-90 !transition-all dark:scale-100 dark:rotate-0"
|
|
138
|
+
/>
|
|
139
|
+
</span>
|
|
140
|
+
{t('theme')}
|
|
141
|
+
</Field.Label>
|
|
142
|
+
<Field.Description>
|
|
143
|
+
{t('themeDescription')}
|
|
144
|
+
</Field.Description>
|
|
145
|
+
</Field.Content>
|
|
146
|
+
<Switch.Root
|
|
147
|
+
id="theme"
|
|
148
|
+
checked={isDark}
|
|
149
|
+
onCheckedChange={(v) => setMode(v ? 'dark' : 'light')}
|
|
150
|
+
/>
|
|
151
|
+
</Field.Field>
|
|
152
|
+
|
|
153
|
+
<Field.Field orientation={isMobile.current ? "vertical" : "horizontal"}>
|
|
154
|
+
<Field.Content>
|
|
155
|
+
<Field.Label for="helpNotes">
|
|
156
|
+
<Icon icon="fluent:question-circle-24-filled" />
|
|
157
|
+
{t('help_notes')}
|
|
158
|
+
</Field.Label>
|
|
159
|
+
<Field.Description>
|
|
160
|
+
{t('help_notes_description')}
|
|
161
|
+
</Field.Description>
|
|
162
|
+
</Field.Content>
|
|
163
|
+
<Switch.Root
|
|
164
|
+
id="helpNotes"
|
|
165
|
+
checked={uiPreferences.hideHelpBubbles}
|
|
166
|
+
onCheckedChange={(v) => uiPreferences.hideHelpBubbles = v}
|
|
167
|
+
/>
|
|
168
|
+
</Field.Field>
|
|
169
|
+
</Card.Content>
|
|
170
|
+
</Card.Root>
|
|
171
|
+
|
|
172
|
+
{#if fixApps && deviceStore.hasNativeBridge}
|
|
173
|
+
<Card.Root class="@container/card w-full max-w-3xl">
|
|
174
|
+
<Card.Header>
|
|
175
|
+
<Card.Title>{t('updates')}</Card.Title>
|
|
176
|
+
</Card.Header>
|
|
177
|
+
<Card.Content class="space-y-6">
|
|
178
|
+
<Field.Field orientation={isMobile.current ? "vertical" : "horizontal"}>
|
|
179
|
+
<Field.Content>
|
|
180
|
+
<Field.Label for="autoUpdate">
|
|
181
|
+
<Icon icon="fluent:arrow-sync-24-filled" />
|
|
182
|
+
{t('auto_update')}
|
|
183
|
+
</Field.Label>
|
|
184
|
+
<Field.Description>
|
|
185
|
+
{t('auto_update_description')}
|
|
186
|
+
</Field.Description>
|
|
187
|
+
</Field.Content>
|
|
188
|
+
<Switch.Root
|
|
189
|
+
id="autoUpdate"
|
|
190
|
+
checked={appUpdate.autoUpdate}
|
|
191
|
+
onCheckedChange={(v) => appUpdate.setAutoUpdate(v)}
|
|
192
|
+
/>
|
|
193
|
+
</Field.Field>
|
|
194
|
+
|
|
195
|
+
<Field.Field orientation={isMobile.current ? "vertical" : "horizontal"}>
|
|
196
|
+
<Field.Content>
|
|
197
|
+
<Field.Label for="idealUpdateHour">
|
|
198
|
+
<Icon icon="fluent:clock-24-filled" />
|
|
199
|
+
{t('ideal_update_hour')}
|
|
200
|
+
</Field.Label>
|
|
201
|
+
<Field.Description>
|
|
202
|
+
{t('ideal_update_hour_description')}
|
|
203
|
+
</Field.Description>
|
|
204
|
+
</Field.Content>
|
|
205
|
+
<Select.Root
|
|
206
|
+
type="single"
|
|
207
|
+
value={String(appUpdate.idealUpdateHour).padStart(2, '0')}
|
|
208
|
+
onValueChange={(v) => v && appUpdate.setIdealUpdateHour(parseInt(v, 10))}
|
|
209
|
+
>
|
|
210
|
+
<Select.Trigger class="w-full max-w-56">
|
|
211
|
+
<span data-slot="select-value">
|
|
212
|
+
{String(appUpdate.idealUpdateHour).padStart(2, '0')}:00
|
|
213
|
+
</span>
|
|
214
|
+
</Select.Trigger>
|
|
215
|
+
<Select.Content class="max-h-64">
|
|
216
|
+
{#each Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0')) as hour}
|
|
217
|
+
<Select.Item value={hour}>
|
|
218
|
+
{hour}:00
|
|
219
|
+
</Select.Item>
|
|
220
|
+
{/each}
|
|
221
|
+
</Select.Content>
|
|
222
|
+
</Select.Root>
|
|
223
|
+
</Field.Field>
|
|
224
|
+
|
|
225
|
+
<Field.Field orientation={isMobile.current ? "vertical" : "horizontal"}>
|
|
226
|
+
<Field.Content>
|
|
227
|
+
<Field.Label>
|
|
228
|
+
<Icon icon="fluent:arrow-clockwise-24-filled" />
|
|
229
|
+
{t('check_for_updates')}
|
|
230
|
+
</Field.Label>
|
|
231
|
+
<Field.Description>
|
|
232
|
+
{t('check_for_updates_description')}
|
|
233
|
+
</Field.Description>
|
|
234
|
+
</Field.Content>
|
|
235
|
+
<div class="flex w-full max-w-56 flex-col items-stretch gap-2">
|
|
236
|
+
{#if appUpdate.status === 'readyToApply'}
|
|
237
|
+
<Button onclick={() => appUpdate.applyUpdate()}>
|
|
238
|
+
{t('update_install_now')}
|
|
239
|
+
</Button>
|
|
240
|
+
{:else if appUpdate.status === 'available'}
|
|
241
|
+
<Button onclick={() => appUpdate.startUpdate()}>
|
|
242
|
+
{t('update_download')}
|
|
243
|
+
</Button>
|
|
244
|
+
{:else if appUpdate.status === 'downloading'}
|
|
245
|
+
<Button variant="secondary" onclick={() => appUpdate.pauseUpdate()}>
|
|
246
|
+
{t('update_pause')}
|
|
247
|
+
</Button>
|
|
248
|
+
{:else if appUpdate.status === 'paused'}
|
|
249
|
+
<Button onclick={() => appUpdate.startUpdate()}>
|
|
250
|
+
{t('update_resume')}
|
|
251
|
+
</Button>
|
|
252
|
+
{:else if appUpdate.status === 'failed'}
|
|
253
|
+
<Button onclick={() => appUpdate.checkForUpdates()}>
|
|
254
|
+
{t('update_retry')}
|
|
255
|
+
</Button>
|
|
256
|
+
{/if}
|
|
257
|
+
<Button
|
|
258
|
+
variant="secondary"
|
|
259
|
+
disabled={appUpdate.status === 'checking' || appUpdate.status === 'downloading' || appUpdate.status === 'verifying' || appUpdate.status === 'paused'}
|
|
260
|
+
onclick={() => void checkNow()}
|
|
261
|
+
>
|
|
262
|
+
{appUpdate.status === 'checking' ? t('update_checking') : t('check_for_updates')}
|
|
263
|
+
</Button>
|
|
264
|
+
{#if appUpdate.status === 'downloading' || appUpdate.status === 'paused'}
|
|
265
|
+
<div class="h-1.5 overflow-hidden rounded-full bg-muted">
|
|
266
|
+
<div class="h-full rounded-full bg-primary transition-all" style="width: {appUpdate.progress}%"></div>
|
|
267
|
+
</div>
|
|
268
|
+
<p class="text-muted-foreground text-center text-xs">
|
|
269
|
+
{appUpdate.status === 'paused' ? t('update_paused') : t('update_downloading')} ({appUpdate.progress}%)
|
|
270
|
+
</p>
|
|
271
|
+
{:else if appUpdate.status === 'verifying'}
|
|
272
|
+
<p class="text-muted-foreground text-center text-xs">{t('update_verifying')}</p>
|
|
273
|
+
{:else if appUpdate.status === 'failed' && appUpdate.error}
|
|
274
|
+
<p class="text-destructive text-center text-xs">{appUpdate.error}</p>
|
|
275
|
+
{/if}
|
|
276
|
+
</div>
|
|
277
|
+
</Field.Field>
|
|
278
|
+
</Card.Content>
|
|
279
|
+
</Card.Root>
|
|
280
|
+
{:else if fixApps}
|
|
281
|
+
<AppDownloadCard />
|
|
282
|
+
{/if}
|
|
283
|
+
|
|
284
|
+
{#if fixApps}
|
|
285
|
+
<div class="flex w-full max-w-3xl items-center justify-between px-1">
|
|
286
|
+
<a href="https://www.zexsoft.eu" target="_blank" rel="noopener noreferrer" class="flex">
|
|
287
|
+
<ZexsoftLogo class="h-7 opacity-10"></ZexsoftLogo>
|
|
288
|
+
</a>
|
|
289
|
+
<p class="text-md font-mono opacity-25">v{appUpdate.currentVersion ?? '—'}</p>
|
|
290
|
+
</div>
|
|
291
|
+
{/if}
|
|
292
|
+
</div>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
/** Shows the FIX Fiscal app updates or downloads and its version; a host app of its own turns it off. */
|
|
3
|
+
fixApps?: boolean;
|
|
4
|
+
};
|
|
5
|
+
declare const PreferencesPage: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
6
|
+
type PreferencesPage = ReturnType<typeof PreferencesPage>;
|
|
7
|
+
export default PreferencesPage;
|
|
@@ -9,7 +9,11 @@ export declare function readQueryCsv(url: URL, key: string): string[] | null;
|
|
|
9
9
|
export declare function csvToSelected<T extends string>(allIds: T[], csv: string[] | null): Record<T, boolean> | null;
|
|
10
10
|
/** Writes selected ids; omits the key when every option is selected (unfiltered). */
|
|
11
11
|
export declare function writeQueryCsv(key: string, selectedIds: string[], allIds: string[]): void;
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Merges `patch` into the current URL query; `null`/empty deletes the key. Coalesces concurrent calls into one write.
|
|
14
|
+
* A macrotask, not a microtask: a table mounted during SvelteKit's start-up (the session restored before the first
|
|
15
|
+
* render) would otherwise write before the router is initialized, which SvelteKit rejects.
|
|
16
|
+
*/
|
|
13
17
|
export declare function applyQuery(patch: Record<string, string | null>): void;
|
|
14
18
|
/** Saved toolbar/list query for `orgId` on the current path, if this tenant was visited. */
|
|
15
19
|
export declare function recallPageFilters(orgId: string | null | undefined): Record<string, string> | undefined;
|
|
@@ -30,13 +30,17 @@ export function writeQueryCsv(key, selectedIds, allIds) {
|
|
|
30
30
|
let pendingPatch = {};
|
|
31
31
|
let scheduled = false;
|
|
32
32
|
let stagedParams = null;
|
|
33
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Merges `patch` into the current URL query; `null`/empty deletes the key. Coalesces concurrent calls into one write.
|
|
35
|
+
* A macrotask, not a microtask: a table mounted during SvelteKit's start-up (the session restored before the first
|
|
36
|
+
* render) would otherwise write before the router is initialized, which SvelteKit rejects.
|
|
37
|
+
*/
|
|
34
38
|
export function applyQuery(patch) {
|
|
35
39
|
Object.assign(pendingPatch, patch);
|
|
36
40
|
if (scheduled)
|
|
37
41
|
return;
|
|
38
42
|
scheduled = true;
|
|
39
|
-
|
|
43
|
+
setTimeout(() => {
|
|
40
44
|
scheduled = false;
|
|
41
45
|
const toApply = pendingPatch;
|
|
42
46
|
pendingPatch = {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zexsoft/fix-core",
|
|
3
|
-
"version": "1.14.
|
|
3
|
+
"version": "1.14.16-staging",
|
|
4
4
|
"description": "Official FIX Fiscal platform library for SvelteKit: the headless API client (auth and sessions, organizations, permissions, notifications, i18n) and the FIX Svelte components, from simple UI to drop-in pages.",
|
|
5
5
|
"author": "Zexsoft®",
|
|
6
6
|
"license": "MIT",
|