feat: minor adjustements

This commit is contained in:
Mohamed Marrouchi 2024-11-27 16:34:52 +01:00
parent cc6e50e5d0
commit eb7d6ebe3c
7 changed files with 112 additions and 63 deletions

View File

@ -70,6 +70,8 @@ export enum FileType {
export enum PayloadType { export enum PayloadType {
location = 'location', location = 'location',
attachments = 'attachments', attachments = 'attachments',
quick_reply = 'quick_reply',
button = 'button',
} }
export type StdOutgoingTextMessage = { text: string }; export type StdOutgoingTextMessage = { text: string };

View File

@ -7,11 +7,7 @@
*/ */
import { IncomingAttachmentPayload } from './attachment'; import { IncomingAttachmentPayload } from './attachment';
import { PayloadType } from './message';
export enum PayloadType {
location = 'location',
attachments = 'attachments',
}
export type Payload = export type Payload =
| { | {

View File

@ -13,10 +13,13 @@ import {
import { BlockFull } from '@/chat/schemas/block.schema'; import { BlockFull } from '@/chat/schemas/block.schema';
import { FileType } from '@/chat/schemas/types/attachment'; import { FileType } from '@/chat/schemas/types/attachment';
import { ButtonType } from '@/chat/schemas/types/button'; import { ButtonType } from '@/chat/schemas/types/button';
import { OutgoingMessageFormat } from '@/chat/schemas/types/message'; import {
OutgoingMessageFormat,
PayloadType,
} from '@/chat/schemas/types/message';
import { BlockOptions, ContentOptions } from '@/chat/schemas/types/options'; import { BlockOptions, ContentOptions } from '@/chat/schemas/types/options';
import { Pattern } from '@/chat/schemas/types/pattern'; import { Pattern } from '@/chat/schemas/types/pattern';
import { PayloadType, QuickReplyType } from '@/chat/schemas/types/quick-reply'; import { QuickReplyType } from '@/chat/schemas/types/quick-reply';
import { CaptureVar } from '@/chat/validation-rules/is-valid-capture'; import { CaptureVar } from '@/chat/validation-rules/is-valid-capture';
import { modelInstance } from './misc'; import { modelInstance } from './misc';

View File

@ -151,7 +151,7 @@ const PatternInput: FC<PatternInputProps> = ({
onChange={(payload) => { onChange={(payload) => {
payload && setPattern(payload); payload && setPattern(payload);
}} }}
value={pattern ? (pattern as PayloadPattern).value : null} defaultValue={pattern as PayloadPattern}
/> />
) : null} ) : null}
{typeof value === "string" && patternType === "regex" ? ( {typeof value === "string" && patternType === "regex" ? (

View File

@ -6,10 +6,17 @@
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/ */
import { Box, Skeleton, Typography } from "@mui/material"; import {
import { useMemo } from "react"; Autocomplete,
Box,
CircularProgress,
InputAdornment,
Skeleton,
Typography,
} from "@mui/material";
import { useMemo, useState } from "react";
import AutoCompleteSelect from "@/app-components/inputs/AutoCompleteSelect"; import { Input } from "@/app-components/inputs/Input";
import { useFind } from "@/hooks/crud/useFind"; import { useFind } from "@/hooks/crud/useFind";
import { useGetFromCache } from "@/hooks/crud/useGet"; import { useGetFromCache } from "@/hooks/crud/useGet";
import { useTranslate } from "@/hooks/useTranslate"; import { useTranslate } from "@/hooks/useTranslate";
@ -26,25 +33,31 @@ import {
import { useBlock } from "../../BlockFormProvider"; import { useBlock } from "../../BlockFormProvider";
type PayloadOption = { type PayloadOption = PayloadPattern & {
id: string; group: string;
label: string; };
group?: string;
const isSamePostback = <T extends PayloadPattern>(a: T, b: T) => {
return a.label === b.label && a.value === b.value;
}; };
type PostbackInputProps = { type PostbackInputProps = {
value?: string | null; defaultValue?: PayloadPattern;
onChange: (pattern: PayloadPattern) => void; onChange: (pattern: PayloadPattern | null) => void;
}; };
export const PostbackInput = ({ value, onChange }: PostbackInputProps) => { export const PostbackInput = ({
defaultValue,
onChange,
}: PostbackInputProps) => {
const block = useBlock(); const block = useBlock();
const [selectedValue, setSelectedValue] = useState(defaultValue || null);
const getBlockFromCache = useGetFromCache(EntityType.BLOCK); const getBlockFromCache = useGetFromCache(EntityType.BLOCK);
const { data: menu } = useFind( const { data: menu, isLoading: isLoadingMenu } = useFind(
{ entity: EntityType.MENU, format: Format.FULL }, { entity: EntityType.MENU, format: Format.FULL },
{ hasCount: false }, { hasCount: false },
); );
const { data: contents } = useFind( const { data: contents, isLoading: isLoadingContent } = useFind(
{ entity: EntityType.CONTENT, format: Format.FULL }, { entity: EntityType.CONTENT, format: Format.FULL },
{ {
hasCount: false, hasCount: false,
@ -54,20 +67,21 @@ export const PostbackInput = ({ value, onChange }: PostbackInputProps) => {
// General options // General options
const generalOptions = [ const generalOptions = [
{ {
id: "GET_STARTED",
label: t("label.get_started"), label: t("label.get_started"),
group: t("label.general"), value: "GET_STARTED",
group: "general",
}, },
{ {
id: "VIEW_MORE",
label: t("label.view_more"), label: t("label.view_more"),
group: t("label.general"), value: "VIEW_MORE",
group: "general",
}, },
{ {
id: "LOCATION",
label: t("label.location"), label: t("label.location"),
group: t("label.general"), value: "LOCATION",
type: PayloadType.location,
group: "general",
}, },
]; ];
// Gather previous blocks buttons // Gather previous blocks buttons
@ -92,8 +106,8 @@ export const PostbackInput = ({ value, onChange }: PostbackInputProps) => {
}, [] as (PostBackButton & { group: string })[]) }, [] as (PostBackButton & { group: string })[])
.map((btn) => { .map((btn) => {
return { return {
id: btn.payload,
label: btn.title, label: btn.title,
value: btn.payload,
group: btn.group, group: btn.group,
}; };
}), }),
@ -125,6 +139,8 @@ export const PostbackInput = ({ value, onChange }: PostbackInputProps) => {
return { return {
id: btn.payload as string, id: btn.payload as string,
label: btn.title as string, label: btn.title as string,
value: btn.payload as string,
type: PayloadType.menu,
group: btn.group, group: btn.group,
}; };
}), }),
@ -132,9 +148,11 @@ export const PostbackInput = ({ value, onChange }: PostbackInputProps) => {
); );
const menuOptions = menu const menuOptions = menu
.filter(({ payload }) => payload) .filter(({ payload }) => payload)
.map(({ title }) => ({ .map(({ title, payload }) => ({
id: title, id: title,
label: title, label: title,
value: payload as string,
type: PayloadType.menu,
group: "menu", group: "menu",
})); }));
const contentOptions = useMemo( const contentOptions = useMemo(
@ -158,15 +176,17 @@ export const PostbackInput = ({ value, onChange }: PostbackInputProps) => {
return (b.options?.content?.buttons || []).reduce((payloads, btn) => { return (b.options?.content?.buttons || []).reduce((payloads, btn) => {
// Return a payload for each node/button combination // Return a payload for each node/button combination
payloads.push({ payloads.push({
id: btn.title,
label: btn.title, label: btn.title,
value: btn.title,
type: PayloadType.content,
group: "content", group: "content",
}); });
return availableContents.reduce((acc, n) => { return availableContents.reduce((acc, n) => {
acc.push({ acc.push({
id: n.title,
label: n.title, label: n.title,
value: n.title,
type: PayloadType.content,
group: "content", group: "content",
}); });
@ -178,52 +198,76 @@ export const PostbackInput = ({ value, onChange }: PostbackInputProps) => {
[block?.previousBlocks, contents, getBlockFromCache], [block?.previousBlocks, contents, getBlockFromCache],
); );
// Concat all previous blocks // Concat all previous blocks
const options = [ const options: PayloadOption[] = [
...generalOptions, ...generalOptions,
...btnOptions, ...btnOptions,
...qrOptions, ...qrOptions,
...menuOptions, ...menuOptions,
...contentOptions, ...contentOptions,
]; ];
const isOptionsReady = !value || options.find((e) => e.id === value); const isOptionsReady =
!defaultValue || options.find((o) => isSamePostback(o, defaultValue));
if (!isOptionsReady) { if (!isOptionsReady) {
return ( return (
<Skeleton animation="wave" variant="rounded" width="100%" height={40} /> <Skeleton animation="wave" variant="rounded" width="100%" height={40} />
); );
} }
const selected = defaultValue
? options.find((o) => {
return isSamePostback(o, defaultValue);
})
: undefined;
return ( return (
<> <Autocomplete<PayloadOption>
<AutoCompleteSelect<PayloadOption, "label", false> size="small"
value={value} defaultValue={selected}
options={options} options={options}
labelKey="label" // label={t("label.postback")}
label={t("label.postback")}
multiple={false} multiple={false}
onChange={(_e, content) => { onChange={(_e, value) => {
content && setSelectedValue(value);
onChange({ if (value) {
label: content.label, const { group: _g, ...payloadPattern } = value;
value: content.id,
type: ["content", "menu"].includes(content.group || "") onChange(payloadPattern);
? PayloadType[content?.group || ""] } else {
: undefined, onChange(null);
}); }
}} }}
groupBy={(option) => { groupBy={(option) => {
return option.group ?? t("label.other"); return option.group ?? t("label.other");
}} }}
getOptionLabel={({ group, label }) => `${group}:${label}`} getOptionLabel={({ label }) => label}
renderGroup={(params) => ( renderGroup={(params) => (
<li key={params.key}> <li key={params.key}>
<Typography component="h4" p={2} fontWeight={700} color="primary"> <Typography component="h4" p={2} fontWeight={700} color="primary">
{params.group} {t(`label.${params.group}`)}
</Typography> </Typography>
<Box>{params.children}</Box> <Box>{params.children}</Box>
</li> </li>
)} )}
renderInput={(props) => {
return (
<Input
{...props}
label={t("label.postback")}
InputProps={{
...props.InputProps,
startAdornment: (
<InputAdornment position="start">
{selectedValue?.type || t("label.postback")}
</InputAdornment>
),
endAdornment:
isLoadingMenu || isLoadingContent ? (
<CircularProgress color="inherit" size={20} />
) : null,
}}
/>
);
}}
/> />
</>
); );
}; };

View File

@ -66,6 +66,8 @@ export interface PayloadPattern {
label: string; label: string;
value: string; value: string;
// @todo : rename 'attachment' to 'attachments' // @todo : rename 'attachment' to 'attachments'
// @todo: If undefined, that means the payload could be either quick_reply or button
// We will move soon so that it will be a required attribute
type?: PayloadType; type?: PayloadType;
} }

View File

@ -28,6 +28,8 @@ export enum PayloadType {
attachments = "attachments", attachments = "attachments",
menu = "menu", menu = "menu",
content = "content", content = "content",
quick_reply = "quick_reply",
button = "button",
} }
export enum FileType { export enum FileType {