Form içerisindeki Workflow custom butonlar
This commit is contained in:
parent
b53a4243ab
commit
1d05d24e41
5 changed files with 122 additions and 23 deletions
|
|
@ -15,13 +15,14 @@ import {
|
||||||
} from 'react-icons/fa'
|
} from 'react-icons/fa'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { GridColumnData } from '../list/GridColumnData'
|
import { GridColumnData } from '../list/GridColumnData'
|
||||||
import { useToolbar } from '../list/useToolbar'
|
import { isWorkflowToolbarItemDisabled, useToolbar } from '../list/useToolbar'
|
||||||
import { PermissionResults, RowMode } from './types'
|
import { PermissionResults, RowMode } from './types'
|
||||||
import { GridDto } from '@/proxy/form/models'
|
import { GridDto } from '@/proxy/form/models'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import { usePermission } from '@/utils/hooks/usePermission'
|
import { usePermission } from '@/utils/hooks/usePermission'
|
||||||
import { usePWA } from '@/utils/hooks/usePWA'
|
import { usePWA } from '@/utils/hooks/usePWA'
|
||||||
import { layoutTypes } from '../admin/listForm/edit/types'
|
import { layoutTypes } from '../admin/listForm/edit/types'
|
||||||
|
import { useStoreState } from '@/store'
|
||||||
|
|
||||||
const FormButtons = (props: {
|
const FormButtons = (props: {
|
||||||
isSubForm?: boolean
|
isSubForm?: boolean
|
||||||
|
|
@ -68,7 +69,7 @@ const FormButtons = (props: {
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
const layout = layoutTypes.grid
|
const layout = layoutTypes.grid
|
||||||
const { toolbarData } = useToolbar({
|
const { toolbarData, toolbarModalData, setToolbarModalData } = useToolbar({
|
||||||
gridDto,
|
gridDto,
|
||||||
listFormCode,
|
listFormCode,
|
||||||
getSelectedRowKeys,
|
getSelectedRowKeys,
|
||||||
|
|
@ -78,6 +79,52 @@ const FormButtons = (props: {
|
||||||
layout,
|
layout,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const currentUser = useStoreState((state) => state.auth.user)
|
||||||
|
const selectedRowData = getSelectedRowsData()?.[0]
|
||||||
|
const rowEvent = {
|
||||||
|
row: {
|
||||||
|
data: selectedRowData,
|
||||||
|
key: id,
|
||||||
|
rowType: 'data',
|
||||||
|
isEditing: mode === 'edit',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveVisible = (visible: unknown) => {
|
||||||
|
if (typeof visible !== 'function') {
|
||||||
|
return visible !== false
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Boolean(visible(rowEvent))
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Button visibility evaluation error:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isToolbarButtonVisible = (item: any) =>
|
||||||
|
item.widget === 'dxButton' &&
|
||||||
|
!toolbarExcludedNames.includes(item.name) &&
|
||||||
|
resolveVisible(item.visible) &&
|
||||||
|
resolveVisible(item.options?.visible)
|
||||||
|
|
||||||
|
const isCommandButtonVisible = (item: any) =>
|
||||||
|
typeof item !== 'string' &&
|
||||||
|
!commandExcludedNames.includes(item.name) &&
|
||||||
|
resolveVisible(item.visible)
|
||||||
|
|
||||||
|
const isToolbarButtonDisabled = (item: any) => {
|
||||||
|
const workflowDisabled = isWorkflowToolbarItemDisabled(
|
||||||
|
item.name,
|
||||||
|
gridDto.gridOptions.workflowDto,
|
||||||
|
selectedRowData ? [selectedRowData] : [],
|
||||||
|
currentUser,
|
||||||
|
)
|
||||||
|
|
||||||
|
return workflowDisabled ?? Boolean(item.options?.disabled)
|
||||||
|
}
|
||||||
|
|
||||||
const openDynamicGridReport = () => {
|
const openDynamicGridReport = () => {
|
||||||
if (!id) return
|
if (!id) return
|
||||||
|
|
||||||
|
|
@ -116,22 +163,16 @@ const FormButtons = (props: {
|
||||||
}
|
}
|
||||||
|
|
||||||
const toolbarExcludedNames = ['deleteAllRecords', 'deleteSelectedRecords', 'refreshButton']
|
const toolbarExcludedNames = ['deleteAllRecords', 'deleteSelectedRecords', 'refreshButton']
|
||||||
const hasVisibleToolbarButtons = toolbarData?.some(
|
const hasVisibleToolbarButtons = toolbarData?.some(isToolbarButtonVisible)
|
||||||
(item) => item.widget == 'dxButton' && !toolbarExcludedNames.includes(item.name!),
|
|
||||||
)
|
|
||||||
|
|
||||||
const commandExcludedNames = ['edit', 'delete', 'detail']
|
const commandExcludedNames = ['edit', 'delete', 'detail']
|
||||||
const hasVisibleCommandButtons = commandColumnData?.buttons?.some(
|
const hasVisibleCommandButtons = commandColumnData?.buttons?.some(isCommandButtonVisible)
|
||||||
(item) => typeof item !== 'string' && !commandExcludedNames.includes(item.name!),
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-row items-center gap-1">
|
<div className="flex flex-row items-center gap-1">
|
||||||
{toolbarData
|
{toolbarData
|
||||||
?.filter(
|
?.filter(isToolbarButtonVisible)
|
||||||
(item) => item.widget == 'dxButton' && !toolbarExcludedNames.includes(item.name!),
|
|
||||||
)
|
|
||||||
.map((item, i) => {
|
.map((item, i) => {
|
||||||
const IconComp = navigationIcon[item.options?.icon]
|
const IconComp = navigationIcon[item.options?.icon]
|
||||||
const hasValidIcon =
|
const hasValidIcon =
|
||||||
|
|
@ -145,7 +186,8 @@ const FormButtons = (props: {
|
||||||
variant="default"
|
variant="default"
|
||||||
size="sm"
|
size="sm"
|
||||||
icon={hasValidIcon ? <IconComp className="text-gray-400" /> : null}
|
icon={hasValidIcon ? <IconComp className="text-gray-400" /> : null}
|
||||||
onClick={item.options?.onClick}
|
disabled={isToolbarButtonDisabled(item)}
|
||||||
|
onClick={(e) => item.options?.onClick?.({ ...e, ...rowEvent })}
|
||||||
>
|
>
|
||||||
{item.options?.text}
|
{item.options?.text}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -155,7 +197,7 @@ const FormButtons = (props: {
|
||||||
{hasVisibleToolbarButtons && <Badge innerClass="bg-blue-500" />}
|
{hasVisibleToolbarButtons && <Badge innerClass="bg-blue-500" />}
|
||||||
|
|
||||||
{commandColumnData?.buttons
|
{commandColumnData?.buttons
|
||||||
?.filter((item) => typeof item !== 'string' && !commandExcludedNames.includes(item.name!))
|
?.filter(isCommandButtonVisible)
|
||||||
.map((item: any, i) => {
|
.map((item: any, i) => {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -165,9 +207,7 @@ const FormButtons = (props: {
|
||||||
title={item.hint}
|
title={item.hint}
|
||||||
onClick={(e: any) => {
|
onClick={(e: any) => {
|
||||||
if (item.onClick) {
|
if (item.onClick) {
|
||||||
const [rowData] = getSelectedRowsData()
|
item.onClick({ ...e, ...rowEvent })
|
||||||
e.row = { data: rowData }
|
|
||||||
item.onClick(e)
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -308,6 +348,13 @@ const FormButtons = (props: {
|
||||||
></Button>
|
></Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<Dialog
|
||||||
|
isOpen={toolbarModalData?.open || false}
|
||||||
|
onClose={() => setToolbarModalData(undefined)}
|
||||||
|
onRequestClose={() => setToolbarModalData(undefined)}
|
||||||
|
>
|
||||||
|
{toolbarModalData?.content}
|
||||||
|
</Dialog>
|
||||||
<Dialog
|
<Dialog
|
||||||
id="confirmDelete"
|
id="confirmDelete"
|
||||||
isOpen={!!deleteRowId}
|
isOpen={!!deleteRowId}
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ const FormView = (
|
||||||
dataSource={dataSource!}
|
dataSource={dataSource!}
|
||||||
permissions={permissionResults}
|
permissions={permissionResults}
|
||||||
handleSubmit={() => {}}
|
handleSubmit={() => {}}
|
||||||
refreshData={() => {}}
|
refreshData={fetchData}
|
||||||
getSelectedRowKeys={() => [id]}
|
getSelectedRowKeys={() => [id]}
|
||||||
getSelectedRowsData={() => [formData]}
|
getSelectedRowsData={() => [formData]}
|
||||||
getFilter={() => filter}
|
getFilter={() => filter}
|
||||||
|
|
|
||||||
|
|
@ -655,6 +655,53 @@ function getCurrentUserWorkflowIdentities(currentUser?: {
|
||||||
return [currentUser?.email, currentUser?.userName, currentUser?.name].filter(Boolean) as string[]
|
return [currentUser?.email, currentUser?.userName, currentUser?.name].filter(Boolean) as string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isWorkflowToolbarItemDisabled(
|
||||||
|
itemName: string | undefined,
|
||||||
|
workflowOptions: WorkflowDto | undefined,
|
||||||
|
selectedRowsData: Record<string, unknown>[] = [],
|
||||||
|
currentUser?: {
|
||||||
|
userName?: string
|
||||||
|
email?: string
|
||||||
|
name?: string
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
if (!itemName || !workflowOptions?.approvalStatusFieldName) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemName === 'workflowStart') {
|
||||||
|
return (
|
||||||
|
selectedRowsData.length === 0 ||
|
||||||
|
!selectedRowsData.every((row) => isWorkflowNotStarted(row, workflowOptions))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!itemName.startsWith('workflowApproval_')) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const criteriaId = itemName.replace('workflowApproval_', '')
|
||||||
|
const criteria = workflowOptions.criteria?.find(
|
||||||
|
(item) => item.kind === 'Approval' && item.id === criteriaId,
|
||||||
|
)
|
||||||
|
if (!criteria) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentUserIdentities = getCurrentUserWorkflowIdentities(currentUser)
|
||||||
|
return (
|
||||||
|
selectedRowsData.length === 0 ||
|
||||||
|
!selectedRowsData.every((row) =>
|
||||||
|
isWorkflowApprovalCriteriaActive(
|
||||||
|
row,
|
||||||
|
workflowOptions,
|
||||||
|
criteria.title,
|
||||||
|
currentUserIdentities,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function updateWorkflowApprovalToolbarItems(
|
export function updateWorkflowApprovalToolbarItems(
|
||||||
component: any,
|
component: any,
|
||||||
workflowOptions: WorkflowDto | undefined,
|
workflowOptions: WorkflowDto | undefined,
|
||||||
|
|
|
||||||
|
|
@ -108,11 +108,15 @@ export const MenuManager = () => {
|
||||||
Design Mode
|
Design Mode
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
|
type="button"
|
||||||
variant="plain"
|
variant="plain"
|
||||||
shape="none"
|
shape="none"
|
||||||
|
role="switch"
|
||||||
|
aria-checked={isDesignMode}
|
||||||
|
aria-label="Toggle design mode"
|
||||||
className={`
|
className={`
|
||||||
relative !inline-flex !h-6 !w-11 !min-w-11 !justify-start !rounded-full !border-0 !px-0 !items-center transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
|
relative !inline-flex !h-6 !w-11 !min-w-11 !items-center !justify-start !rounded-full !border-0 !px-0 shadow-inner transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
|
||||||
${isDesignMode ? 'bg-blue-600 hover:!bg-blue-600 dark:bg-blue-700 dark:hover:!bg-blue-700' : 'bg-gray-200 hover:!bg-gray-200 dark:bg-gray-700 dark:hover:!bg-gray-700'}
|
${isDesignMode ? '!bg-blue-600 hover:!bg-blue-600 dark:!bg-blue-700 dark:hover:!bg-blue-700' : '!bg-gray-300 hover:!bg-gray-300 dark:!bg-gray-700 dark:hover:!bg-gray-700'}
|
||||||
`}
|
`}
|
||||||
onClick={handleToggleDesignMode}
|
onClick={handleToggleDesignMode}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,9 @@ export default defineConfig(async ({ mode }) => {
|
||||||
mode === 'production'
|
mode === 'production'
|
||||||
? {
|
? {
|
||||||
globDirectory: 'dist',
|
globDirectory: 'dist',
|
||||||
// Precache only the application shell. Feature chunks and the
|
// Eski bir sekme yeni deploy sırasında henüz açmadığı lazy
|
||||||
// many optional DevExtreme themes are cached on first use.
|
// chunk'ları da çalıştırabilsin; tüm uygulama dosyalarını sakla.
|
||||||
globPatterns: ['index.html', 'assets/css/index-*.css', 'assets/js/index-*.js'],
|
globPatterns: ['**/*.{js,css,html,wasm}'],
|
||||||
|
|
||||||
// Büyük asset'leri de cache'leyebil
|
// Büyük asset'leri de cache'leyebil
|
||||||
maximumFileSizeToCacheInBytes: 15 * 1024 * 1024,
|
maximumFileSizeToCacheInBytes: 15 * 1024 * 1024,
|
||||||
|
|
@ -52,9 +52,10 @@ export default defineConfig(async ({ mode }) => {
|
||||||
runtimeCaching: [
|
runtimeCaching: [
|
||||||
{
|
{
|
||||||
urlPattern: /\.(?:js|css|wasm)$/,
|
urlPattern: /\.(?:js|css|wasm)$/,
|
||||||
handler: 'CacheFirst',
|
handler: 'NetworkFirst',
|
||||||
options: {
|
options: {
|
||||||
cacheName: 'static-resources-v2',
|
cacheName: 'static-resources-v2',
|
||||||
|
networkTimeoutSeconds: 5,
|
||||||
expiration: {
|
expiration: {
|
||||||
maxEntries: 200,
|
maxEntries: 200,
|
||||||
maxAgeSeconds: 30 * 24 * 60 * 60,
|
maxAgeSeconds: 30 * 24 * 60 * 60,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue