From 0478c4104acedecd3b56cda618ccbcd247086a59 Mon Sep 17 00:00:00 2001 From: Akansha Sakhre Date: Thu, 4 Jul 2024 08:41:33 +0530 Subject: [PATCH 01/21] added template flows in drop down --- src/assets/images/icons/ViewLight.svg | 15 ++++++ src/containers/Flow/Flow.tsx | 4 ++ src/containers/Flow/FlowList/FlowList.tsx | 46 +++++++++++++++---- .../AuthenticatedRoute/AuthenticatedRoute.tsx | 1 + 4 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 src/assets/images/icons/ViewLight.svg diff --git a/src/assets/images/icons/ViewLight.svg b/src/assets/images/icons/ViewLight.svg new file mode 100644 index 0000000000..3a9d55b862 --- /dev/null +++ b/src/assets/images/icons/ViewLight.svg @@ -0,0 +1,15 @@ + + + View + + + + + + + + + diff --git a/src/containers/Flow/Flow.tsx b/src/containers/Flow/Flow.tsx index 901b32642e..3893e339a6 100644 --- a/src/containers/Flow/Flow.tsx +++ b/src/containers/Flow/Flow.tsx @@ -44,6 +44,10 @@ export const Flow = () => { const [ignoreKeywords, setIgnoreKeywords] = useState(false); const { t } = useTranslation(); + let isViewing = false; + if (location) { + console.log(location); + } const { data: tag } = useQuery(GET_TAGS, { variables: {}, fetchPolicy: 'network-only', diff --git a/src/containers/Flow/FlowList/FlowList.tsx b/src/containers/Flow/FlowList/FlowList.tsx index 3c5a2b5ebd..929815252a 100644 --- a/src/containers/Flow/FlowList/FlowList.tsx +++ b/src/containers/Flow/FlowList/FlowList.tsx @@ -11,6 +11,7 @@ import DuplicateIcon from 'assets/images/icons/Duplicate.svg?react'; import ExportIcon from 'assets/images/icons/Flow/Export.svg?react'; import ConfigureIcon from 'assets/images/icons/Configure/UnselectedDark.svg?react'; import PinIcon from 'assets/images/icons/Pin/Active.svg?react'; +import ViewIcon from 'assets/images/icons/ViewLight.svg?react'; import { FILTER_FLOW, GET_FLOW_COUNT, EXPORT_FLOW, RELEASE_FLOW } from 'graphql/queries/Flow'; import { DELETE_FLOW, IMPORT_FLOW } from 'graphql/mutations/Flow'; import { List } from 'containers/List/List'; @@ -78,6 +79,7 @@ const queries = { }; const configureIcon = ; +const viewIcon = ; export const FlowList = () => { const navigate = useNavigate(); @@ -156,7 +158,24 @@ export const FlowList = () => { /> ); - const additionalAction = () => [ + const templateFlowActions = [ + { + label: 'View it', + icon: viewIcon, + parameter: 'uuid', + insideMore: false, + link: '/flow/view', + }, + { + label: 'Use it', + icon: configureIcon, + parameter: 'uuid', + insideMore: false, + link: '/flow/configure', + }, + ]; + + const actions = [ { label: t('Configure'), icon: configureIcon, @@ -179,6 +198,8 @@ export const FlowList = () => { }, ]; + const additionalAction = () => (filter === 'isTemplate' ? templateFlowActions : actions); + const getColumns = ({ name, keywords, @@ -215,6 +236,7 @@ export const FlowList = () => { const filterList = [ { label: 'Active', value: true }, { label: 'Inactive', value: false }, + { label: 'Template Flows', value: 'isTemplate' }, ]; const { data: tag } = useQuery(GET_TAGS, { variables: {}, @@ -230,7 +252,7 @@ export const FlowList = () => { value={filter} onChange={(event) => { const { value } = event.target; - setFilter(JSON.parse(value)); + setFilter(value); }} className={styles.SearchBar} > @@ -259,13 +281,20 @@ export const FlowList = () => { ); - const filters = useMemo( - () => ({ - isActive: filter, + const filters = useMemo(() => { + let filters = { ...(selectedtag?.id && { tagIds: [parseInt(selectedtag?.id)] }), - }), - [filter, selectedtag, importing] - ); + }; + if (filter === 'isTemplate') { + filters = { ...filters, isTemplate: true }; + } else { + filters = { ...filters, isActive: filter }; + } + return filters; + }, [filter, selectedtag, importing]); + + const restrictedAction = () => + filter === 'isTemplate' ? { delete: false, edit: false } : { edit: true, delete: true }; return ( <> @@ -287,6 +316,7 @@ export const FlowList = () => { filters={filters} filterList={activeFilter} loadingList={importing} + restrictedAction={restrictedAction} /> ); diff --git a/src/routes/AuthenticatedRoute/AuthenticatedRoute.tsx b/src/routes/AuthenticatedRoute/AuthenticatedRoute.tsx index 815a89a6a4..cb51d5b93c 100644 --- a/src/routes/AuthenticatedRoute/AuthenticatedRoute.tsx +++ b/src/routes/AuthenticatedRoute/AuthenticatedRoute.tsx @@ -97,6 +97,7 @@ const routeAdmin = ( } /> } /> } /> + } /> } /> } /> } /> From a31fea31c475c4eeeae0483ffd8021c3244b0bd3 Mon Sep 17 00:00:00 2001 From: Akansha Sakhre Date: Mon, 8 Jul 2024 17:47:06 +0530 Subject: [PATCH 02/21] added support for viewing template flow --- .../floweditor/FlowEditor.helper.ts | 4 ++-- src/components/floweditor/FlowEditor.tsx | 7 ++++-- src/containers/Flow/Flow.tsx | 24 ++++++++++++++----- src/containers/Flow/FlowList/FlowList.tsx | 8 ++++--- src/containers/Form/FormLayout.tsx | 16 +++++++++++-- 5 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/components/floweditor/FlowEditor.helper.ts b/src/components/floweditor/FlowEditor.helper.ts index 8b59d0943e..321dd91814 100644 --- a/src/components/floweditor/FlowEditor.helper.ts +++ b/src/components/floweditor/FlowEditor.helper.ts @@ -4,14 +4,14 @@ import '@nyaruka/temba-components/dist/temba-components.js'; const glificBase = FLOW_EDITOR_API; -export const setConfig = (uuid: any) => { +export const setConfig = (uuid: any, isTemplate: boolean) => { const services = JSON.parse(localStorage.getItem('organizationServices') || '{}'); const config = { flow: uuid, flowType: 'messaging', localStorage: true, - mutable: true, + mutable: !isTemplate, showNodeLabel: false, attachmentsEnabled: false, filters: ['whatsapp', 'classifier', 'profile', 'optins', 'ticketer'], diff --git a/src/components/floweditor/FlowEditor.tsx b/src/components/floweditor/FlowEditor.tsx index 3d71a4b7ae..62a8a89b7e 100644 --- a/src/components/floweditor/FlowEditor.tsx +++ b/src/components/floweditor/FlowEditor.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { useMutation, useLazyQuery, useQuery } from '@apollo/client'; -import { Navigate, useNavigate, useParams } from 'react-router-dom'; +import { Navigate, useLocation, useNavigate, useParams } from 'react-router-dom'; import { Menu, MenuItem, Typography } from '@mui/material'; import BackIconFlow from 'assets/images/icons/BackIconFlow.svg?react'; import WarningIcon from 'assets/images/icons/Warning.svg?react'; @@ -31,11 +31,13 @@ export const FlowEditor = () => { const params = useParams(); const { uuid } = params; const navigate = useNavigate(); + const location = useLocation(); const [publishDialog, setPublishDialog] = useState(false); const [loading, setLoading] = useState(true); const [flowEditorLoaded, setFlowEditorLoaded] = useState(false); const [flowId, setFlowId] = useState(); - const config = setConfig(uuid); + const isTemplate = location?.state === 'template'; + const config = setConfig(uuid, isTemplate); const [published, setPublished] = useState(false); const [showSimulator, setShowSimulator] = useState(false); const [stayOnPublish, setStayOnPublish] = useState(false); @@ -55,6 +57,7 @@ export const FlowEditor = () => { const handleClose = () => { setAnchorEl(null); }; + console.log(location?.state); const isTranslationEnabled = getOrganizationServices('autoTranslationEnabled'); diff --git a/src/containers/Flow/Flow.tsx b/src/containers/Flow/Flow.tsx index 3893e339a6..960ccb76d7 100644 --- a/src/containers/Flow/Flow.tsx +++ b/src/containers/Flow/Flow.tsx @@ -44,9 +44,9 @@ export const Flow = () => { const [ignoreKeywords, setIgnoreKeywords] = useState(false); const { t } = useTranslation(); - let isViewing = false; - if (location) { - console.log(location); + let isTemplate = false; + if (location.state === 'template') { + isTemplate = true; } const { data: tag } = useQuery(GET_TAGS, { variables: {}, @@ -142,7 +142,11 @@ export const Flow = () => { const dialogMessage = t("You won't be able to use this flow again."); - const additionalAction = { label: t('Configure'), link: '/flow/configure' }; + const additionalAction = { + label: isTemplate ? t('View') : t('Configure'), + link: '/flow/configure', + state: isTemplate && 'template', + }; const formFields = [ { @@ -150,6 +154,7 @@ export const Flow = () => { name: 'name', type: 'text', label: t('Name'), + disabled: isTemplate, }, { component: Input, @@ -157,6 +162,7 @@ export const Flow = () => { type: 'text', label: t('Keywords'), helperText: t('Enter comma separated keywords that trigger this flow.'), + disabled: isTemplate, }, { component: Input, @@ -165,13 +171,14 @@ export const Flow = () => { textArea: true, rows: 2, label: t('Description'), + disabled: isTemplate, }, { component: AutoComplete, name: 'tagId', options: tag ? tag.tags : [], optionLabel: 'label', - disabled: false, + disabled: isTemplate, handleCreateItem: handleCreateLabel, hasCreateOption: true, multiple: false, @@ -189,25 +196,28 @@ export const Flow = () => { }, darkCheckbox: true, className: styles.Checkbox, + disabled: isTemplate, }, { component: Checkbox, name: 'isActive', title: t('Is active?'), darkCheckbox: true, + disabled: isTemplate, }, { component: Checkbox, name: 'isPinned', title: t('Is pinned?'), darkCheckbox: !isPinnedDisable, - disabled: isPinnedDisable, + disabled: isPinnedDisable || isTemplate, }, { component: Checkbox, name: 'isBackground', title: t('Run this flow in the background'), darkCheckbox: true, + disabled: isTemplate, }, ]; @@ -286,6 +296,8 @@ export const Flow = () => { customHandler={customHandler} helpData={flowInfo} backLinkButton="/flow" + buttonState={{ text: 'Save', status: isTemplate }} + restrictButtonStatus={{ status: isTemplate }} /> ); }; diff --git a/src/containers/Flow/FlowList/FlowList.tsx b/src/containers/Flow/FlowList/FlowList.tsx index 929815252a..6d78ad7c51 100644 --- a/src/containers/Flow/FlowList/FlowList.tsx +++ b/src/containers/Flow/FlowList/FlowList.tsx @@ -162,16 +162,18 @@ export const FlowList = () => { { label: 'View it', icon: viewIcon, - parameter: 'uuid', + parameter: 'id', insideMore: false, - link: '/flow/view', + dialog: (id: any) => { + navigate(`/flow/${id}/edit`, { state: 'template' }); + }, }, { label: 'Use it', icon: configureIcon, parameter: 'uuid', insideMore: false, - link: '/flow/configure', + link: '/flow/configure?template=true', }, ]; diff --git a/src/containers/Form/FormLayout.tsx b/src/containers/Form/FormLayout.tsx index c066a3c64a..88cab13b48 100644 --- a/src/containers/Form/FormLayout.tsx +++ b/src/containers/Form/FormLayout.tsx @@ -1,5 +1,5 @@ import { useState, Fragment, useEffect } from 'react'; -import { Navigate, useParams } from 'react-router-dom'; +import { Navigate, useNavigate, useParams } from 'react-router-dom'; import { Formik, Form, Field } from 'formik'; // eslint-disable-next-line no-unused-vars import { DocumentNode, ApolloError, useQuery, useMutation } from '@apollo/client'; @@ -75,6 +75,10 @@ export interface FormLayoutProps { helpData?: HelpDataProps; noHeading?: boolean; partialPage?: boolean; + restrictButtonStatus?: { + text?: string; + status?: boolean; + }; } export const FormLayout = ({ @@ -126,6 +130,7 @@ export const FormLayout = ({ languageAttributes = {}, noHeading = false, partialPage = false, + restrictButtonStatus, }: FormLayoutProps) => { const [showDialog, setShowDialog] = useState(false); const [formSubmitted, setFormSubmitted] = useState(false); @@ -138,6 +143,7 @@ export const FormLayout = ({ const [isLoadedData, setIsLoadedData] = useState(false); const [customError, setCustomError] = useState(null); const params = useParams(); + const navigate = useNavigate(); const { t } = useTranslation(); @@ -494,9 +500,10 @@ export const FormLayout = ({ data-testid="remove-icon" className={styles.DeleteButton} onClick={() => setShowDialog(true)} + disabled={restrictButtonStatus?.status} > - Remove + {restrictButtonStatus?.text || 'Remove'} ) : null; @@ -562,6 +569,11 @@ export const FormLayout = ({ variant="outlined" color="primary" onClick={() => { + if (additionalAction?.state === 'template') { + navigate(`${additionalAction.link}/${link}`, { + state: additionalAction?.state, + }); + } submitForm(); setAction(true); }} From b516ed5fe7e235bfb755dc5517d0281d350bd7cf Mon Sep 17 00:00:00 2001 From: Akansha Sakhre Date: Thu, 11 Jul 2024 11:51:55 +0530 Subject: [PATCH 03/21] create options --- .../Flow/FlowList/FlowList.module.css | 4 +++ src/containers/Flow/FlowList/FlowList.tsx | 29 ++++++++++++++++++- src/containers/List/List.tsx | 3 ++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/containers/Flow/FlowList/FlowList.module.css b/src/containers/Flow/FlowList/FlowList.module.css index b762fe109b..f8c1b52445 100644 --- a/src/containers/Flow/FlowList/FlowList.module.css +++ b/src/containers/Flow/FlowList/FlowList.module.css @@ -191,3 +191,7 @@ .ImportDialog { max-width: 395px; } + +.DialogContent { + text-align: center; +} diff --git a/src/containers/Flow/FlowList/FlowList.tsx b/src/containers/Flow/FlowList/FlowList.tsx index 6d78ad7c51..80a3e950ef 100644 --- a/src/containers/Flow/FlowList/FlowList.tsx +++ b/src/containers/Flow/FlowList/FlowList.tsx @@ -24,6 +24,7 @@ import { AutoComplete } from 'components/UI/Form/AutoComplete/AutoComplete'; import { flowInfo } from 'common/HelpData'; import { DialogBox } from 'components/UI/DialogBox/DialogBox'; import { setErrorMessage, setNotification } from 'common/notification'; +import { Button } from 'components/UI/Form/Button/Button'; const getName = (text: string, keywordsList: any, roles: any) => { const keywords = keywordsList.map((keyword: any) => keyword); @@ -89,6 +90,7 @@ export const FlowList = () => { const [flowName, setFlowName] = useState(''); const [importing, setImporting] = useState(false); const [importStatus, setImportStatus] = useState([]); + const [showDialog, setShowDialog] = useState(false); const [releaseFlow] = useLazyQuery(RELEASE_FLOW); @@ -298,9 +300,34 @@ export const FlowList = () => { const restrictedAction = () => filter === 'isTemplate' ? { delete: false, edit: false } : { edit: true, delete: true }; + let dialogBox: any; + if (showDialog) { + dialogBox = ( + { + setFilter('isTemplate'); + setShowDialog(false); + }} + handleOk={() => { + navigate('/flow/add'); + }} + > +
+ Do you want to create a flow from scratch or use a template flow? +
+
+ ); + } + return ( <> {dialog} + {dialogBox} { {...columnAttributes} searchParameter={['name_or_keyword_or_tags']} additionalAction={additionalAction} - button={{ show: true, label: t('Create') }} + button={{ show: true, label: t('Create'), action: () => setShowDialog(true) }} secondaryButton={importButton} filters={filters} filterList={activeFilter} diff --git a/src/containers/List/List.tsx b/src/containers/List/List.tsx index 3e1d8c877e..49a1d95136 100644 --- a/src/containers/List/List.tsx +++ b/src/containers/List/List.tsx @@ -700,6 +700,7 @@ export const List = ({ let buttonContent; if (button.action) { + console.log('if'); buttonContent = (