Turned the mailtypes ul into a reusable selectable list control.
This commit is contained in:
parent
1e16b2676e
commit
745473759d
@ -11,26 +11,3 @@
|
|||||||
grid-template-columns: fit-content(50%) auto;
|
grid-template-columns: fit-content(50%) auto;
|
||||||
grid-gap: $gridGap;
|
grid-gap: $gridGap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mail-types {
|
|
||||||
padding-left: 0rem;
|
|
||||||
list-style: none;
|
|
||||||
width: $mailtemplateNameListWidth;
|
|
||||||
|
|
||||||
li {
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
background-color: #0078d4;
|
|
||||||
color: white;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -29,6 +29,7 @@
|
|||||||
@import "./_expandableCell.scss";
|
@import "./_expandableCell.scss";
|
||||||
@import "./_errorLogs.scss";
|
@import "./_errorLogs.scss";
|
||||||
@import "./addTaskButton.scss";
|
@import "./addTaskButton.scss";
|
||||||
|
@import "./selectableList.scss";
|
||||||
|
|
||||||
//Changes needed to make MS Edge behave the same as other browsers
|
//Changes needed to make MS Edge behave the same as other browsers
|
||||||
input::-ms-reveal {
|
input::-ms-reveal {
|
||||||
|
|||||||
22
src/Sass/selectableList.scss
Normal file
22
src/Sass/selectableList.scss
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
.selectable-list {
|
||||||
|
padding-left: 0rem;
|
||||||
|
list-style: none;
|
||||||
|
width: $mailtemplateNameListWidth;
|
||||||
|
|
||||||
|
li {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background-color: #0078d4;
|
||||||
|
color: white;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/components/common/SelectableList.tsx
Normal file
33
src/components/common/SelectableList.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export interface SelectableListProps<T> {
|
||||||
|
items: T[];
|
||||||
|
selectedValue?: t | null;
|
||||||
|
renderLabel: (item: T) => React.ReactNode;
|
||||||
|
onSelect: (item: T) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SelectableList = <T,>(
|
||||||
|
props: SelectableListProps<T>,
|
||||||
|
): JSX.Element => {
|
||||||
|
const { items, selectedValue, renderLabel, onSelect } = props;
|
||||||
|
|
||||||
|
const listClassName = "selectable-list";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className={listClassName}>
|
||||||
|
{items.map((item, index) => {
|
||||||
|
const isSelected = selectedValue === item;
|
||||||
|
const className = isSelected
|
||||||
|
? ["selected"].filter(Boolean).join(" ")
|
||||||
|
: "";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={index} onClick={() => onSelect(item)} className={className}>
|
||||||
|
{renderLabel(item)}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -102,6 +102,7 @@ export const useForm = (initialState: FormState): UseFormReturn => {
|
|||||||
const initialDataRef = useRef<FormData>(
|
const initialDataRef = useRef<FormData>(
|
||||||
JSON.parse(JSON.stringify(initialState.data)),
|
JSON.parse(JSON.stringify(initialState.data)),
|
||||||
);
|
);
|
||||||
|
const wasLoadedRef = useRef<boolean>(initialState.loaded);
|
||||||
|
|
||||||
const setState = useCallback((updates: Partial<FormState>) => {
|
const setState = useCallback((updates: Partial<FormState>) => {
|
||||||
setStateInternal((prev) => ({ ...prev, ...updates }));
|
setStateInternal((prev) => ({ ...prev, ...updates }));
|
||||||
@ -621,6 +622,14 @@ export const useForm = (initialState: FormState): UseFormReturn => {
|
|||||||
return cleanup;
|
return cleanup;
|
||||||
}, [setupNavigationGuard]);
|
}, [setupNavigationGuard]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!wasLoadedRef.current && state.loaded) {
|
||||||
|
initialDataRef.current = JSON.parse(JSON.stringify(state.data));
|
||||||
|
}
|
||||||
|
|
||||||
|
wasLoadedRef.current = state.loaded;
|
||||||
|
}, [state.loaded, state.data]);
|
||||||
|
|
||||||
const api: any = {
|
const api: any = {
|
||||||
state,
|
state,
|
||||||
schema: schemaRef.current,
|
schema: schemaRef.current,
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import React, { useEffect, useState, useCallback, Suspense } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import mailTemplatesService from "../serrvices/mailTemplatesService";
|
import mailTemplatesService from "../serrvices/mailTemplatesService";
|
||||||
import HOCEmailTemplateEditor from "./EmailTemplateEditor";
|
import HOCEmailTemplateEditor from "./EmailTemplateEditor";
|
||||||
import Loading from "../../../../components/common/Loading";
|
import Loading from "../../../../components/common/Loading";
|
||||||
import { Namespaces } from "../../../../i18n/i18n";
|
import { Namespaces } from "../../../../i18n/i18n";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { SelectableList } from "../../../../components/common/SelectableList";
|
||||||
|
|
||||||
interface MailType {
|
interface MailType {
|
||||||
mailType: string;
|
mailType: string;
|
||||||
@ -13,35 +14,27 @@ interface MailType {
|
|||||||
|
|
||||||
const MailTemplatesTabContent: React.FC<{
|
const MailTemplatesTabContent: React.FC<{
|
||||||
types: MailType[];
|
types: MailType[];
|
||||||
currentMailType: string;
|
currentMailType: MailType | null;
|
||||||
domainId: string | undefined;
|
domainId: string | undefined;
|
||||||
onClick: (e: React.MouseEvent<HTMLElement>) => void;
|
onClick: (item: MailType) => void;
|
||||||
}> = ({ types, currentMailType, domainId, onClick }) => {
|
}> = ({ types, currentMailType, domainId, onClick }) => {
|
||||||
const { t: tMail } = useTranslation(Namespaces.MailTypes);
|
const { t: tMail } = useTranslation(Namespaces.MailTypes);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="two-column-grid">
|
<div className="two-column-grid">
|
||||||
<div className="fit-content-width">
|
<div className="fit-content-width">
|
||||||
<ul className="mail-types">
|
<SelectableList
|
||||||
{types.map((x) => {
|
items={types}
|
||||||
return (
|
selectedValue={currentMailType}
|
||||||
<li
|
renderLabel={(x) => tMail(x.mailType)}
|
||||||
key={x.mailType}
|
onSelect={(item) => onClick(item)}
|
||||||
value={x.mailType}
|
/>
|
||||||
onClick={onClick}
|
|
||||||
className={currentMailType === x.mailType ? "selected" : ""}
|
|
||||||
>
|
|
||||||
{tMail(x.mailType)}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{domainId && currentMailType ? (
|
{domainId && currentMailType ? (
|
||||||
<HOCEmailTemplateEditor
|
<HOCEmailTemplateEditor
|
||||||
domainId={domainId}
|
domainId={domainId}
|
||||||
currentMailType={currentMailType}
|
currentMailType={currentMailType.mailType}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
@ -51,7 +44,7 @@ const MailTemplatesTabContent: React.FC<{
|
|||||||
|
|
||||||
const MailTemplatesTab: React.FC = () => {
|
const MailTemplatesTab: React.FC = () => {
|
||||||
const [loaded, setLoaded] = useState(false);
|
const [loaded, setLoaded] = useState(false);
|
||||||
const [currentMailType, setCurrentMailType] = useState("");
|
const [currentMailType, setCurrentMailType] = useState<MailType | null>(null);
|
||||||
const [types, setTypes] = useState<MailType[]>([]);
|
const [types, setTypes] = useState<MailType[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -62,7 +55,7 @@ const MailTemplatesTab: React.FC = () => {
|
|||||||
setTypes(nextTypes);
|
setTypes(nextTypes);
|
||||||
|
|
||||||
if (nextTypes.length > 0) {
|
if (nextTypes.length > 0) {
|
||||||
setCurrentMailType(nextTypes[0].mailType);
|
setCurrentMailType(nextTypes[0]);
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
console.error(ex);
|
console.error(ex);
|
||||||
@ -74,34 +67,20 @@ const MailTemplatesTab: React.FC = () => {
|
|||||||
void loadTypes();
|
void loadTypes();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const selectTemplate = useCallback(
|
const onClick = (item: MailType) => {
|
||||||
(emailType: string) => {
|
setCurrentMailType(item);
|
||||||
if (currentMailType !== emailType) {
|
|
||||||
setCurrentMailType(emailType);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[currentMailType],
|
|
||||||
);
|
|
||||||
|
|
||||||
const onClick = (e: React.MouseEvent<HTMLElement>) => {
|
|
||||||
const value = (e.target as HTMLElement).getAttribute("value");
|
|
||||||
if (value) {
|
|
||||||
selectTemplate(value);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const { domainId } = useParams<{ domainId: string }>();
|
const { domainId } = useParams<{ domainId: string }>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Loading loaded={loaded}>
|
<Loading loaded={loaded}>
|
||||||
<Suspense fallback={<div>Loading...</div>}>
|
<MailTemplatesTabContent
|
||||||
<MailTemplatesTabContent
|
types={types}
|
||||||
types={types}
|
currentMailType={currentMailType}
|
||||||
currentMailType={currentMailType}
|
domainId={domainId}
|
||||||
domainId={domainId}
|
onClick={onClick}
|
||||||
onClick={onClick}
|
/>
|
||||||
/>
|
|
||||||
</Suspense>
|
|
||||||
</Loading>
|
</Loading>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import {
|
import {
|
||||||
TaskDefinition,
|
TaskDefinition,
|
||||||
TaskMetadata,
|
TaskMetadata,
|
||||||
@ -6,6 +6,7 @@ import {
|
|||||||
import AddTaskButton from "./AddTaskButton";
|
import AddTaskButton from "./AddTaskButton";
|
||||||
import { Namespaces } from "../../../../i18n/i18n";
|
import { Namespaces } from "../../../../i18n/i18n";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { SelectableList } from "../../../../components/common/SelectableList";
|
||||||
|
|
||||||
interface TaskListProps {
|
interface TaskListProps {
|
||||||
tasks: TaskDefinition[];
|
tasks: TaskDefinition[];
|
||||||
@ -16,14 +17,30 @@ interface TaskListProps {
|
|||||||
const TaskList: React.FC<TaskListProps> = ({ tasks, taskType, onChange }) => {
|
const TaskList: React.FC<TaskListProps> = ({ tasks, taskType, onChange }) => {
|
||||||
const { t: tTaskType } = useTranslation(Namespaces.TaskTypes);
|
const { t: tTaskType } = useTranslation(Namespaces.TaskTypes);
|
||||||
|
|
||||||
|
const [currentTask, setCurrentTask] = useState<TaskDefinition | null>(null);
|
||||||
|
|
||||||
|
const formatNewTaskName = (
|
||||||
|
displayName: string,
|
||||||
|
tasks: TaskDefinition<Record<string, unknown>>[],
|
||||||
|
) => {
|
||||||
|
return `${tTaskType(displayName)} ${tasks.length + 1}`;
|
||||||
|
};
|
||||||
|
|
||||||
const handleAddTask = (selectedType: TaskMetadata) => {
|
const handleAddTask = (selectedType: TaskMetadata) => {
|
||||||
|
const formattedName = formatNewTaskName(selectedType.displayName, tasks);
|
||||||
|
|
||||||
const newTask: TaskDefinition = {
|
const newTask: TaskDefinition = {
|
||||||
type: selectedType.taskType,
|
type: selectedType.taskType,
|
||||||
|
|
||||||
config: { name: tTaskType(selectedType.displayName) },
|
config: {
|
||||||
|
name: formattedName,
|
||||||
|
guid: crypto.randomUUID(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("Add Task clicked");
|
if (tasks.length === 0) {
|
||||||
|
setCurrentTask(newTask);
|
||||||
|
}
|
||||||
onChange([...tasks, newTask]);
|
onChange([...tasks, newTask]);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -31,11 +48,12 @@ const TaskList: React.FC<TaskListProps> = ({ tasks, taskType, onChange }) => {
|
|||||||
<div>
|
<div>
|
||||||
<AddTaskButton taskType={taskType} onAdd={handleAddTask} />
|
<AddTaskButton taskType={taskType} onAdd={handleAddTask} />
|
||||||
|
|
||||||
<ul>
|
<SelectableList
|
||||||
{tasks.map((task, index) => (
|
items={tasks}
|
||||||
<li key={index}>{task.type}</li>
|
selectedValue={currentTask}
|
||||||
))}
|
renderLabel={(x) => x.config.name as string}
|
||||||
</ul>
|
onSelect={(item) => setCurrentTask(item)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -30,7 +30,7 @@ export type ReadWorkflowTemplateVersion = {
|
|||||||
|
|
||||||
export interface TaskDefinition<TConfig = Record<string, unknown>> {
|
export interface TaskDefinition<TConfig = Record<string, unknown>> {
|
||||||
type: string;
|
type: string;
|
||||||
config?: TConfig;
|
config: TConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateWorkflowTemplateVersion extends FormData {
|
export interface CreateWorkflowTemplateVersion extends FormData {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user