diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 00000000..b58b603f
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,5 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 00000000..03d9549e
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 00000000..8db3b771
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/sphinx-tribes-frontend.iml b/.idea/sphinx-tribes-frontend.iml
new file mode 100644
index 00000000..24643cc3
--- /dev/null
+++ b/.idea/sphinx-tribes-frontend.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..35eb1ddf
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cypress/e2e/58_addUserStoriesToFeature.cy.ts b/cypress/e2e/58_addUserStoriesToFeature.cy.ts
index 51d29a1f..42353c2e 100644
--- a/cypress/e2e/58_addUserStoriesToFeature.cy.ts
+++ b/cypress/e2e/58_addUserStoriesToFeature.cy.ts
@@ -3,7 +3,7 @@ describe('Add user stories to features', () => {
cy.login('carol');
cy.wait(1000);
- const WorkSpaceName = 'user story';
+ const WorkSpaceName = 'user story8';
const workspace = {
loggedInAs: 'carol',
@@ -46,12 +46,36 @@ describe('Add user stories to features', () => {
cy.wait(1000);
const userStory = 'this is the story of a user';
- cy.get('[data-testid="story-input"]').type(userStory);
- cy.get('[data-testid="story-input-update-btn"]').click();
+ for (let i = 1; i <= 2; i++) {
+ const userStoryWithNumber = `${userStory} ${i}`;
+ cy.get('[data-testid="story-input"]').type(userStoryWithNumber);
+ cy.get('[data-testid="story-input-update-btn"]').click();
+ cy.wait(1000);
+
+ cy.contains(userStoryWithNumber).should('exist', { timeout: 1000 });
+ cy.wait(1000);
+ }
+
+ cy.get('[data-testid="0-user-story-option-btn"]').click();
+ cy.get('[data-testid="user-story-edit-btn"]').click();
cy.wait(1000);
+ cy.get('[data-testid="edit-story-input"]').clear();
+ const updatedUserStory = 'this is the story of a user changed';
+ cy.get('[data-testid="edit-story-input"]').type(updatedUserStory);
+ cy.get('[data-testid="user-story-save-btn"]').click();
+ cy.wait(1000);
+
+ cy.contains(updatedUserStory).should('exist', { timeout: 1000 });
- cy.contains(userStory).should('exist', { timeout: 1000 });
- cy.wait(5000);
+ cy.get('[data-testid="1-user-story-option-btn"]').click();
+ cy.get('[data-testid="user-story-edit-btn"]').click();
+ cy.wait(1000);
+ cy.get('[data-testid="user-story-delete-btn"]').click();
+ cy.wait(1000);
+ cy.contains('Delete').click({ force: true });
+ cy.wait(1000);
+ const userStoryWithNumber = `${userStory} ${2}`;
+ cy.contains(userStoryWithNumber).should('not.exist', { timeout: 1000 });
cy.logout('carol');
});
});
diff --git a/src/pages/tickets/style.ts b/src/pages/tickets/style.ts
index b1b76e95..30482e87 100644
--- a/src/pages/tickets/style.ts
+++ b/src/pages/tickets/style.ts
@@ -171,6 +171,24 @@ export const OptionsWrap = styled.div`
}
`;
+export const UserStoryOptionWrap = styled.div`
+ position: relative;
+ display: inline-block;
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 5px;
+
+ button {
+ border: 0.5px solid #000000;
+ font-size: 0.8rem;
+ font-weight: 700;
+ border-radius: 5px;
+ padding: 2px 10px;
+ }
+`;
export const TextArea = styled.textarea`
padding: 0.5rem 1rem;
border-radius: 0.375rem;
diff --git a/src/people/widgetViews/WorkspaceFeature.tsx b/src/people/widgetViews/WorkspaceFeature.tsx
index 465f65c6..2ee18ee7 100644
--- a/src/people/widgetViews/WorkspaceFeature.tsx
+++ b/src/people/widgetViews/WorkspaceFeature.tsx
@@ -9,16 +9,19 @@ import {
OptionsWrap,
TextArea,
InputField,
- Input
+ Input,
+ UserStoryOptionWrap
} from 'pages/tickets/style';
import React, { useCallback, useEffect, useState } from 'react';
import history from 'config/history';
import { useParams } from 'react-router-dom';
import { useStores } from 'store';
import { mainStore } from 'store/main';
-import { Feature } from 'store/interface';
+import { Feature, FeatureStory } from 'store/interface';
import MaterialIcon from '@material/react-material-icon';
-import { FeatureStory } from 'store/interface';
+import { EuiOverlayMask, EuiModalHeader, EuiModalFooter, EuiText } from '@elastic/eui';
+import { Box } from '@mui/system';
+import { useDeleteConfirmationModal } from '../../components/common';
import {
ActionButton,
ButtonWrap,
@@ -27,7 +30,12 @@ import {
WorkspaceName,
WorkspaceOption,
UserStoryField,
- UserStoryFields
+ UserStoryFields,
+ UserStoryOption,
+ StyledModal,
+ ModalBody,
+ ButtonGroup,
+ StoryButtonWrap
} from './workspace/style';
type DispatchSetStateAction = React.Dispatch>;
@@ -175,6 +183,81 @@ const WorkspaceEditableField = ({
);
};
+interface UserStoryModalProps {
+ open: boolean;
+ storyDescription: string;
+ handleClose: () => void;
+ handleSave: (inputValue: string) => void;
+ handleDelete: () => void;
+}
+
+const UserStoryModal: React.FC = ({
+ open,
+ storyDescription,
+ handleClose,
+ handleSave,
+ handleDelete
+}: UserStoryModalProps) => {
+ const [inputValue, setInputValue] = useState(storyDescription);
+
+ useEffect(() => {
+ setInputValue(storyDescription);
+ }, [storyDescription]);
+ const handleInputChange = (event: React.ChangeEvent) => {
+ setInputValue(event.target.value);
+ };
+
+ return (
+ <>
+ {open && (
+
+
+
+
+ Edit User Story
+
+
+
+
+
+
+
+
+
+ handleSave(inputValue)}
+ >
+ Save
+
+
+ Cancel
+
+
+
+ Delete
+
+
+
+
+
+ )}
+ >
+ );
+};
+
const WorkspaceFeature: React.FC = () => {
const { main, ui } = useStores();
const { feature_uuid } = useParams<{ feature_uuid: string }>();
@@ -192,6 +275,11 @@ const WorkspaceFeature: React.FC = () => {
const [displayBriefOptions, setDisplayBriefOptions] = useState(false);
const [displayArchitectureOptions, setDisplayArchitectureOptions] = useState(false);
const [displayRequirementsOptions, setDisplayRequirementsOptions] = useState(false);
+ const [displayUserStoryOptions, setDisplayUserStoryOptions] = useState>(
+ {}
+ );
+ const [modalOpen, setModalOpen] = useState(false);
+ const [editUserStory, setEditUserStory] = useState();
const getFeatureData = useCallback(async (): Promise => {
if (!feature_uuid) return;
@@ -250,6 +338,69 @@ const WorkspaceFeature: React.FC = () => {
setUserStory(e.target.value);
};
+ const handleUserStoryOptionClick = (storyId: number) => {
+ setDisplayUserStoryOptions((prev: Record) => ({
+ ...prev,
+ [storyId]: !prev[storyId]
+ }));
+ };
+
+ const handleUserStoryEdit = (featureStory: FeatureStory) => {
+ const storyId = featureStory?.id as number;
+ setEditUserStory(featureStory);
+ setModalOpen(true);
+ setDisplayUserStoryOptions((prev: Record) => ({
+ ...prev,
+ [storyId]: !prev[storyId]
+ }));
+ };
+
+ const handleModalClose = () => {
+ setModalOpen(false);
+ };
+
+ const handleModalSave = async (inputValue: string) => {
+ const body = {
+ uuid: editUserStory?.uuid,
+ feature_uuid: editUserStory?.feature_uuid ?? '',
+ description: inputValue,
+ priority: editUserStory?.priority
+ };
+ await main.addFeatureStory(body);
+ await getFeatureStoryData();
+ setModalOpen(false);
+ };
+
+ const deleteUserStory = async () => {
+ await main.deleteFeatureStory(
+ editUserStory?.feature_uuid as string,
+ editUserStory?.uuid as string
+ );
+ await getFeatureStoryData();
+ return;
+ };
+
+ const { openDeleteConfirmation } = useDeleteConfirmationModal();
+
+ const deleteHandler = () => {
+ openDeleteConfirmation({
+ onDelete: deleteUserStory,
+ children: (
+
+ Are you sure you want to
+
+ delete this User Story?
+
+
+ )
+ });
+ };
+
+ const handleModalDelete = async () => {
+ setModalOpen(false);
+ deleteHandler();
+ };
+
const toastsEl = (
{
- {featureStories?.map((story: FeatureStory) => (
-
-
- {story.description}
-
- ))}
+ {featureStories
+ ?.sort((a: FeatureStory, b: FeatureStory) => a.priority - b.priority)
+ ?.map((story: FeatureStory) => (
+
+
+ handleUserStoryOptionClick(story.id as number)}
+ data-testid={`${story.priority}-user-story-option-btn`}
+ />
+ {displayUserStoryOptions[story?.id as number] && (
+
+
+ - handleUserStoryEdit(story)}
+ >
+ Edit
+
+
+
+ )}
+
+ {story.description}
+
+ ))}
@@ -356,6 +527,13 @@ const WorkspaceFeature: React.FC = () => {
/>
{toastsEl}
+
);
};
diff --git a/src/people/widgetViews/workspace/style.ts b/src/people/widgetViews/workspace/style.ts
index 7f5e96c6..80f857d8 100644
--- a/src/people/widgetViews/workspace/style.ts
+++ b/src/people/widgetViews/workspace/style.ts
@@ -996,12 +996,14 @@ interface ButtonProps {
color?: string;
marginTop?: string;
height?: string;
+ borderRadius?: string;
}
export const ActionButton = styled.button`
padding: 5px 20px;
+ width: 120px;
border-radius: 5px;
- border-radius: 0.375rem;
+ border-radius: ${(p: any) => p.borderRadius ?? '0.375rem'};
font-family: 'Barlow';
font-size: 0.9375rem;
font-style: normal;
@@ -1104,6 +1106,45 @@ export const WorkspaceOption = styled.div`
}
`;
+export const UserStoryOption = styled.div`
+ position: absolute;
+ z-index: 2;
+ top: 100%;
+ right: -65px;
+ width: 80px;
+ height: 30px;
+ background: #fff;
+ border: 1px solid #ccc;
+ border-radius: 6px;
+ box-shadow: 0px 4px 20px 0px rgba(0, 0, 0, 0.25);
+ ul {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ }
+
+ li {
+ padding: 5px;
+ text-align: center;
+ cursor: pointer;
+ font-family: 'Barlow', sans-serif;
+ font-size: 0.8rem;
+ font-weight: 500;
+ line-height: 18px;
+ border-bottom: 0.5px solid #ccc;
+ transition: background-color 0.3s ease;
+
+ &:hover {
+ background-color: #f0f0f0;
+ color: #3c3f41;
+ border-radius: 6px;
+ }
+
+ &:last-child {
+ border-bottom: none;
+ }
+ }
+`;
export const UserStoryFields = styled.div`
margin-top: 20px;
`;
@@ -1113,3 +1154,29 @@ export const UserStoryField = styled.div`
display: flex;
align-items: center;
`;
+
+export const StyledModal = styled.div`
+ background: #fff;
+ width: 600px;
+ border: 2px solid dimgray;
+`;
+
+export const ModalBody = styled.div`
+ margin: 20px 0 20px 40px;
+ width: 100%;
+`;
+
+export const StoryButtonWrap = styled.div`
+ margin-right: auto;
+ margin-top: 10px;
+ display: flex;
+ gap: 15px;
+`;
+
+export const ButtonGroup = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ margin-top: 20px;
+`;
diff --git a/src/store/main.ts b/src/store/main.ts
index c38348e2..fb55522d 100644
--- a/src/store/main.ts
+++ b/src/store/main.ts
@@ -2619,6 +2619,30 @@ export class MainStore {
}
}
+ async deleteFeatureStory(
+ feature_uuid: string,
+ uuid: string
+ ): Promise {
+ try {
+ if (!uiStore.meInfo) return undefined;
+ const info = uiStore.meInfo;
+
+ const r: any = await fetch(`${TribesURL}/features/${feature_uuid}/story/${uuid}`, {
+ method: 'DELETE',
+ mode: 'cors',
+ headers: {
+ 'x-jwt': info.tribe_jwt,
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ return r.json();
+ } catch (e) {
+ console.error('getFeaturesByUuid', e);
+ return undefined;
+ }
+ }
+
async addWorkspaceFeature(body: CreateFeatureInput): Promise {
try {
if (!uiStore.meInfo) return {};