diff --git a/app.tsx b/app.tsx new file mode 100644 index 0000000..ed07d4c --- /dev/null +++ b/app.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import ReactDOM from 'react-dom'; + +function App() { + return ( +
+

Hello World!

+

Basic React web page

+
+ ); +} + +ReactDOM.render(, document.getElementById('root')); \ No newline at end of file diff --git a/src/components/loader/CustomLoader.tsx b/src/components/loader/CustomLoader.tsx new file mode 100644 index 0000000..4fac1da --- /dev/null +++ b/src/components/loader/CustomLoader.tsx @@ -0,0 +1,11 @@ +import { Center, Loader } from "@mantine/core"; + +const CustomLoader = () => { + return ( +
+ +
+ ); +} + +export default CustomLoader; \ No newline at end of file diff --git a/src/components/loader/index.tsx b/src/components/loader/index.tsx new file mode 100644 index 0000000..9b1c916 --- /dev/null +++ b/src/components/loader/index.tsx @@ -0,0 +1 @@ +export { default } from "./CustomLoader"; \ No newline at end of file diff --git a/src/components/notifications/errors.tsx b/src/components/notifications/errors.tsx index a0c142a..0a7a598 100644 --- a/src/components/notifications/errors.tsx +++ b/src/components/notifications/errors.tsx @@ -56,4 +56,76 @@ export const partErrorNotification = () => { color: "red", icon: , }); +}; + +export const statusErrorNotification = () => { + showNotification({ + title: "Unable to update status", + message: "You don't have the permission to perform this action", + color: "red", + icon: , + }); +}; + +export const statusError2Notification = () => { + showNotification({ + title: "Unable to update status", + message: "You can't update the status of this card", + color: "red", + icon: , + }); +}; + +export const deleteErrorNotification = () => { + showNotification({ + title: "Unable to delete", + message: "You don't have the permission to delete this card", + color: "red", + icon: , + }); +}; + +export const deleteError2Notification = () => { + showNotification({ + title: "Unable to delete", + message: "This card as already been approved", + color: "red", + icon: , + }); +}; + +export const createErrorNotification = () => { + showNotification({ + title: "Unable to create", + message: "An error occured, please try again", + color: "red", + icon: , + }); +}; + +export const editErrorNotification = () => { + showNotification({ + title: "Unable to edit", + message: "An error occured, please try again", + color: "red", + icon: , + }); +}; + +export const approveErrorNotification = () => { + showNotification({ + title: "Unable to approve", + message: "This card as already been approved", + color: "red", + icon: , + }); +}; + +export const rejectErrorNotification = () => { + showNotification({ + title: "Unable to reject", + message: "This card as already been approved", + color: "red", + icon: , + }); }; \ No newline at end of file diff --git a/src/components/notifications/success.tsx b/src/components/notifications/success.tsx index e22e926..9a6f4c0 100644 --- a/src/components/notifications/success.tsx +++ b/src/components/notifications/success.tsx @@ -38,4 +38,67 @@ export const partCreatedNotification = () => { color: "green", icon: , }); +}; + +export const cacheRefreshedNotification = () => { + showNotification({ + title: "API cache refreshed", + message: "API cache refresh with success !", + color: "green", + icon: , + }); +}; + +export const configNotification = () => { + showNotification({ + title: "Config edited", + message: "New config has been saved !", + color: "green", + icon: , + }); +}; + +export const createdCardNotification = () => { + showNotification({ + title: "Card created", + message: "Card successfully added to your collection !", + color: "green", + icon: , + }); +}; + +export const editCardNotification = () => { + showNotification({ + title: "Card edited", + message: "Card successfully edited !", + color: "green", + icon: , + }); +}; + +export const deleteCardNotification = () => { + showNotification({ + title: "Card deleted", + message: "Card successfully deleted from your collection", + color: "green", + icon: , + }); +}; + +export const approveCardNotification = () => { + showNotification({ + title: "Card approved", + message: "Card successfully approved !", + color: "green", + icon: , + }); +}; + +export const rejectCardNotification = () => { + showNotification({ + title: "Card rejected", + message: "Card successfully rejected !", + color: "green", + icon: , + }); }; \ No newline at end of file diff --git a/src/features/authentication/login/components/LoginFields.tsx b/src/features/authentication/login/components/LoginFields.tsx index a160631..5af67e6 100644 --- a/src/features/authentication/login/components/LoginFields.tsx +++ b/src/features/authentication/login/components/LoginFields.tsx @@ -36,7 +36,7 @@ const LoginFields: React.FC = () => { } }) ) - router.push("/dashboard") + router.push("/home") } }, [result]) diff --git a/src/features/global/cards/UserCard.tsx b/src/features/global/cards/UserCard.tsx new file mode 100644 index 0000000..49dc29d --- /dev/null +++ b/src/features/global/cards/UserCard.tsx @@ -0,0 +1,62 @@ +import { useEffect } from 'react'; + +import { Card, Group, Badge } from '@mantine/core'; + +import { Card as CardType } from "types/apiTypes"; + +import { statusTranslate, statusColor } from './utils/dataTranslate'; + +import { CardsStyle } from './style/CardsStyle'; + +import { useUpdateCardStatusMutation } from 'store/api/cardAPI'; + +import CardMenu from './components/CardMenu'; + +import { statusError2Notification, statusErrorNotification } from 'components/notifications/errors'; + +import CardBody from './components/cardParts/CardBody'; +import CardTitle from './components/cardParts/CardTitle'; +import CardStatusControl from './components/cardParts/CardStatusControl'; +import CardManagement from './components/cardParts/CardManagement'; + +interface Props { + card: CardType; + refetch: any; + edition: boolean; +} + +const UserCard: React.FC = ({ card, refetch, edition }) => { + const { classes } = CardsStyle(); + + const [updateStatus, statusResult] = useUpdateCardStatusMutation(); + + useEffect(() => { + if (statusResult.isError) { + if (statusResult.error.status === 403) + statusErrorNotification(); + if (statusResult.error.status === 400) + statusError2Notification(); + } else if (statusResult.isSuccess) { + refetch(); + } + }, [statusResult]) + + return ( + + + + {statusTranslate[`${card.status}`]} + + + + + + { edition + ? + : + } + + ); +} + +export default UserCard; \ No newline at end of file diff --git a/src/features/global/cards/components/CardMenu.tsx b/src/features/global/cards/components/CardMenu.tsx new file mode 100644 index 0000000..e2cd05f --- /dev/null +++ b/src/features/global/cards/components/CardMenu.tsx @@ -0,0 +1,68 @@ +import { useEffect, useState } from "react"; + +import { useMantineTheme, ActionIcon, Menu } from "@mantine/core"; + +import { HiOutlineDotsHorizontal, HiOutlineTrash } from "react-icons/hi"; +import { RiEditLine } from "react-icons/ri"; +import { TbCheckbox } from "react-icons/tb"; + +import DodsModal from "./modals/DodsModal"; + +import { Card } from "types/apiTypes"; + +import EditModal from "./modals/EditCardModal"; +import { useDeleteCardMutation } from "store/api/cardAPI"; +import { deleteError2Notification, deleteErrorNotification } from "components/notifications/errors"; +import { deleteCardNotification } from "components/notifications/success"; + +interface Props { + card: Card; + refetch: any; +} + +const CardMenu: React.FC = ({ card, refetch }) => { + const [openDods, setOpenDods] = useState(false); + const [openEdit, setOpenEdit] = useState(false); + const theme = useMantineTheme(); + + const [deleteCard, resultDelete] = useDeleteCardMutation(); + + useEffect(() => { + if (resultDelete.isError) { + if (resultDelete.error.status === 403) + deleteErrorNotification(); + if (resultDelete.error.status === 400) + deleteError2Notification(); + } else if (resultDelete.isSuccess) { + deleteCardNotification(); + refetch(); + } + }, [resultDelete]) + + return ( + <> + + + + + + + + + + + Card + } onClick={() => setOpenDods(true)}>DoDs + + + + Danger zone + setOpenEdit(true)} icon={}>Edit + } onClick={() => deleteCard(card.id)}>Delete card + + + + ); +} + +export default CardMenu; \ No newline at end of file diff --git a/src/features/global/cards/components/CardParts/CardBody.tsx b/src/features/global/cards/components/CardParts/CardBody.tsx new file mode 100644 index 0000000..62d2925 --- /dev/null +++ b/src/features/global/cards/components/CardParts/CardBody.tsx @@ -0,0 +1,48 @@ +import { Card, Group, Spoiler, Text } from "@mantine/core"; + +import { Card as CardType } from "types/apiTypes"; +import { CardsStyle } from "../../style/CardsStyle"; + +interface Props { + card: CardType; +} + +const CardBody: React.FC = ({ card }) => { + const { classes } = CardsStyle(); + + return ( + + + { card.status === "REJECTED" && + <> + Rejection reason : + + {card.rejectionReason} + + + } + + En tant que {card.asWho.charAt(0).toLowerCase() + card.asWho.slice(1)} je veux {card.task.charAt(0).toLowerCase() + card.task.slice(1)} + + + Description + + + {card.description} + + + Assignees + + + {card.assignees.map((person, index) => + + - {person.firstname} {person.lastname} + + )} + + + + ); +} + +export default CardBody; \ No newline at end of file diff --git a/src/features/global/cards/components/CardParts/CardManagement.tsx b/src/features/global/cards/components/CardParts/CardManagement.tsx new file mode 100644 index 0000000..decb1c8 --- /dev/null +++ b/src/features/global/cards/components/CardParts/CardManagement.tsx @@ -0,0 +1,47 @@ +import { useEffect, useState } from "react"; + +import { Button, Card, Group } from "@mantine/core"; +import { useApproveCardMutation } from "store/api/cardAPI"; + +import { Card as CardType } from "types/apiTypes"; + +import { CardsStyle } from "../../style/CardsStyle"; + +import { approveErrorNotification } from "components/notifications/errors"; +import { approveCardNotification } from "components/notifications/success"; + +import RejectionModal from "../modals/RejectionModal"; + +interface Props { + card: CardType; + refetch: any; +} + +const CardManagement: React.FC = ({ card, refetch }) => { + const { classes } = CardsStyle(); + + const [opened, setOpened] = useState(false); + const [approveCard, approveResult] = useApproveCardMutation(); + + useEffect(() => { + if (approveResult.isError) { + if (approveResult.error.status === 400) + approveErrorNotification(); + } else if (approveResult.isSuccess) { + approveCardNotification(); + refetch(); + } + }, [approveResult]) + + return ( + + + + + + + + ); +} + +export default CardManagement; \ No newline at end of file diff --git a/src/features/global/cards/components/CardParts/CardStatusControl.tsx b/src/features/global/cards/components/CardParts/CardStatusControl.tsx new file mode 100644 index 0000000..c83e13c --- /dev/null +++ b/src/features/global/cards/components/CardParts/CardStatusControl.tsx @@ -0,0 +1,45 @@ +import { ActionIcon, Card, Group, Tooltip, useMantineTheme } from "@mantine/core"; +import { BsCheck2, BsClock } from "react-icons/bs"; +import { HiXMark } from "react-icons/hi2"; + +import { Card as CardType } from "types/apiTypes"; + +import { CardsStyle } from "../../style/CardsStyle"; + +interface Props { + card: CardType; + updateStatus: any; +} + +const CardStatusControl: React.FC = ({ card, updateStatus }) => { + const { classes } = CardsStyle(); + const theme = useMantineTheme(); + + return ( + <> + {card.status !== "WAITING_APPROVAL" && card.status !== "REJECTED" && + + + + updateStatus({id: card.id, status: "finished"})} variant={card.status === "FINISHED" ? "light" : "transparent"} color={"green"} size={32}> + + + + + updateStatus({id: card.id, status: "inprogress"})} variant={card.status === "STARTED" ? "light" : "transparent"} color={"yellow"} size={32}> + + + + + updateStatus({id: card.id, status: "notstarted"})} variant={card.status === "NOT_STARTED" ? "light" : "transparent"} color={"red"} size={32}> + + + + + + } + + ); +} + +export default CardStatusControl; \ No newline at end of file diff --git a/src/features/global/cards/components/CardParts/CardTitle.tsx b/src/features/global/cards/components/CardParts/CardTitle.tsx new file mode 100644 index 0000000..dbf780c --- /dev/null +++ b/src/features/global/cards/components/CardParts/CardTitle.tsx @@ -0,0 +1,29 @@ +import { Badge, Card, Group, Text, useMantineTheme } from "@mantine/core"; + +import { Card as CardType } from "types/apiTypes"; +import { CardsStyle } from "../../style/CardsStyle"; + +interface Props { + card: CardType; +} + +const CardTitle: React.FC = ({ card }) => { + const { classes } = CardsStyle(); + const theme = useMantineTheme(); + + return ( + + +
+ {card.part.name} + {card.sprintId}.{card.partId}.{card.idInSprint === -1 ? "U" : card.idInSprint} - {card.name} + + {card.workingDays} Jour(s)/Homme + +
+
+
+ ) +} + +export default CardTitle; \ No newline at end of file diff --git a/src/features/global/cards/components/modals/AddCardModal.tsx b/src/features/global/cards/components/modals/AddCardModal.tsx new file mode 100644 index 0000000..783b1cb --- /dev/null +++ b/src/features/global/cards/components/modals/AddCardModal.tsx @@ -0,0 +1,102 @@ +import { useEffect, useState } from "react"; + +import { Modal, useMantineTheme, NumberInput, Container, Group, TextInput, Select, Button, MultiSelect, Textarea, Title, Switch, Checkbox, Center, Loader } from "@mantine/core"; +import { addCardForm } from "../../utils/addCardForm"; + +import { RxCardStackPlus } from "react-icons/rx"; + +import { useListParts } from "hooks/api/useListParts"; +import { useListUsers } from "hooks/api/useListUsers"; + +import { numberFormater } from "../../utils/dataTranslate"; +import { handleAssignees } from "../../utils/assigneesGesture"; + +import { useAppSelector } from "store/hooks/hooks"; +import { useCreateCardMutation } from "store/api/cardAPI"; +import { createdCardNotification } from "components/notifications/success"; +import { createErrorNotification } from "components/notifications/errors"; + +interface Props { + openAdd: boolean; + setOpenAdd: React.Dispatch>; + refetch: any; +} + +const AddCardModal: React.FC = ({ openAdd, setOpenAdd, refetch }) => { + const userId = useAppSelector((state) => state.user.auth.userId); + + const [multiple, setMultiple] = useState(false); + const { data: parts } = useListParts(false); + const { data: users } = useListUsers(false); + const [createCard, createResult] = useCreateCardMutation(); + + const theme = useMantineTheme(); + const form = addCardForm(); + + useEffect(() => { + if (createResult.isError) { + if (createResult.error.status === 400) + createErrorNotification(); + } else if (createResult.isSuccess) { + createdCardNotification(); + refetch(); + } + setOpenAdd(false); + form.reset(); + }, [createResult]) + + return parts && users ? ( + (setOpenAdd(false), form.reset())} + size={"90%"} + title={Add a Card} + overlayColor={theme.colorScheme === 'dark' ? theme.colors.dark[9] : theme.colors.gray[2]} + overlayOpacity={0.55} + overlayBlur={3} + radius={10} + > + +
createCard({values}))}> + + + + + +