shutro-erp 0.3.2 → 0.3.3
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/account_portal/.kosh/bindings.js +1 -1
- package/dist/account_portal/.kosh/schema.d.ts +29 -1
- package/dist/accounting/.kosh/bindings.js +1 -1
- package/dist/accounting/.kosh/schema.d.ts +29 -1
- package/dist/assistant/index.js +1 -1
- package/dist/contacts/.kosh/bindings.js +1 -1
- package/dist/contacts/.kosh/schema.d.ts +29 -1
- package/dist/l10n_bd/.kosh/bindings.js +1 -1
- package/dist/l10n_bd/.kosh/schema.d.ts +29 -1
- package/dist/l10n_in/.kosh/bindings.js +1 -1
- package/dist/l10n_in/.kosh/schema.d.ts +29 -4
- package/dist/l10n_in/module.d.ts +0 -62
- package/dist/l10n_in/module.js +2 -4
- package/dist/payment/.kosh/bindings.js +1 -1
- package/dist/payment/.kosh/schema.d.ts +30 -2
- package/dist/payment/bkash.d.ts +28 -0
- package/dist/payment/bkash.js +169 -0
- package/dist/payment/index.d.ts +4 -0
- package/dist/payment/index.js +80 -24
- package/dist/payment/module.d.ts +7 -4
- package/dist/payment/module.js +3 -0
- package/dist/payment/money.d.ts +7 -0
- package/dist/payment/money.js +37 -0
- package/dist/payment/providers.d.ts +13 -2
- package/dist/payment/razorpay.d.ts +12 -0
- package/dist/payment/razorpay.js +149 -0
- package/dist/payment/sslcommerz.js +1 -1
- package/dist/payment/stripe.d.ts +15 -0
- package/dist/payment/stripe.js +182 -0
- package/dist/permissions/admin.d.ts +3 -0
- package/dist/permissions/admin.js +108 -10
- package/dist/permissions/index.d.ts +1 -0
- package/dist/permissions/index.js +3 -0
- package/dist/permissions/roles.d.ts +2 -0
- package/dist/permissions/roles.js +236 -0
- package/dist/portal/.kosh/bindings.js +1 -1
- package/dist/portal/.kosh/schema.d.ts +29 -1
- package/dist/product/.kosh/bindings.js +1 -1
- package/dist/product/.kosh/schema.d.ts +29 -1
- package/dist/project/.kosh/bindings.js +1 -1
- package/dist/project/.kosh/schema.d.ts +29 -1
- package/dist/project_portal/.kosh/bindings.js +1 -1
- package/dist/project_portal/.kosh/schema.d.ts +29 -1
- package/dist/purchase/.kosh/bindings.js +1 -1
- package/dist/purchase/.kosh/schema.d.ts +29 -1
- package/dist/purchase_account/.kosh/bindings.js +1 -1
- package/dist/purchase_account/.kosh/schema.d.ts +29 -1
- package/dist/purchase_portal/.kosh/bindings.js +1 -1
- package/dist/purchase_portal/.kosh/schema.d.ts +29 -1
- package/dist/sale_account/.kosh/bindings.js +1 -1
- package/dist/sale_account/.kosh/schema.d.ts +29 -1
- package/dist/sale_portal/.kosh/bindings.js +1 -1
- package/dist/sale_portal/.kosh/schema.d.ts +29 -1
- package/dist/sale_project/.kosh/bindings.js +1 -1
- package/dist/sale_project/.kosh/schema.d.ts +29 -1
- package/dist/sales/.kosh/bindings.js +1 -1
- package/dist/sales/.kosh/schema.d.ts +29 -1
- package/dist/server/auth/base.js +22 -0
- package/dist/server/auth/routes.d.ts +3 -0
- package/dist/server/auth/routes.js +29 -0
- package/dist/server/auth/service.d.ts +52 -0
- package/dist/server/auth/service.js +168 -3
- package/dist/server/identity.d.ts +2 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/server.js +11 -3
- package/dist/settings/company.d.ts +34 -0
- package/dist/settings/company.js +187 -0
- package/dist/settings/index.d.ts +10 -10
- package/dist/settings/index.js +159 -30
- package/dist/settings/people.d.ts +16 -0
- package/dist/settings/people.js +505 -0
- package/dist/settings/security.d.ts +13 -0
- package/dist/settings/security.js +158 -0
- package/dist/settings/web/AuditPage.d.ts +8 -0
- package/dist/settings/web/AuditPage.js +74 -0
- package/dist/settings/web/CompaniesPage.d.ts +8 -0
- package/dist/settings/web/CompaniesPage.js +52 -0
- package/dist/settings/web/CompanyPage.d.ts +9 -0
- package/dist/settings/web/CompanyPage.js +94 -0
- package/dist/settings/web/OverviewPage.d.ts +9 -0
- package/dist/settings/web/OverviewPage.js +66 -0
- package/dist/settings/web/PeoplePage.d.ts +9 -0
- package/dist/settings/web/PeoplePage.js +163 -0
- package/dist/settings/web/ProfilePage.d.ts +14 -0
- package/dist/settings/web/ProfilePage.js +194 -0
- package/dist/settings/web/RolesEditor.d.ts +35 -0
- package/dist/settings/web/RolesEditor.js +49 -0
- package/dist/settings/web/RolesPage.d.ts +9 -0
- package/dist/settings/web/RolesPage.js +296 -0
- package/dist/settings/web/SecurityPage.d.ts +8 -0
- package/dist/settings/web/SecurityPage.js +81 -0
- package/dist/settings/web/index.d.ts +9 -0
- package/dist/settings/web/index.js +26 -1
- package/dist/settings/web/messages.js +566 -0
- package/dist/ui/api.d.ts +4 -0
- package/dist/ui/app.d.ts +5 -0
- package/dist/ui/app.js +108 -31
- package/dist/ui/components/primitives.d.ts +9 -1
- package/dist/ui/components/primitives.js +12 -4
- package/dist/ui/context.d.ts +10 -1
- package/dist/ui/context.js +60 -2
- package/dist/ui/i18n/messages.d.ts +46 -0
- package/dist/ui/i18n/messages.js +92 -0
- package/dist/ui/index.d.ts +2 -1
- package/dist/ui/index.js +2 -1
- package/dist/ui/lib/accent.d.ts +13 -0
- package/dist/ui/lib/accent.js +36 -0
- package/dist/ui/shell/Activate.d.ts +6 -0
- package/dist/ui/shell/Activate.js +39 -0
- package/dist/ui/shell/CommandPalette.d.ts +7 -1
- package/dist/ui/shell/CommandPalette.js +16 -2
- package/dist/ui/shell/Sidebar.d.ts +27 -0
- package/dist/ui/shell/Sidebar.js +84 -0
- package/dist/ui/shell/TwoFactorSetup.d.ts +5 -0
- package/dist/ui/shell/TwoFactorSetup.js +32 -0
- package/dist/ui/shell/shortcuts.d.ts +3 -0
- package/dist/ui/shell/shortcuts.js +13 -4
- package/dist/ui/styles.css +70 -28
- package/dist/ui/views/DateField.d.ts +10 -0
- package/dist/ui/views/DateField.js +129 -0
- package/dist/ui/views/FormView.js +161 -21
- package/dist/ui/views/ListView.d.ts +2 -0
- package/dist/ui/views/ListView.js +8 -3
- package/dist/ui/views/StatusChip.d.ts +10 -0
- package/dist/ui/views/StatusChip.js +22 -0
- package/dist/ui/views/widgets.d.ts +3 -0
- package/dist/ui/views/widgets.js +26 -8
- package/dist/views/types.d.ts +7 -0
- package/dist/views/validate.js +4 -0
- package/package.json +2 -2
package/dist/ui/app.js
CHANGED
|
@@ -12,28 +12,32 @@ import { compileSearch, decodeSearch, encodeSearch } from '../search/index.js';
|
|
|
12
12
|
import { onlineManager, QueryClient, QueryClientProvider, useQuery, useQueryClient, } from '@tanstack/react-query';
|
|
13
13
|
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
|
|
14
14
|
import { createRootRoute, createRoute, createRouter, Link, Navigate, Outlet, RouterProvider, useNavigate, useParams, useSearch, } from '@tanstack/react-router';
|
|
15
|
-
import { CalendarDays, ChartColumn, ChartGantt, Columns3, Grid2x2,
|
|
15
|
+
import { CalendarDays, ChartColumn, ChartGantt, ChevronRight, ChevronsUpDown, Columns3, Grid2x2, Languages, LayoutDashboard, List, LogOut, Map as MapIcon, Monitor, Moon, Network, PanelLeft, Plus, Search, Sheet, Sun, Table2, } from 'lucide-react';
|
|
16
16
|
import { Direction } from 'radix-ui';
|
|
17
17
|
import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from 'react';
|
|
18
18
|
import { I18nextProvider, useTranslation } from 'react-i18next';
|
|
19
19
|
import { ApiError } from './api.js';
|
|
20
|
-
import { Alert, Button, Menu, MenuCheckboxItem, MenuContent, MenuItem, MenuLabel, MenuSeparator, MenuTrigger,
|
|
20
|
+
import { Alert, Button, Menu, MenuCheckboxItem, MenuContent, MenuItem, MenuLabel, MenuSeparator, MenuTrigger, Spinner, } from './components/primitives.js';
|
|
21
21
|
import { KoshProvider, useKosh } from './context.js';
|
|
22
22
|
import { DesignProvider } from './design.js';
|
|
23
23
|
import { addMessages, createI18n, directionOf } from './i18n/index.js';
|
|
24
24
|
import { LANGUAGES } from './i18n/messages.js';
|
|
25
|
+
import { cn } from './lib/cn.js';
|
|
25
26
|
import { claimOfflineCache, createOfflinePersister, dropOfflineCache, OFFLINE_MAX_AGE_MS, offlinePersistOptions, } from './offline/persist.js';
|
|
26
27
|
import { OfflineQueue } from './offline/queue.js';
|
|
27
28
|
import { setPrefill, takePrefill } from './prefill.js';
|
|
28
29
|
import { onAppUpdate, registerServiceWorker } from './pwa/register.js';
|
|
29
30
|
import { createRealtimeClient } from './realtime.js';
|
|
30
31
|
import { SearchBar } from './search/SearchBar.js';
|
|
32
|
+
import { Activate } from './shell/Activate.js';
|
|
31
33
|
import { CommandPalette, PaletteContext } from './shell/CommandPalette.js';
|
|
32
34
|
import { Home } from './shell/Home.js';
|
|
33
35
|
import { Login } from './shell/Login.js';
|
|
34
36
|
import { OfflineMenu } from './shell/OfflineMenu.js';
|
|
35
37
|
import { rememberRecord } from './shell/recent.js';
|
|
38
|
+
import { Sidebar, SidebarDrawer, useSidebarCollapsed } from './shell/Sidebar.js';
|
|
36
39
|
import { ShortcutsHelp, useShortcuts } from './shell/shortcuts.js';
|
|
40
|
+
import { TwoFactorSetup } from './shell/TwoFactorSetup.js';
|
|
37
41
|
import { ToastProvider, useToast } from './shell/toast.js';
|
|
38
42
|
import { neighbours } from './views/listMemory.js';
|
|
39
43
|
/**
|
|
@@ -100,6 +104,11 @@ function useServices() {
|
|
|
100
104
|
throw new Error('Shutro ERP routes render inside <KoshApp>.');
|
|
101
105
|
return value;
|
|
102
106
|
}
|
|
107
|
+
/** The app's language and its switch (as the language menu does: direction, the server's labels). */
|
|
108
|
+
export function useLanguage() {
|
|
109
|
+
const { lang, setLang } = useServices();
|
|
110
|
+
return { lang, setLang };
|
|
111
|
+
}
|
|
103
112
|
// ------------------------------------------------------------------------------------------------- shell
|
|
104
113
|
function firstAction(menus) {
|
|
105
114
|
for (const m of menus) {
|
|
@@ -111,61 +120,91 @@ function firstAction(menus) {
|
|
|
111
120
|
}
|
|
112
121
|
return undefined;
|
|
113
122
|
}
|
|
114
|
-
function MenuLinks({ app, menus, }) {
|
|
115
|
-
const navigate = useNavigate();
|
|
116
|
-
const entries = menus.flatMap((root) => (root.action === undefined ? root.children : [root]));
|
|
117
|
-
return (_jsx("ul", { className: "flex flex-wrap items-center gap-1", children: entries.map((m) => m.children.length > 0 ? (_jsx("li", { children: _jsxs(Menu, { children: [_jsx(MenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "sm", children: m.label }) }), _jsx(MenuContent, { align: "start", children: m.children.map((c) => c.action === undefined ? null : (_jsx(MenuItem, { onSelect: () => void navigate({
|
|
118
|
-
to: '/$app/a/$action',
|
|
119
|
-
params: { app, action: c.action },
|
|
120
|
-
}), children: c.label }, c.id))) })] }) }, m.id)) : m.action === undefined ? null : (_jsx("li", { children: _jsx(Link, { to: "/$app/a/$action", params: { app, action: m.action }, className: "rounded-md px-3 py-1.5 text-sm hover:bg-muted", activeProps: { 'aria-current': 'page', className: 'bg-muted font-medium' }, children: m.label }) }, m.id))) }));
|
|
121
|
-
}
|
|
122
123
|
function ThemeMenu() {
|
|
123
124
|
const { t } = useTranslation();
|
|
124
|
-
const { theme, setTheme } = useKosh();
|
|
125
|
+
const { theme, setTheme, density, setDensity } = useKosh();
|
|
125
126
|
const icons = { light: Sun, dark: Moon, system: Monitor };
|
|
126
127
|
const Icon = icons[theme];
|
|
127
|
-
return (_jsxs(Menu, { children: [_jsx(MenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": t('shell.theme'), children: _jsx(Icon, { "aria-hidden": true }) }) }), _jsxs(MenuContent, { children: [_jsx(MenuLabel, { children: t('shell.theme') }), ['light', 'dark', 'system'].map((choice) => (_jsx(MenuCheckboxItem, { checked: theme === choice, onCheckedChange: () => setTheme(choice), children: t(`shell.${choice}`) }, choice)))] })] }));
|
|
128
|
+
return (_jsxs(Menu, { children: [_jsx(MenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": t('shell.theme'), children: _jsx(Icon, { "aria-hidden": true }) }) }), _jsxs(MenuContent, { children: [_jsx(MenuLabel, { children: t('shell.theme') }), ['light', 'dark', 'system'].map((choice) => (_jsx(MenuCheckboxItem, { checked: theme === choice, onCheckedChange: () => setTheme(choice), children: t(`shell.${choice}`) }, choice))), _jsx(MenuSeparator, {}), _jsx(MenuLabel, { children: t('shell.density') }), ['comfortable', 'compact'].map((choice) => (_jsx(MenuCheckboxItem, { checked: density === choice, onCheckedChange: () => setDensity(choice), children: t(`views.${choice}`) }, choice)))] })] }));
|
|
128
129
|
}
|
|
129
130
|
function LanguageMenu() {
|
|
130
131
|
const { t } = useTranslation();
|
|
131
132
|
const { lang, setLang } = useServices();
|
|
132
133
|
return (_jsxs(Menu, { children: [_jsx(MenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": t('shell.language'), children: _jsx(Languages, { "aria-hidden": true }) }) }), _jsxs(MenuContent, { children: [_jsx(MenuLabel, { children: t('shell.language') }), LANGUAGES.map((l) => (_jsx(MenuCheckboxItem, { checked: lang === l.code, onCheckedChange: () => setLang(l.code), children: _jsx("span", { lang: l.code.replace('_', '-'), children: l.name }) }, l.code)))] })] }));
|
|
133
134
|
}
|
|
134
|
-
|
|
135
|
+
/** The company at the top of the sidebar: its initial and name, or the switcher when the user has several. */
|
|
136
|
+
function CompanyBlock({ me, company, collapsed, onChange, }) {
|
|
135
137
|
const { t } = useTranslation();
|
|
136
138
|
const { api } = useServices();
|
|
137
139
|
const names = useQuery({
|
|
138
140
|
queryKey: ['names', 'res.company', me.companies],
|
|
139
141
|
queryFn: () => api.names('res.company', { ids: me.companies }),
|
|
140
|
-
enabled: me.companies.length >
|
|
142
|
+
enabled: me.companies.length > 0,
|
|
141
143
|
});
|
|
144
|
+
const name = names.data?.find((c) => c.id === company)?.displayName ?? t('app.name');
|
|
145
|
+
const badge = (_jsx("span", { "aria-hidden": true, className: "grid size-7 shrink-0 place-items-center rounded-md bg-primary text-xs font-semibold text-primary-foreground", children: name.slice(0, 1).toUpperCase() }));
|
|
146
|
+
if (collapsed)
|
|
147
|
+
return _jsx("span", { title: name, children: badge });
|
|
142
148
|
if (me.companies.length < 2)
|
|
143
|
-
return
|
|
144
|
-
|
|
149
|
+
return (_jsxs("div", { className: "flex items-center gap-2.5 px-1 py-1", children: [badge, _jsx("span", { className: "truncate text-sm font-semibold", children: name })] }));
|
|
150
|
+
// Several companies: a switcher (also in the palette: "Switch to …").
|
|
151
|
+
return (_jsxs(Menu, { children: [_jsx(MenuTrigger, { asChild: true, children: _jsxs("button", { type: "button", "aria-label": t('shell.switchCompany', { name }), className: "flex w-full items-center gap-2.5 rounded-md px-1 py-1 text-start hover:bg-muted", children: [badge, _jsx("span", { className: "min-w-0 flex-1 truncate text-sm font-semibold", children: name }), _jsx(ChevronsUpDown, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": true })] }) }), _jsxs(MenuContent, { align: "start", className: "w-64", children: [_jsx(MenuLabel, { children: t('shell.company') }), (names.data ?? []).map((c) => (_jsx(MenuCheckboxItem, { checked: c.id === company, onCheckedChange: () => {
|
|
152
|
+
if (c.id !== company)
|
|
153
|
+
onChange(c.id);
|
|
154
|
+
}, children: _jsxs("span", { className: "flex items-center gap-2", children: [_jsx("span", { "aria-hidden": true, className: "grid size-5 place-items-center rounded bg-muted text-[10px] font-semibold", children: c.displayName.slice(0, 1).toUpperCase() }), c.displayName] }) }, c.id)))] })] }));
|
|
145
155
|
}
|
|
146
|
-
|
|
147
|
-
|
|
156
|
+
/** The user at the bottom of the sidebar: their pages and signing out. */
|
|
157
|
+
function UserMenu({ user, collapsed, }) {
|
|
148
158
|
const { t } = useTranslation();
|
|
159
|
+
const { api, registry, persister } = useServices();
|
|
149
160
|
const queryClient = useQueryClient();
|
|
150
161
|
const navigate = useNavigate();
|
|
162
|
+
const initials = user.name
|
|
163
|
+
.split(/\s+/)
|
|
164
|
+
.map((w) => w.slice(0, 1))
|
|
165
|
+
.join('')
|
|
166
|
+
.slice(0, 2)
|
|
167
|
+
.toUpperCase();
|
|
168
|
+
return (_jsxs(Menu, { children: [_jsx(MenuTrigger, { asChild: true, children: _jsxs("button", { type: "button", "aria-label": user.name, className: cn('flex w-full min-w-0 items-center gap-2 rounded-md px-1.5 py-1.5 text-sm hover:bg-muted', collapsed && 'justify-center'), children: [_jsx("span", { "aria-hidden": true, className: "grid size-7 shrink-0 place-items-center rounded-full bg-muted text-xs font-medium", children: initials }), collapsed ? null : _jsx("span", { className: "truncate", children: user.name })] }) }), _jsxs(MenuContent, { align: "start", side: "top", children: [_jsx(MenuLabel, { children: user.name }), registry.userPages().map((p) => (_jsx(MenuItem, { onSelect: () => void navigate({ to: '/me/$page', params: { page: p.name } }), children: t(p.label) }, p.name))), _jsxs(MenuItem, { onSelect: async () => {
|
|
169
|
+
await api.logout();
|
|
170
|
+
queryClient.clear();
|
|
171
|
+
await dropOfflineCache(persister);
|
|
172
|
+
await navigate({ to: '/login' });
|
|
173
|
+
}, children: [_jsx(LogOut, { className: "size-4", "aria-hidden": true }), t('auth.signOut')] })] })] }));
|
|
174
|
+
}
|
|
175
|
+
function Shell() {
|
|
176
|
+
const { api, registry, lang, realtime, offline } = useServices();
|
|
177
|
+
const { t } = useTranslation();
|
|
178
|
+
const queryClient = useQueryClient();
|
|
151
179
|
const params = useParams({ strict: false });
|
|
152
180
|
const me = useQuery({ queryKey: ['me'], queryFn: () => api.me(), retry: false });
|
|
153
181
|
const [company, setCompany] = useState(undefined);
|
|
154
182
|
const [palette, setPalette] = useState(false);
|
|
155
183
|
const [help, setHelp] = useState(false);
|
|
156
|
-
const
|
|
184
|
+
const [drawer, setDrawer] = useState(false);
|
|
185
|
+
const [collapsed, setCollapsed] = useSidebarCollapsed();
|
|
186
|
+
const shortcutHandlers = useMemo(() => ({
|
|
187
|
+
palette: () => setPalette(true),
|
|
188
|
+
help: () => setHelp(true),
|
|
189
|
+
sidebar: () => setCollapsed(!collapsed),
|
|
190
|
+
}), [collapsed, setCollapsed]);
|
|
157
191
|
useShortcuts(shortcutHandlers);
|
|
158
192
|
const openHelp = useCallback(() => setHelp(true), []);
|
|
159
193
|
const openPalette = useCallback(() => setPalette(true), []);
|
|
160
194
|
const apps = useQuery({
|
|
161
195
|
queryKey: ['apps', company],
|
|
162
196
|
queryFn: () => api.apps(),
|
|
163
|
-
enabled: me.isSuccess,
|
|
197
|
+
enabled: me.isSuccess && me.data.twoFactorSetupRequired !== true,
|
|
198
|
+
});
|
|
199
|
+
const companyNames = useQuery({
|
|
200
|
+
queryKey: ['names', 'res.company', me.data?.companies],
|
|
201
|
+
queryFn: () => api.names('res.company', { ids: me.data?.companies ?? [] }),
|
|
202
|
+
enabled: (me.data?.companies.length ?? 0) > 1,
|
|
164
203
|
});
|
|
165
204
|
const menus = useQuery({
|
|
166
205
|
queryKey: ['menus', params.app, company],
|
|
167
206
|
queryFn: () => api.menus(params.app),
|
|
168
|
-
enabled: me.isSuccess && params.app !== undefined,
|
|
207
|
+
enabled: me.isSuccess && me.data.twoFactorSetupRequired !== true && params.app !== undefined,
|
|
169
208
|
});
|
|
170
209
|
if (me.error instanceof ApiError && me.error.status === 401)
|
|
171
210
|
return _jsx(Navigate, { to: "/login" });
|
|
@@ -174,19 +213,47 @@ function Shell() {
|
|
|
174
213
|
return _jsx(Alert, { children: me.error.message });
|
|
175
214
|
if (me.data === undefined)
|
|
176
215
|
return _jsx(Spinner, { label: t('app.loading') });
|
|
216
|
+
// The policy asks everyone for a second factor (RFC 0019 §3.5): nothing else until it is set up.
|
|
217
|
+
if (me.data.twoFactorSetupRequired === true)
|
|
218
|
+
return _jsx(TwoFactorSetup, { api: api });
|
|
177
219
|
const user = me.data;
|
|
178
220
|
const current = company ?? user.company ?? user.companies[0] ?? 0;
|
|
179
221
|
const app = apps.data?.find((a) => a.code === params.app);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
222
|
+
// A page of the user menu (`/me/<page>`): the page bar names it.
|
|
223
|
+
const userPage = params.page === undefined
|
|
224
|
+
? undefined
|
|
225
|
+
: registry.userPages().find((p) => p.name === params.page);
|
|
226
|
+
/** The open section's label (the menu entry of the current action), for the page bar. */
|
|
227
|
+
const section = (() => {
|
|
228
|
+
const walk = (nodes) => {
|
|
229
|
+
for (const n of nodes) {
|
|
230
|
+
if (n.action !== undefined && n.action === params.action)
|
|
231
|
+
return n.label;
|
|
232
|
+
const found = walk(n.children);
|
|
233
|
+
if (found !== undefined)
|
|
234
|
+
return found;
|
|
235
|
+
}
|
|
236
|
+
return undefined;
|
|
237
|
+
};
|
|
238
|
+
return params.action === undefined ? undefined : walk(menus.data ?? []);
|
|
239
|
+
})();
|
|
240
|
+
const switchCompany = (id) => {
|
|
241
|
+
setPalette(false);
|
|
242
|
+
api.setCompany(id);
|
|
243
|
+
setCompany(id);
|
|
244
|
+
void queryClient.invalidateQueries();
|
|
245
|
+
};
|
|
246
|
+
const sidebar = (inDrawer) => (_jsx(Sidebar, { api: api, apps: apps.data ?? [], current: params.app, company: current, collapsed: inDrawer ? false : collapsed, onCollapse: inDrawer ? undefined : setCollapsed, onSearch: () => {
|
|
247
|
+
setDrawer(false);
|
|
248
|
+
setPalette(true);
|
|
249
|
+
}, top: _jsx(CompanyBlock, { me: user, company: current, collapsed: inDrawer ? false : collapsed, onChange: switchCompany }), bottom: _jsx(UserMenu, { user: user, collapsed: inDrawer ? false : collapsed }), onNavigate: inDrawer ? () => setDrawer(false) : undefined }));
|
|
250
|
+
return (_jsx(KoshProvider, { api: api, registry: registry, uid: user.id, companyId: current, lang: lang, tz: Intl.DateTimeFormat().resolvedOptions().timeZone, grouping: user.groupings?.[String(current)] ?? 'international', accent: user.accents?.[String(current)] ?? null, ...(realtime === undefined ? {} : { realtime }), ...(offline === undefined ? {} : { offline }), switchCompany: switchCompany, children: _jsx(ToastProvider, { children: _jsxs(DesignProvider, { children: [_jsx(OfflineAgent, { uid: user.id }), _jsxs(PaletteContext.Provider, { value: openPalette, children: [_jsx("a", { href: "#content", className: "sr-only focus:not-sr-only focus:absolute focus:start-2 focus:top-2 focus:z-50 focus:rounded focus:bg-surface focus:px-3 focus:py-2", children: t('shell.skip') }), _jsxs("div", { className: "flex min-h-screen", children: [_jsx("aside", { "aria-label": t('shell.sidebar'), className: cn('sticky top-0 hidden h-screen shrink-0 border-e bg-surface transition-[width] lg:block', collapsed ? 'w-14' : 'w-60'), children: sidebar(false) }), _jsx(SidebarDrawer, { open: drawer, onOpenChange: setDrawer, children: sidebar(true) }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("header", { className: "sticky top-0 z-30 border-b bg-surface", children: _jsxs("div", { className: "flex h-12 items-center gap-2 px-3 lg:px-4", children: [_jsx(Button, { variant: "ghost", size: "icon", className: "lg:hidden", "aria-label": t('shell.openMenu'), onClick: () => setDrawer(true), children: _jsx(PanelLeft, { className: "rtl:rotate-180", "aria-hidden": true }) }), _jsx("nav", { "aria-label": t('shell.location'), className: "flex min-w-0 flex-1 items-center gap-1.5 text-sm", children: app === undefined ? (_jsx("span", { className: "font-semibold", children: userPage !== undefined
|
|
251
|
+
? t(userPage.label)
|
|
252
|
+
: params.app === undefined
|
|
253
|
+
? t('shell.home')
|
|
254
|
+
: t('app.name') })) : (_jsxs(_Fragment, { children: [_jsx(Link, { to: "/$app", params: { app: app.code }, className: "font-semibold hover:underline", children: app.label }), section === undefined ? null : (_jsxs(_Fragment, { children: [_jsx(ChevronRight, { className: "size-3.5 text-muted-foreground rtl:rotate-180", "aria-hidden": true }), _jsx(Link, { to: "/$app/a/$action", params: { app: app.code, action: params.action ?? '' }, className: "truncate text-muted-foreground hover:text-foreground hover:underline", children: section })] }))] })) }), _jsx(Button, { variant: "ghost", size: "icon", className: "lg:hidden", "aria-label": t('palette.button'), onClick: () => setPalette(true), children: _jsx(Search, { "aria-hidden": true }) }), _jsx(OfflineMenu, {}), registry.headerItems().map(({ name, component: Item }) => (_jsx(Item, { api: api }, name))), _jsx(LanguageMenu, {}), _jsx(ThemeMenu, {})] }) }), user.administratorNeedsSecondFactor ? (_jsx("div", { className: "px-4 pt-3", children: _jsx(Alert, { tone: "info", children: t('auth.adminNeeds2fa') }) })) : null, _jsx("main", { id: "content", className: "mx-auto w-full max-w-[100rem] flex-1 p-4 lg:p-6", children: _jsx(Outlet, {}) })] })] }), _jsx(CommandPalette, { open: palette, onOpenChange: setPalette, api: api, onShortcuts: openHelp, companies: (companyNames.data ?? [])
|
|
255
|
+
.filter((c) => c.id !== current)
|
|
256
|
+
.map((c) => ({ id: c.id, name: c.displayName })), onSwitchCompany: switchCompany }), _jsx(ShortcutsHelp, { open: help, onOpenChange: setHelp })] })] }) }) }));
|
|
190
257
|
}
|
|
191
258
|
function AppHome() {
|
|
192
259
|
const { api } = useServices();
|
|
@@ -410,6 +477,10 @@ function OpenRecordPage() {
|
|
|
410
477
|
return _jsx(Alert, { children: t('app.notFound') });
|
|
411
478
|
return (_jsx(Navigate, { to: "/$app/a/$action/$id", params: { app: route.app, action: route.action, id }, replace: true }));
|
|
412
479
|
}
|
|
480
|
+
function ActivatePage() {
|
|
481
|
+
const navigate = useNavigate();
|
|
482
|
+
return _jsx(Activate, { api: useServices().api, onSignedIn: () => void navigate({ to: '/' }) });
|
|
483
|
+
}
|
|
413
484
|
function LoginPage() {
|
|
414
485
|
const navigate = useNavigate();
|
|
415
486
|
return _jsx(Login, { api: useServices().api, onSignedIn: () => void navigate({ to: '/' }) });
|
|
@@ -421,6 +492,11 @@ const loginRoute = createRoute({
|
|
|
421
492
|
path: '/login',
|
|
422
493
|
component: LoginPage,
|
|
423
494
|
});
|
|
495
|
+
const activateRoute = createRoute({
|
|
496
|
+
getParentRoute: () => rootRoute,
|
|
497
|
+
path: '/activate',
|
|
498
|
+
component: ActivatePage,
|
|
499
|
+
});
|
|
424
500
|
const shellRoute = createRoute({ getParentRoute: () => rootRoute, id: 'shell', component: Shell });
|
|
425
501
|
const indexRoute = createRoute({
|
|
426
502
|
getParentRoute: () => shellRoute,
|
|
@@ -469,6 +545,7 @@ const userPageRoute = createRoute({
|
|
|
469
545
|
});
|
|
470
546
|
const routeTree = rootRoute.addChildren([
|
|
471
547
|
loginRoute,
|
|
548
|
+
activateRoute,
|
|
472
549
|
shellRoute.addChildren([
|
|
473
550
|
indexRoute,
|
|
474
551
|
openRoute,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Checkbox as RCheckbox, DropdownMenu as RMenu, Tabs as RTabs } from 'radix-ui';
|
|
1
|
+
import { Checkbox as RCheckbox, DropdownMenu as RMenu, Popover as RPopover, Tabs as RTabs } from 'radix-ui';
|
|
2
2
|
import type { ComponentProps, ReactElement, ReactNode } from 'react';
|
|
3
3
|
export type ButtonProps = ComponentProps<'button'> & {
|
|
4
4
|
readonly variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'link';
|
|
@@ -10,6 +10,10 @@ export declare function Button({ className, variant, size, asChild, busy, childr
|
|
|
10
10
|
export declare function Input({ className, ...props }: ComponentProps<'input'>): ReactElement;
|
|
11
11
|
export declare function Textarea({ className, ...props }: ComponentProps<'textarea'>): ReactElement;
|
|
12
12
|
export declare function Label({ className, ...props }: ComponentProps<'label'>): ReactElement;
|
|
13
|
+
/** The badge's classes, for elements that look like one (the status chip). */
|
|
14
|
+
export declare const badgeVariants: (props?: {
|
|
15
|
+
readonly tone?: 'neutral' | 'info' | 'success' | 'warning' | 'danger' | null;
|
|
16
|
+
}) => string;
|
|
13
17
|
export declare function Badge({ className, tone, ...props }: ComponentProps<'span'> & {
|
|
14
18
|
readonly tone?: 'neutral' | 'info' | 'success' | 'warning' | 'danger' | null;
|
|
15
19
|
}): ReactElement;
|
|
@@ -36,6 +40,10 @@ export declare function MenuCheckboxItem({ className, children, ...props }: Comp
|
|
|
36
40
|
export declare function MenuLabel({ className, ...props }: ComponentProps<typeof RMenu.Label>): ReactElement;
|
|
37
41
|
export declare function MenuSeparator(): ReactElement;
|
|
38
42
|
/** A modal dialog with a title (required for screen readers) and a close button. */
|
|
43
|
+
/** A popover (Radix): focus moves in and back, Escape closes, positioned beside its trigger. */
|
|
44
|
+
export declare const Popover: typeof RPopover.Root;
|
|
45
|
+
export declare const PopoverTrigger: typeof RPopover.Trigger;
|
|
46
|
+
export declare function PopoverContent({ className, align, sideOffset, ...props }: ComponentProps<typeof RPopover.Content>): ReactElement;
|
|
39
47
|
export declare function Dialog({ open, onOpenChange, title, description, children, footer, wide, }: {
|
|
40
48
|
readonly open: boolean;
|
|
41
49
|
readonly onOpenChange: (open: boolean) => void;
|
|
@@ -6,7 +6,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
*/
|
|
7
7
|
import { cva } from 'class-variance-authority';
|
|
8
8
|
import { Check, ChevronDown, Loader2, X } from 'lucide-react';
|
|
9
|
-
import { Checkbox as RCheckbox, Dialog as RDialog, DropdownMenu as RMenu, Select as RSelect, Tabs as RTabs, Slot, } from 'radix-ui';
|
|
9
|
+
import { Checkbox as RCheckbox, Dialog as RDialog, DropdownMenu as RMenu, Popover as RPopover, Select as RSelect, Tabs as RTabs, Slot, } from 'radix-ui';
|
|
10
10
|
import { cn } from '../lib/cn.js';
|
|
11
11
|
const buttonVariants = cva('inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0', {
|
|
12
12
|
variants: {
|
|
@@ -17,7 +17,7 @@ const buttonVariants = cva('inline-flex items-center justify-center gap-2 whites
|
|
|
17
17
|
danger: 'bg-danger text-danger-foreground hover:bg-danger/90',
|
|
18
18
|
link: 'text-primary underline-offset-4 hover:underline',
|
|
19
19
|
},
|
|
20
|
-
size: { sm: 'h-8 px-3', md: 'h-
|
|
20
|
+
size: { sm: 'h-8 px-3', md: 'h-[var(--control-h)] px-4', icon: 'size-[var(--control-h)]' },
|
|
21
21
|
},
|
|
22
22
|
defaultVariants: { variant: 'secondary', size: 'md' },
|
|
23
23
|
});
|
|
@@ -27,7 +27,7 @@ export function Button({ className, variant, size, asChild, busy, children, disa
|
|
|
27
27
|
}
|
|
28
28
|
const field = 'w-full rounded-md border border-input bg-surface px-3 text-sm text-surface-foreground placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-60 read-only:bg-muted aria-invalid:border-danger';
|
|
29
29
|
export function Input({ className, ...props }) {
|
|
30
|
-
return _jsx("input", { className: cn(field, 'h-
|
|
30
|
+
return _jsx("input", { className: cn(field, 'h-[var(--control-h)]', className), ...props });
|
|
31
31
|
}
|
|
32
32
|
export function Textarea({ className, ...props }) {
|
|
33
33
|
return _jsx("textarea", { className: cn(field, 'min-h-20 py-2', className), ...props });
|
|
@@ -47,6 +47,8 @@ const badge = cva('inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-
|
|
|
47
47
|
},
|
|
48
48
|
defaultVariants: { tone: 'neutral' },
|
|
49
49
|
});
|
|
50
|
+
/** The badge's classes, for elements that look like one (the status chip). */
|
|
51
|
+
export const badgeVariants = badge;
|
|
50
52
|
export function Badge({ className, tone, ...props }) {
|
|
51
53
|
return _jsx("span", { className: cn(badge({ tone }), className), ...props });
|
|
52
54
|
}
|
|
@@ -55,7 +57,7 @@ export function Checkbox({ className, ...props }) {
|
|
|
55
57
|
}
|
|
56
58
|
/** A select over `options` (`null` value: empty). */
|
|
57
59
|
export function Select({ value, options, onChange, disabled, placeholder, id, label, className, }) {
|
|
58
|
-
return (_jsxs(RSelect.Root, { ...(value === null ? {} : { value }), onValueChange: onChange, ...(disabled === true ? { disabled } : {}), children: [_jsxs(RSelect.Trigger, { ...(id === undefined ? {} : { id }), "aria-label": label, className: cn(field, 'flex h-
|
|
60
|
+
return (_jsxs(RSelect.Root, { ...(value === null ? {} : { value }), onValueChange: onChange, ...(disabled === true ? { disabled } : {}), children: [_jsxs(RSelect.Trigger, { ...(id === undefined ? {} : { id }), "aria-label": label, className: cn(field, 'flex h-[var(--control-h)] items-center justify-between gap-2 text-start', className), children: [_jsx(RSelect.Value, { placeholder: placeholder }), _jsx(RSelect.Icon, { children: _jsx(ChevronDown, { className: "size-4 opacity-60", "aria-hidden": true }) })] }), _jsx(RSelect.Portal, { children: _jsx(RSelect.Content, { position: "popper", sideOffset: 4, className: "z-50 min-w-[var(--radix-select-trigger-width)] overflow-hidden rounded-md border bg-surface text-surface-foreground shadow-lg", children: _jsx(RSelect.Viewport, { className: "p-1", children: options.map((o) => (_jsxs(RSelect.Item, { value: o.value, className: "relative flex cursor-default select-none items-center rounded-sm py-1.5 pe-2 ps-7 text-sm outline-none data-[highlighted]:bg-muted", children: [_jsx(RSelect.ItemIndicator, { className: "absolute start-2", children: _jsx(Check, { className: "size-3.5", "aria-hidden": true }) }), _jsx(RSelect.ItemText, { children: o.label })] }, o.value))) }) }) })] }));
|
|
59
61
|
}
|
|
60
62
|
export const Menu = RMenu.Root;
|
|
61
63
|
export const MenuTrigger = RMenu.Trigger;
|
|
@@ -75,6 +77,12 @@ export function MenuSeparator() {
|
|
|
75
77
|
return _jsx(RMenu.Separator, { className: "my-1 h-px bg-border" });
|
|
76
78
|
}
|
|
77
79
|
/** A modal dialog with a title (required for screen readers) and a close button. */
|
|
80
|
+
/** A popover (Radix): focus moves in and back, Escape closes, positioned beside its trigger. */
|
|
81
|
+
export const Popover = RPopover.Root;
|
|
82
|
+
export const PopoverTrigger = RPopover.Trigger;
|
|
83
|
+
export function PopoverContent({ className, align = 'start', sideOffset = 6, ...props }) {
|
|
84
|
+
return (_jsx(RPopover.Portal, { children: _jsx(RPopover.Content, { align: align, sideOffset: sideOffset, className: cn('z-50 rounded-lg border bg-surface p-3 text-surface-foreground shadow-lg outline-none', className), ...props }) }));
|
|
85
|
+
}
|
|
78
86
|
export function Dialog({ open, onOpenChange, title, description, children, footer, wide, }) {
|
|
79
87
|
return (_jsx(RDialog.Root, { open: open, onOpenChange: onOpenChange, children: _jsxs(RDialog.Portal, { children: [_jsx(RDialog.Overlay, { className: "fixed inset-0 z-40 bg-black/40" }), _jsxs(RDialog.Content, { className: cn('fixed start-1/2 top-1/2 z-50 -translate-y-1/2 rtl:translate-x-1/2 ltr:-translate-x-1/2 rounded-lg border bg-surface p-5 text-surface-foreground shadow-xl', wide === true
|
|
80
88
|
? 'max-h-[calc(100vh-2rem)] w-[min(56rem,calc(100vw-2rem))] overflow-y-auto'
|
package/dist/ui/context.d.ts
CHANGED
|
@@ -17,16 +17,22 @@ export interface KoshContextValue {
|
|
|
17
17
|
readonly format: Formatter;
|
|
18
18
|
readonly theme: ThemeChoice;
|
|
19
19
|
setTheme(theme: ThemeChoice): void;
|
|
20
|
+
/** Comfortable or compact (RFC 0018): lists, forms and controls. */
|
|
21
|
+
readonly density: Density;
|
|
22
|
+
setDensity(density: Density): void;
|
|
20
23
|
/** The realtime gateway's client, when the app has one: live lists, records and presence. */
|
|
21
24
|
readonly realtime?: RealtimeClient;
|
|
22
25
|
/** Changes saved offline, when the app keeps them: a form that cannot reach the server saves here. */
|
|
23
26
|
readonly offline?: OfflineQueue;
|
|
27
|
+
/** Switches to another of the user's companies, when the shell offers it (Settings › Companies). */
|
|
28
|
+
readonly switchCompany?: (company: number) => void;
|
|
24
29
|
}
|
|
25
30
|
/** The renderer context. @throws outside {@link KoshProvider} */
|
|
26
31
|
export declare function useKosh(): KoshContextValue;
|
|
32
|
+
export type Density = 'comfortable' | 'compact';
|
|
27
33
|
/** Applies the theme to `<html>` (the `dark` class) and follows the system when asked to. */
|
|
28
34
|
declare function useTheme(): [ThemeChoice, (t: ThemeChoice) => void];
|
|
29
|
-
export declare function KoshProvider({ api, registry, uid, companyId, lang, tz, grouping, realtime, offline, children, }: {
|
|
35
|
+
export declare function KoshProvider({ api, registry, uid, companyId, lang, tz, grouping, accent, realtime, offline, switchCompany, children, }: {
|
|
30
36
|
readonly api: KoshApi;
|
|
31
37
|
readonly registry: Registry;
|
|
32
38
|
readonly uid: number;
|
|
@@ -35,8 +41,11 @@ export declare function KoshProvider({ api, registry, uid, companyId, lang, tz,
|
|
|
35
41
|
readonly tz: string;
|
|
36
42
|
/** The company's number grouping (RFC 0016). */
|
|
37
43
|
readonly grouping?: 'international' | 'lakh';
|
|
44
|
+
/** The company's brand colour (`#rrggbb`, RFC 0018). */
|
|
45
|
+
readonly accent?: string | null;
|
|
38
46
|
readonly realtime?: RealtimeClient;
|
|
39
47
|
readonly offline?: OfflineQueue;
|
|
48
|
+
readonly switchCompany?: (company: number) => void;
|
|
40
49
|
readonly children: ReactNode;
|
|
41
50
|
}): ReactElement;
|
|
42
51
|
/** Theme switching without a signed-in user (the sign-in page). */
|
package/dist/ui/context.js
CHANGED
|
@@ -4,6 +4,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
4
4
|
* formatter for the user's language and time zone, and the theme.
|
|
5
5
|
*/
|
|
6
6
|
import { createContext, useContext, useEffect, useMemo, useState, } from 'react';
|
|
7
|
+
import { accentVariables } from './lib/accent.js';
|
|
7
8
|
import { createFormatter } from './lib/format.js';
|
|
8
9
|
const Context = createContext(null);
|
|
9
10
|
/** The renderer context. @throws outside {@link KoshProvider} */
|
|
@@ -14,6 +15,45 @@ export function useKosh() {
|
|
|
14
15
|
return value;
|
|
15
16
|
}
|
|
16
17
|
const THEME_KEY = 'kosh.theme';
|
|
18
|
+
const DENSITY_KEY = 'kosh.density';
|
|
19
|
+
function readDensity() {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(localStorage.getItem(DENSITY_KEY) ?? 'null') === 'compact'
|
|
22
|
+
? 'compact'
|
|
23
|
+
: 'comfortable';
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return 'comfortable';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Applies the density to `<html>` (`data-density`), remembered per browser. */
|
|
30
|
+
function useDensity() {
|
|
31
|
+
const [density, setDensity] = useState(readDensity);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
document.documentElement.dataset['density'] = density;
|
|
34
|
+
try {
|
|
35
|
+
localStorage.setItem(DENSITY_KEY, JSON.stringify(density));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// storage unavailable: the choice lasts for this page only
|
|
39
|
+
}
|
|
40
|
+
}, [density]);
|
|
41
|
+
return [density, setDensity];
|
|
42
|
+
}
|
|
43
|
+
/** Sets the company's brand colour as the accent (RFC 0018), or the default when it has none. */
|
|
44
|
+
function useAccent(accent) {
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const root = document.documentElement.style;
|
|
47
|
+
const variables = accentVariables(accent);
|
|
48
|
+
for (const key of ['--accent-h', '--accent-c']) {
|
|
49
|
+
const value = variables[key];
|
|
50
|
+
if (value === undefined)
|
|
51
|
+
root.removeProperty(key);
|
|
52
|
+
else
|
|
53
|
+
root.setProperty(key, value);
|
|
54
|
+
}
|
|
55
|
+
}, [accent]);
|
|
56
|
+
}
|
|
17
57
|
function readTheme() {
|
|
18
58
|
try {
|
|
19
59
|
const stored = localStorage.getItem(THEME_KEY);
|
|
@@ -47,8 +87,10 @@ function useTheme() {
|
|
|
47
87
|
}, [theme]);
|
|
48
88
|
return [theme, setTheme];
|
|
49
89
|
}
|
|
50
|
-
export function KoshProvider({ api, registry, uid, companyId, lang, tz, grouping, realtime, offline, children, }) {
|
|
90
|
+
export function KoshProvider({ api, registry, uid, companyId, lang, tz, grouping, accent, realtime, offline, switchCompany, children, }) {
|
|
51
91
|
const [theme, setTheme] = useTheme();
|
|
92
|
+
const [density, setDensity] = useDensity();
|
|
93
|
+
useAccent(accent);
|
|
52
94
|
const format = useMemo(() => createFormatter(lang, tz, grouping === undefined ? {} : { grouping }), [lang, tz, grouping]);
|
|
53
95
|
const value = useMemo(() => ({
|
|
54
96
|
api,
|
|
@@ -58,9 +100,25 @@ export function KoshProvider({ api, registry, uid, companyId, lang, tz, grouping
|
|
|
58
100
|
format,
|
|
59
101
|
theme,
|
|
60
102
|
setTheme,
|
|
103
|
+
density,
|
|
104
|
+
setDensity,
|
|
61
105
|
...(realtime === undefined ? {} : { realtime }),
|
|
62
106
|
...(offline === undefined ? {} : { offline }),
|
|
63
|
-
|
|
107
|
+
...(switchCompany === undefined ? {} : { switchCompany }),
|
|
108
|
+
}), [
|
|
109
|
+
api,
|
|
110
|
+
registry,
|
|
111
|
+
uid,
|
|
112
|
+
companyId,
|
|
113
|
+
format,
|
|
114
|
+
theme,
|
|
115
|
+
setTheme,
|
|
116
|
+
density,
|
|
117
|
+
setDensity,
|
|
118
|
+
realtime,
|
|
119
|
+
offline,
|
|
120
|
+
switchCompany,
|
|
121
|
+
]);
|
|
64
122
|
return _jsx(Context.Provider, { value: value, children: children });
|
|
65
123
|
}
|
|
66
124
|
/** Theme switching without a signed-in user (the sign-in page). */
|
|
@@ -12,6 +12,14 @@ export declare const en: {
|
|
|
12
12
|
unknownPage: string;
|
|
13
13
|
};
|
|
14
14
|
auth: {
|
|
15
|
+
activateTitle: string;
|
|
16
|
+
activateIntro: string;
|
|
17
|
+
activateNoToken: string;
|
|
18
|
+
newPassword: string;
|
|
19
|
+
repeatPassword: string;
|
|
20
|
+
passwordRule: string;
|
|
21
|
+
mismatch: string;
|
|
22
|
+
activate: string;
|
|
15
23
|
signIn: string;
|
|
16
24
|
login: string;
|
|
17
25
|
password: string;
|
|
@@ -21,11 +29,32 @@ export declare const en: {
|
|
|
21
29
|
signOut: string;
|
|
22
30
|
invalid: string;
|
|
23
31
|
adminNeeds2fa: string;
|
|
32
|
+
setupTitle: string;
|
|
33
|
+
setupIntro: string;
|
|
34
|
+
setupStart: string;
|
|
35
|
+
setupStep1: string;
|
|
36
|
+
setupOpenApp: string;
|
|
37
|
+
setupStep2: string;
|
|
38
|
+
setupStep3: string;
|
|
39
|
+
setupTurnOn: string;
|
|
40
|
+
setupCodes: string;
|
|
41
|
+
setupContinue: string;
|
|
24
42
|
};
|
|
25
43
|
shell: {
|
|
26
44
|
apps: string;
|
|
27
45
|
menu: string;
|
|
28
46
|
theme: string;
|
|
47
|
+
density: string;
|
|
48
|
+
home: string;
|
|
49
|
+
appNamed: string;
|
|
50
|
+
switchCompany: string;
|
|
51
|
+
sidebar: string;
|
|
52
|
+
collapseSidebar: string;
|
|
53
|
+
expandSidebar: string;
|
|
54
|
+
openMenu: string;
|
|
55
|
+
closeMenu: string;
|
|
56
|
+
menuHint: string;
|
|
57
|
+
location: string;
|
|
29
58
|
light: string;
|
|
30
59
|
dark: string;
|
|
31
60
|
system: string;
|
|
@@ -62,6 +91,20 @@ export declare const en: {
|
|
|
62
91
|
exportFailed: string;
|
|
63
92
|
columns: string;
|
|
64
93
|
density: string;
|
|
94
|
+
statusOf: string;
|
|
95
|
+
nextState: string;
|
|
96
|
+
recordActions: string;
|
|
97
|
+
summary: string;
|
|
98
|
+
details: string;
|
|
99
|
+
recordPanel: string;
|
|
100
|
+
showPanel: string;
|
|
101
|
+
hidePanel: string;
|
|
102
|
+
previousMonth: string;
|
|
103
|
+
nextMonth: string;
|
|
104
|
+
pickDate: string;
|
|
105
|
+
datePlaceholder: string;
|
|
106
|
+
today: string;
|
|
107
|
+
clear: string;
|
|
65
108
|
proposed: string;
|
|
66
109
|
design: {
|
|
67
110
|
addField: string;
|
|
@@ -197,6 +240,8 @@ export declare const en: {
|
|
|
197
240
|
close: string;
|
|
198
241
|
};
|
|
199
242
|
palette: {
|
|
243
|
+
switchCompany: string;
|
|
244
|
+
profile: string;
|
|
200
245
|
button: string;
|
|
201
246
|
title: string;
|
|
202
247
|
description: string;
|
|
@@ -220,6 +265,7 @@ export declare const en: {
|
|
|
220
265
|
shortcuts: {
|
|
221
266
|
title: string;
|
|
222
267
|
palette: string;
|
|
268
|
+
sidebar: string;
|
|
223
269
|
search: string;
|
|
224
270
|
new: string;
|
|
225
271
|
views: string;
|