diff --git a/src/components/EntityList.jsx b/src/components/EntityList.jsx index 9ff1abc..8d62028 100644 --- a/src/components/EntityList.jsx +++ b/src/components/EntityList.jsx @@ -19,9 +19,11 @@ import { TbListDetails, TbPencil, TbPhoto, + TbPlayerPlay, TbPlaylist, TbRefresh, TbSchool, + TbSitemap, TbTrash, TbTrophy, TbUsers, @@ -96,6 +98,8 @@ const ACTION_ICONS = { topic: TbBook, viewers: TbEye, duplicate: TbCopy, + editor: TbSitemap, + monitor: TbPlayerPlay, report: TbChartBar, questions: TbHelpCircle, responses: TbPlaylist, @@ -238,6 +242,25 @@ export default function EntityList({ module: config }) { }; }, [config.module]); + const onDuplicate = async (row) => { + if (typeof config.duplicateItem !== 'function') return; + setBusyAction(`duplicate-${row.id}`); + setError(''); + try { + const result = await config.duplicateItem(row); + if (!result?.ok) { + setError(result?.message || 'Unable to duplicate'); + return; + } + setMessage(result.message || 'Duplicated'); + load(); + } catch (err) { + setError(err.message || 'Unable to duplicate'); + } finally { + setBusyAction(''); + } + }; + const onDelete = async (id) => { if (!window.confirm('Delete this record?')) return; const path = `${config.module}/delete`; @@ -404,6 +427,20 @@ export default function EntityList({ module: config }) { ); } if (button.name === 'duplicate') { + if (typeof config.duplicateItem === 'function') { + return ( + + ); + } const href = typeof button.to === 'function' ? button.to(row) : button.to || `${config.baseUrl}/addnew?duplicate=${row.id}`; return ( diff --git a/src/layouts/menu.js b/src/layouts/menu.js index 3cd3f9a..d85c840 100644 --- a/src/layouts/menu.js +++ b/src/layouts/menu.js @@ -11,6 +11,11 @@ import { const hasAny = (permissions, keys) => keys.some((key) => permissions.includes(key)); +const ZENOVA_DASHBOARD_ROLES = [ + '1650360764-424692c0-654a-4af2-zenova-64ce55c66088', // HO + '1787808155-9945ee91-7a22-4be6-b184-733610e66ddc', // Admin +]; + const child = (key, label, url, permission, extra = {}) => ({ key, label, @@ -109,6 +114,15 @@ export function getMenuItems({ permissions = [], roles = [], domain = '', email }); } + if (domain === 'zenova' && roles.some((role) => ZENOVA_DASHBOARD_ROLES.includes(role))) { + items.push({ + key: 'glipiq-sitazit', + label: 'Custom Dashboard', + icon: LuFileText, + url: '/zenovareport/dashboard', + }); + } + if (domain === 'pulmocare' && roles.includes('1650360330-5bf35594-674a-4f31-HO_Audit-47738d2151ab')) { items.push({ key: 'healthy-lung', @@ -266,23 +280,18 @@ export function getMenuItems({ permissions = [], roles = [], domain = '', email 'formaction_get_all', 'all_data_access', 'formexports_get_all', - 'quiz_get_all', 'roles_leaderboard_get_all', 'workflow_definition_get_all', 'workflow_instance_get_all', - 'formproduct_get_all', ]) ) { const formChildren = [ child('forms', 'Forms', '/formentitys', 'formentity_get_all'), child('formhooks', 'Form Hook', '/formhooks', 'form_hooks_get_all'), - child('surveys', 'Survey', '/surveys', 'survey_get_all'), - child('quizs', 'Quiz', '/quizs', 'quiz_get_all'), child('formactions', 'Quick Action', '/formactions', 'formaction_get_all'), child('formexports', 'Form Data Export', '/formexports', 'formexports_get_all'), child('reminders', 'Reminders', '/reminders', 'reminder_get_all'), child('leaderboards', 'Leaderboard', '/leaderboards', 'roles_leaderboard_get_all'), - child('formproducts', 'Products', '/formproducts', 'formproduct_get_all'), ].filter((item) => !item.permission || permissions.includes(item.permission)); if (hasAny(permissions, ['workflow_definition_get_all', 'workflow_instance_get_all'])) { diff --git a/src/modules/banners.js b/src/modules/banners.js index 78eb7f4..a9d55c7 100644 --- a/src/modules/banners.js +++ b/src/modules/banners.js @@ -7,14 +7,7 @@ function splitHash(value) { } async function formOptions() { - const values = {}; - const [forms, surveys, quizzes] = await Promise.all([ - dropdownMap('form', 'formentity', (item) => item.title || item.name), - dropdownMap('form', 'survey', (item) => `${item.title || item.name} (Survey)`), - dropdownMap('form', 'quiz', (item) => `${item.name || item.title} (Quiz)`), - ]); - Object.assign(values, forms, surveys, quizzes); - return values; + return dropdownMap('form', 'formentity', (item) => item.title || item.name); } const banners = { @@ -68,8 +61,6 @@ const banners = { values: { internal_action: 'Internal Action', form: 'Web Form', - survey: 'Surveys', - quiz: 'Quizs', doctorlist: 'Doctor List', external: 'External Webpage', external_with_token: 'External Webpage with Token', diff --git a/src/modules/formactions.js b/src/modules/formactions.js index fe87cd2..63ba735 100644 --- a/src/modules/formactions.js +++ b/src/modules/formactions.js @@ -4,14 +4,7 @@ import { splitHash } from '@/helpers/metric'; const STATUS = { active: 'Active', disabled: 'Disabled' }; async function formOptions() { - const values = {}; - const [forms, surveys, quizzes] = await Promise.all([ - dropdownMap('form', 'formentity', (item) => item.title || item.name), - dropdownMap('form', 'survey', (item) => `${item.title || item.name} (Survey)`), - dropdownMap('form', 'quiz', (item) => `${item.name || item.title} (Quiz)`), - ]); - Object.assign(values, forms, surveys, quizzes); - return values; + return dropdownMap('form', 'formentity', (item) => item.title || item.name); } function joinHash(value) { @@ -52,8 +45,6 @@ const formactions = { required: true, values: { form: 'Web Form', - survey: 'Surveys', - quiz: 'Quizs', doctorlist: 'Doctor List', external: 'External Webpage', internal_action: 'Internal Action', diff --git a/src/modules/leaderboards.js b/src/modules/leaderboards.js index 7a4c2a6..68e2f98 100644 --- a/src/modules/leaderboards.js +++ b/src/modules/leaderboards.js @@ -1,3 +1,4 @@ +import { signedGet, isSuccess } from '@/api/client'; import { dropdownMap } from '@/api/dropdowns'; import { DEFAULT_INTERVAL_VALUES, @@ -10,6 +11,12 @@ import { const STATUS = { active: 'Active', disabled: 'Disabled' }; const YES_NO = { yes: 'Yes', no: 'No' }; +const MANAGER_LEVELS = { + l1manager: 'L1 Manager', + l2manager: 'L2 Manager', + l3manager: 'L3 Manager', + l4manager: 'L4 Manager', +}; function asArray(value) { if (Array.isArray(value)) return value.filter((part) => part !== null && part !== undefined && part !== ''); @@ -39,7 +46,17 @@ const leaderboards = { sort_order: item.sort_order ?? 0, })), formFields: async () => { - const [metrics, roles] = await Promise.all([metricOptions(), dropdownMap('ums', 'roles', (item) => item.name)]); + const [metrics, roles, userAttrRes] = await Promise.all([ + metricOptions(), + dropdownMap('ums', 'roles', (item) => item.name), + signedGet('ums', 'userattribute/get_all'), + ]); + const userAttributes = {}; + const userAttrItems = isSuccess(userAttrRes) ? userAttrRes.data?.items || [] : []; + userAttrItems.forEach((attr) => { + if (!attr?.code || attr.status === 'disabled') return; + userAttributes[attr.code] = attr.attribute || attr.code; + }); return [ { key: 'name', title: 'Name', type: 'text', required: true }, { key: 'sort_order', title: 'Sort Order', type: 'number', required: true }, @@ -68,6 +85,20 @@ const leaderboards = { required: true, values: { all: 'All', loggedin: 'Logged In' }, }, + { key: 'show_units_export', title: 'Show Units Export', type: 'select', required: true, values: YES_NO, default: 'yes' }, + { key: 'show_user_attributes', title: 'Show User Attributes', type: 'select', required: true, values: YES_NO, default: 'no' }, + { + key: 'user_attributes', + title: 'Include User Attributes', + type: 'multiselect', + values: userAttributes, + default: [], + showIf: { show_user_attributes: 'yes' }, + }, + { key: 'show_normal_metrics', title: 'Show Normal Metrics', type: 'select', required: true, values: YES_NO, default: 'yes' }, + { key: 'show_combined_metrics', title: 'Show Combined Metrics', type: 'select', required: true, values: YES_NO, default: 'yes' }, + { key: 'manager_details', title: 'Manager Details', type: 'multiselect', values: MANAGER_LEVELS, default: ['l1manager'] }, + { key: 'total_units_name', title: 'Total Units Name', type: 'text', default: '' }, { key: 'status', title: 'Status', type: 'select', required: true, values: STATUS }, ]; }, @@ -76,6 +107,14 @@ const leaderboards = { next.sort_order = next.sort_order ?? 0; next.show_positions = next.show_positions || 'no'; next.show_positions_type = next.show_positions_type || 'all'; + next.show_units_export = next.show_units_export || 'yes'; + next.show_user_attributes = next.show_user_attributes === 'yes' ? 'yes' : 'no'; + next.user_attributes = asArray(next.user_attributes); + next.show_normal_metrics = next.show_normal_metrics || 'yes'; + next.show_combined_metrics = next.show_combined_metrics || 'yes'; + next.manager_details = asArray(next.manager_details); + if (!next.manager_details.length) next.manager_details = ['l1manager']; + next.total_units_name = next.total_units_name || ''; next.status = next.status || 'active'; next.default_interval = next.default_interval ?? ''; next.validity_start = toDateInput(next.validity_start); @@ -119,6 +158,14 @@ const leaderboards = { weight: row.weight === '' || row.weight == null ? '1' : String(row.weight), })); payload.combined_metrics = Array.isArray(payload.combined_metrics) ? payload.combined_metrics : []; + payload.show_units_export = payload.show_units_export || 'yes'; + payload.show_user_attributes = payload.show_user_attributes === 'yes' ? 'yes' : 'no'; + payload.user_attributes = asArray(payload.user_attributes); + payload.show_normal_metrics = payload.show_normal_metrics || 'yes'; + payload.show_combined_metrics = payload.show_combined_metrics || 'yes'; + payload.manager_details = asArray(payload.manager_details); + if (!payload.manager_details.length) payload.manager_details = ['l1manager']; + payload.total_units_name = payload.total_units_name || ''; return payload; }, }; diff --git a/src/modules/magmedias.js b/src/modules/magmedias.js index 04dcdc7..c2a653d 100644 --- a/src/modules/magmedias.js +++ b/src/modules/magmedias.js @@ -2,18 +2,11 @@ import { signedGet, isSuccess } from '@/api/client'; import { dropdownMap } from '@/api/dropdowns'; const FILE_TYPES = ['image', 'video', 'pdf', 'audio', 'scorm']; -const FORM_TYPES = ['form', 'quiz', 'survey']; +const FORM_TYPES = ['form']; const LINK_TYPES = ['form', 'external']; async function formOptions() { - const values = {}; - const [forms, surveys, quizzes] = await Promise.all([ - dropdownMap('form', 'formentity', (item) => item.title || item.name), - dropdownMap('form', 'survey', (item) => `${item.title || item.name} (Survey)`), - dropdownMap('form', 'quiz', (item) => `${item.name || item.title} (Quiz)`), - ]); - Object.assign(values, forms, surveys, quizzes); - return values; + return dropdownMap('form', 'formentity', (item) => item.title || item.name); } async function metricOptions() { @@ -102,8 +95,6 @@ const magmedias = { audio: 'Audio', gallery: 'Gallery', form: 'Form', - quiz: 'Quiz', - survey: 'Survey', external: 'External Url', scorm: 'Scorm', }, diff --git a/src/modules/metriccampaigns.js b/src/modules/metriccampaigns.js index 53146bd..92002f9 100644 --- a/src/modules/metriccampaigns.js +++ b/src/modules/metriccampaigns.js @@ -67,6 +67,12 @@ function normalizeCombined(row) { } const YES_NO = { yes: 'Yes', no: 'No' }; +const MANAGER_LEVELS = { + l1manager: 'L1 Manager', + l2manager: 'L2 Manager', + l3manager: 'L3 Manager', + l4manager: 'L4 Manager', +}; const metriccampaigns = { service: 'metric', @@ -96,6 +102,7 @@ const metriccampaigns = { signedGet('ums', 'userattribute/get_all'), ]); const attributes = {}; + const userAttributes = {}; const attributesMeta = {}; const attrItems = isSuccess(attrsRes) ? attrsRes.data?.items || [] : []; attrItems.forEach((attr) => { @@ -109,6 +116,7 @@ const metriccampaigns = { options[value] = String(opt.display ?? value); }); attributes[code] = `${attr.attribute || code} (${code})`; + userAttributes[code] = attr.attribute || code; attributesMeta[code] = { code, attribute: attr.attribute || code, @@ -171,6 +179,20 @@ const metriccampaigns = { required: true, values: { all: 'All', loggedin: 'Logged In' }, }, + { key: 'show_units_export', title: 'Show Units Export', type: 'select', required: true, values: YES_NO, default: 'yes' }, + { key: 'show_user_attributes', title: 'Show User Attributes', type: 'select', required: true, values: YES_NO, default: 'no' }, + { + key: 'user_attributes', + title: 'Include User Attributes', + type: 'multiselect', + values: userAttributes, + default: [], + showIf: { show_user_attributes: 'yes' }, + }, + { key: 'show_normal_metrics', title: 'Show Normal Metrics', type: 'select', required: true, values: YES_NO, default: 'yes' }, + { key: 'show_combined_metrics', title: 'Show Combined Metrics', type: 'select', required: true, values: YES_NO, default: 'yes' }, + { key: 'manager_details', title: 'Manager Details', type: 'multiselect', values: MANAGER_LEVELS, default: ['l1manager'] }, + { key: 'total_units_name', title: 'Total Units Name', type: 'text', default: '' }, { key: 'highlight_top', title: 'Highlight Top', type: 'text', placeholder: 'e.g. 10, 20, 30' }, { key: 'highlight_color', title: 'Highlight Color', type: 'text', placeholder: 'R-G-B-A or CSS color' }, { @@ -214,6 +236,14 @@ const metriccampaigns = { next.validity_end = toDateInput(next.validity_end); if (next.teams_rank) next.teams_rank = String(next.teams_rank).toLowerCase().slice(0, 2); if (next.sort_order === '' || next.sort_order == null) next.sort_order = 0; + next.show_units_export = next.show_units_export || 'yes'; + next.show_user_attributes = next.show_user_attributes === 'yes' ? 'yes' : 'no'; + next.user_attributes = asArray(next.user_attributes); + next.show_normal_metrics = next.show_normal_metrics || 'yes'; + next.show_combined_metrics = next.show_combined_metrics || 'yes'; + next.manager_details = asArray(next.manager_details); + if (!next.manager_details.length) next.manager_details = ['l1manager']; + next.total_units_name = next.total_units_name || ''; return next; }, beforeSave: (data) => { @@ -254,6 +284,13 @@ const metriccampaigns = { reporting_roles: asArray(data.reporting_roles), intervals: asArray(data.intervals), sort_order: Number.parseInt(data.sort_order, 10) || 0, + show_units_export: data.show_units_export || 'yes', + show_user_attributes: data.show_user_attributes === 'yes' ? 'yes' : 'no', + user_attributes: asArray(data.user_attributes), + show_normal_metrics: data.show_normal_metrics || 'yes', + show_combined_metrics: data.show_combined_metrics || 'yes', + manager_details: asArray(data.manager_details).length ? asArray(data.manager_details) : ['l1manager'], + total_units_name: data.total_units_name || '', validity_start: toApiDate(data.validity_start), validity_end: toApiDate(data.validity_end), }; diff --git a/src/modules/metricviews.js b/src/modules/metricviews.js index bce992b..4ea537f 100644 --- a/src/modules/metricviews.js +++ b/src/modules/metricviews.js @@ -189,8 +189,10 @@ const metricviews = { types: VIEW_COMBINED_TYPES, }, { key: 'report_roles', title: 'Data Report from Roles', type: 'multiselect', required: true, values: roles }, + { key: 'team_leaderboard_widget_roles', title: 'Team Leaderboard Widget Roles', type: 'multiselect', values: roles }, { key: 'status', title: 'Status', type: 'select', required: true, values: { active: 'Active', disabled: 'Disabled' } }, { key: 'show_positions', title: 'Show Positions', type: 'select', required: true, values: { yes: 'Yes', no: 'No' } }, + { key: 'stateheadwise_leaderboard', title: 'Stateheadwise Leaderboard', type: 'select', required: true, values: { yes: 'Yes', no: 'No' }, default: 'no' }, { key: 'show_positions_type', title: 'Show Positions Type', @@ -218,6 +220,7 @@ const metricviews = { show_positions: 'yes', show_positions_type: 'all', show_user_attributes: 'no', + stateheadwise_leaderboard: 'no', user_attributes: [], list_chart_type: 'progressbar', value_views: ['value'], @@ -226,6 +229,7 @@ const metricviews = { }; next.designations = asArray(next.designations); next.report_roles = asArray(next.report_roles); + next.team_leaderboard_widget_roles = asArray(next.team_leaderboard_widget_roles); next.graphical_views = asArray(next.graphical_views); next.details_views = asArray(next.details_views); next.trend_views = asArray(next.trend_views); @@ -241,6 +245,7 @@ const metricviews = { next.intervals = asArray(next.intervals); next.contact_attributes = asArray(next.contact_attributes); next.show_user_attributes = next.show_user_attributes === 'yes' ? 'yes' : 'no'; + next.stateheadwise_leaderboard = next.stateheadwise_leaderboard === 'yes' ? 'yes' : 'no'; next.user_attributes = asArray(next.user_attributes); next.combined_metrics = parseCombinedMetrics(next.combined_metrics); next.validity_start = toDateInput(next.validity_start); @@ -299,6 +304,7 @@ const metricviews = { metrics, designations: asArray(data.designations), report_roles: asArray(data.report_roles), + team_leaderboard_widget_roles: asArray(data.team_leaderboard_widget_roles), graphical_views: asArray(data.graphical_views), details_views: asArray(data.details_views), trend_views: asArray(data.trend_views), @@ -310,6 +316,7 @@ const metricviews = { intervals: asArray(data.intervals), contact_attributes: asArray(data.contact_attributes), show_user_attributes: data.show_user_attributes === 'yes' ? 'yes' : 'no', + stateheadwise_leaderboard: data.stateheadwise_leaderboard === 'yes' ? 'yes' : 'no', user_attributes: asArray(data.user_attributes), combined_metrics: stringifyCombinedMetrics( (data.combined_metrics || []) diff --git a/src/modules/milestones.js b/src/modules/milestones.js index 1e52cf3..ab0f345 100644 --- a/src/modules/milestones.js +++ b/src/modules/milestones.js @@ -4,6 +4,12 @@ import { metricOptions, toDateInput } from '@/helpers/metric'; const STATUS = { active: 'Active', disabled: 'Disabled' }; const YES_NO = { yes: 'Yes', no: 'No' }; +const MANAGER_LEVELS = { + l1manager: 'L1 Manager', + l2manager: 'L2 Manager', + l3manager: 'L3 Manager', + l4manager: 'L4 Manager', +}; const DAYS = { MON: 'Monday', TUE: 'Tuesday', @@ -153,7 +159,7 @@ const milestones = { { key: 'status', title: 'Status', type: 'select' }, ], formFields: async () => { - const [metrics, roles, badges, categories, images] = await Promise.all([ + const [metrics, roles, badges, categories, images, userAttrRes] = await Promise.all([ metricOptions(), dropdownMap('ums', 'roles', (item) => item.name), getAllItems('recognition', 'badge_list/get_all').then(titleMap), @@ -165,7 +171,14 @@ const milestones = { }); return values; }), + signedGet('ums', 'userattribute/get_all'), ]); + const userAttributes = {}; + const userAttrItems = isSuccess(userAttrRes) ? userAttrRes.data?.items || [] : []; + userAttrItems.forEach((attr) => { + if (!attr?.code || attr.status === 'disabled') return; + userAttributes[attr.code] = attr.attribute || attr.code; + }); return [ { key: 'title', title: 'Title', type: 'text', required: true }, { key: 'interval_start_date', title: 'Interval Start Date', type: 'date', required: true }, @@ -195,6 +208,20 @@ const milestones = { required: true, values: { all: 'All', loggedin: 'Logged In' }, }, + { key: 'show_units_export', title: 'Show Units Export', type: 'select', required: true, values: YES_NO, default: 'yes' }, + { key: 'show_user_attributes', title: 'Show User Attributes', type: 'select', required: true, values: YES_NO, default: 'no' }, + { + key: 'user_attributes', + title: 'Include User Attributes', + type: 'multiselect', + values: userAttributes, + default: [], + showIf: { show_user_attributes: 'yes' }, + }, + { key: 'show_normal_metrics', title: 'Show Normal Metrics', type: 'select', required: true, values: YES_NO, default: 'yes' }, + { key: 'show_combined_metrics', title: 'Show Combined Metrics', type: 'select', required: true, values: YES_NO, default: 'yes' }, + { key: 'manager_details', title: 'Manager Details', type: 'multiselect', values: MANAGER_LEVELS, default: ['l1manager'] }, + { key: 'total_units_name', title: 'Total Units Name', type: 'text', default: '' }, { key: 'send_report', title: 'Send Report', type: 'select', required: true, values: YES_NO }, { key: 'performance_period', @@ -327,6 +354,14 @@ const milestones = { next.show_target_average = fromBool(reportConfig.show_target_average ?? false); next.show_overall_average = fromBool(reportConfig.show_overall_average ?? false); next.send_report = fromBool(next.send_report ?? false); + next.show_units_export = next.show_units_export || 'yes'; + next.show_user_attributes = next.show_user_attributes === 'yes' ? 'yes' : 'no'; + next.user_attributes = toStringList(next.user_attributes); + next.show_normal_metrics = next.show_normal_metrics || 'yes'; + next.show_combined_metrics = next.show_combined_metrics || 'yes'; + next.manager_details = toStringList(next.manager_details); + if (!next.manager_details.length) next.manager_details = ['l1manager']; + next.total_units_name = next.total_units_name || ''; return next; }, beforeSave: (data) => { @@ -340,6 +375,14 @@ const milestones = { payload.milestones = mapMilestonesForSave(payload.milestones); const daysOrDates = payload.days_or_dates || 'days'; + payload.show_units_export = payload.show_units_export || 'yes'; + payload.show_user_attributes = payload.show_user_attributes === 'yes' ? 'yes' : 'no'; + payload.user_attributes = toStringList(payload.user_attributes); + payload.show_normal_metrics = payload.show_normal_metrics || 'yes'; + payload.show_combined_metrics = payload.show_combined_metrics || 'yes'; + payload.manager_details = toStringList(payload.manager_details); + if (!payload.manager_details.length) payload.manager_details = ['l1manager']; + payload.total_units_name = payload.total_units_name || ''; payload.send_report = toBool(payload.send_report ?? 'no'); payload.report_config = { days_or_dates: daysOrDates, diff --git a/src/modules/roles.js b/src/modules/roles.js index 9a6ddb8..e2b7dd8 100644 --- a/src/modules/roles.js +++ b/src/modules/roles.js @@ -8,7 +8,7 @@ const roles = { baseUrl: '/role', listPath: '/roles', listFields: [ - { key: 'actions', title: 'Actions', buttons: ['edit', 'delete'] }, + { key: 'actions', title: 'Actions', buttons: ['edit', 'duplicate', 'delete'] }, { key: 'name', title: 'Role Name', type: 'text' }, { key: 'code', title: 'Role Code', type: 'text' }, { key: 'status', title: 'Status', type: 'select' }, @@ -31,14 +31,20 @@ const roles = { { key: 'permissions', title: 'permissions', type: 'bigmultiselect', required: true, values: permissions, fullwidth: true }, ]; }, - beforeForm: (item) => ({ - ...item, - permissions: Array.isArray(item.permissions) - ? item.permissions.map(String) - : String(item.permissions || '') - .split(/[#~,]/) - .filter(Boolean), - }), + beforeForm: (item, _mode, context) => { + const next = { + ...item, + permissions: Array.isArray(item.permissions) + ? item.permissions.map(String) + : String(item.permissions || '') + .split(/[#~,]/) + .filter(Boolean), + }; + if (context?.duplicate && next.code && !String(next.code).endsWith('-copy')) { + next.code = `${next.code}-copy`; + } + return next; + }, }; export default roles; diff --git a/src/modules/workflows.js b/src/modules/workflows.js index da3d430..128a158 100644 --- a/src/modules/workflows.js +++ b/src/modules/workflows.js @@ -1,4 +1,4 @@ -import { isSuccess, signedGet } from '@/api/client'; +import { isSuccess, signedGet, signedPost } from '@/api/client'; const workflows = { service: 'workflow', @@ -15,11 +15,13 @@ const workflows = { 'edit', 'delete', { name: 'editor', to: (row) => `/workflow/editor?id=${row.id}`, title: 'Editor' }, - { name: 'monitor', to: (row) => `/workflow/monitor?definition_id=${row.id}`, title: 'Monitor' }, + { name: 'monitor', to: (row) => `/workflow/monitor?definition_id=${row.id}`, title: 'Runs' }, + 'duplicate', ], }, { key: 'name', title: 'Name', type: 'text' }, { key: 'entity', title: 'Entity', type: 'text' }, + { key: 'run_on', title: 'Run on', type: 'text' }, { key: 'published_version', title: 'Published', type: 'text' }, { key: 'status', title: 'Status', type: 'text' }, ], @@ -54,6 +56,15 @@ const workflows = { values: forms, showIf: { entity: 'form_submission' }, }, + { + key: 'run_on', + title: 'Run on', + type: 'select', + required: true, + values: { insert: 'Insert', update: 'Update', both: 'Both' }, + default: 'insert', + showIf: { entity: 'form_submission' }, + }, ]; }, beforeForm: (item) => ({ @@ -61,10 +72,33 @@ const workflows = { entity: item.entity || 'manual', description: item.description || '', form_id: item.form_id || '', + run_on: item.id ? (item.run_on || '') : (item.run_on || 'insert'), }), + duplicateItem: async (row) => { + const response = await signedGet('workflow', 'workflow_definition/get_one', { id: row.id }); + if (!isSuccess(response)) return { ok: false, message: response?.message || 'Unable to load workflow' }; + const item = response.data?.item || {}; + const created = await signedPost('workflow', 'workflow_definition/create', { + name: `${item.name || 'Workflow'} (Copy)`, + description: item.description || '', + entity: item.entity || 'manual', + form_id: item.entity === 'form_submission' ? item.form_id || '' : '', + ...(item.entity === 'form_submission' ? { run_on: item.run_on || '' } : {}), + nodes: item.nodes || [], + edges: item.edges || [], + layout: item.layout || {}, + }); + if (!isSuccess(created)) return { ok: false, message: created?.message || 'Unable to duplicate workflow' }; + return { ok: true, message: 'Workflow duplicated' }; + }, beforeSave: (data) => { const payload = { ...data }; - if (payload.entity !== 'form_submission') payload.form_id = ''; + if (payload.entity !== 'form_submission') { + payload.form_id = ''; + delete payload.run_on; + } else if (!payload.run_on) { + delete payload.run_on; + } return payload; }, }; diff --git a/src/routes/index.jsx b/src/routes/index.jsx index 3e47784..472fa07 100644 --- a/src/routes/index.jsx +++ b/src/routes/index.jsx @@ -35,10 +35,10 @@ import milestones from '@/modules/milestones'; import formentitys from '@/modules/formentitys'; import formquestions from '@/modules/formquestions'; import formhooks from '@/modules/formhooks'; -import surveys from '@/modules/surveys'; -import surveyquestions from '@/modules/surveyquestions'; -import quizs from '@/modules/quizs'; -import quizquestions from '@/modules/quizquestions'; +// import surveys from '@/modules/surveys'; +// import surveyquestions from '@/modules/surveyquestions'; +// import quizs from '@/modules/quizs'; +// import quizquestions from '@/modules/quizquestions'; import formactions from '@/modules/formactions'; import formexports from '@/modules/formexports'; import reminders from '@/modules/reminders'; @@ -56,7 +56,7 @@ import rcpaproductattributes from '@/modules/rcpaproductattributes'; import brands from '@/modules/brands'; import products from '@/modules/products'; import productattributes from '@/modules/productattributes'; -import formproducts from '@/modules/formproducts'; +// import formproducts from '@/modules/formproducts'; import formpersonalizations from '@/modules/formpersonalizations'; import courses from '@/modules/courses'; import lessons from '@/modules/lessons'; @@ -169,10 +169,10 @@ export const routes = [ ...moduleRoutes(formentitys), ...moduleRoutes(formquestions), ...moduleRoutes(formhooks), - ...moduleRoutes(surveys), - ...moduleRoutes(surveyquestions), - ...moduleRoutes(quizs), - ...moduleRoutes(quizquestions), + // ...moduleRoutes(surveys), + // ...moduleRoutes(surveyquestions), + // ...moduleRoutes(quizs), + // ...moduleRoutes(quizquestions), ...moduleRoutes(formactions), ...moduleRoutes(formexports), ...moduleRoutes(reminders), @@ -193,7 +193,7 @@ export const routes = [ ...moduleRoutes(brands), ...moduleRoutes(products), ...moduleRoutes(productattributes), - ...moduleRoutes(formproducts), + // ...moduleRoutes(formproducts), ...moduleRoutes(formpersonalizations), ...moduleRoutes(courses), ...moduleRoutes(lessons), diff --git a/src/views/workflow/Editor.jsx b/src/views/workflow/Editor.jsx index 15f7fca..fd8cd50 100644 --- a/src/views/workflow/Editor.jsx +++ b/src/views/workflow/Editor.jsx @@ -12,14 +12,50 @@ import { } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; import { Button, Card, Col, Container, Form, Modal, Row, Spinner } from 'react-bootstrap'; +import Select from 'react-select'; import PageBreadcrumb from '@/components/PageBreadcrumb'; import { isSuccess, signedGet, signedPost } from '@/api/client'; -import { defaultConfig, fromFlow, labelFor, toFlow, NODE_TYPES, edgeLabel } from '@/views/workflow/graph'; +import { dropdownMap } from '@/api/dropdowns'; +import { defaultConfig, fromFlow, labelFor, toFlow, NODE_TYPES, edgeLabel, contactMappings } from '@/views/workflow/graph'; function newId() { return crypto.randomUUID(); } +const MANAGER_LEVELS = [ + { value: 'l1manager', label: 'L1 Manager' }, + { value: 'l2manager', label: 'L2 Manager' }, + { value: 'l3manager', label: 'L3 Manager' }, + { value: 'l4manager', label: 'L4 Manager' }, +]; + +function approverGroups(roles, users) { + return [ + { label: 'Manager', options: MANAGER_LEVELS }, + { label: 'Roles', options: Object.entries(roles).map(([value, label]) => ({ value, label })) }, + { label: 'Users', options: Object.entries(users).map(([value, label]) => ({ value, label })) }, + ]; +} + +function ApproverSelect({ values, groups, onChange }) { + const known = groups.flatMap((group) => group.options || []); + const saved = (values || []).filter((value) => !known.some((option) => option.value === value)).map((value) => ({ value, label: value })); + const options = saved.length ? groups.concat({ label: 'Saved', options: saved }) : groups; + const byValue = new Map(known.concat(saved).map((option) => [option.value, option])); + return ( +