import { Button, Notification, toast } from '@/components/ui' import { GridDto, UiCommandButtonPositionTypeEnum, WorkflowDto } from '@/proxy/form/models' import { dynamicFetch } from '@/services/form.service' import { useLocalization } from '@/utils/hooks/useLocalization' import { usePermission } from '@/utils/hooks/usePermission' import { DataGridTypes } from 'devextreme-react/data-grid' import { ToolbarItem } from 'devextreme/ui/data_grid_types' import { useEffect, useState } from 'react' import { useDialogContext } from '../shared/DialogContext' import { usePWA } from '@/utils/hooks/usePWA' import { layoutTypes, ListViewLayoutType } from '../admin/listForm/edit/types' import { useStoreState } from '@/store' import { workflowService } from '@/services/workflow.service' type ToolbarModalData = { open: boolean content?: JSX.Element } // https://js.devexpress.com/Documentation/ApiReference/UI_Components/dxDataGrid/Configuration/toolbar/ // item.name > Accepted Values: 'addRowButton', 'applyFilterButton', 'columnChooserButton', 'exportButton', 'groupPanel', 'revertButton', 'saveButton', 'searchPanel' const useToolbar = ({ gridDto, listFormCode, getSelectedRowKeys, getSelectedRowsData, refreshData, getFilter, layout, expandAll, collapseAll, }: { gridDto?: GridDto listFormCode: string getSelectedRowKeys: () => void getSelectedRowsData: () => any refreshData: () => void getFilter: () => void layout: ListViewLayoutType | string expandAll?: () => void collapseAll?: () => void }): { toolbarData: ToolbarItem[] toolbarModalData: ToolbarModalData | undefined setToolbarModalData: (data: ToolbarModalData | undefined) => void } => { const dialog: any = useDialogContext() const { translate } = useLocalization() const { checkPermission } = usePermission() const isPwaMode = usePWA() const currentUser = useStoreState((state) => state.auth.user) const [toolbarData, setToolbarData] = useState([]) const [toolbarModalData, setToolbarModalData] = useState() const grdOpt = gridDto?.gridOptions function getToolbarData() { const items: ToolbarItem[] = [] if (!gridDto || !grdOpt) { setToolbarData(items) return } // Add searchPanel if (grdOpt.searchPanelDto?.visible) { items.push({ locateInMenu: 'auto', showText: 'inMenu', name: 'searchPanel', }) } // Add InsertNewRecord button if (grdOpt.editingOptionDto?.allowAdding && checkPermission(grdOpt.permissionDto?.c)) { if (grdOpt.editingOptionDto?.addPageUrl) { items.push({ widget: 'dxButton', showText: 'always', name: 'addRowButton', location: 'after', options: { text: translate('::ListForms.ListForm.AddNewRecord'), hint: translate('::ListForms.ListForm.AddNewRecord'), onClick() { window.open(grdOpt.editingOptionDto?.addPageUrl, isPwaMode ? '_self' : '_blank') }, }, }) } else { items.push({ locateInMenu: 'auto', showText: 'always', name: 'addRowButton', location: 'after', options: { text: translate('::ListForms.ListForm.AddNewRecord'), hint: translate('::ListForms.ListForm.AddNewRecord'), }, }) } } items.push({ widget: 'dxButton', name: 'refreshButton', options: { icon: 'refresh', onClick: refreshData, text: translate('::ListForms.ListForm.Refresh'), }, location: 'after', }) const workflowOptions = grdOpt.workflowDto const approvalCriteria = workflowOptions?.criteria?.filter((item) => item.kind === 'Approval') ?? [] if ( workflowOptions?.approvalStatusFieldName && approvalCriteria.length > 0 && grdOpt.updateServiceAddress ) { items.push({ widget: 'dxButton', name: 'workflowStart', location: 'after', options: { icon: 'play', text: 'Workflow Start', hint: 'Workflow Start', visible: true, disabled: true, onClick: async () => { const keys = (await Promise.resolve(getSelectedRowKeys() as any)) as unknown[] if (!keys?.length) { toast.push( {translate('::ListForms.ListForm.SelectRecord')} , { placement: 'top-end' }, ) return } const selectedRows = ((await Promise.resolve(getSelectedRowsData() as any)) || []) as Record[] if ( selectedRows.length === 0 || !selectedRows.every((row) => isWorkflowNotStarted(row, workflowOptions)) ) { toast.push( Secili kayit icin workflow zaten baslamis. , { placement: 'top-end' }, ) return } try { await workflowService.startWorkflow(listFormCode, keys) refreshData() } catch (error: any) { toast.push( {error?.response?.data?.error?.message || error?.response?.data?.message || error?.message || 'Workflow baslatilamadi.'} , { placement: 'top-end' }, ) } }, }, }) approvalCriteria.forEach((criteria) => { items.push({ widget: 'dxButton', name: `workflowApproval_${criteria.id}`, location: 'after', options: { icon: 'check', text: criteria.title, hint: criteria.title, visible: true, disabled: true, onClick: async () => { const keys = (await Promise.resolve(getSelectedRowKeys() as any)) as unknown[] if (!keys?.length) { toast.push( {translate('::ListForms.ListForm.SelectRecord')} , { placement: 'top-end' }, ) return } const selectedRows = ((await Promise.resolve(getSelectedRowsData() as any)) || []) as Record[] const activeRows = selectedRows.filter((row) => isWorkflowApprovalCriteriaActive( row, workflowOptions, criteria.title, getCurrentUserWorkflowIdentities(currentUser), ), ) if (activeRows.length !== selectedRows.length) { toast.push( Secili kayit bu onay adiminda veya onay kullanicisinda beklemiyor. , { placement: 'top-end' }, ) return } setToolbarModalData({ open: true, content: ( <> setToolbarModalData(undefined)} onCompleted={() => { refreshData() setToolbarModalData(undefined) }} /> ), }) }, }, }) }) } // Add Expand All button for TreeList if (layout === layoutTypes.tree && grdOpt.treeOptionDto?.parentIdExpr) { items.push({ widget: 'dxButton', name: 'expandAllButton', options: { icon: 'plus', text: translate('::ListForms.ListFormEdit.ExpandAll'), onClick: expandAll, }, location: 'after', }) // Add Collapse All button for TreeList items.push({ widget: 'dxButton', name: 'collapseAllButton', options: { icon: 'minus', text: translate('::ListForms.ListFormEdit.CollapseAll'), onClick: collapseAll, }, location: 'after', }) } // field chooser panel if (grdOpt.columnOptionDto?.columnChooserEnabled) { items.push({ locateInMenu: 'auto', showText: 'inMenu', name: 'columnChooserButton', options: { hint: translate('::ListForms.ListForm.ColumnChooser'), }, }) } // Add group panel if (grdOpt.groupPanelDto?.visible) { items.push({ locateInMenu: 'auto', showText: 'inMenu', name: 'groupPanel', }) } // Add DeleteSelectedRecords button // coklu silme icin if (grdOpt.editingOptionDto?.allowDeleting && checkPermission(grdOpt.permissionDto?.d)) { items.push({ location: 'after', widget: 'dxButton', locateInMenu: 'auto', showText: 'inMenu', name: 'deleteSelectedRecords', options: { text: translate('::ListForms.ListForm.DeleteSelectedRecords'), icon: 'trash', visible: false, onClick() { if (!grdOpt.deleteServiceAddress) { return } dynamicFetch(grdOpt.deleteServiceAddress, 'POST', null, { keys: getSelectedRowKeys(), listFormCode, }).then(() => { refreshData() }) }, }, }) // Add DeleteAllRecords button // butun kayitlari (filtreli) icin if (grdOpt.editingOptionDto?.allowAllDeleting) { const buttonDeleteAll: DataGridTypes.ToolbarItem = { location: 'after', widget: 'dxButton', name: 'deleteAllRecords', options: { text: translate('::ListForms.ListForm.DeleteAllRecords'), hint: translate('::ListForms.ListForm.DeleteAllRecords'), icon: 'trash', visible: true, onClick() { const parameters = { listFormCode, filter: JSON.stringify(getFilter()), onlyTotalCountQuery: true, createDeleteQuery: false, } dynamicFetch('list-form-select/select', 'GET', parameters).then((r: any) => { setToolbarModalData({ open: true, content: ( <>
Delete All Records

Are you sure to delete all {r.data.totalCount} records?

), }) }) }, }, } items.push(buttonDeleteAll) } } // #region Toolbar icin kullanici tanimli dinamik butonlari ekler for (let i = 0; i < grdOpt.commandColumnDto.length; i++) { const action = grdOpt.commandColumnDto[i] // action.buttonPosition == 1 ise Toolbar butonudur, burada sadece Toolbar butonunu eklenir if (action.buttonPosition !== UiCommandButtonPositionTypeEnum.Toolbar) { continue } if (checkPermission(action.authName)) { const buttonCustom: DataGridTypes.ToolbarItem = { location: 'after', widget: 'dxButton', name: action.hint, options: { hint: translate('::' + action.hint), text: translate('::' + action.text), icon: action.icon, visible: true, onClick(e: any) { if (typeof e.event?.preventDefault === 'function') { e?.event?.preventDefault() } if (action.url) { let url = action.url // griddeki secili satirlari al const selectedRowsData = getSelectedRowsData() // constsa her bir secili satir icin donguye gir for (let i = 0; i < selectedRowsData.length; i++) { // secili satirin objesine ait property verilerini al const keys = Object.keys(selectedRowsData[i]) // secili satirin her bir property si icin donguye gir for (let j = 0; j < keys.length; j++) { // secili satirin j indexine sahip property ismini al const fieldName = keys[j] // secili satirin j indexine sahip property isminin degerini al const fieldValue = selectedRowsData[i][fieldName] // url icerisindeki {PropertyName} seklindeki anahtarlari secili satirdaki uyusan propertyler ile degistir url = url.replace(`@${fieldName}`, fieldValue) } break // Url cagirmak icin kullanilacak parametreler sadece secili olan ilk satirdan alinir! } window.open(url, isPwaMode ? '_self' : action.urlTarget) } else if (action.dialogName) { if (action.dialogParameters) { var dynamicMap = JSON.parse(action.dialogParameters) for (const [key, value] of Object.entries(dynamicMap)) { dynamicMap[key] = value.startsWith('@') ? e.row.data[value.replace('@', '')] : value } dialog.setConfig({ component: action.dialogName, props: dynamicMap, }) } } else if (action.onClick) { eval(action.onClick) } }, }, } items.push(buttonCustom) } } // #endregion // batch editing icin kaydet ve geri al butonu if ( grdOpt.editingOptionDto?.allowUpdating && grdOpt.editingOptionDto?.mode == 'batch' && checkPermission(grdOpt.permissionDto?.u) ) { items.push({ locateInMenu: 'auto', showText: 'inMenu', name: 'saveButton', options: { hint: translate('::App.SaveChanges') }, }) items.push({ locateInMenu: 'auto', showText: 'inMenu', name: 'revertButton', options: { hint: translate('::App.UndoChanges') }, }) } // #endregion setToolbarData(items) } useEffect(() => { if (!gridDto && !listFormCode) return getToolbarData() }, [gridDto, listFormCode, currentUser]) return { toolbarData, toolbarModalData, setToolbarModalData, } } function isWorkflowApprovalCriteriaActive( row: Record, workflowOptions: WorkflowDto, criteriaTitle: string, currentUserIdentities: string[] = [], ) { if (!workflowOptions.approvalStatusFieldName || !criteriaTitle) { return false } const statusMatches = normalizeWorkflowValue(row?.[workflowOptions.approvalStatusFieldName]) === normalizeWorkflowValue(criteriaTitle) if (!statusMatches) { return false } if (!workflowOptions.approvalUserFieldName) { return true } const approver = normalizeWorkflowValue(row?.[workflowOptions.approvalUserFieldName]) return currentUserIdentities.some((identity) => normalizeWorkflowValue(identity) === approver) } function normalizeWorkflowValue(value: unknown) { return String(value ?? '').trim().toLocaleLowerCase('tr-TR') } function isWorkflowNotStarted(row: Record, workflowOptions: WorkflowDto) { return normalizeWorkflowValue(row?.[workflowOptions.approvalStatusFieldName]) === '' } function getCurrentUserWorkflowIdentities(currentUser?: { userName?: string email?: string name?: string }) { return [currentUser?.email, currentUser?.userName, currentUser?.name].filter(Boolean) as string[] } export function updateWorkflowApprovalToolbarItems( component: any, workflowOptions: WorkflowDto | undefined, selectedRowsData: Record[] = [], currentUser?: { userName?: string email?: string name?: string }, ) { const approvalCriteria = workflowOptions?.criteria?.filter((item) => item.kind === 'Approval') ?? [] if (!component || !workflowOptions?.approvalStatusFieldName || !approvalCriteria.length) { return } const toolbarOptions = component.option('toolbar') if (!toolbarOptions?.items || !Array.isArray(toolbarOptions.items)) { return } const workflowStartItemIndex = toolbarOptions.items .map((item: any) => item.name) .indexOf('workflowStart') if (workflowStartItemIndex >= 0) { const startEnabled = selectedRowsData.length > 0 && selectedRowsData.every((row) => isWorkflowNotStarted(row, workflowOptions)) const startOptionPath = `toolbar.items[${workflowStartItemIndex}].options.disabled` const nextStartDisabled = !startEnabled if (component.option(startOptionPath) !== nextStartDisabled) { component.option(startOptionPath, nextStartDisabled) } } const currentUserIdentities = getCurrentUserWorkflowIdentities(currentUser) approvalCriteria.forEach((criteria) => { const toolbarItemIndex = toolbarOptions.items .map((item: any) => item.name) .indexOf(`workflowApproval_${criteria.id}`) if (toolbarItemIndex < 0) { return } const enabled = selectedRowsData.length > 0 && selectedRowsData.every((row) => isWorkflowApprovalCriteriaActive(row, workflowOptions, criteria.title, currentUserIdentities), ) const optionPath = `toolbar.items[${toolbarItemIndex}].options.disabled` const nextDisabled = !enabled if (component.option(optionPath) !== nextDisabled) { component.option(optionPath, nextDisabled) } }) } function WorkflowApprovalDecisionDialog({ criteriaTitle, keys, listFormCode, criteriaId, onCancel, onCompleted, }: { criteriaTitle: string keys: unknown[] listFormCode: string criteriaId: string onCancel: () => void onCompleted: () => void }) { const { translate } = useLocalization() const [note, setNote] = useState('') const [submitting, setSubmitting] = useState(false) const decide = async (approved: boolean) => { setSubmitting(true) try { await Promise.all( keys.map((key) => workflowService.decideWorkflow( listFormCode, [key], approved, note, criteriaId, ), ), ) onCompleted() } catch (error: any) { toast.push( {error?.response?.data?.error?.message || error?.response?.data?.message || error?.message || 'Workflow karari verilemedi.'} , { placement: 'top-end' }, ) } finally { setSubmitting(false) } } return ( <>
{criteriaTitle}

{keys.length} kayit icin workflow karari verilecek.