From e59ad0964ebe9b5a4d186fc609cf01fe84d38c8b Mon Sep 17 00:00:00 2001 From: ARREY-ETTA BESSONG EKEP OBASI Date: Fri, 24 May 2024 02:54:46 +0100 Subject: [PATCH 1/8] feat: added phases to workspace features --- src/pages/tickets/style.ts | 3 + src/people/widgetViews/WorkspaceFeature.tsx | 68 ++++-- .../widgetViews/workspace/WorkspacePhase.tsx | 230 ++++++++++++++++++ .../workspace/WorkspacePhasingModals.tsx | 176 ++++++++++++++ src/people/widgetViews/workspace/interface.ts | 14 ++ src/people/widgetViews/workspace/style.ts | 61 +++++ src/store/main.ts | 79 +++++- 7 files changed, 613 insertions(+), 18 deletions(-) create mode 100644 src/people/widgetViews/workspace/WorkspacePhase.tsx create mode 100644 src/people/widgetViews/workspace/WorkspacePhasingModals.tsx diff --git a/src/pages/tickets/style.ts b/src/pages/tickets/style.ts index 7ead20ba..7c4aa317 100644 --- a/src/pages/tickets/style.ts +++ b/src/pages/tickets/style.ts @@ -77,10 +77,13 @@ export const DataWrap = styled.div` export const FeatureDataWrap = styled.div` display: flex; flex-direction: column; +<<<<<<< HEAD justify-content: space-between; padding: 40px 50px; width: 60%; margin: 0 auto; +======= +>>>>>>> 0a9db7e (feat: added phases to workspace features) align-items: left; justify-content: center; diff --git a/src/people/widgetViews/WorkspaceFeature.tsx b/src/people/widgetViews/WorkspaceFeature.tsx index b9211198..2d6f06e7 100644 --- a/src/people/widgetViews/WorkspaceFeature.tsx +++ b/src/people/widgetViews/WorkspaceFeature.tsx @@ -37,6 +37,8 @@ import { ButtonGroup, StoryButtonWrap } from './workspace/style'; +import WorkspacePhasingTabs from './workspace/WorkspacePhase'; +import { Phase, Toast } from './workspace/interface'; type DispatchSetStateAction = React.Dispatch>; @@ -113,9 +115,10 @@ const WorkspaceEditableField = ({ // Replace the markdown image syntax with HTML tag return input - .replace(imageMarkdownRegex, (match: string, p1: string) => { - return `Uploaded Image`; - }) + .replace( + imageMarkdownRegex, + (match: string, p1: string) => `Uploaded Image` + ) .replace(/\n/g, '
'); }; @@ -256,7 +259,7 @@ const UserStoryModal: React.FC = ({ ); }; -const WorkspaceFeature: React.FC = () => { +const WorkspaceFeature = () => { const { main, ui } = useStores(); const { feature_uuid } = useParams<{ feature_uuid: string }>(); const [featureData, setFeatureData] = useState(null); @@ -265,6 +268,7 @@ const WorkspaceFeature: React.FC = () => { const [userStoryPriority, setUserStoryPriority] = useState(0); const [featureStories, setFeatureStories] = useState([]); const [brief, setBrief] = useState(''); + const [phases, setPhases] = useState([]); const [architecture, setArchitecture] = useState(''); const [requirements, setRequirements] = useState(''); const [editBrief, setEditBrief] = useState(false); @@ -278,8 +282,9 @@ const WorkspaceFeature: React.FC = () => { ); const [modalOpen, setModalOpen] = useState(false); const [editUserStory, setEditUserStory] = useState(); + const [toasts, setToasts] = useState([]); - const getFeatureData = useCallback(async (): Promise => { + const getFeatureData = useCallback(async () => { if (!feature_uuid) return; const data = await main.getFeaturesByUuid(feature_uuid); @@ -289,7 +294,18 @@ const WorkspaceFeature: React.FC = () => { setArchitecture(data.architecture); setRequirements(data.requirements); } - setLoading(false); + + return data; + }, [feature_uuid, main]); + + const getFeaturePhaseData = useCallback(async () => { + if (!feature_uuid) return; + const phases = await main.getFeaturePhases(feature_uuid); + + if (phases) { + setPhases(phases); + } + return phases; }, [feature_uuid, main]); const getFeatureStoryData = useCallback(async (): Promise => { @@ -303,9 +319,10 @@ const WorkspaceFeature: React.FC = () => { }, [feature_uuid, main]); useEffect(() => { - getFeatureData(); - getFeatureStoryData(); - }, [getFeatureData, getFeatureStoryData]); + Promise.all([getFeatureData(), getFeaturePhaseData(), getFeatureStoryData()]).finally(() => { + setLoading(false); + }); + }, [getFeatureData, getFeaturePhaseData, getFeatureStoryData]); const submitField = async ( field: string, @@ -399,9 +416,21 @@ const WorkspaceFeature: React.FC = () => { deleteHandler(); }; + const updateFeaturePhase = (reason: any, title: string, message: string) => { + getFeaturePhaseData(); + setToasts([ + { + id: '1', + title, + color: (reason === 'success' ? 'sucess' : 'danger') as any, + text: message + } + ]); + }; + const toastsEl = ( ui.setToasts([])} toastLifeTimeMs={3000} /> @@ -523,15 +552,20 @@ const WorkspaceFeature: React.FC = () => { onSubmit={() => submitField('architecture', architecture, setEditArchitecture)} main={main} /> + + {toastsEl} - ); }; diff --git a/src/people/widgetViews/workspace/WorkspacePhase.tsx b/src/people/widgetViews/workspace/WorkspacePhase.tsx new file mode 100644 index 00000000..f21dc2f2 --- /dev/null +++ b/src/people/widgetViews/workspace/WorkspacePhase.tsx @@ -0,0 +1,230 @@ +import React, { useMemo, useState } from 'react'; +import { EuiSpacer, EuiTabbedContentProps, EuiTabbedContentTab } from '@elastic/eui'; +import { Button } from 'components/common'; +import MaterialIcon from '@material/react-material-icon'; +import { FieldWrap, Label } from 'pages/tickets/style'; +import { useStores } from 'store'; +import { + RowFlex, + StyledEuiTabbedContent, + TabContent, + TabContentOptions, + WorkspaceOption +} from '../workspace/style'; +import { Phase, PhaseOperationMessage, PhaseOperationType, Toast } from './interface'; +import { AddPhaseModal, DeletePhaseModal, EditPhaseModal } from './WorkspacePhasingModals'; + +interface PhaseOptionProps { + handleClose: () => void; +} + +const PhaseOptions = (props: PhaseOptionProps) => { + const [showOptions, setShowOptions] = useState(false); + const toggleOptions = () => setShowOptions(!showOptions); + const { handleClose } = props; + + const close = () => { + toggleOptions(); + handleClose(); + }; + + return ( + + + {showOptions && ( + +
    +
  • + Edit +
  • +
+
+ )} +
+ ); +}; + +const phaseOperationMessages: Record = { + create: { + title: 'Phase Created', + message: 'The phase has been successfully created.' + }, + edit: { + title: 'Phase Edited', + message: 'The phase has been successfully edited.' + }, + delete: { + title: 'Phase Deleted', + message: 'The phase has been successfully deleted.' + } +}; + +interface WorkspacePhaseProps { + featureId: string; + phases: Phase[]; + updateFeaturePhase: (reason: Toast['color'], title: string, message: string) => void; +} + +const WorkspacePhasingTabs = (props: WorkspacePhaseProps) => { + const { main } = useStores(); + const { featureId, phases, updateFeaturePhase } = props; + const [selectedIndex, setSelectedIndex] = useState(0); + const [showEditPhaseModal, setShowEditPhaseModal] = useState(false); + const [showAddPhaseModal, setShowAddPhaseModal] = useState(false); + const [showDeletePhaseModal, setShowDeletePhaseModal] = useState(false); + const [phaseName, setPhaseName] = useState(''); + + const handleTabClick = (selectedTab: EuiTabbedContentTab) => { + setSelectedIndex(parseInt(selectedTab.id)); + }; + + const handleAddPhaseClick = () => { + setShowAddPhaseModal(true); + }; + + const handleEditPhaseClick = () => { + setShowEditPhaseModal(true); + }; + + const handleAddPhaseModalClose = () => { + setShowAddPhaseModal(false); + }; + + const handleEditPhaseModalClose = () => { + setShowEditPhaseModal(false); + }; + + const handleDeletePhaseModalClose = () => { + setShowDeletePhaseModal(false); + }; + + const handlePhaseNameChange = (name: string) => setPhaseName(name); + + const createOrUpdateFeaturePhase = async (op: PhaseOperationType) => { + if (!featureId) return; + + const phase = phases[selectedIndex]; + + const body = { + uuid: op === 'edit' ? phase.uuid || '' : '', + feature_uuid: featureId, + name: phaseName || phase.name, + priority: phase?.priority + }; + + try { + await main.createOrUpdatePhase(body); + updateFeaturePhase( + 'success', + phaseOperationMessages[op].title, + phaseOperationMessages[op].message + ); + } catch { + updateFeaturePhase( + 'danger', + phaseOperationMessages[op].title, + phaseOperationMessages[op].message + ); + } + }; + + const deletePhaseFromFeature = async () => { + const op = 'delete'; + if (!featureId) return; + + const phase = phases[selectedIndex]; + + try { + await main.deletePhase(featureId, phase.uuid); + setSelectedIndex(0); + updateFeaturePhase( + 'success', + phaseOperationMessages[op].title, + phaseOperationMessages[op].message + ); + } catch { + updateFeaturePhase( + 'success', + phaseOperationMessages[op].title, + phaseOperationMessages[op].message + ); + } + }; + + const tabs: EuiTabbedContentProps['tabs'] = useMemo( + () => + phases.map((phase: Phase, index: number) => ({ + id: `${index}`, + name: phase.name, + prepend: , + content: ( + +

No Bounties Yet!

+
+ ) + })), + [phases] + ); + + const selectedTab = useMemo(() => tabs[selectedIndex], [selectedIndex, tabs]); + + return ( + <> + + + +