parent
366c8c1c4e
commit
fe03c32046
|
|
@ -0,0 +1,2 @@
|
||||||
|
VITE_API_MODE=local
|
||||||
|
VITE_LARAVEL_ORIGIN=http://127.0.0.1:8000
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules/
|
||||||
|
*.cursor/
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
"dev:local": "vite --mode localapi",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import {
|
||||||
} from '@/components/metric/MetricFieldControls';
|
} from '@/components/metric/MetricFieldControls';
|
||||||
import MilestoneMappingField from '@/components/recognition/MilestoneMappingField';
|
import MilestoneMappingField from '@/components/recognition/MilestoneMappingField';
|
||||||
import EmailNotificationRulesField from '@/components/form/EmailNotificationRulesField';
|
import EmailNotificationRulesField from '@/components/form/EmailNotificationRulesField';
|
||||||
|
import { DataDesignerField, normalizeDataDesigner } from '@/components/form/DataDesignerField';
|
||||||
import RankRolesField from '@/components/form/RankRolesField';
|
import RankRolesField from '@/components/form/RankRolesField';
|
||||||
import ReminderRowsField from '@/components/form/ReminderRowsField';
|
import ReminderRowsField from '@/components/form/ReminderRowsField';
|
||||||
import DependentSelectField from '@/components/form/DependentSelectField';
|
import DependentSelectField from '@/components/form/DependentSelectField';
|
||||||
|
|
@ -36,6 +37,7 @@ function defaultValue(field, item) {
|
||||||
const raw = fromItem !== undefined && fromItem !== null ? fromItem : field.default;
|
const raw = fromItem !== undefined && fromItem !== null ? fromItem : field.default;
|
||||||
if (raw === undefined || raw === null) {
|
if (raw === undefined || raw === null) {
|
||||||
if (field.type === 'multiselect' || field.type === 'bigmultiselect' || field.type === 'checkbox') return [];
|
if (field.type === 'multiselect' || field.type === 'bigmultiselect' || field.type === 'checkbox') return [];
|
||||||
|
if (field.type === 'data_designer') return { fields: [] };
|
||||||
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') {
|
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 [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
@ -44,6 +46,7 @@ function defaultValue(field, item) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
if (field.type === 'combined_metrics') return parseCombinedMetrics(raw);
|
if (field.type === 'combined_metrics') return parseCombinedMetrics(raw);
|
||||||
|
if (field.type === 'data_designer') return normalizeDataDesigner(raw);
|
||||||
if (field.type === 'email_notifications') {
|
if (field.type === 'email_notifications') {
|
||||||
if (typeof raw === 'string' && raw.trim()) {
|
if (typeof raw === 'string' && raw.trim()) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -475,6 +478,9 @@ function FieldControl({ field, control, register, watch, moduleName, onUploading
|
||||||
if (field.type === 'email_notifications') {
|
if (field.type === 'email_notifications') {
|
||||||
return <EmailNotificationRulesField field={field} control={control} />;
|
return <EmailNotificationRulesField field={field} control={control} />;
|
||||||
}
|
}
|
||||||
|
if (field.type === 'data_designer') {
|
||||||
|
return <DataDesignerField field={field} control={control} />;
|
||||||
|
}
|
||||||
if (field.type === 'rank_roles') {
|
if (field.type === 'rank_roles') {
|
||||||
return <RankRolesField field={field} control={control} />;
|
return <RankRolesField field={field} control={control} />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,334 @@
|
||||||
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
import { useSearchParams } from 'react-router';
|
||||||
|
import { Controller } from 'react-hook-form';
|
||||||
|
import { Button, Dropdown, Form } from 'react-bootstrap';
|
||||||
|
import { closestCenter, DndContext, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
|
||||||
|
import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
||||||
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
|
import { signedGet, isSuccess } from '@/api/client';
|
||||||
|
|
||||||
|
const DEFAULT_HIERARCHY_LEVELS = ['Country', 'Zone', 'Region', 'Area', 'Territory'];
|
||||||
|
const MANAGER_LEVELS = [
|
||||||
|
['l1', 'L1'],
|
||||||
|
['l2', 'L2'],
|
||||||
|
['l3', 'L3'],
|
||||||
|
];
|
||||||
|
const MANAGER_KEYS = [
|
||||||
|
['name', 'Name'],
|
||||||
|
['email', 'Email'],
|
||||||
|
['employee_code', 'Employee Code'],
|
||||||
|
['position_name', 'Position Name'],
|
||||||
|
];
|
||||||
|
|
||||||
|
function emptyDesigner() {
|
||||||
|
return { fields: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
function asDesigner(raw) {
|
||||||
|
let value = raw;
|
||||||
|
if (typeof raw === 'string' && raw.trim()) {
|
||||||
|
try {
|
||||||
|
value = JSON.parse(raw);
|
||||||
|
} catch {
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const fields = Array.isArray(value?.fields) ? value.fields : [];
|
||||||
|
return {
|
||||||
|
fields: fields
|
||||||
|
.filter((row) => row && row.id && row.source && row.key)
|
||||||
|
.map((row) => ({
|
||||||
|
id: String(row.id),
|
||||||
|
source: String(row.source),
|
||||||
|
key: String(row.key),
|
||||||
|
label: row.label || String(row.key),
|
||||||
|
level: row.level || undefined,
|
||||||
|
show_in_export: row.show_in_export !== false,
|
||||||
|
show_in_view: Boolean(row.show_in_view),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeDataDesigner(raw) {
|
||||||
|
return asDesigner(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
const SOURCE_LABELS = {
|
||||||
|
user: 'User attributes',
|
||||||
|
hierarchy: 'Hierarchy',
|
||||||
|
manager: 'Manager info',
|
||||||
|
question: 'Form questions',
|
||||||
|
manual: 'Manual',
|
||||||
|
};
|
||||||
|
|
||||||
|
function managerStoredId(level, key) {
|
||||||
|
const number = String(level || '').replace('l', '');
|
||||||
|
const suffix = key === 'employee_code' ? 'code' : key === 'position_name' ? 'position' : key;
|
||||||
|
return `manager${number}_${suffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function option(id, source, key, label, extra = {}) {
|
||||||
|
return { value: id, label, row: { id, source, key, label, show_in_export: true, show_in_view: false, ...extra } };
|
||||||
|
}
|
||||||
|
|
||||||
|
function CheckDropdown({ title, count, options, taken, onToggle, emptyText, disabled, disabledText }) {
|
||||||
|
return (
|
||||||
|
<Dropdown autoClose="outside">
|
||||||
|
<Dropdown.Toggle variant="outline-dark" disabled={disabled}>
|
||||||
|
{disabled ? disabledText : `${title} (${count})`}
|
||||||
|
</Dropdown.Toggle>
|
||||||
|
<Dropdown.Menu className="p-2 border shadow-sm" style={{ maxHeight: 280, overflowY: 'auto' }}>
|
||||||
|
{options.length ? options.map((item) => (
|
||||||
|
<Dropdown.Item key={item.value} as="div" className="px-1">
|
||||||
|
<Form.Check
|
||||||
|
type="checkbox"
|
||||||
|
label={item.label}
|
||||||
|
checked={taken.has(item.value)}
|
||||||
|
onChange={(event) => onToggle(item, event.target.checked)}
|
||||||
|
/>
|
||||||
|
</Dropdown.Item>
|
||||||
|
)) : <div className="text-muted small px-2">{emptyText}</div>}
|
||||||
|
</Dropdown.Menu>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DesignerRow({ row, onPatch, onRemove }) {
|
||||||
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: row.id });
|
||||||
|
const style = {
|
||||||
|
transform: CSS.Transform.toString(transform),
|
||||||
|
transition,
|
||||||
|
opacity: isDragging ? 0.6 : 1,
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div ref={setNodeRef} style={style} className="d-flex align-items-center gap-2 border rounded px-2 py-2 mb-2 bg-white">
|
||||||
|
<button type="button" className="btn btn-light btn-sm" aria-label="Reorder" {...attributes} {...listeners}>
|
||||||
|
::
|
||||||
|
</button>
|
||||||
|
<div className="flex-grow-1">
|
||||||
|
<div className="fw-semibold">{row.label}</div>
|
||||||
|
<div className="text-muted small">{SOURCE_LABELS[row.source] || row.source}</div>
|
||||||
|
</div>
|
||||||
|
<Form.Check
|
||||||
|
type="checkbox"
|
||||||
|
label="Form Data Export"
|
||||||
|
checked={row.show_in_export !== false}
|
||||||
|
onChange={(event) => onPatch(row.id, { show_in_export: event.target.checked })}
|
||||||
|
/>
|
||||||
|
<Form.Check
|
||||||
|
type="checkbox"
|
||||||
|
label="Form Data View"
|
||||||
|
checked={Boolean(row.show_in_view)}
|
||||||
|
onChange={(event) => onPatch(row.id, { show_in_view: event.target.checked })}
|
||||||
|
/>
|
||||||
|
<Button type="button" variant="outline-danger" size="sm" onClick={() => onRemove(row.id)}>
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DataDesignerEditor({ value, onChange, userAttributes = {} }) {
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const formId = searchParams.get('id') || '';
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [questions, setQuestions] = useState([]);
|
||||||
|
const [hierarchyLevels, setHierarchyLevels] = useState([]);
|
||||||
|
const [manualOpen, setManualOpen] = useState(false);
|
||||||
|
const [manualKey, setManualKey] = useState('');
|
||||||
|
const [manualLabel, setManualLabel] = useState('');
|
||||||
|
const designer = asDesigner(value);
|
||||||
|
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 6 } }));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!formId) {
|
||||||
|
setQuestions([]);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
let cancelled = false;
|
||||||
|
signedGet('form', 'formquestion/get_all', { entity_id: formId }).then((response) => {
|
||||||
|
if (cancelled || !isSuccess(response)) return;
|
||||||
|
const items = response.data?.items || [];
|
||||||
|
setQuestions(Array.isArray(items) ? items : Object.values(items));
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [formId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
signedGet('ums', 'domain/get_one').then((response) => {
|
||||||
|
if (cancelled) return;
|
||||||
|
const types = isSuccess(response) ? response.data?.item?.territory_types : [];
|
||||||
|
const levels = Array.isArray(types) ? types.map((type) => String(type || '').trim()).filter(Boolean) : [];
|
||||||
|
setHierarchyLevels(levels.length ? levels : DEFAULT_HIERARCHY_LEVELS);
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const taken = useMemo(() => new Set(designer.fields.map((row) => row.id)), [designer.fields]);
|
||||||
|
|
||||||
|
const userOptions = useMemo(() => {
|
||||||
|
const fixed = [
|
||||||
|
['name', 'Name'],
|
||||||
|
['email', 'Email'],
|
||||||
|
['employee_code', 'Employee Code'],
|
||||||
|
];
|
||||||
|
const attributes = Object.entries(userAttributes);
|
||||||
|
const labelKey = (label) => String(label || '').trim().toLowerCase();
|
||||||
|
const usedCodes = new Set();
|
||||||
|
const usedLabels = new Set();
|
||||||
|
const rows = [];
|
||||||
|
const addRow = (key, label) => {
|
||||||
|
const normalized = labelKey(label);
|
||||||
|
if (!key || usedCodes.has(key) || usedLabels.has(normalized)) return;
|
||||||
|
usedCodes.add(key);
|
||||||
|
usedLabels.add(normalized);
|
||||||
|
rows.push(option(key, 'user', key, label));
|
||||||
|
};
|
||||||
|
fixed.forEach(([key, label]) => {
|
||||||
|
const match = attributes.find(([code, attrLabel]) => code === key || labelKey(attrLabel) === labelKey(label));
|
||||||
|
if (match) addRow(match[0], match[1]);
|
||||||
|
else addRow(key, label);
|
||||||
|
});
|
||||||
|
attributes.forEach(([code, label]) => addRow(code, label));
|
||||||
|
return rows;
|
||||||
|
}, [userAttributes]);
|
||||||
|
|
||||||
|
const hierarchyOptions = hierarchyLevels.map((level) => option(String(level).trim().toLowerCase(), 'hierarchy', level, level));
|
||||||
|
|
||||||
|
const managerOptions = MANAGER_LEVELS.flatMap(([level, title]) =>
|
||||||
|
MANAGER_KEYS.map(([key, name]) =>
|
||||||
|
option(managerStoredId(level, key), 'manager', key, `${title} Manager ${name}`, { level }),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const questionOptions = [
|
||||||
|
...questions
|
||||||
|
.filter((question) => question?.id && question.status !== 'disabled')
|
||||||
|
.map((question) =>
|
||||||
|
option(question.code || String(question.id), 'question', question.code || String(question.id), question.question || question.code || question.id),
|
||||||
|
),
|
||||||
|
option('submission_id', 'question', 'submission_id', 'Submission ID'),
|
||||||
|
option('rec_date', 'question', 'rec_date', 'Record Date'),
|
||||||
|
option('created_date', 'question', 'created_date', 'Submitted Date'),
|
||||||
|
option('updated_date', 'question', 'updated_date', 'Last Updated Date'),
|
||||||
|
option('workflow_status', 'question', 'workflow_status', 'Workflow Status'),
|
||||||
|
];
|
||||||
|
|
||||||
|
const appendRows = (picked) => {
|
||||||
|
const rows = (picked || []).map((item) => item.row).filter((row) => row && !taken.has(row.id));
|
||||||
|
if (!rows.length) return;
|
||||||
|
onChange({ fields: [...designer.fields, ...rows] });
|
||||||
|
};
|
||||||
|
|
||||||
|
const patchRow = (id, patch) => {
|
||||||
|
onChange({
|
||||||
|
fields: designer.fields.map((row) => (row.id === id ? { ...row, ...patch } : row)),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeRow = (id) => {
|
||||||
|
onChange({ fields: designer.fields.filter((row) => row.id !== id) });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDragEnd = (event) => {
|
||||||
|
const { active, over } = event;
|
||||||
|
if (!over || active.id === over.id) return;
|
||||||
|
const from = designer.fields.findIndex((row) => row.id === active.id);
|
||||||
|
const to = designer.fields.findIndex((row) => row.id === over.id);
|
||||||
|
if (from < 0 || to < 0) return;
|
||||||
|
onChange({ fields: arrayMove(designer.fields, from, to) });
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleOption = (item, checked) => {
|
||||||
|
if (checked) appendRows([item]);
|
||||||
|
else removeRow(item.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const countFor = (source) => designer.fields.filter((row) => row.source === source).length;
|
||||||
|
|
||||||
|
const addManual = () => {
|
||||||
|
const key = manualKey.trim();
|
||||||
|
const label = manualLabel.trim();
|
||||||
|
if (!key || !label) return;
|
||||||
|
const id = key;
|
||||||
|
if (taken.has(id)) return;
|
||||||
|
onChange({
|
||||||
|
fields: [...designer.fields, { id, source: 'manual', key, label, show_in_export: true, show_in_view: false }],
|
||||||
|
});
|
||||||
|
setManualKey('');
|
||||||
|
setManualLabel('');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="border rounded p-3">
|
||||||
|
<div className="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<strong>Data Designer</strong>
|
||||||
|
<Button type="button" variant="dark" size="sm" onClick={() => setOpen((current) => !current)}>
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{open ? (
|
||||||
|
<div className="mb-3">
|
||||||
|
<div className="d-flex flex-wrap gap-2 align-items-start mb-3">
|
||||||
|
<CheckDropdown title="User attributes" count={countFor('user')} options={userOptions} taken={taken} onToggle={toggleOption} emptyText="No user fields." />
|
||||||
|
<CheckDropdown title="Hierarchy" count={countFor('hierarchy')} options={hierarchyOptions} taken={taken} onToggle={toggleOption} emptyText="No hierarchy levels." />
|
||||||
|
<CheckDropdown title="Manager info" count={countFor('manager')} options={managerOptions} taken={taken} onToggle={toggleOption} emptyText="No manager fields." />
|
||||||
|
<CheckDropdown
|
||||||
|
title="Form questions"
|
||||||
|
count={countFor('question')}
|
||||||
|
options={questionOptions}
|
||||||
|
taken={taken}
|
||||||
|
onToggle={toggleOption}
|
||||||
|
emptyText="No questions."
|
||||||
|
disabled={!formId}
|
||||||
|
disabledText="Save the form before choosing questions"
|
||||||
|
/>
|
||||||
|
<Button type="button" variant={manualOpen ? 'dark' : 'outline-dark'} onClick={() => setManualOpen((current) => !current)}>
|
||||||
|
Manual ({countFor('manual')})
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{manualOpen ? (
|
||||||
|
<div className="d-flex flex-wrap gap-2 align-items-end">
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>Field name</Form.Label>
|
||||||
|
<Form.Control value={manualKey} onChange={(event) => setManualKey(event.target.value)} placeholder="erdostyne_capsule_value" />
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>Column title</Form.Label>
|
||||||
|
<Form.Control value={manualLabel} onChange={(event) => setManualLabel(event.target.value)} placeholder="ERDOSTYNE CAPSULE 10s Value" />
|
||||||
|
</Form.Group>
|
||||||
|
<Button type="button" variant="outline-dark" onClick={addManual}>
|
||||||
|
Add manual field
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>
|
||||||
|
<SortableContext items={designer.fields.map((row) => row.id)} strategy={verticalListSortingStrategy}>
|
||||||
|
{designer.fields.map((row) => (
|
||||||
|
<DesignerRow key={row.id} row={row} onPatch={patchRow} onRemove={removeRow} />
|
||||||
|
))}
|
||||||
|
</SortableContext>
|
||||||
|
</DndContext>
|
||||||
|
{!designer.fields.length ? <p className="text-muted mb-0">No fields selected.</p> : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DataDesignerField({ field, control }) {
|
||||||
|
return (
|
||||||
|
<Controller
|
||||||
|
name={field.key}
|
||||||
|
control={control}
|
||||||
|
render={({ field: rhf }) => (
|
||||||
|
<DataDesignerEditor value={rhf.value} onChange={rhf.onChange} userAttributes={field.userAttributes || {}} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { signedGet, signedPost, isSuccess } from '@/api/client';
|
import { signedGet, signedPost, isSuccess } from '@/api/client';
|
||||||
|
import { normalizeDataDesigner } from '@/components/form/DataDesignerField';
|
||||||
import { dropdownMap, fetchUserAttributes } from '@/api/dropdowns';
|
import { dropdownMap, fetchUserAttributes } from '@/api/dropdowns';
|
||||||
import { splitHash } from '@/helpers/metric';
|
import { splitHash } from '@/helpers/metric';
|
||||||
|
|
||||||
|
|
@ -201,6 +202,13 @@ const formentitys = {
|
||||||
roles,
|
roles,
|
||||||
users: notifyUsers,
|
users: notifyUsers,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'data_designer',
|
||||||
|
type: 'data_designer',
|
||||||
|
fullwidth: true,
|
||||||
|
hide_label: true,
|
||||||
|
userAttributes,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
beforeForm: (item) => {
|
beforeForm: (item) => {
|
||||||
|
|
@ -237,6 +245,7 @@ const formentitys = {
|
||||||
next.roles_push_notification = asArray(next.roles_push_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'];
|
next.workflow_approvers = Array.isArray(next.workflow_approvers) && next.workflow_approvers.length ? next.workflow_approvers : ['l1manager'];
|
||||||
next.workflow_definition_id = next.workflow_definition_id || '';
|
next.workflow_definition_id = next.workflow_definition_id || '';
|
||||||
|
next.data_designer = normalizeDataDesigner(next.data_designer);
|
||||||
return next;
|
return next;
|
||||||
},
|
},
|
||||||
beforeSave: async (data, mode) => {
|
beforeSave: async (data, mode) => {
|
||||||
|
|
@ -245,6 +254,7 @@ const formentitys = {
|
||||||
payload.export_manager_levels = asArray(payload.export_manager_levels);
|
payload.export_manager_levels = asArray(payload.export_manager_levels);
|
||||||
payload.manager_de_roles = asArray(payload.manager_de_roles);
|
payload.manager_de_roles = asArray(payload.manager_de_roles);
|
||||||
payload.contact_attributes = asArray(payload.contact_attributes);
|
payload.contact_attributes = asArray(payload.contact_attributes);
|
||||||
|
payload.data_designer = normalizeDataDesigner(payload.data_designer);
|
||||||
payload.show_user_attributes = payload.show_user_attributes === 'yes' ? 'yes' : 'no';
|
payload.show_user_attributes = payload.show_user_attributes === 'yes' ? 'yes' : 'no';
|
||||||
payload.user_attributes = asArray(payload.user_attributes);
|
payload.user_attributes = asArray(payload.user_attributes);
|
||||||
if (payload.user_attributes.length) payload.show_user_attributes = 'yes';
|
if (payload.user_attributes.length) payload.show_user_attributes = 'yes';
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ const formexports = {
|
||||||
{ title: 'Add New', to: '/formexport/addnew' },
|
{ title: 'Add New', to: '/formexport/addnew' },
|
||||||
{ title: 'Refresh', action: 'reload' },
|
{ title: 'Refresh', action: 'reload' },
|
||||||
],
|
],
|
||||||
|
mapItems: (items) =>
|
||||||
|
[...(items || [])].sort((a, b) => new Date(b.createdAt || 0) - new Date(a.createdAt || 0)),
|
||||||
formFields: async () => {
|
formFields: async () => {
|
||||||
const [forms, dropdown] = await Promise.all([
|
const [forms, dropdown] = await Promise.all([
|
||||||
dropdownMap('form', 'formentity', (item) => item.title),
|
dropdownMap('form', 'formentity', (item) => item.title),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig , loadEnv } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import { resolve } from 'node:path';
|
import { resolve } from 'node:path';
|
||||||
|
|
||||||
|
|
@ -57,18 +57,20 @@ const laravelProxy = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [react()],
|
export default defineConfig(({ mode }) => {
|
||||||
resolve: {
|
const env = loadEnv(mode, process.cwd(), '');
|
||||||
alias: {
|
return {
|
||||||
'@': resolve(__dirname, 'src'),
|
plugins: [react()],
|
||||||
|
resolve: {
|
||||||
|
alias: { '@': resolve(__dirname, 'src') },
|
||||||
},
|
},
|
||||||
},
|
server: {
|
||||||
server: {
|
port: 5173,
|
||||||
port: 5173,
|
proxy: {
|
||||||
proxy: {
|
...laravelProxy,
|
||||||
...laravelProxy,
|
...(env.VITE_API_MODE === 'local' ? localProxy : productionProxy),
|
||||||
...(process.env.VITE_API_MODE === 'local' ? localProxy : productionProxy),
|
},
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue