diff --git a/src/components/EntityForm.jsx b/src/components/EntityForm.jsx
index 11a47c6..30d48b9 100644
--- a/src/components/EntityForm.jsx
+++ b/src/components/EntityForm.jsx
@@ -18,6 +18,7 @@ import RankRolesField from '@/components/form/RankRolesField';
import ReminderRowsField from '@/components/form/ReminderRowsField';
import DependentSelectField from '@/components/form/DependentSelectField';
import BigMultiSelectField from '@/components/form/BigMultiSelectField';
+import TemplatePositionsField from '@/components/form/TemplatePositionsField';
import S3FileField from '@/components/S3FileField';
import { parseCombinedMetrics } from '@/helpers/metric';
@@ -30,7 +31,7 @@ function defaultValue(field, item) {
const raw = fromItem !== undefined && fromItem !== null ? fromItem : field.default;
if (raw === undefined || raw === null) {
if (field.type === 'multiselect' || field.type === 'bigmultiselect' || field.type === 'checkbox') return [];
- if (field.type === 'mapping' || field.type === 'color_ranges' || field.type === 'ranking_attributes' || field.type === 'combined_metrics' || field.type === 'milestonemapping' || field.type === 'email_notifications' || field.type === 'rank_roles' || field.type === 'reminders') {
+ if (field.type === 'mapping' || field.type === 'color_ranges' || field.type === 'ranking_attributes' || field.type === 'combined_metrics' || field.type === 'milestonemapping' || field.type === 'email_notifications' || field.type === 'rank_roles' || field.type === 'reminders' || field.type === 'template_positions') {
return [];
}
if (field.type === 'approverlist') return ['l1manager'];
@@ -49,7 +50,7 @@ function defaultValue(field, item) {
}
return Array.isArray(raw) ? raw : [];
}
- if ((field.type === 'mapping' || field.type === 'color_ranges' || field.type === 'ranking_attributes' || field.type === 'milestonemapping' || field.type === 'rank_roles' || field.type === 'reminders') && !Array.isArray(raw)) {
+ if ((field.type === 'mapping' || field.type === 'color_ranges' || field.type === 'ranking_attributes' || field.type === 'milestonemapping' || field.type === 'rank_roles' || field.type === 'reminders' || field.type === 'template_positions') && !Array.isArray(raw)) {
return [];
}
if (field.type === 'multiselect' || field.type === 'bigmultiselect') {
@@ -94,12 +95,28 @@ function defaultValue(field, item) {
return raw === undefined || raw === null ? '' : String(raw);
}
+function toColorPickerValue(value, fallback = '#ffffff') {
+ const raw = String(value || '').trim();
+ if (/^#[0-9A-Fa-f]{6}$/.test(raw)) return raw;
+ if (/^#[0-9A-Fa-f]{3}$/.test(raw)) {
+ return `#${raw[1]}${raw[1]}${raw[2]}${raw[2]}${raw[3]}${raw[3]}`;
+ }
+ if (/^[0-9A-Fa-f]{6}$/.test(raw)) return `#${raw}`;
+ const fallbackRaw = String(fallback || '').trim();
+ if (/^#[0-9A-Fa-f]{6}$/.test(fallbackRaw)) return fallbackRaw;
+ return '#ffffff';
+}
+
function isFieldVisible(field, values) {
if (field.workflowOnly && values?.workflow !== 'yes') return false;
if (!field.showIf) return true;
return Object.entries(field.showIf).every(([key, expected]) => {
const actual = values?.[key];
- if (Array.isArray(expected)) return expected.includes(actual);
+ if (Array.isArray(expected)) {
+ if (Array.isArray(actual)) return expected.some((value) => actual.map(String).includes(String(value)));
+ return expected.map(String).includes(String(actual));
+ }
+ if (Array.isArray(actual)) return actual.map(String).includes(String(expected));
return actual === expected;
});
}
@@ -114,7 +131,7 @@ function applyExclusive(next, previous, exclusiveValue) {
return next;
}
-function FieldControl({ field, control, register, moduleName, onUploadingChange }) {
+function FieldControl({ field, control, register, watch, moduleName, onUploadingChange }) {
const common = { className: 'form-control', id: `field-${field.key}` };
if (field.type === 'hidden') {
return ;
@@ -224,9 +241,10 @@ function FieldControl({ field, control, register, moduleName, onUploadingChange
rhf.onChange(e.target.value)}
- style={{ width: 50, padding: 2 }}
+ style={{ width: 50, height: 38, padding: 2, colorScheme: 'light' }}
/>
-
-
-
+ (
+
+
rhf.onChange('yes')}
+ />
+ rhf.onChange('no')}
+ />
+
+ )}
+ />
);
}
if (field.type === 'radio') {
@@ -264,11 +307,33 @@ function FieldControl({ field, control, register, moduleName, onUploadingChange
}
if (field.type === 'checkbox') {
return (
-
- {Object.entries(field.values || {}).map(([value, label]) => (
-
- ))}
-
+ {
+ const selected = Array.isArray(rhf.value) ? rhf.value.map(String) : [];
+ return (
+
+ {Object.entries(field.values || {}).map(([value, label]) => (
+
{
+ const next = event.target.checked
+ ? [...selected, value]
+ : selected.filter((item) => item !== String(value));
+ rhf.onChange(next);
+ }}
+ />
+ ))}
+
+ );
+ }}
+ />
);
}
if (field.type === 'bigmultiselect') {
@@ -411,6 +476,16 @@ function FieldControl({ field, control, register, moduleName, onUploadingChange
if (field.type === 'reminders') {
return ;
}
+ if (field.type === 'template_positions') {
+ return (
+
+ );
+ }
if (field.type === 'dependent_select') {
return ;
}
@@ -581,7 +656,7 @@ export default function EntityForm({ module: config }) {
{fields.map((field) => {
if (!isFieldVisible(field, watched)) return null;
return field.type === 'hidden' ? (
-
+
) : (
{field.hide_label ? null : (
@@ -589,7 +664,7 @@ export default function EntityForm({ module: config }) {
{field.title} {field.required ? * : null}
)}
-
+
);
})}
diff --git a/src/components/form/DependentSelectField.jsx b/src/components/form/DependentSelectField.jsx
index 99e154e..15f0991 100644
--- a/src/components/form/DependentSelectField.jsx
+++ b/src/components/form/DependentSelectField.jsx
@@ -1,33 +1,44 @@
-import { Controller } from 'react-hook-form';
+import { useEffect } from 'react';
+import { useController } from 'react-hook-form';
import { Form } from 'react-bootstrap';
export default function DependentSelectField({ field, control, watch }) {
const parentValue = watch(field.dependsOn);
- const staticOptions = field.staticOptions || {};
- const questions = (field.questions || []).filter((question) => question.entity_id === parentValue);
- const options = { ...staticOptions };
- questions.forEach((question) => {
- if (question.code) options[question.code] = `${question.question || question.code} (Only Experts)`;
+ const staticOptions = Object.entries(field.staticOptions || {}).map(([value, label]) => ({ value, label }));
+ const allowedTypes = field.questionTypes || (field.questionType ? [field.questionType] : null);
+ const questions = (field.questions || []).filter((question) => {
+ const questionFormId = question.entity_id ?? question.form_id;
+ if (String(questionFormId ?? '') !== String(parentValue || '')) return false;
+ if (!allowedTypes) return true;
+ return allowedTypes.includes(String(question.type || '').toLowerCase());
});
+ const options = [
+ ...staticOptions,
+ ...questions
+ .filter((question) => question.code)
+ .map((question) => ({
+ value: question.code,
+ label: `${question.question || question.code} (Only Experts)`,
+ })),
+ ];
+ const optionValues = new Set(options.map((option) => String(option.value)));
+ const fallback = staticOptions[0]?.value || '';
+ const { field: rhf } = useController({ name: field.key, control });
+ const selected = optionValues.has(String(rhf.value || '')) ? rhf.value : fallback;
+
+ useEffect(() => {
+ if (String(rhf.value || '') === String(selected || '')) return;
+ rhf.onChange(selected);
+ }, [parentValue, selected, rhf.value, rhf.onChange]);
return (
- (
- rhf.onChange(e.target.value)}
- >
-
- {Object.entries(options).map(([value, label]) => (
-
- ))}
-
- )}
- />
+ rhf.onChange(e.target.value)}>
+ {staticOptions.length ? null : }
+ {options.map((option, index) => (
+
+ ))}
+
);
}
diff --git a/src/components/form/ReminderRowsField.jsx b/src/components/form/ReminderRowsField.jsx
index eec5548..f107bc3 100644
--- a/src/components/form/ReminderRowsField.jsx
+++ b/src/components/form/ReminderRowsField.jsx
@@ -31,6 +31,7 @@ function emptyRow() {
checksubmission_days: '',
message: '',
plan_date_field: '',
+ notification_type: 'normal',
};
}
@@ -165,6 +166,17 @@ export default function ReminderRowsField({ field, control }) {
onChange={(e) => rhf.onChange(patchRow(rows, index, { plan_date_field: e.target.value }))}
/>
+
+ Notification Type
+ rhf.onChange(patchRow(rows, index, { notification_type: e.target.value }))}
+ >
+
+
+
+
);
diff --git a/src/components/form/TemplatePositionsField.jsx b/src/components/form/TemplatePositionsField.jsx
new file mode 100644
index 0000000..f152850
--- /dev/null
+++ b/src/components/form/TemplatePositionsField.jsx
@@ -0,0 +1,123 @@
+import { Button, Col, Form, Row } from 'react-bootstrap';
+import { Controller } from 'react-hook-form';
+import S3FileField from '@/components/S3FileField';
+
+const YES_NO = [
+ { value: 'no', label: 'No' },
+ { value: 'yes', label: 'Yes' },
+];
+
+function emptyRow() {
+ return {
+ template: '',
+ createpdf: 'no',
+ addlink: 'no',
+ linkfield: '',
+ positions: '',
+ };
+}
+
+function patchRow(rows, index, patch) {
+ return rows.map((row, i) => (i === index ? { ...row, ...patch } : row));
+}
+
+export default function TemplatePositionsField({ field, control, moduleName, onUploadingChange }) {
+ const linkFields = field.values || field.linkFields || {};
+
+ return (
+ {
+ const rows = Array.isArray(rhf.value) ? rhf.value : [];
+ return (
+
+
+
+
+ {rows.length === 0 ?
No templates
: null}
+ {rows.map((row, index) => (
+
+
+
+ rhf.onChange(patchRow(rows, index, { template: url }))}
+ moduleName={moduleName}
+ onUploadingChange={onUploadingChange}
+ />
+
+
+ Include in PDF
+ rhf.onChange(patchRow(rows, index, { createpdf: e.target.value }))}
+ >
+ {YES_NO.map((option) => (
+
+ ))}
+
+ Add Link to PDF
+ rhf.onChange(patchRow(rows, index, { addlink: e.target.value }))}
+ >
+ {YES_NO.map((option) => (
+
+ ))}
+
+ Link Field
+ rhf.onChange(patchRow(rows, index, { linkfield: e.target.value }))}
+ >
+
+ {Object.entries(linkFields).map(([value, label]) => (
+
+ ))}
+
+
+
+
+
+
+ rhf.onChange(patchRow(rows, index, { positions: e.target.value }))}
+ />
+
+
+
+ ))}
+
+ );
+ }}
+ />
+ );
+}
diff --git a/src/modules/formentitys.js b/src/modules/formentitys.js
index 2540da1..6abbfcb 100644
--- a/src/modules/formentitys.js
+++ b/src/modules/formentitys.js
@@ -122,7 +122,6 @@ const formentitys = {
type: 'multiselect',
values: userAttributes,
default: [],
- showIf: { show_user_attributes: 'yes' },
},
{
key: 'form_type',
@@ -234,6 +233,7 @@ const formentitys = {
next.contact_attributes = asArray(next.contact_attributes);
next.show_user_attributes = yesno(next.show_user_attributes, 'no');
next.user_attributes = asArray(next.user_attributes);
+ if (next.user_attributes.length) next.show_user_attributes = 'yes';
next.roles_email_notification = asArray(next.roles_email_notification);
next.roles_push_notification = asArray(next.roles_push_notification);
next.workflow_approvers = Array.isArray(next.workflow_approvers) && next.workflow_approvers.length ? next.workflow_approvers : ['l1manager'];
@@ -248,6 +248,7 @@ const formentitys = {
payload.contact_attributes = asArray(payload.contact_attributes);
payload.show_user_attributes = payload.show_user_attributes === 'yes' ? 'yes' : 'no';
payload.user_attributes = asArray(payload.user_attributes);
+ if (payload.user_attributes.length) payload.show_user_attributes = 'yes';
payload.enrollment_form_attributes = asArray(payload.enrollment_form_attributes);
payload.roles_email_notification = asArray(payload.roles_email_notification);
payload.roles_push_notification = asArray(payload.roles_push_notification);
diff --git a/src/modules/formexports.js b/src/modules/formexports.js
index 1f75284..c08f92a 100644
--- a/src/modules/formexports.js
+++ b/src/modules/formexports.js
@@ -44,6 +44,8 @@ const formexports = {
required: true,
dependsOn: 'form_id',
questions,
+ questionType: 'date',
+ default: 'record_date',
staticOptions: { record_date: 'Default Date (Recommended)' },
},
{ key: 'start_date', title: 'Start Date', type: 'date', required: true },
diff --git a/src/modules/formpersonalizations.js b/src/modules/formpersonalizations.js
index b24b361..52ae45d 100644
--- a/src/modules/formpersonalizations.js
+++ b/src/modules/formpersonalizations.js
@@ -3,6 +3,43 @@ import { splitHash } from '@/helpers/metric';
const STATUS = { active: 'Active', disabled: 'Disabled' };
+function asList(value) {
+ if (value == null || value === '') return [];
+ if (Array.isArray(value)) return value;
+ if (typeof value === 'object') return Object.values(value);
+ return [value];
+}
+
+function stringifyPositions(value) {
+ if (value == null || value === '') return '';
+ if (typeof value === 'string') return value;
+ try {
+ return JSON.stringify(value, null, 2);
+ } catch {
+ return String(value);
+ }
+}
+
+function flattenMappings(item) {
+ const templates = asList(item?.templates);
+ const positions = asList(item?.positions);
+ const createpdf = asList(item?.createpdf);
+ const addlink = asList(item?.addlink);
+ const linkfield = asList(item?.linkfield);
+ const length = Math.max(templates.length, positions.length, createpdf.length, addlink.length, linkfield.length);
+ const rows = [];
+ for (let index = 0; index < length; index += 1) {
+ rows.push({
+ template: templates[index] || '',
+ createpdf: createpdf[index] === 'yes' ? 'yes' : 'no',
+ addlink: addlink[index] === 'yes' ? 'yes' : 'no',
+ linkfield: linkfield[index] || '',
+ positions: stringifyPositions(positions[index]),
+ });
+ }
+ return rows;
+}
+
const formpersonalizations = {
service: 'form',
module: 'personalization',
@@ -26,24 +63,55 @@ const formpersonalizations = {
formFields: async ({ item } = {}) => {
const entityId = item?.entity_id || '';
const questions = {};
+ const linkFields = {};
if (entityId) {
const response = await signedGet('form', 'formquestion/get_all', { entity_id: entityId });
- (isSuccess(response) ? response.data?.items || [] : []).forEach((q) => {
- if (q?.id) questions[q.id] = q.question || q.title || q.id;
+ (isSuccess(response) ? response.data?.items || [] : []).forEach((question) => {
+ if (question?.id) questions[question.id] = question.question || question.title || question.id;
+ if (question?.code) linkFields[question.code] = question.question || question.title || question.code;
});
}
return [
{ key: 'title', title: 'Title', type: 'text', required: true, fullwidth: true },
- { key: 'questions', title: 'Select 1 or Multiple questions (to be included in this personalization)', type: 'checkbox', values: questions, required: true },
+ {
+ key: 'questions',
+ title: 'Select 1 or Multiple questions (to be included in this personalization)',
+ type: 'checkbox',
+ values: questions,
+ required: true,
+ },
{ key: 'status', title: 'Status', type: 'select', required: true, values: STATUS },
{ key: 'icon', title: 'Icon', type: 'image', required: true },
+ {
+ key: 'mappings',
+ title: 'Template and Positions',
+ type: 'template_positions',
+ fullwidth: true,
+ values: linkFields,
+ },
];
},
- beforeForm: (item, _mode, ctx) => ({
- ...item,
- entity_id: item.entity_id || ctx?.searchParams?.get('id') || '',
- questions: splitHash(item.questions),
- }),
+ beforeForm: (item, _mode, ctx) => {
+ const next = {
+ ...item,
+ entity_id: item.entity_id || ctx?.searchParams?.get('id') || '',
+ questions: splitHash(item.questions),
+ };
+ next.mappings = flattenMappings(next);
+ return next;
+ },
+ beforeSave: (data) => {
+ const mappings = Array.isArray(data.mappings) ? data.mappings : [];
+ const payload = { ...data };
+ payload.questions = Array.isArray(payload.questions) ? payload.questions.filter(Boolean) : [];
+ payload.templates = mappings.map((row) => row.template || '');
+ payload.positions = mappings.map((row) => row.positions || '');
+ payload.createpdf = mappings.map((row) => (row.createpdf === 'yes' ? 'yes' : 'no'));
+ payload.addlink = mappings.map((row) => (row.addlink === 'yes' ? 'yes' : 'no'));
+ payload.linkfield = mappings.map((row) => row.linkfield || '');
+ delete payload.mappings;
+ return payload;
+ },
};
export default formpersonalizations;
diff --git a/src/modules/formquestions.js b/src/modules/formquestions.js
index fb5c714..db198a2 100644
--- a/src/modules/formquestions.js
+++ b/src/modules/formquestions.js
@@ -16,6 +16,7 @@ const FORM_QUESTION_TYPES = {
};
const STATUS = { active: 'Active', disabled: 'Disabled' };
+const OPTION_TYPES = ['dropdown', 'checkbox', 'radio', 'radiometrix', 'multitextbox'];
const formquestions = {
service: 'form',
@@ -49,7 +50,7 @@ const formquestions = {
{ key: 'required', title: 'Is Required', type: 'yesno', required: true },
{ key: 'sort_order', title: 'Sort Order', type: 'number' },
{ key: 'status', title: 'Status', type: 'select', required: true, values: STATUS },
- { key: 'options', title: 'Options', type: 'mapping', variant: 'question_options', fullwidth: true },
+ { key: 'options', title: 'Options', type: 'mapping', variant: 'question_options', fullwidth: true, showIf: { type: OPTION_TYPES } },
],
beforeForm: (item) => ({
...item,
@@ -59,7 +60,7 @@ const formquestions = {
beforeSave: (data) => ({
...data,
sort_order: data.sort_order === '' || data.sort_order == null ? 0 : Number(data.sort_order),
- options: Array.isArray(data.options) ? data.options : [],
+ options: OPTION_TYPES.includes(data.type) && Array.isArray(data.options) ? data.options : [],
}),
};
diff --git a/src/modules/leaderboards.js b/src/modules/leaderboards.js
index 7a4c2a6..03e2682 100644
--- a/src/modules/leaderboards.js
+++ b/src/modules/leaderboards.js
@@ -1,21 +1,73 @@
+import { signedGet, isSuccess } from '@/api/client';
import { dropdownMap } from '@/api/dropdowns';
import {
- DEFAULT_INTERVAL_VALUES,
INTERVAL_VALUES,
metricOptions,
parseCombinedMetrics,
splitHash,
+ stringifyCombinedMetrics,
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 DEFAULT_INTERVAL_VALUES = {
+ '': 'Overall / All',
+ all: 'Overall',
+ 'month-0': 'This Month',
+ 'month-1': 'Last Month',
+ 'month-3': 'Last 3 Months',
+ 'month-6': 'Last 6 Months',
+ 'month-12': 'Last 12 Months',
+ 'day-0': 'Today',
+ 'day-1': 'Yesterday',
+ 'day-7': 'Last 7 Days',
+ 'day-30': 'Last 30 Days',
+ 'week-0': 'This week',
+ 'week-1': 'Last week',
+ 'yearfy-0': 'This Financial Year',
+ 'yearfy-1': 'Last Financial Year',
+ 'yearcy-0': 'This Calender Year',
+ 'yearcy-1': 'Last Calender Year',
+};
function asArray(value) {
if (Array.isArray(value)) return value.filter((part) => part !== null && part !== undefined && part !== '');
return splitHash(value);
}
+function bySortOrder(left, right) {
+ return (Number(left?.sort_order) || 0) - (Number(right?.sort_order) || 0);
+}
+
+function itemsToMap(items) {
+ const values = {};
+ (items || []).forEach((item) => {
+ const id = item?.id;
+ if (id === undefined || id === null || id === '') return;
+ values[id] = item.name || item.title || id;
+ });
+ return values;
+}
+
+async function teamOptions() {
+ const fromDropdown = await dropdownMap('detailing', 'team', (item) => item.name);
+ if (Object.keys(fromDropdown).length) return fromDropdown;
+ for (const path of ['team/get_all', 'team/list']) {
+ const response = await signedGet('detailing', path);
+ const items = isSuccess(response) ? response.data?.items || response.data || [] : [];
+ const values = itemsToMap(Array.isArray(items) ? items : []);
+ if (Object.keys(values).length) return values;
+ }
+ return {};
+}
+
const leaderboards = {
service: 'reports',
module: 'roles_leaderboard',
@@ -26,6 +78,7 @@ const leaderboards = {
listFields: [
{ key: 'actions', title: 'Actions', buttons: ['edit', 'delete', { name: 'report', to: (row) => `/leaderboard/dashboard?id=${row.id}`, title: 'Dashboard' }] },
{ key: 'name', title: 'Name', type: 'text' },
+ { key: 'leaderboard_roles_type_label', title: 'Type', type: 'text' },
{ key: 'sort_order', title: 'Sort Order', type: 'number' },
{ key: 'visible_roles_label', title: 'Visible Roles', type: 'text' },
{ key: 'validity_start', title: 'Validity start', type: 'text' },
@@ -36,18 +89,47 @@ const leaderboards = {
(items || []).map((item) => ({
...item,
visible_roles_label: asArray(item.visible_roles).join(', '),
+ leaderboard_roles_type_label: item.leaderboard_roles_type === 'team' ? 'Team' : 'Roles',
sort_order: item.sort_order ?? 0,
})),
formFields: async () => {
- const [metrics, roles] = await Promise.all([metricOptions(), dropdownMap('ums', 'roles', (item) => item.name)]);
+ const [metrics, roles, teams] = await Promise.all([
+ metricOptions(),
+ dropdownMap('ums', 'roles', (item) => item.name),
+ teamOptions(),
+ ]);
return [
{ key: 'name', title: 'Name', type: 'text', required: true },
{ key: 'sort_order', title: 'Sort Order', type: 'number', required: true },
{ key: 'validity_start', title: 'Validity start', type: 'date', required: true },
{ key: 'validity_end', title: 'Validity end', type: 'date', required: true },
+ {
+ key: 'leaderboard_roles_type',
+ title: 'Leaderboard Type',
+ type: 'select',
+ required: true,
+ values: { roles: 'Roles', team: 'Team' },
+ },
+ {
+ key: 'leaderboard_team_ids',
+ title: 'Teams',
+ type: 'multiselect',
+ values: teams,
+ fullwidth: true,
+ required: true,
+ showIf: { leaderboard_roles_type: 'team' },
+ },
{ key: 'visible_roles', title: 'Visible Roles', type: 'multiselect', values: roles, required: true },
{ key: 'reporting_roles', title: 'Reporting Roles', type: 'multiselect', values: roles, required: true },
- { key: 'rank_roles', title: 'Rank Roles', type: 'rank_roles', roles, required: true, fullwidth: true },
+ {
+ key: 'rank_roles',
+ title: 'Rank Roles',
+ type: 'rank_roles',
+ roles,
+ required: true,
+ fullwidth: true,
+ showIf: { leaderboard_roles_type: 'roles' },
+ },
{
key: 'metrics',
title: 'Choose Metric',
@@ -59,7 +141,7 @@ const leaderboards = {
},
{ key: 'combined_metrics', title: 'Combined Metrics (if any)', type: 'combined_metrics', fullwidth: true },
{ key: 'intervals', title: 'Intervals', type: 'multiselect', required: true, values: INTERVAL_VALUES },
- { key: 'default_interval', title: 'Default Interval', type: 'select', values: { '': 'Overall / All', all: 'Overall', ...DEFAULT_INTERVAL_VALUES } },
+ { key: 'default_interval', title: 'Default Interval', type: 'select', values: DEFAULT_INTERVAL_VALUES },
{ key: 'show_positions', title: 'Show Positions', type: 'select', required: true, values: YES_NO },
{
key: 'show_positions_type',
@@ -68,40 +150,70 @@ const leaderboards = {
required: true,
values: { all: 'All', loggedin: 'Logged In' },
},
+ { key: 'show_units_export', title: 'Show Units Export', type: 'select', values: YES_NO },
+ { key: 'show_normal_metrics', title: 'Show Normal Metrics', type: 'select', values: YES_NO },
+ { key: 'show_combined_metrics', title: 'Show Combined Metrics', type: 'select', values: YES_NO },
+ { key: 'manager_details', title: 'Manager Details', type: 'multiselect', values: MANAGER_LEVELS },
+ { key: 'total_units_name', title: 'Total Units Name', type: 'text', placeholder: 'Total Units' },
+ { key: 'bg_color', title: 'Background Color', type: 'color' },
+ { key: 'bg_image', title: 'Background Image', type: 'image', uploadModule: 'leaderboard' },
+ { key: 'icon', title: 'Icon', type: 'image', uploadModule: 'leaderboard' },
{ key: 'status', title: 'Status', type: 'select', required: true, values: STATUS },
];
},
beforeForm: (item) => {
const next = { ...item };
next.sort_order = next.sort_order ?? 0;
- next.show_positions = next.show_positions || 'no';
+ next.show_positions = String(next.show_positions || 'no').toLowerCase();
next.show_positions_type = next.show_positions_type || 'all';
+ next.show_units_export = next.show_units_export === 'no' ? 'no' : 'yes';
+ next.show_normal_metrics = next.show_normal_metrics === 'no' ? 'no' : 'yes';
+ next.show_combined_metrics = next.show_combined_metrics === 'no' ? 'no' : 'yes';
+ next.total_units_name = next.total_units_name || '';
next.status = next.status || 'active';
next.default_interval = next.default_interval ?? '';
+ next.leaderboard_roles_type = next.leaderboard_roles_type === 'team' ? 'team' : 'roles';
+ next.bg_color = next.bg_color || '';
+ next.bg_image = next.bg_image || '';
+ next.icon = next.icon || '';
next.validity_start = toDateInput(next.validity_start);
next.validity_end = toDateInput(next.validity_end);
next.visible_roles = asArray(next.visible_roles);
next.reporting_roles = asArray(next.reporting_roles);
next.intervals = asArray(next.intervals);
- next.rank_roles = Array.isArray(next.rank_roles) ? next.rank_roles : [];
- next.metrics = Array.isArray(next.metrics)
- ? next.metrics.map((row) => ({
- metric: row.metric || '',
- display: row.display || '',
- sort_order: row.sort_order ?? 0,
- weight: row.weight ?? 1,
- list_view: row.list_view || 'yes',
- }))
- : [];
+ next.leaderboard_team_ids = asArray(next.leaderboard_team_ids);
+ next.manager_details = asArray(next.manager_details);
+ if (!next.manager_details.length) next.manager_details = ['l1manager'];
+ next.rank_roles = (Array.isArray(next.rank_roles) ? next.rank_roles : []).slice().sort(bySortOrder);
+ next.metrics = (Array.isArray(next.metrics) ? next.metrics : [])
+ .map((row) => ({
+ metric: row.metric || '',
+ display: row.display || '',
+ sort_order: row.sort_order ?? 0,
+ weight: row.weight ?? 1,
+ list_view: row.list_view || 'yes',
+ }))
+ .sort(bySortOrder);
next.combined_metrics = parseCombinedMetrics(next.combined_metrics);
return next;
},
beforeSave: (data) => {
const payload = { ...data };
+ const leaderboardType = payload.leaderboard_roles_type === 'team' ? 'team' : 'roles';
+ payload.leaderboard_roles_type = leaderboardType;
payload.sort_order = payload.sort_order === '' || payload.sort_order == null ? 0 : Number(payload.sort_order);
payload.visible_roles = asArray(payload.visible_roles);
payload.reporting_roles = asArray(payload.reporting_roles);
payload.intervals = asArray(payload.intervals);
+ payload.leaderboard_team_ids = leaderboardType === 'team' ? asArray(payload.leaderboard_team_ids) : [];
+ payload.show_units_export = payload.show_units_export === 'no' ? 'no' : 'yes';
+ payload.show_normal_metrics = payload.show_normal_metrics === 'no' ? 'no' : 'yes';
+ payload.show_combined_metrics = payload.show_combined_metrics === 'no' ? 'no' : 'yes';
+ payload.total_units_name = payload.total_units_name || '';
+ payload.manager_details = asArray(payload.manager_details);
+ if (!payload.manager_details.length) payload.manager_details = ['l1manager'];
+ payload.bg_color = Array.isArray(payload.bg_color) ? '' : payload.bg_color || '';
+ payload.default_interval = payload.default_interval ?? '';
payload.rank_roles = (Array.isArray(payload.rank_roles) ? payload.rank_roles : [])
.filter((row) => row.title || row.role)
.map((row) => ({
@@ -118,7 +230,11 @@ const leaderboards = {
sort_order: row.sort_order === '' || row.sort_order == null ? 0 : Number(row.sort_order),
weight: row.weight === '' || row.weight == null ? '1' : String(row.weight),
}));
- payload.combined_metrics = Array.isArray(payload.combined_metrics) ? payload.combined_metrics : [];
+ payload.combined_metrics = stringifyCombinedMetrics(
+ (Array.isArray(payload.combined_metrics) ? payload.combined_metrics : []).filter((row) => row?.type),
+ );
+ if (!payload.bg_image) delete payload.bg_image;
+ if (!payload.icon) delete payload.icon;
return payload;
},
};
diff --git a/src/modules/metriccampaigns.js b/src/modules/metriccampaigns.js
index 53146bd..2d54aa6 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',
@@ -171,6 +177,11 @@ const metriccampaigns = {
required: true,
values: { all: 'All', loggedin: 'Logged In' },
},
+ { key: 'show_units_export', title: 'Show Units Export', type: 'select', values: YES_NO },
+ { key: 'show_normal_metrics', title: 'Show Normal Metrics', type: 'select', values: YES_NO },
+ { key: 'show_combined_metrics', title: 'Show Combined Metrics', type: 'select', values: YES_NO },
+ { key: 'manager_details', title: 'Manager Details', type: 'multiselect', values: MANAGER_LEVELS },
+ { key: 'total_units_name', title: 'Total Units Name', type: 'text', placeholder: 'Total Units' },
{ 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' },
{
@@ -202,11 +213,22 @@ const metriccampaigns = {
showManagerTeamRank: 'yes',
highlight_top: '',
highlight_color: '',
+ show_units_export: 'yes',
+ show_normal_metrics: 'yes',
+ show_combined_metrics: 'yes',
+ manager_details: ['l1manager'],
+ total_units_name: '',
...item,
};
next.campaign_visible_roles = asArray(next.campaign_visible_roles);
next.reporting_roles = asArray(next.reporting_roles);
next.intervals = asArray(next.intervals);
+ next.manager_details = asArray(next.manager_details);
+ if (!next.manager_details.length) next.manager_details = ['l1manager'];
+ next.show_units_export = next.show_units_export === 'no' ? 'no' : 'yes';
+ next.show_normal_metrics = next.show_normal_metrics === 'no' ? 'no' : 'yes';
+ next.show_combined_metrics = next.show_combined_metrics === 'no' ? 'no' : 'yes';
+ next.total_units_name = next.total_units_name || '';
next.metrics = (Array.isArray(next.metrics) ? next.metrics : []).map(normalizeCampaignMetric);
next.combined_metrics = parseCombinedMetrics(next.combined_metrics).map(normalizeCombined);
next.ranking_attributes = normalizeRankingAttributes(next.ranking_attributes);
@@ -253,6 +275,11 @@ const metriccampaigns = {
campaign_visible_roles: asArray(data.campaign_visible_roles),
reporting_roles: asArray(data.reporting_roles),
intervals: asArray(data.intervals),
+ manager_details: asArray(data.manager_details).length ? asArray(data.manager_details) : ['l1manager'],
+ show_units_export: data.show_units_export === 'no' ? 'no' : 'yes',
+ show_normal_metrics: data.show_normal_metrics === 'no' ? 'no' : 'yes',
+ show_combined_metrics: data.show_combined_metrics === 'no' ? 'no' : 'yes',
+ total_units_name: data.total_units_name || '',
sort_order: Number.parseInt(data.sort_order, 10) || 0,
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..300b6e2 100644
--- a/src/modules/metricviews.js
+++ b/src/modules/metricviews.js
@@ -199,6 +199,12 @@ const metricviews = {
values: { all: 'All', loggedin: 'Logged In' },
},
{ key: 'contact_attributes', title: 'Include Doctor Attributes', type: 'multiselect', values: contactAttributes },
+ {
+ key: 'team_leaderboard_widget_roles',
+ title: 'Team Leaderboard Widget Roles',
+ type: 'multiselect',
+ values: roles,
+ },
{ key: 'show_user_attributes', title: 'Show User Attributes', type: 'yesno', required: true, default: 'no' },
{
key: 'user_attributes',
@@ -206,7 +212,13 @@ const metricviews = {
type: 'multiselect',
values: userAttributes,
default: [],
- showIf: { show_user_attributes: 'yes' },
+ },
+ {
+ key: 'stateheadwise_leaderboard',
+ title: 'Stateheadwise Leaderboard',
+ type: 'yesno',
+ required: true,
+ default: 'no',
},
];
},
@@ -219,9 +231,11 @@ const metricviews = {
show_positions_type: 'all',
show_user_attributes: 'no',
user_attributes: [],
+ stateheadwise_leaderboard: 'no',
list_chart_type: 'progressbar',
value_views: ['value'],
default_tab: '',
+ team_leaderboard_widget_roles: [],
...item,
};
next.designations = asArray(next.designations);
@@ -240,8 +254,15 @@ const metricviews = {
next.default_tab = next.default_tab || '';
next.intervals = asArray(next.intervals);
next.contact_attributes = asArray(next.contact_attributes);
- next.show_user_attributes = next.show_user_attributes === 'yes' ? 'yes' : 'no';
+ next.team_leaderboard_widget_roles = asArray(
+ next.team_leaderboard_widget_roles || next.leaderboard_widget_roles || next.widget_roles,
+ );
+ next.show_user_attributes = ['yes', '1', 'true', true, 1].includes(next.show_user_attributes) ? 'yes' : 'no';
next.user_attributes = asArray(next.user_attributes);
+ if (next.user_attributes.length && next.show_user_attributes !== 'yes') {
+ next.show_user_attributes = 'yes';
+ }
+ next.stateheadwise_leaderboard = next.stateheadwise_leaderboard === 'yes' ? 'yes' : 'no';
next.combined_metrics = parseCombinedMetrics(next.combined_metrics);
next.validity_start = toDateInput(next.validity_start);
next.validity_end = toDateInput(next.validity_end);
@@ -309,8 +330,10 @@ const metricviews = {
default_tab: data.default_tab || '',
intervals: asArray(data.intervals),
contact_attributes: asArray(data.contact_attributes),
+ team_leaderboard_widget_roles: asArray(data.team_leaderboard_widget_roles),
show_user_attributes: data.show_user_attributes === 'yes' ? 'yes' : 'no',
user_attributes: asArray(data.user_attributes),
+ stateheadwise_leaderboard: data.stateheadwise_leaderboard === 'yes' ? 'yes' : 'no',
combined_metrics: stringifyCombinedMetrics(
(data.combined_metrics || [])
.filter((row) => row.type)
diff --git a/src/modules/milestones.js b/src/modules/milestones.js
index 1e52cf3..357bed2 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',
@@ -195,6 +201,11 @@ const milestones = {
required: true,
values: { all: 'All', loggedin: 'Logged In' },
},
+ { key: 'show_units_export', title: 'Show Units Export', type: 'select', values: YES_NO },
+ { key: 'show_normal_metrics', title: 'Show Normal Metrics', type: 'select', values: YES_NO },
+ { key: 'show_combined_metrics', title: 'Show Combined Metrics', type: 'select', values: YES_NO },
+ { key: 'manager_details', title: 'Manager Details', type: 'multiselect', values: MANAGER_LEVELS },
+ { key: 'total_units_name', title: 'Total Units Name', type: 'text', placeholder: 'Total Units' },
{ key: 'send_report', title: 'Send Report', type: 'select', required: true, values: YES_NO },
{
key: 'performance_period',
@@ -276,6 +287,18 @@ const milestones = {
values: YES_NO,
showIf: { send_report: 'yes' },
},
+ {
+ key: 'subject',
+ title: 'Report Subject',
+ type: 'text',
+ showIf: { send_report: 'yes' },
+ },
+ {
+ key: 'body',
+ title: 'Report Body',
+ type: 'textarea',
+ showIf: { send_report: 'yes' },
+ },
{
key: 'metrics',
title: 'Metric Mapping',
@@ -326,7 +349,15 @@ const milestones = {
next.send_pending_users = fromBool(reportConfig.send_pending_users ?? false);
next.show_target_average = fromBool(reportConfig.show_target_average ?? false);
next.show_overall_average = fromBool(reportConfig.show_overall_average ?? false);
+ next.subject = reportConfig.subject || '';
+ next.body = reportConfig.body || '';
next.send_report = fromBool(next.send_report ?? false);
+ next.show_units_export = next.show_units_export === 'no' ? 'no' : 'yes';
+ next.show_normal_metrics = next.show_normal_metrics === 'no' ? 'no' : 'yes';
+ next.show_combined_metrics = next.show_combined_metrics === 'no' ? 'no' : '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) => {
@@ -354,7 +385,16 @@ const milestones = {
send_pending_users: toBool(payload.send_pending_users ?? 'no'),
show_target_average: toBool(payload.show_target_average ?? 'no'),
show_overall_average: toBool(payload.show_overall_average ?? 'no'),
+ subject: (payload.subject || '').trim(),
+ body: (payload.body || '').trim(),
};
+ payload.show_units_export = payload.show_units_export === 'no' ? 'no' : 'yes';
+ payload.show_normal_metrics = payload.show_normal_metrics === 'no' ? 'no' : 'yes';
+ payload.show_combined_metrics = payload.show_combined_metrics === 'no' ? 'no' : 'yes';
+ payload.manager_details = toStringList(payload.manager_details).length
+ ? toStringList(payload.manager_details)
+ : ['l1manager'];
+ payload.total_units_name = payload.total_units_name || '';
delete payload.interval_start_date;
delete payload.interval_end_date;
@@ -370,6 +410,8 @@ const milestones = {
delete payload.send_pending_users;
delete payload.show_target_average;
delete payload.show_overall_average;
+ delete payload.subject;
+ delete payload.body;
return payload;
},
};
diff --git a/src/modules/reminders.js b/src/modules/reminders.js
index a42789c..5a0f803 100644
--- a/src/modules/reminders.js
+++ b/src/modules/reminders.js
@@ -42,6 +42,9 @@ function flattenReminders(rows) {
checksubmission_days: item.checksubmission_days || '',
message: item.message || '',
plan_date_field: item.plan_date_field || '',
+ notification_type: ['normal', 'overlay_custom'].includes(item.notification_type)
+ ? item.notification_type
+ : 'normal',
};
});
}
@@ -108,6 +111,7 @@ const reminders = {
checksubmission_days: row.checksubmission === 'yes' ? row.checksubmission_days || '' : '',
message: row.message || '',
plan_date_field: row.plan_date_field || '',
+ notification_type: row.notification_type === 'overlay_custom' ? 'overlay_custom' : 'normal',
}));
return payload;
},