form-craft-package 1.11.6 → 1.11.9-dev.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/package.json +1 -1
- package/src/api/client.ts +4 -4
- package/src/components/auth-layouts/index.tsx +63 -24
- package/src/components/companies/2-unauthenticated/index.tsx +15 -11
- package/src/components/companies/3-config-provider/index.tsx +36 -34
- package/src/components/form/layout-renderer/1-row/index.tsx +21 -3
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-send-notification.hook.ts +15 -4
- package/src/components/form/layout-renderer/3-element/11-breadcrumb/index.tsx +131 -131
- package/src/components/form/layout-renderer/3-element/18-gallery/upload.modal.tsx +29 -29
- package/src/components/form/layout-renderer/3-element/2-field-element.tsx +2 -2
- package/src/components/form/layout-renderer/3-element/index.tsx +7 -2
- package/src/constants/index.ts +21 -2
- package/src/enums/index.ts +8 -0
- package/src/functions/forms/index.ts +17 -8
- package/src/types/companies/index.ts +20 -4
- package/src/types/forms/index.ts +1 -0
- package/src/types/forms/layout-elements/index.ts +5 -0
- package/src/types/index.ts +1 -0
package/package.json
CHANGED
package/src/api/client.ts
CHANGED
|
@@ -208,10 +208,10 @@ export const auth = async (
|
|
|
208
208
|
limit: 1,
|
|
209
209
|
})
|
|
210
210
|
} catch {}
|
|
211
|
-
if (userRolesRes?.data?.totalRecords === 0 && !JSON.parse(loginAuthRes.data.roles).includes('Admin')) {
|
|
212
|
-
cookieHandler.empty()
|
|
213
|
-
throw { status: 400, response: { status: 400, data: 'User is not found!' } }
|
|
214
|
-
}
|
|
211
|
+
if (userRolesRes?.data?.totalRecords === 0 && !JSON.parse(loginAuthRes.data.roles).includes('Admin')) {
|
|
212
|
+
cookieHandler.empty()
|
|
213
|
+
throw { status: 400, response: { status: 400, data: 'User is not found!' } }
|
|
214
|
+
}
|
|
215
215
|
const fetchedRoles = userRolesRes?.data?.data?.[0]?.Data_roles
|
|
216
216
|
if (Array.isArray(fetchedRoles))
|
|
217
217
|
userRoleIds = fetchedRoles.filter((role): role is string => typeof role === 'string' && role.length > 0)
|
|
@@ -4,7 +4,7 @@ import { ICompanyConfig_Public, ILayoutTemplateProps } from '../../types/compani
|
|
|
4
4
|
import { Link } from 'react-router-dom'
|
|
5
5
|
import { isEncodedURI } from '../../functions/forms'
|
|
6
6
|
import { lazy, Suspense, useCallback, useMemo, useState, useSyncExternalStore } from 'react'
|
|
7
|
-
import { Button, Drawer, Dropdown, Layout } from 'antd'
|
|
7
|
+
import { Button, Drawer, Dropdown, Grid, Layout } from 'antd'
|
|
8
8
|
import { UserAuth } from '../../api/user'
|
|
9
9
|
import CompanyLogoSection from '../common/company-logo'
|
|
10
10
|
import { MenuItemRenderer, useMenuRenderer } from './template-hooks'
|
|
@@ -35,7 +35,12 @@ export const TemplateLayout = ({
|
|
|
35
35
|
const navigationWidth = siteConfigs?.custom?.navigationWidth || 200
|
|
36
36
|
const horizontalPadding = siteConfigs?.custom?.horizontalPadding || 20
|
|
37
37
|
const verticalPadding = siteConfigs?.custom?.verticalPadding || 20
|
|
38
|
+
const headerHeight = siteConfigs?.Layout?.headerHeight || 60
|
|
39
|
+
const menuPaddingInline = siteConfigs?.Link?.paddingInline ?? 20
|
|
40
|
+
const menuPaddingBlock = siteConfigs?.Link?.paddingBlock ?? 8
|
|
38
41
|
const isCollapsed = navigationWidth < 200
|
|
42
|
+
const screens = Grid.useBreakpoint()
|
|
43
|
+
const isDesktop = !!screens.lg
|
|
39
44
|
const logoElement = useMemo(
|
|
40
45
|
() => <CompanyLogoSection logoUrl={config?.siteIdentity?.logoUrl} isPreview={isPreview} />,
|
|
41
46
|
[config?.siteIdentity?.logoUrl, isPreview],
|
|
@@ -55,9 +60,12 @@ export const TemplateLayout = ({
|
|
|
55
60
|
borderRadius: `${siteConfigs?.Link.borderRadius}px`,
|
|
56
61
|
backgroundColor: active ? siteConfigs?.Link.colors.activeBackground : siteConfigs?.Link.colors.background,
|
|
57
62
|
color: active ? siteConfigs?.Link.colors.activeText : siteConfigs?.Link.colors.text,
|
|
58
|
-
|
|
63
|
+
paddingTop: `${menuPaddingBlock}px`,
|
|
64
|
+
paddingRight: `${menuPaddingInline}px`,
|
|
65
|
+
paddingBottom: `${menuPaddingBlock}px`,
|
|
66
|
+
paddingLeft: `${level * 16 + menuPaddingInline}px`,
|
|
59
67
|
}}
|
|
60
|
-
className={`fc-navigation-menu-item flex items-center gap-x-3
|
|
68
|
+
className={`fc-navigation-menu-item flex items-center gap-x-3 leading-4 transition-all duration-200 ${
|
|
61
69
|
active ? 'bg-opacity-25 text-opacity-25 font-semibold' : ''
|
|
62
70
|
}`}
|
|
63
71
|
onMouseEnter={(e) => {
|
|
@@ -88,14 +96,14 @@ export const TemplateLayout = ({
|
|
|
88
96
|
</div>
|
|
89
97
|
)
|
|
90
98
|
},
|
|
91
|
-
[navigationWidth, siteConfigs],
|
|
99
|
+
[menuPaddingBlock, menuPaddingInline, navigationWidth, siteConfigs],
|
|
92
100
|
)
|
|
93
101
|
|
|
94
102
|
const dropdownMenuRenderer = useCallback<MenuItemRenderer>(
|
|
95
103
|
({ form, meta, level, renderChildren, onNavigate }) => {
|
|
96
104
|
const active = isActive(window.location.pathname, meta.href)
|
|
97
105
|
return (
|
|
98
|
-
<div key={form.id} className="
|
|
106
|
+
<div key={form.id} className="group fc-navigation-menu-item">
|
|
99
107
|
<Link
|
|
100
108
|
to={meta.href}
|
|
101
109
|
target={meta.target}
|
|
@@ -104,7 +112,10 @@ export const TemplateLayout = ({
|
|
|
104
112
|
borderRadius: `${siteConfigs?.Link.borderRadius}px`,
|
|
105
113
|
backgroundColor: active ? siteConfigs?.Link.colors.activeBackground : siteConfigs?.Link.colors.background,
|
|
106
114
|
color: active ? siteConfigs?.Link.colors.activeText : siteConfigs?.Link.colors.text,
|
|
107
|
-
|
|
115
|
+
paddingTop: `${menuPaddingBlock}px`,
|
|
116
|
+
paddingRight: `${menuPaddingInline}px`,
|
|
117
|
+
paddingBottom: `${menuPaddingBlock}px`,
|
|
118
|
+
paddingLeft: `${level * 16 + menuPaddingInline}px`,
|
|
108
119
|
}}
|
|
109
120
|
onMouseEnter={(e) => {
|
|
110
121
|
e.currentTarget.style.backgroundColor = siteConfigs?.Link.colors.hoverBackground || ''
|
|
@@ -118,7 +129,7 @@ export const TemplateLayout = ({
|
|
|
118
129
|
? siteConfigs?.Link.colors.activeText || ''
|
|
119
130
|
: siteConfigs?.Link.colors.text || ''
|
|
120
131
|
}}
|
|
121
|
-
className={`flex items-center gap-x-3
|
|
132
|
+
className={`flex items-center gap-x-3 text-md leading-4 transition-all duration-200 bg-white text-primary hover:bg-opacity-75 hover:text-opacity-25 ${
|
|
122
133
|
active ? 'fc-navigation-menu-item-active font-semibold' : ''
|
|
123
134
|
}`}
|
|
124
135
|
>
|
|
@@ -127,7 +138,7 @@ export const TemplateLayout = ({
|
|
|
127
138
|
<meta.IconComponent className="w-4 h-4" />
|
|
128
139
|
</div>
|
|
129
140
|
)}
|
|
130
|
-
<span
|
|
141
|
+
<span>{form.displayName || form.name}</span>
|
|
131
142
|
</Link>
|
|
132
143
|
{meta.isParentMenu && (
|
|
133
144
|
<div className="absolute left-0 top-full hidden group-hover:flex flex-col bg-white shadow-md rounded-md z-50 min-w-[180px]">
|
|
@@ -137,7 +148,7 @@ export const TemplateLayout = ({
|
|
|
137
148
|
</div>
|
|
138
149
|
)
|
|
139
150
|
},
|
|
140
|
-
[siteConfigs],
|
|
151
|
+
[menuPaddingBlock, menuPaddingInline, siteConfigs],
|
|
141
152
|
)
|
|
142
153
|
|
|
143
154
|
const compactMenuRenderer = useCallback<MenuItemRenderer>(
|
|
@@ -154,9 +165,12 @@ export const TemplateLayout = ({
|
|
|
154
165
|
borderRadius: `${siteConfigs?.Link.borderRadius}px`,
|
|
155
166
|
backgroundColor: active ? siteConfigs?.Link.colors.activeBackground : siteConfigs?.Link.colors.background,
|
|
156
167
|
color: active ? siteConfigs?.Link.colors.activeText : siteConfigs?.Link.colors.text,
|
|
157
|
-
|
|
168
|
+
paddingTop: `${menuPaddingBlock}px`,
|
|
169
|
+
paddingRight: `${menuPaddingInline}px`,
|
|
170
|
+
paddingBottom: `${menuPaddingBlock}px`,
|
|
171
|
+
paddingLeft: `${level * 16 + menuPaddingInline}px`,
|
|
158
172
|
}}
|
|
159
|
-
className={`fc-navigation-menu-item flex items-center gap-x-3
|
|
173
|
+
className={`fc-navigation-menu-item flex items-center gap-x-3 leading-4 transition-all duration-200 ${
|
|
160
174
|
active ? 'bg-opacity-25 text-opacity-25 font-semibold' : ''
|
|
161
175
|
}`}
|
|
162
176
|
onMouseEnter={(e) => {
|
|
@@ -187,7 +201,7 @@ export const TemplateLayout = ({
|
|
|
187
201
|
</div>
|
|
188
202
|
)
|
|
189
203
|
},
|
|
190
|
-
[navigationWidth, siteConfigs],
|
|
204
|
+
[menuPaddingBlock, menuPaddingInline, navigationWidth, siteConfigs],
|
|
191
205
|
)
|
|
192
206
|
|
|
193
207
|
const rendererMap = useMemo(
|
|
@@ -233,12 +247,13 @@ export const TemplateLayout = ({
|
|
|
233
247
|
</Drawer>
|
|
234
248
|
)
|
|
235
249
|
|
|
236
|
-
const mobileMenuButton =
|
|
237
|
-
|
|
238
|
-
<
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
250
|
+
const mobileMenuButton =
|
|
251
|
+
!isPreview && !isDesktop ? (
|
|
252
|
+
<button type="button" onClick={() => setSidebarOpen(true)} className="-m-2.5 block p-2.5 text-gray-700">
|
|
253
|
+
<span className="sr-only">Open sidebar</span>
|
|
254
|
+
<HiMenu aria-hidden="true" className="size-6" />
|
|
255
|
+
</button>
|
|
256
|
+
) : null
|
|
242
257
|
|
|
243
258
|
const renderProfileDropdown = (
|
|
244
259
|
trigger: Array<'hover' | 'click'>,
|
|
@@ -324,7 +339,14 @@ export const TemplateLayout = ({
|
|
|
324
339
|
<Sider width={navigationWidth} className={`${isPreview ? 'flex' : 'hidden lg:flex'} fc-navigation-bar`}>
|
|
325
340
|
<div className="flex flex-col px-1 gap-2 py-2 fc-navigation-menu">{renderMenuList()}</div>
|
|
326
341
|
</Sider>
|
|
327
|
-
<Content
|
|
342
|
+
<Content
|
|
343
|
+
style={{
|
|
344
|
+
padding: `${verticalPadding}px ${horizontalPadding}px`,
|
|
345
|
+
minHeight: `calc(100vh - ${headerHeight}px)`,
|
|
346
|
+
backgroundColor: siteConfigs?.Layout.colors.bodyBg,
|
|
347
|
+
}}
|
|
348
|
+
className="fc-content"
|
|
349
|
+
>
|
|
328
350
|
{children}
|
|
329
351
|
</Content>
|
|
330
352
|
</Layout>
|
|
@@ -347,19 +369,29 @@ export const TemplateLayout = ({
|
|
|
347
369
|
|
|
348
370
|
return (
|
|
349
371
|
<Layout className="fc-layout min-h-screen">
|
|
350
|
-
<Header
|
|
372
|
+
<Header
|
|
373
|
+
className="fc-header sticky top-0 z-40 flex items-center shadow-sm justify-between px-1 overflow-visible"
|
|
374
|
+
style={{ height: headerHeight, lineHeight: 'normal' }}
|
|
375
|
+
>
|
|
351
376
|
<div className="flex">
|
|
352
377
|
<a href="/">{logoElement}</a>
|
|
353
378
|
{mobileMenuButton}
|
|
354
379
|
</div>
|
|
355
380
|
<div
|
|
356
|
-
className={`fc-navigation-menu justify-center items-center ${isPreview ? 'flex' : 'hidden lg:flex'}
|
|
381
|
+
className={`fc-navigation-menu justify-center items-center gap-3 px-3 ${isPreview ? 'flex' : 'hidden lg:flex'}`}
|
|
357
382
|
>
|
|
358
383
|
{renderMenuList()}
|
|
359
384
|
</div>
|
|
360
385
|
<div className="fc-profile-menu">{renderProfileDropdown(['hover'], 'bottomRight')}</div>
|
|
361
386
|
</Header>
|
|
362
|
-
<Content
|
|
387
|
+
<Content
|
|
388
|
+
style={{
|
|
389
|
+
padding: `${verticalPadding}px ${horizontalPadding}px`,
|
|
390
|
+
minHeight: `calc(100vh - ${headerHeight}px)`,
|
|
391
|
+
backgroundColor: siteConfigs?.Layout.colors.bodyBg,
|
|
392
|
+
}}
|
|
393
|
+
className="fc-content"
|
|
394
|
+
>
|
|
363
395
|
{children}
|
|
364
396
|
</Content>
|
|
365
397
|
</Layout>
|
|
@@ -402,7 +434,7 @@ export const TemplateLayout = ({
|
|
|
402
434
|
{renderProfileDropdown(['click'], 'top', isCollapsed)}
|
|
403
435
|
</div>
|
|
404
436
|
</Sider>
|
|
405
|
-
<Layout style={{ paddingLeft: `${navigationWidth}px
|
|
437
|
+
<Layout style={{ paddingLeft: `${navigationWidth}px`, minHeight: '100vh', backgroundColor: siteConfigs?.Layout.colors.bodyBg }}>
|
|
406
438
|
<Header className="fc-header lg:hidden sticky top-0 z-40 flex items-center gap-x-4 px-2 shadow-sm">
|
|
407
439
|
<div className="flex p-2 fc-logo">
|
|
408
440
|
<a href="/">{logoElement}</a>
|
|
@@ -412,7 +444,14 @@ export const TemplateLayout = ({
|
|
|
412
444
|
{renderProfileDropdown(['click'], 'bottomRight', isCollapsed, '-m-1.5 flex items-center p-1.5')}
|
|
413
445
|
</div>
|
|
414
446
|
</Header>
|
|
415
|
-
<Content
|
|
447
|
+
<Content
|
|
448
|
+
style={{
|
|
449
|
+
padding: `${verticalPadding}px ${horizontalPadding}px`,
|
|
450
|
+
minHeight: isDesktop ? '100vh' : `calc(100vh - ${headerHeight}px)`,
|
|
451
|
+
backgroundColor: siteConfigs?.Layout.colors.bodyBg,
|
|
452
|
+
}}
|
|
453
|
+
className="fc-content"
|
|
454
|
+
>
|
|
416
455
|
{children}
|
|
417
456
|
</Content>
|
|
418
457
|
</Layout>
|
|
@@ -30,17 +30,17 @@ export function UnauthenticatedLayout() {
|
|
|
30
30
|
</div>
|
|
31
31
|
)
|
|
32
32
|
|
|
33
|
+
const loginBackground =
|
|
34
|
+
loginLayout?.backgroundType === 'color'
|
|
35
|
+
? loginLayout?.background
|
|
36
|
+
: loginLayout?.backgroundType === 'image'
|
|
37
|
+
? `url(${loginLayout?.backgroundImage}) center/cover no-repeat`
|
|
38
|
+
: loginLayout?.background || '#f0f0f0'
|
|
39
|
+
|
|
33
40
|
const imageSection = (
|
|
34
41
|
<div
|
|
35
|
-
style={{
|
|
36
|
-
|
|
37
|
-
loginLayout?.backgroundType === 'color'
|
|
38
|
-
? loginLayout?.background
|
|
39
|
-
: loginLayout?.backgroundType === 'image'
|
|
40
|
-
? `url(${loginLayout?.backgroundImage}) center/cover no-repeat`
|
|
41
|
-
: '#f0f0f0',
|
|
42
|
-
}}
|
|
43
|
-
className="w-1/2 h-full flex items-center justify-center align-middle text-center text-gray-400 text-sm bg-gray-100"
|
|
42
|
+
style={{ background: loginBackground }}
|
|
43
|
+
className="w-1/2 h-full flex items-center justify-center align-middle text-center text-gray-400 text-sm"
|
|
44
44
|
></div>
|
|
45
45
|
)
|
|
46
46
|
|
|
@@ -61,6 +61,8 @@ export function UnauthenticatedLayout() {
|
|
|
61
61
|
minWidth: '400px',
|
|
62
62
|
padding: `${loginLayout?.form?.padding || 16}px`,
|
|
63
63
|
background: loginLayout?.form?.background,
|
|
64
|
+
borderRadius:
|
|
65
|
+
loginLayout?.formStyle === 'full' ? undefined : `${loginLayout?.form?.borderRadius ?? 12}px`,
|
|
64
66
|
}}
|
|
65
67
|
className={`fc-login-panel max-h-screen overflow-auto shadow content-center text-center space-y-4 ${
|
|
66
68
|
loginLayout?.formStyle === 'full' ? 'h-screen' : ''
|
|
@@ -102,6 +104,7 @@ export function UnauthenticatedLayout() {
|
|
|
102
104
|
return (
|
|
103
105
|
<Form.Item
|
|
104
106
|
style={spacingStyle}
|
|
107
|
+
labelCol={{ style: { paddingBottom: loginLayout?.form?.labelMarginBottom ?? 4 } }}
|
|
105
108
|
label={field.charAt(0).toUpperCase() + field.slice(1)}
|
|
106
109
|
name={field}
|
|
107
110
|
key={field}
|
|
@@ -114,6 +117,7 @@ export function UnauthenticatedLayout() {
|
|
|
114
117
|
return (
|
|
115
118
|
<Form.Item
|
|
116
119
|
style={spacingStyle}
|
|
120
|
+
labelCol={{ style: { paddingBottom: loginLayout?.form?.labelMarginBottom ?? 4 } }}
|
|
117
121
|
label="Password"
|
|
118
122
|
name="password"
|
|
119
123
|
key="password"
|
|
@@ -167,10 +171,10 @@ export function UnauthenticatedLayout() {
|
|
|
167
171
|
return (
|
|
168
172
|
<Spin spinning={loading.login}>
|
|
169
173
|
<div
|
|
170
|
-
className={`h-screen w-
|
|
174
|
+
className={`min-h-screen w-screen flex overflow-hidden ${
|
|
171
175
|
loginLayout?.layout === 'center' ? 'flex-col items-center justify-center' : 'flex-row'
|
|
172
176
|
}`}
|
|
173
|
-
style={{ background:
|
|
177
|
+
style={{ background: loginBackground, minHeight: '100vh' }}
|
|
174
178
|
>
|
|
175
179
|
{loginLayout?.layout === 'center' && formSection}
|
|
176
180
|
{loginLayout?.layout === 'split-left' && (
|
|
@@ -19,39 +19,41 @@ export function ConfigProviderLayout({ children }: { children: ReactNode }): JSX
|
|
|
19
19
|
location.pathname.startsWith('/login')
|
|
20
20
|
|
|
21
21
|
return (
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
22
|
+
<div className="relative">
|
|
23
|
+
{showSwitchCompany && (
|
|
24
|
+
<div style={{ position: 'absolute', left: 10, top: 10, zIndex: 20, background: 'white', borderRadius: 8 }}>
|
|
25
|
+
<Button_FillerPortal
|
|
26
|
+
outline
|
|
27
|
+
onClick={() => {
|
|
28
|
+
localStorage.removeItem(LOCAL_STORAGE_KEYS_ENUM.Domain)
|
|
29
|
+
navigate(0)
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
Switch Project
|
|
33
|
+
</Button_FillerPortal>
|
|
34
|
+
</div>
|
|
35
|
+
)}
|
|
36
|
+
<ConfigContext.Provider value={{ config }}>
|
|
37
|
+
<ConfigProvider
|
|
38
|
+
theme={{
|
|
39
|
+
...theme,
|
|
40
|
+
token: {
|
|
41
|
+
...theme.token,
|
|
42
|
+
screenXSMin: 320,
|
|
43
|
+
screenXS: 375,
|
|
44
|
+
screenXSMax: 425,
|
|
45
|
+
screenSMMax: 768,
|
|
46
|
+
screenMDMax: 1024,
|
|
47
|
+
screenLGMin: 1025,
|
|
48
|
+
screenLG: 1100,
|
|
49
|
+
screenLGMax: 1200,
|
|
50
|
+
screenXLMax: 1440,
|
|
51
|
+
},
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
{children}
|
|
55
|
+
</ConfigProvider>
|
|
56
|
+
</ConfigContext.Provider>
|
|
57
|
+
</div>
|
|
56
58
|
)
|
|
57
59
|
}
|
|
@@ -9,6 +9,7 @@ import { IDynamicButton_DisplayStateProps } from '../3-element/1-dynamic-button'
|
|
|
9
9
|
import { ELEMENTS_DEFAULT_CLASS } from '../../../../constants'
|
|
10
10
|
import { useHiddenIds } from '../../../common/custom-hooks/use-node-condition.hook/use-node-condition.hook'
|
|
11
11
|
import { ICustomComponents } from '../3-element'
|
|
12
|
+
import { useConfigContext } from '../../../companies/context'
|
|
12
13
|
|
|
13
14
|
export const LayoutRendererRow = memo(
|
|
14
15
|
({
|
|
@@ -21,9 +22,26 @@ export const LayoutRendererRow = memo(
|
|
|
21
22
|
renderButton,
|
|
22
23
|
}: ILayoutRendererRow) => {
|
|
23
24
|
const hiddenIds = useHiddenIds()
|
|
25
|
+
const { config } = useConfigContext()
|
|
26
|
+
|
|
27
|
+
const rowStyle = useMemo(
|
|
28
|
+
() =>
|
|
29
|
+
rowData.styleTemplateId
|
|
30
|
+
? (config?.styleTemplates?.find((template) => template.id === rowData.styleTemplateId)?.style ?? rowData.style)
|
|
31
|
+
: rowData.style,
|
|
32
|
+
[config?.styleTemplates, rowData.style, rowData.styleTemplateId],
|
|
33
|
+
)
|
|
34
|
+
const headerStyle = useMemo(
|
|
35
|
+
() =>
|
|
36
|
+
rowData.props?.header?.styleTemplateId
|
|
37
|
+
? (config?.styleTemplates?.find((template) => template.id === rowData.props?.header?.styleTemplateId)?.style ??
|
|
38
|
+
rowData.props?.header?.style)
|
|
39
|
+
: rowData.props?.header?.style,
|
|
40
|
+
[config?.styleTemplates, rowData.props?.header?.style, rowData.props?.header?.styleTemplateId],
|
|
41
|
+
)
|
|
24
42
|
|
|
25
43
|
const gridStyle = useMemo(() => {
|
|
26
|
-
const baseStyle = { ...(
|
|
44
|
+
const baseStyle = { ...(rowStyle || {}), ...getGridContainerStyle(rowData.display) }
|
|
27
45
|
let template = ''
|
|
28
46
|
|
|
29
47
|
if (typeof baseStyle.gridTemplateColumns === 'string') {
|
|
@@ -41,7 +59,7 @@ export const LayoutRendererRow = memo(
|
|
|
41
59
|
}
|
|
42
60
|
|
|
43
61
|
return { ...baseStyle, gridTemplateColumns: template }
|
|
44
|
-
}, [rowData, hiddenIds])
|
|
62
|
+
}, [rowData, rowStyle, hiddenIds])
|
|
45
63
|
|
|
46
64
|
return (
|
|
47
65
|
<div
|
|
@@ -52,7 +70,7 @@ export const LayoutRendererRow = memo(
|
|
|
52
70
|
<LayoutRowConditionalHeaderRenderer
|
|
53
71
|
formId={formContext.formId}
|
|
54
72
|
rowId={rowData.id}
|
|
55
|
-
header={rowData.props?.header}
|
|
73
|
+
header={rowData.props?.header ? { ...rowData.props.header, style: headerStyle } : undefined}
|
|
56
74
|
>
|
|
57
75
|
<LayoutRowRepeatableRenderer
|
|
58
76
|
formId={formContext.formId}
|
package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-send-notification.hook.ts
CHANGED
|
@@ -64,10 +64,16 @@ export const useSendNotificationAction = ({
|
|
|
64
64
|
try {
|
|
65
65
|
if (!formId || !resolvedFormDataId) return []
|
|
66
66
|
|
|
67
|
-
const
|
|
67
|
+
const templateReportsForForm = formTemplateReports?.[formId]
|
|
68
|
+
if (!templateReportsForForm || !Array.isArray(templateReportsForForm.templates)) {
|
|
69
|
+
console.warn(
|
|
70
|
+
`Notification attachment generation skipped: templateReportConfig is missing for form ${formId}.`,
|
|
71
|
+
)
|
|
72
|
+
return []
|
|
73
|
+
}
|
|
68
74
|
|
|
69
75
|
const joinedFormData = await client
|
|
70
|
-
.post(`/api/report/${formId}/${resolvedFormDataId}`, { joins:
|
|
76
|
+
.post(`/api/report/${formId}/${resolvedFormDataId}`, { joins: templateReportsForForm.joins || [] })
|
|
71
77
|
.then((res) => (res.status === 200 ? res.data : {}))
|
|
72
78
|
.catch(() => ({}))
|
|
73
79
|
|
|
@@ -97,10 +103,15 @@ export const useSendNotificationAction = ({
|
|
|
97
103
|
for (const attachment of attachments) {
|
|
98
104
|
const resolvedBlobName = resolveAttachmentBlobName(attachment)
|
|
99
105
|
if (!resolvedBlobName) continue
|
|
100
|
-
const selectedTemplateInfo =
|
|
106
|
+
const selectedTemplateInfo = templateReportsForForm.templates.find(
|
|
101
107
|
(tR: IFormTemplateReport) => tR.fileBlobName === resolvedBlobName,
|
|
102
108
|
)
|
|
103
|
-
if (!selectedTemplateInfo)
|
|
109
|
+
if (!selectedTemplateInfo) {
|
|
110
|
+
console.warn(
|
|
111
|
+
`Notification attachment generation skipped: template report "${resolvedBlobName}" was not found in form ${formId} templateReportConfig.`,
|
|
112
|
+
)
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
104
115
|
|
|
105
116
|
const generatedAttachment = await replaceTemplateReportAndUpload({
|
|
106
117
|
templateInfo: selectedTemplateInfo,
|
|
@@ -1,132 +1,132 @@
|
|
|
1
|
-
import { IBreadcrumb, useBreadcrumb } from '../../../../common/custom-hooks/use-breadcrumb.hook'
|
|
2
|
-
import { Button_FillerPortal } from '../../../../common/button'
|
|
3
|
-
import { useNavigate } from 'react-router-dom'
|
|
4
|
-
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa6'
|
|
5
|
-
import React, { memo, useMemo, useSyncExternalStore } from 'react'
|
|
6
|
-
import { ELEMENTS_DEFAULT_CLASS } from '../../../../../constants'
|
|
7
|
-
import { IElementBaseProps } from '../'
|
|
8
|
-
import useGetCurrentBreakpoint from '../../../../common/custom-hooks/use-window-width.hook'
|
|
9
|
-
import { Select } from 'antd'
|
|
10
|
-
import { CountryEnum, DeviceBreakpointEnum, PageViewTypEnum } from '../../../../../enums'
|
|
11
|
-
import { useConfigContext } from '../../../../companies/context'
|
|
12
|
-
import { buildBreadcrumbTranslationKey } from '../../../../../functions'
|
|
13
|
-
import { translationStore } from '../../../../common/custom-hooks/use-translation.hook/store'
|
|
14
|
-
|
|
15
|
-
import './index.scss'
|
|
16
|
-
|
|
17
|
-
function LayoutRenderer_Breadcrumb(_: { formId?: number } & IElementBaseProps) {
|
|
18
|
-
const currentBreakpoint = useGetCurrentBreakpoint()
|
|
19
|
-
const navigate = useNavigate()
|
|
20
|
-
const { breadcrumbs, sliceCrumbAt } = useBreadcrumb()
|
|
21
|
-
const { config } = useConfigContext()
|
|
22
|
-
const { selectedLanguage } = useSyncExternalStore(
|
|
23
|
-
translationStore.subscribe.bind(translationStore),
|
|
24
|
-
translationStore.getSnapshot.bind(translationStore),
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
const translations = config?.translations
|
|
28
|
-
const fallbackTexts = { Detail: 'detail', List: 'list', New: 'New' }
|
|
29
|
-
const getTranslation = useMemo(
|
|
30
|
-
() => (key: string) => (selectedLanguage ? translations?.[key]?.[selectedLanguage as CountryEnum] ?? '' : ''),
|
|
31
|
-
[selectedLanguage, translations],
|
|
32
|
-
)
|
|
33
|
-
const getFormName = useMemo(
|
|
34
|
-
() =>
|
|
35
|
-
(bc: IBreadcrumb, field: 'Detail' | 'List' | 'New') =>
|
|
36
|
-
bc.formId
|
|
37
|
-
? getTranslation(buildBreadcrumbTranslationKey(bc.formId, field)) || bc.formName?.split(' ')?.[0] || bc.label
|
|
38
|
-
: bc.label,
|
|
39
|
-
[getTranslation],
|
|
40
|
-
)
|
|
41
|
-
const getBreadcrumbText = useMemo(
|
|
42
|
-
() =>
|
|
43
|
-
(field: 'Detail' | 'List' | 'New') => {
|
|
44
|
-
const defaultText = getTranslation(buildBreadcrumbTranslationKey('Default', field))
|
|
45
|
-
return defaultText || fallbackTexts[field]
|
|
46
|
-
},
|
|
47
|
-
[getTranslation],
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
const showAsSelect = useMemo(
|
|
51
|
-
() =>
|
|
52
|
-
([DeviceBreakpointEnum.Mobile, DeviceBreakpointEnum.TabletPortrait].includes(currentBreakpoint) &&
|
|
53
|
-
breadcrumbs.length > 1) ||
|
|
54
|
-
breadcrumbs.length > 3,
|
|
55
|
-
[currentBreakpoint, breadcrumbs],
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
if (showAsSelect)
|
|
59
|
-
return (
|
|
60
|
-
<Select
|
|
61
|
-
suffixIcon={null}
|
|
62
|
-
value={breadcrumbs[breadcrumbs.length - 1].href}
|
|
63
|
-
optionFilterProp="label"
|
|
64
|
-
allowClear={false}
|
|
65
|
-
className={`${ELEMENTS_DEFAULT_CLASS.Breadcrumb} responsive max-w-[250px]`}
|
|
66
|
-
onChange={(bc) => {
|
|
67
|
-
const decodedUri = decodeURIComponent(bc)
|
|
68
|
-
const bcIdx = breadcrumbs.findIndex((bc) => bc.href === decodedUri)
|
|
69
|
-
|
|
70
|
-
sliceCrumbAt(bcIdx)
|
|
71
|
-
navigate(decodedUri)
|
|
72
|
-
}}
|
|
73
|
-
options={breadcrumbs.map((bc) => ({
|
|
74
|
-
value: bc.href,
|
|
75
|
-
label: (
|
|
76
|
-
<div className="flex gap-2 items-center">
|
|
77
|
-
<FaChevronLeft className="text-primary" size={10} />
|
|
78
|
-
{renderText(bc, getFormName, getBreadcrumbText)}
|
|
79
|
-
</div>
|
|
80
|
-
),
|
|
81
|
-
}))}
|
|
82
|
-
/>
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
return (
|
|
86
|
-
<div className={`${ELEMENTS_DEFAULT_CLASS.Breadcrumb} flex items-center text-12`}>
|
|
87
|
-
{breadcrumbs.map((bc, bcIdx) => (
|
|
88
|
-
<React.Fragment key={bcIdx}>
|
|
89
|
-
<Button_FillerPortal
|
|
90
|
-
link
|
|
91
|
-
disabled={breadcrumbs.length - 1 === bcIdx}
|
|
92
|
-
onClick={() => {
|
|
93
|
-
sliceCrumbAt(bcIdx)
|
|
94
|
-
navigate(bc.href)
|
|
95
|
-
}}
|
|
96
|
-
>
|
|
97
|
-
{renderText(bc, getFormName, getBreadcrumbText)}
|
|
98
|
-
</Button_FillerPortal>
|
|
99
|
-
{breadcrumbs.length - 1 !== bcIdx && <FaChevronRight className="text-primary" />}
|
|
100
|
-
</React.Fragment>
|
|
101
|
-
))}
|
|
102
|
-
</div>
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export default memo(LayoutRenderer_Breadcrumb)
|
|
107
|
-
|
|
108
|
-
const renderText = (
|
|
109
|
-
bc: IBreadcrumb,
|
|
110
|
-
getFormName: (bc: IBreadcrumb, field: 'Detail' | 'List' | 'New') => string,
|
|
111
|
-
getBreadcrumbText: (field: 'Detail' | 'List' | 'New') => string,
|
|
112
|
-
) => {
|
|
113
|
-
const label =
|
|
114
|
-
bc.type === PageViewTypEnum.Details
|
|
115
|
-
? getFormName(bc, 'Detail')
|
|
116
|
-
: bc.type === PageViewTypEnum.List
|
|
117
|
-
? getFormName(bc, 'List')
|
|
118
|
-
: bc.type === PageViewTypEnum.New
|
|
119
|
-
? getFormName(bc, 'New')
|
|
120
|
-
: bc.label
|
|
121
|
-
const detailText = getBreadcrumbText('Detail')
|
|
122
|
-
const listText = getBreadcrumbText('List')
|
|
123
|
-
const newText = getBreadcrumbText('New')
|
|
124
|
-
|
|
125
|
-
return (
|
|
126
|
-
<span className="font-normal italic">
|
|
127
|
-
{bc.type === PageViewTypEnum.New ? `${newText} ` : ''}
|
|
128
|
-
{label}
|
|
129
|
-
{bc.type === PageViewTypEnum.Details ? ` ${detailText}` : bc.type === PageViewTypEnum.List ? ` ${listText}` : ''}
|
|
130
|
-
</span>
|
|
131
|
-
)
|
|
1
|
+
import { IBreadcrumb, useBreadcrumb } from '../../../../common/custom-hooks/use-breadcrumb.hook'
|
|
2
|
+
import { Button_FillerPortal } from '../../../../common/button'
|
|
3
|
+
import { useNavigate } from 'react-router-dom'
|
|
4
|
+
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa6'
|
|
5
|
+
import React, { memo, useMemo, useSyncExternalStore } from 'react'
|
|
6
|
+
import { ELEMENTS_DEFAULT_CLASS } from '../../../../../constants'
|
|
7
|
+
import { IElementBaseProps } from '../'
|
|
8
|
+
import useGetCurrentBreakpoint from '../../../../common/custom-hooks/use-window-width.hook'
|
|
9
|
+
import { Select } from 'antd'
|
|
10
|
+
import { CountryEnum, DeviceBreakpointEnum, PageViewTypEnum } from '../../../../../enums'
|
|
11
|
+
import { useConfigContext } from '../../../../companies/context'
|
|
12
|
+
import { buildBreadcrumbTranslationKey } from '../../../../../functions'
|
|
13
|
+
import { translationStore } from '../../../../common/custom-hooks/use-translation.hook/store'
|
|
14
|
+
|
|
15
|
+
import './index.scss'
|
|
16
|
+
|
|
17
|
+
function LayoutRenderer_Breadcrumb(_: { formId?: number } & IElementBaseProps) {
|
|
18
|
+
const currentBreakpoint = useGetCurrentBreakpoint()
|
|
19
|
+
const navigate = useNavigate()
|
|
20
|
+
const { breadcrumbs, sliceCrumbAt } = useBreadcrumb()
|
|
21
|
+
const { config } = useConfigContext()
|
|
22
|
+
const { selectedLanguage } = useSyncExternalStore(
|
|
23
|
+
translationStore.subscribe.bind(translationStore),
|
|
24
|
+
translationStore.getSnapshot.bind(translationStore),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
const translations = config?.translations
|
|
28
|
+
const fallbackTexts = { Detail: 'detail', List: 'list', New: 'New' }
|
|
29
|
+
const getTranslation = useMemo(
|
|
30
|
+
() => (key: string) => (selectedLanguage ? translations?.[key]?.[selectedLanguage as CountryEnum] ?? '' : ''),
|
|
31
|
+
[selectedLanguage, translations],
|
|
32
|
+
)
|
|
33
|
+
const getFormName = useMemo(
|
|
34
|
+
() =>
|
|
35
|
+
(bc: IBreadcrumb, field: 'Detail' | 'List' | 'New') =>
|
|
36
|
+
bc.formId
|
|
37
|
+
? getTranslation(buildBreadcrumbTranslationKey(bc.formId, field)) || bc.formName?.split(' ')?.[0] || bc.label
|
|
38
|
+
: bc.label,
|
|
39
|
+
[getTranslation],
|
|
40
|
+
)
|
|
41
|
+
const getBreadcrumbText = useMemo(
|
|
42
|
+
() =>
|
|
43
|
+
(field: 'Detail' | 'List' | 'New') => {
|
|
44
|
+
const defaultText = getTranslation(buildBreadcrumbTranslationKey('Default', field))
|
|
45
|
+
return defaultText || fallbackTexts[field]
|
|
46
|
+
},
|
|
47
|
+
[getTranslation],
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
const showAsSelect = useMemo(
|
|
51
|
+
() =>
|
|
52
|
+
([DeviceBreakpointEnum.Mobile, DeviceBreakpointEnum.TabletPortrait].includes(currentBreakpoint) &&
|
|
53
|
+
breadcrumbs.length > 1) ||
|
|
54
|
+
breadcrumbs.length > 3,
|
|
55
|
+
[currentBreakpoint, breadcrumbs],
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if (showAsSelect)
|
|
59
|
+
return (
|
|
60
|
+
<Select
|
|
61
|
+
suffixIcon={null}
|
|
62
|
+
value={breadcrumbs[breadcrumbs.length - 1].href}
|
|
63
|
+
optionFilterProp="label"
|
|
64
|
+
allowClear={false}
|
|
65
|
+
className={`${ELEMENTS_DEFAULT_CLASS.Breadcrumb} responsive max-w-[250px]`}
|
|
66
|
+
onChange={(bc) => {
|
|
67
|
+
const decodedUri = decodeURIComponent(bc)
|
|
68
|
+
const bcIdx = breadcrumbs.findIndex((bc) => bc.href === decodedUri)
|
|
69
|
+
|
|
70
|
+
sliceCrumbAt(bcIdx)
|
|
71
|
+
navigate(decodedUri)
|
|
72
|
+
}}
|
|
73
|
+
options={breadcrumbs.map((bc) => ({
|
|
74
|
+
value: bc.href,
|
|
75
|
+
label: (
|
|
76
|
+
<div className="flex gap-2 items-center">
|
|
77
|
+
<FaChevronLeft className="text-primary" size={10} />
|
|
78
|
+
{renderText(bc, getFormName, getBreadcrumbText)}
|
|
79
|
+
</div>
|
|
80
|
+
),
|
|
81
|
+
}))}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className={`${ELEMENTS_DEFAULT_CLASS.Breadcrumb} flex items-center text-12`}>
|
|
87
|
+
{breadcrumbs.map((bc, bcIdx) => (
|
|
88
|
+
<React.Fragment key={bcIdx}>
|
|
89
|
+
<Button_FillerPortal
|
|
90
|
+
link
|
|
91
|
+
disabled={breadcrumbs.length - 1 === bcIdx}
|
|
92
|
+
onClick={() => {
|
|
93
|
+
sliceCrumbAt(bcIdx)
|
|
94
|
+
navigate(bc.href)
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
{renderText(bc, getFormName, getBreadcrumbText)}
|
|
98
|
+
</Button_FillerPortal>
|
|
99
|
+
{breadcrumbs.length - 1 !== bcIdx && <FaChevronRight className="text-primary" />}
|
|
100
|
+
</React.Fragment>
|
|
101
|
+
))}
|
|
102
|
+
</div>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export default memo(LayoutRenderer_Breadcrumb)
|
|
107
|
+
|
|
108
|
+
const renderText = (
|
|
109
|
+
bc: IBreadcrumb,
|
|
110
|
+
getFormName: (bc: IBreadcrumb, field: 'Detail' | 'List' | 'New') => string,
|
|
111
|
+
getBreadcrumbText: (field: 'Detail' | 'List' | 'New') => string,
|
|
112
|
+
) => {
|
|
113
|
+
const label =
|
|
114
|
+
bc.type === PageViewTypEnum.Details
|
|
115
|
+
? getFormName(bc, 'Detail')
|
|
116
|
+
: bc.type === PageViewTypEnum.List
|
|
117
|
+
? getFormName(bc, 'List')
|
|
118
|
+
: bc.type === PageViewTypEnum.New
|
|
119
|
+
? getFormName(bc, 'New')
|
|
120
|
+
: bc.label
|
|
121
|
+
const detailText = getBreadcrumbText('Detail')
|
|
122
|
+
const listText = getBreadcrumbText('List')
|
|
123
|
+
const newText = getBreadcrumbText('New')
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<span className="font-normal italic">
|
|
127
|
+
{bc.type === PageViewTypEnum.New ? `${newText} ` : ''}
|
|
128
|
+
{label}
|
|
129
|
+
{bc.type === PageViewTypEnum.Details ? ` ${detailText}` : bc.type === PageViewTypEnum.List ? ` ${listText}` : ''}
|
|
130
|
+
</span>
|
|
131
|
+
)
|
|
132
132
|
}
|
|
@@ -4,7 +4,7 @@ import { saveFile } from '../../../../../functions/forms/form'
|
|
|
4
4
|
import { TranslationTextSubTypeEnum, TranslationTextTypeEnum } from '../../../../../enums'
|
|
5
5
|
import { RcFile, UploadFile, UploadProps } from 'antd/es/upload'
|
|
6
6
|
import { Form, Modal, Spin, Upload } from 'antd'
|
|
7
|
-
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
7
|
+
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
8
8
|
import { FaUpload } from 'react-icons/fa6'
|
|
9
9
|
import { Button_FillerPortal } from '../../../../common/button'
|
|
10
10
|
import { useNotification, useTranslation } from '../../../../common/custom-hooks'
|
|
@@ -77,19 +77,19 @@ export default function GalleryUploadModal({
|
|
|
77
77
|
[elementKey, label, placeholder, t],
|
|
78
78
|
)
|
|
79
79
|
|
|
80
|
-
const uploadedBlobNames = useMemo(
|
|
80
|
+
const uploadedBlobNames = useMemo(
|
|
81
81
|
() =>
|
|
82
82
|
uploadedFiles
|
|
83
83
|
.map((file) =>
|
|
84
84
|
typeof file.response === 'object' && file.response ? (file.response as { blobName?: string }).blobName : '',
|
|
85
85
|
)
|
|
86
86
|
.filter((blobName): blobName is string => !!blobName),
|
|
87
|
-
[uploadedFiles],
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
if (!isOpen) setUploadedFiles([])
|
|
92
|
-
}, [isOpen])
|
|
87
|
+
[uploadedFiles],
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (!isOpen) setUploadedFiles([])
|
|
92
|
+
}, [isOpen])
|
|
93
93
|
|
|
94
94
|
const uploadProps: UploadProps = {
|
|
95
95
|
multiple: true,
|
|
@@ -112,14 +112,14 @@ export default function GalleryUploadModal({
|
|
|
112
112
|
},
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
const discardUploadedFiles = useCallback(async () => {
|
|
116
|
-
setIsDiscarding(true)
|
|
117
|
-
try {
|
|
118
|
-
await Promise.all(uploadedBlobNames.map((blobName) => client.delete(`/api/attachment/${blobName}`)))
|
|
119
|
-
setUploadedFiles([])
|
|
120
|
-
onDiscardSuccess()
|
|
121
|
-
} finally {
|
|
122
|
-
setIsDiscarding(false)
|
|
115
|
+
const discardUploadedFiles = useCallback(async () => {
|
|
116
|
+
setIsDiscarding(true)
|
|
117
|
+
try {
|
|
118
|
+
await Promise.all(uploadedBlobNames.map((blobName) => client.delete(`/api/attachment/${blobName}`)))
|
|
119
|
+
setUploadedFiles([])
|
|
120
|
+
onDiscardSuccess()
|
|
121
|
+
} finally {
|
|
122
|
+
setIsDiscarding(false)
|
|
123
123
|
}
|
|
124
124
|
}, [onDiscardSuccess, uploadedBlobNames])
|
|
125
125
|
|
|
@@ -140,19 +140,19 @@ export default function GalleryUploadModal({
|
|
|
140
140
|
...(Array.isArray(existingBlobNames) ? existingBlobNames : []),
|
|
141
141
|
...uploadedBlobNames,
|
|
142
142
|
]
|
|
143
|
-
const res = await client.put(`/api/formdata/${formId}/${formDataId}`, {
|
|
144
|
-
name: formDataRes.data.name,
|
|
145
|
-
version: formDataRes.data.version,
|
|
146
|
-
private: formDataRes.data.private,
|
|
147
|
-
data: JSON.stringify(parsedData),
|
|
148
|
-
})
|
|
149
|
-
if (res.status === 200) {
|
|
150
|
-
setUploadedFiles([])
|
|
151
|
-
onSaveSuccess(uploadedBlobNames)
|
|
152
|
-
}
|
|
153
|
-
} finally {
|
|
154
|
-
setIsSaving(false)
|
|
155
|
-
}
|
|
143
|
+
const res = await client.put(`/api/formdata/${formId}/${formDataId}`, {
|
|
144
|
+
name: formDataRes.data.name,
|
|
145
|
+
version: formDataRes.data.version,
|
|
146
|
+
private: formDataRes.data.private,
|
|
147
|
+
data: JSON.stringify(parsedData),
|
|
148
|
+
})
|
|
149
|
+
if (res.status === 200) {
|
|
150
|
+
setUploadedFiles([])
|
|
151
|
+
onSaveSuccess(uploadedBlobNames)
|
|
152
|
+
}
|
|
153
|
+
} finally {
|
|
154
|
+
setIsSaving(false)
|
|
155
|
+
}
|
|
156
156
|
}, [formDataId, formId, formItem.path, onSaveSuccess, uploadedBlobNames])
|
|
157
157
|
|
|
158
158
|
return (
|
|
@@ -282,9 +282,9 @@ const fieldRenderers: Partial<Record<ElementTypeEnum, FieldRenderer>> = {
|
|
|
282
282
|
[ElementTypeEnum.Radio]: ({ options = [], disabled, isMultiValue, isCustom }) =>
|
|
283
283
|
isMultiValue ? (
|
|
284
284
|
<Checkbox.Group disabled={disabled} className={ELEMENTS_DEFAULT_CLASS.CheckboxGroup}>
|
|
285
|
-
<div className="flex items-center gap-1">
|
|
285
|
+
<div className="flex items-center gap-1 flex-wrap">
|
|
286
286
|
{options.map((o, i) => (
|
|
287
|
-
<Checkbox key={i} value={o.value}>
|
|
287
|
+
<Checkbox key={i} value={o.value} className='whitespace-nowrap'>
|
|
288
288
|
{o.label}
|
|
289
289
|
</Checkbox>
|
|
290
290
|
))}
|
|
@@ -139,6 +139,7 @@ function LayoutRendererElement({
|
|
|
139
139
|
|
|
140
140
|
const { t, selectedLanguage } = useTranslation(formContext.formId)
|
|
141
141
|
const { config } = useConfigContext()
|
|
142
|
+
const styleTemplates = config?.styleTemplates
|
|
142
143
|
const { maxValue, minValue } = findMaxAndMinValues(validations, {
|
|
143
144
|
fieldValues: (formContext.formRef?.getFieldsValue(true) ?? {}) as Record<string, any>,
|
|
144
145
|
formDataId: formContext.formDataId,
|
|
@@ -146,6 +147,8 @@ function LayoutRendererElement({
|
|
|
146
147
|
})
|
|
147
148
|
|
|
148
149
|
const isElementDisabled = useIsNodeDisabled(key)
|
|
150
|
+
const resolveStyleTemplate = (styleTemplateId?: string) =>
|
|
151
|
+
styleTemplateId ? styleTemplates?.find((template) => template.id === styleTemplateId)?.style : undefined
|
|
149
152
|
|
|
150
153
|
const validationsAfterIsHidden = useMemo(
|
|
151
154
|
() => (isHidden && validations ? validations.filter((v) => v.rule !== FieldValidationEnum.Required) : validations),
|
|
@@ -320,7 +323,8 @@ function LayoutRendererElement({
|
|
|
320
323
|
},
|
|
321
324
|
|
|
322
325
|
[ElementTypeEnum.ReadFieldData]: () => {
|
|
323
|
-
let style = (elementData as IReadFieldDataElement).
|
|
326
|
+
let style = resolveStyleTemplate((elementData as IReadFieldDataElement).styleTemplateId)
|
|
327
|
+
if (!style) style = (elementData as IReadFieldDataElement).style?.[currentBreakpoint]
|
|
324
328
|
if (!style) style = (elementData as IReadFieldDataElement).style?.[DeviceBreakpointEnum.Default]
|
|
325
329
|
|
|
326
330
|
return (
|
|
@@ -352,7 +356,8 @@ function LayoutRendererElement({
|
|
|
352
356
|
),
|
|
353
357
|
|
|
354
358
|
[ElementTypeEnum.Text]: () => {
|
|
355
|
-
let style = (elementData as ITextElement).
|
|
359
|
+
let style = resolveStyleTemplate((elementData as ITextElement).styleTemplateId)
|
|
360
|
+
if (!style) style = (elementData as ITextElement).style?.[currentBreakpoint]
|
|
356
361
|
if (!style) style = (elementData as ITextElement).style?.[DeviceBreakpointEnum.Default]
|
|
357
362
|
|
|
358
363
|
if (props.type === TextElementTypeEnum.Plain)
|
package/src/constants/index.ts
CHANGED
|
@@ -48,6 +48,9 @@ export const DEFAULT_SITE_CONFIG = {
|
|
|
48
48
|
},
|
|
49
49
|
Link: {
|
|
50
50
|
borderRadius: 8,
|
|
51
|
+
paddingInline: 20,
|
|
52
|
+
paddingBlock: 8,
|
|
53
|
+
activeBorderRadius: 8,
|
|
51
54
|
colors: {
|
|
52
55
|
background: '#ffffff',
|
|
53
56
|
text: '#000000',
|
|
@@ -55,6 +58,7 @@ export const DEFAULT_SITE_CONFIG = {
|
|
|
55
58
|
hoverText: '#245780',
|
|
56
59
|
activeBackground: '#245780',
|
|
57
60
|
activeText: '#f5f5f5',
|
|
61
|
+
activeBorderColor: '#1b4566',
|
|
58
62
|
},
|
|
59
63
|
},
|
|
60
64
|
Layout: {
|
|
@@ -95,12 +99,21 @@ export const DEFAULT_SITE_CONFIG = {
|
|
|
95
99
|
bodySortBg: '#fafafa',
|
|
96
100
|
colorBgContainer: '#ffffff',
|
|
97
101
|
rowHoverBg: '#fafafa',
|
|
102
|
+
rowSelectedBg: '#e6f4ff',
|
|
103
|
+
rowSelectedHoverBg: '#d9efff',
|
|
104
|
+
rowExpandedBg: '#fafcff',
|
|
98
105
|
borderColor: '#f0f0f0',
|
|
99
106
|
headerSplitColor: '#f0f0f0',
|
|
100
107
|
headerBg: '#C6D4E2AA',
|
|
101
108
|
headerColor: '#245780',
|
|
102
109
|
colorText: '#000000',
|
|
103
110
|
headerSortActiveBg: '#f0f0f0',
|
|
111
|
+
headerSortHoverBg: '#f5f5f5',
|
|
112
|
+
footerBg: '#fafafa',
|
|
113
|
+
footerColor: '#000000',
|
|
114
|
+
fixedHeaderSortActiveBg: '#e6edf5',
|
|
115
|
+
filterDropdownMenuBg: '#ffffff',
|
|
116
|
+
stickyScrollBarBg: 'rgba(0, 0, 0, 0.25)',
|
|
104
117
|
},
|
|
105
118
|
headerBorderRadius: 8,
|
|
106
119
|
cellPaddingInline: 16,
|
|
@@ -108,8 +121,9 @@ export const DEFAULT_SITE_CONFIG = {
|
|
|
108
121
|
lineHeight: 1.57,
|
|
109
122
|
lineWidth: 1,
|
|
110
123
|
cellFontSize: 14,
|
|
111
|
-
|
|
112
|
-
|
|
124
|
+
stickyScrollBarBorderRadius: 100,
|
|
125
|
+
selectionColumnWidth: 32,
|
|
126
|
+
expandIconMarginTop: 16,
|
|
113
127
|
},
|
|
114
128
|
Form: {
|
|
115
129
|
colors: {
|
|
@@ -117,6 +131,9 @@ export const DEFAULT_SITE_CONFIG = {
|
|
|
117
131
|
labelRequiredMarkColor: '#ff4d4f',
|
|
118
132
|
},
|
|
119
133
|
labelFontSize: 14,
|
|
134
|
+
itemMarginBottom: 24,
|
|
135
|
+
verticalLabelPadding: '0 0 8px',
|
|
136
|
+
labelHeight: 32,
|
|
120
137
|
},
|
|
121
138
|
},
|
|
122
139
|
}
|
|
@@ -145,6 +162,8 @@ export const DEFAULT_COMPANY_CONFIG = {
|
|
|
145
162
|
width: 400,
|
|
146
163
|
weight: 700,
|
|
147
164
|
padding: 20,
|
|
165
|
+
borderRadius: 12,
|
|
166
|
+
labelMarginBottom: 4,
|
|
148
167
|
spacing: 20,
|
|
149
168
|
fields: ['email', 'password', 'forgot-password'],
|
|
150
169
|
},
|
package/src/enums/index.ts
CHANGED
|
@@ -47,6 +47,14 @@ export enum TextElementTypeEnum {
|
|
|
47
47
|
Plain = 'Plain',
|
|
48
48
|
Rich = 'Rich',
|
|
49
49
|
}
|
|
50
|
+
export enum StyleTemplateTypeEnum {
|
|
51
|
+
ContainerStyle = 'ContainerStyle',
|
|
52
|
+
ContainerHeaderStyle = 'ContainerHeaderStyle',
|
|
53
|
+
TextStyle = 'TextStyle',
|
|
54
|
+
ReportTableStyle = 'ReportTableStyle',
|
|
55
|
+
ListStyle = 'ListStyle',
|
|
56
|
+
ImageStyle = 'ImageStyle',
|
|
57
|
+
}
|
|
50
58
|
export enum DndLayoutNodeEnum { // when saved, everything in the layout will have either of these node types
|
|
51
59
|
Element = 'Element', // it's the actual display of the form (can be a field, a divider etc.)
|
|
52
60
|
Row = 'Row',
|
|
@@ -244,16 +244,25 @@ export const replaceAndGetFileBlob = async (
|
|
|
244
244
|
companyKey?: string
|
|
245
245
|
formId?: number
|
|
246
246
|
},
|
|
247
|
-
): Promise<
|
|
247
|
+
): Promise<Blob | ''> => {
|
|
248
248
|
if (!contextData?.companyKey) return ''
|
|
249
249
|
|
|
250
250
|
try {
|
|
251
251
|
const replacements = await Promise.all(
|
|
252
252
|
tInfo.replacements.map(async (rep) => {
|
|
253
|
+
if (!rep.renderConfig) rep.renderConfig = { type: DataRenderTypeEnum.Default }
|
|
254
|
+
|
|
255
|
+
if (!rep.field) {
|
|
256
|
+
return {
|
|
257
|
+
placeholder: rep.placeholder,
|
|
258
|
+
value: '',
|
|
259
|
+
type: rep.type,
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
253
263
|
const fieldPath: string[] = rep.field.split('.')
|
|
254
264
|
let repValue = fieldPath.reduce((curr, n) => curr?.[n] ?? {}, formData)
|
|
255
265
|
|
|
256
|
-
if (!rep.renderConfig) rep.renderConfig = { type: DataRenderTypeEnum.Default }
|
|
257
266
|
|
|
258
267
|
if (typeof repValue === 'object') {
|
|
259
268
|
if (MongoDbExtendedJsonObjectKeys.Date in repValue) repValue = repValue[MongoDbExtendedJsonObjectKeys.Date]
|
|
@@ -371,8 +380,13 @@ export const replaceTemplateReportAndUpload = async ({
|
|
|
371
380
|
const replacedFileBlob = await replaceAndGetFileBlob(templateInfo, formData, { companyKey, formId })
|
|
372
381
|
if (!replacedFileBlob) return null
|
|
373
382
|
|
|
383
|
+
const resolvedFileName = fileNameHint ?? templateInfo.templateName
|
|
384
|
+
const normalizedFileName = resolvedFileName.toLowerCase().endsWith('.pdf')
|
|
385
|
+
? resolvedFileName
|
|
386
|
+
: `${resolvedFileName.replace(/\.[^./\]]+$/, '')}.pdf`
|
|
387
|
+
|
|
374
388
|
const uploadPayload = new FormData()
|
|
375
|
-
uploadPayload.append('file', replacedFileBlob)
|
|
389
|
+
uploadPayload.append('file', replacedFileBlob, normalizedFileName)
|
|
376
390
|
const uploadUrl = isPublic ? `/api/attachment/create/${companyKey}` : '/api/attachment'
|
|
377
391
|
|
|
378
392
|
const blobName = await client
|
|
@@ -382,11 +396,6 @@ export const replaceTemplateReportAndUpload = async ({
|
|
|
382
396
|
|
|
383
397
|
if (!blobName) return null
|
|
384
398
|
|
|
385
|
-
const resolvedFileName = fileNameHint ?? templateInfo.templateName
|
|
386
|
-
const normalizedFileName = resolvedFileName.toLowerCase().endsWith('.pdf')
|
|
387
|
-
? resolvedFileName
|
|
388
|
-
: `${resolvedFileName.replace(/\.[^./\]]+$/, '')}.pdf`
|
|
389
|
-
|
|
390
399
|
return { blobName, fileName: normalizedFileName }
|
|
391
400
|
} catch (err) {
|
|
392
401
|
console.error('replaceTemplateReportAndUpload failed:', err)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IconType } from 'react-icons'
|
|
2
|
-
import { SystemEnvironmentEnum, SystemRolePermissionEnum } from '../../enums'
|
|
3
|
-
import { IFormTranslations } from '../forms'
|
|
2
|
+
import { StyleTemplateTypeEnum, SystemEnvironmentEnum, SystemRolePermissionEnum } from '../../enums'
|
|
3
|
+
import { ICssStyle, IFormTranslations } from '../forms'
|
|
4
4
|
|
|
5
5
|
export interface ICompanyViewModel {
|
|
6
6
|
id: number
|
|
@@ -24,8 +24,15 @@ export interface ICompanyConfig_Public {
|
|
|
24
24
|
systemRoles: ISystemRole[]
|
|
25
25
|
changePasswordConfig?: ICompanyConfig_ChangePasswordConfig
|
|
26
26
|
translations: ICompanyTranslations
|
|
27
|
+
styleTemplates?: IStyleTemplate[]
|
|
27
28
|
}
|
|
28
29
|
export type ICompanyTranslations = IFormTranslations
|
|
30
|
+
export interface IStyleTemplate {
|
|
31
|
+
id: string
|
|
32
|
+
name: string
|
|
33
|
+
type: StyleTemplateTypeEnum
|
|
34
|
+
style: ICssStyle
|
|
35
|
+
}
|
|
29
36
|
export interface ISystemRole {
|
|
30
37
|
id: string
|
|
31
38
|
name: string
|
|
@@ -43,6 +50,8 @@ export interface ILoginLayout {
|
|
|
43
50
|
export interface ILoginFormLayout {
|
|
44
51
|
width: number
|
|
45
52
|
padding: number
|
|
53
|
+
borderRadius?: number
|
|
54
|
+
labelMarginBottom?: number
|
|
46
55
|
title: string
|
|
47
56
|
background: string
|
|
48
57
|
size: number
|
|
@@ -136,6 +145,9 @@ export interface ICompanyConfig_LayoutConfig {
|
|
|
136
145
|
export interface ICompanyConfig_LinkConfig {
|
|
137
146
|
colors: ICompanyConfig_SiteColors
|
|
138
147
|
borderRadius: number
|
|
148
|
+
paddingInline?: number
|
|
149
|
+
paddingBlock?: number
|
|
150
|
+
activeBorderRadius?: number
|
|
139
151
|
}
|
|
140
152
|
export interface ICompanyConfig_CustomButtonColors {
|
|
141
153
|
colorTextDisabled?: string
|
|
@@ -187,12 +199,16 @@ export interface ICompanyConfig_TableConfig {
|
|
|
187
199
|
lineHeight?: number
|
|
188
200
|
lineWidth?: number
|
|
189
201
|
cellFontSize?: number
|
|
190
|
-
|
|
191
|
-
|
|
202
|
+
stickyScrollBarBorderRadius?: number
|
|
203
|
+
selectionColumnWidth?: number
|
|
204
|
+
expandIconMarginTop?: number
|
|
192
205
|
}
|
|
193
206
|
export interface ICompanyConfig_FormConfig {
|
|
194
207
|
colors: ICompanyConfig_SiteColors
|
|
195
208
|
labelFontSize?: number
|
|
209
|
+
itemMarginBottom?: number
|
|
210
|
+
verticalLabelPadding?: string
|
|
211
|
+
labelHeight?: number
|
|
196
212
|
}
|
|
197
213
|
export interface ICompanyConfig_SiteConfigs {
|
|
198
214
|
fontFamily: string
|
package/src/types/forms/index.ts
CHANGED
|
@@ -226,6 +226,7 @@ export interface IPlaceholderElement extends BaseFormLayoutElement {
|
|
|
226
226
|
export interface IReportTableElement extends BaseFormLayoutElement {
|
|
227
227
|
elementType: ElementTypeEnum.ReportTable
|
|
228
228
|
style?: ICssStyle | ICssStylePerBreakpoint
|
|
229
|
+
styleTemplateId?: string
|
|
229
230
|
props: IReportTableElementProps
|
|
230
231
|
}
|
|
231
232
|
|
|
@@ -258,6 +259,7 @@ export interface IDividerElementProps {
|
|
|
258
259
|
export interface IReadFieldDataElement extends BaseFormLayoutElement {
|
|
259
260
|
elementType: ElementTypeEnum.ReadFieldData
|
|
260
261
|
style?: ICssStyle | ICssStylePerBreakpoint
|
|
262
|
+
styleTemplateId?: string
|
|
261
263
|
props: IReadFieldDataElementProps
|
|
262
264
|
}
|
|
263
265
|
|
|
@@ -273,6 +275,7 @@ export interface IButtonElement extends BaseFormLayoutElement {
|
|
|
273
275
|
export interface ITextElement extends BaseFormLayoutElement {
|
|
274
276
|
elementType: ElementTypeEnum.Text
|
|
275
277
|
style?: ICssStyle | ICssStylePerBreakpoint
|
|
278
|
+
styleTemplateId?: string
|
|
276
279
|
props: ITextElementProps
|
|
277
280
|
}
|
|
278
281
|
export interface ITextElementProps {
|
|
@@ -328,6 +331,7 @@ export interface IColorPickerElement extends BaseFormLayoutElement {
|
|
|
328
331
|
export interface IListElement extends BaseFormLayoutElement {
|
|
329
332
|
elementType: ElementTypeEnum.OrderedList | ElementTypeEnum.UnorderedList
|
|
330
333
|
style?: ICssStyle
|
|
334
|
+
styleTemplateId?: string
|
|
331
335
|
props: {
|
|
332
336
|
pageBreak?: ReportPageBreakEnum
|
|
333
337
|
}
|
|
@@ -338,6 +342,7 @@ export interface IListElement extends BaseFormLayoutElement {
|
|
|
338
342
|
export interface IImageElement extends BaseFormLayoutElement {
|
|
339
343
|
elementType: ElementTypeEnum.Image
|
|
340
344
|
style?: ICssStyle
|
|
345
|
+
styleTemplateId?: string
|
|
341
346
|
props: {
|
|
342
347
|
pageBreak?: ReportPageBreakEnum
|
|
343
348
|
fit?: boolean
|