diff --git a/src/app/(dashboard)/planning/PlanningTable.jsx b/src/app/(dashboard)/planning/PlanningTable.jsx index 0db1822080d008a6a286261b3dfbb57f361cd64f..2a958056b4269a714a7bda378ae5f6a63a24c7dc 100644 --- a/src/app/(dashboard)/planning/PlanningTable.jsx +++ b/src/app/(dashboard)/planning/PlanningTable.jsx @@ -1,26 +1,139 @@ import React, {useEffect, useState} from 'react'; import fetchRequest from "@/app/lib/fetchRequest"; import {useNotification} from "@/context/NotificationContext"; +import {blankData} from "@/app/lib/constants"; +import ConfirmationModal from "@/app/ui/ConfirmationModal"; +import ArrowUturnLeft from "@/static/image/svg/arrow-uturn-left.svg"; +import DeleteIcon from "@/static/image/svg/trash-bold.svg"; +import EditIcon from "@/static/image/svg/edit.svg"; -const PlanningTable = ({ data, typePresences, onTypePresenceChange, selectedProject }) => { +const PlanningTable = ({ project, typePresences}) => { // fetch type presence const [errors, setErrors] = useState(); const [loading, setLoading] = useState(false); const {toggleNotification} = useNotification() const [selectedPresence, setSelectedPresence] = useState(''); + const [planningData, setPlanningData] = useState(project.planning ? project.planning : blankData); + const [isModalOpen, setModalOpen] = useState(false); + + const handleSave = async () => { + // verify if planing data has an empty value + const emptyValue = planningData.planning_data.some(week => week.days.some(day => day.nom === '')); + if (emptyValue) { + toggleNotification({ + visible: true, + message: 'Veuillez remplir toutes les valeurs de planification', + type: 'error' + }); + return; + } + setLoading(true); + const requestBody = {id_project: project.id, planning_data: planningData.planning_data}; + console.log({id_project: project.id, planning_data: planningData.planning_data}) + const {isSuccess, errors,data} = await fetchRequest(`/plannings/`, { + method: 'POST', + body: JSON.stringify(requestBody) + }); + setLoading(false); + if (isSuccess) { + toggleNotification({ + visible: true, + message: 'Données de planification enregistrées avec succès', + type: 'success' + }); + setPlanningData(data); + } else { + setErrors(errors); + toggleNotification({ + visible: true, + message: 'Échec de lenregistrement des données de planification', + type: 'error' + }); + } + }; + + const handleUpdate = async () => { + setLoading(true); + const requestBody = {id_project: project.id, planning_data: planningData.planning_data}; + const {isSuccess, errors} = await fetchRequest(`/plannings/${planningData.id}/`, { + method: 'PUT', + body: JSON.stringify(requestBody) + }); + setLoading(false); + if (isSuccess) { + toggleNotification({ + visible: true, + message: 'Données de planification mises à jour avec succès', + type: 'success' + }); + } else { + setErrors(errors); + toggleNotification({ + visible: true, + message: 'Échec de la mise à jour des données de planification', + type: 'error' + }); + } + } + + const handleDelete = async () => { + setLoading(true); + // delete by planning id not project id + const {isSuccess, errors} = await fetchRequest(`/plannings/${planningData.id}/`, { + method: 'DELETE' + }); + setLoading(false); + if (isSuccess) { + setPlanningData(blankData); + toggleNotification({ + visible: true, + message: 'Données de planification supprimées avec succès', + type: 'success' + }); + } else { + setErrors(errors); + toggleNotification({ + visible: true, + message: 'Échec de la suppression des données de planification', + type: 'error' + }); + } + }; + const handleDeleteClick = () => { + setModalOpen(true); + }; + const handleConfirmDelete = () => { + handleDelete(); + setModalOpen(false); + }; + const handleTypePresenceChange = (weekIndex, dayIndex, value) => { + // const updatedData = {...planningData}; + // get the presence type by id (value is a string convert it to int + const typePresence = typePresences.find(typePresence => typePresence.id === parseInt(value)); + + // the value I want to add should be {"id": 1, "nom": "Travail à Temps Partiel"} and not just the id + // updatedData.planning_data[weekIndex].days[dayIndex] = typePresence; + // setPlanningData(updatedData); + setPlanningData((prevData) => { // prevData is the previous state of planningData + const updatedData = {...prevData}; + updatedData.planning_data[weekIndex].days[dayIndex] = typePresence; + return updatedData; + }); + }; const handleRadioChange = (e) => { const newPresence = e.target.value; setSelectedPresence(newPresence); - data.forEach((row, rowIndex) => { + console.log('sata',planningData); + planningData?.planning_data.forEach((row, rowIndex) => { row.days.forEach((day, dayIndex) => { - onTypePresenceChange(rowIndex, dayIndex, newPresence); + handleTypePresenceChange(rowIndex, dayIndex, newPresence); }); }); }; return ( -
+
Réglez Tout sur : {typePresences.map((typePresence) => ( @@ -38,27 +151,27 @@ const PlanningTable = ({ data, typePresences, onTypePresenceChange, selectedProj ))}
- - - - - - - - + + + + + + + + - {data.map((row, rowIndex) => ( - - + {planningData?.planning_data.map((row, rowIndex) => ( + + {row.days.map((day, dayIndex) => ( -
Semaine/JourJ1J2J3J4J5
Semaine/JourLundi (J1)Mardi (J2)Mercredi (J3)Jeudi (J4)Vendredi (J5)
Semaine {row.week}
Semaine {row.week} +
+ {/* crud for buttons*/} +
+ + {planningData.id ? + <> + + + : + + } + +
+ + setModalOpen(false)} + onConfirm={handleConfirmDelete} + message={`Etes-vous sûr de vouloir supprimer ces données de planification ?`} + />
); }; diff --git a/src/app/(dashboard)/planning/page.jsx b/src/app/(dashboard)/planning/page.jsx index f6dc6d33ac762cd5301da62de4c97a9ae1ac4785..e2237139c66fc0ede6705b67a7c5891851d1758a 100644 --- a/src/app/(dashboard)/planning/page.jsx +++ b/src/app/(dashboard)/planning/page.jsx @@ -1,34 +1,27 @@ 'use client'; -import { useEffect, useState } from 'react'; +import React, {useEffect, useState} from 'react'; import Dropdown from "@/app/ui/Dropdown"; import PlanningTable from "@/app/(dashboard)/planning/PlanningTable"; import fetchRequest from "@/app/lib/fetchRequest"; -import { useNotification } from "@/context/NotificationContext"; +import {useNotification} from "@/context/NotificationContext"; import ConfirmationModal from "@/app/ui/ConfirmationModal"; +import ArrowUturnLeft from "@/static/image/svg/arrow-uturn-left.svg"; +import SearchIcon from "@/static/image/svg/search.svg"; +import AddChart from "@/static/image/svg/addchart.svg"; +import TypePresence from "@/app/(dashboard)/planning/type-presence/TypePresence"; +import Accordion from "@/app/ui/Accordion"; +import {blankData} from "@/app/lib/constants"; const PlanningPage = () => { - const blankData = { - planning_data: [ - { week: 1, days: [{"id": 2, "nom": "Teletravail"}, {"id": 2, "nom": "Teletravail"}, {"id": 2, "nom": "Teletravail"}, {"id": 2, "nom": "Teletravail"}, {"id": 2, "nom": "Teletravail"}] }, - { week: 2, days: [{"id": 2, "nom": "Teletravail"}, {"id": 2, "nom": "Teletravail"}, {"id": 2, "nom": "Teletravail"}, {"id": 2, "nom": "Teletravail"}, {"id": 2, "nom": "Teletravail"}] }, - { week: 3, days: [{"id": 2, "nom": "Teletravail"}, {"id": 2, "nom": "Teletravail"}, {"id": 2, "nom": "Teletravail"}, {"id": 2, "nom": "Teletravail"}, {"id": 2, "nom": "Teletravail"}] }, - { week: 4, days: [{"id": 2, "nom": "Teletravail"}, {"id": 2, "nom": "Teletravail"}, {"id": 2, "nom": "Teletravail"}, {"id": 2, "nom": "Teletravail"}, {"id": 2, "nom": "Teletravail"}] }, - { week: 5, days: [{"id": 2, "nom": "Teletravail"}, {"id": 2, "nom": "Teletravail"}, {"id": 2, "nom": "Teletravail"}, {"id": 2, "nom": "Teletravail"}, {"id": 2, "nom": "Teletravail"}] }, - ] - } const [projects, setProjects] = useState([]); - const [selectedProject, setSelectedProject] = useState(''); - const [planningData, setPlanningData] = useState(blankData); + const [selectedProject, setSelectedProject] = useState({}); const [errors, setErrors] = useState(); - const [loading, setLoading] = useState(false); - const { toggleNotification } = useNotification(); const [typePresences, setTypePresences] = useState([]); - const [isModalOpen, setModalOpen] = useState(false); - + const [openPopup, setOpenPopup] = useState(false); const fetchProjects = async () => { - const { isSuccess, errors, data } = await fetchRequest('/projects/'); + const {isSuccess, errors, data} = await fetchRequest('/projects/'); if (isSuccess) { setProjects(data); setErrors(null); @@ -42,34 +35,17 @@ const PlanningPage = () => { fetchProjects(); }, []); - const handleProjectChange = (e) => setSelectedProject(e.target.value); - - const fetchPlanningData = async () => { - if (!selectedProject) return; - - // const { isSuccess, errors, data } = await fetchRequest(`/planning/?project=${selectedProject}`); - const { isSuccess, errors, data } = await fetchRequest(`/plannings/project/${selectedProject}/`); - if (isSuccess) { - // if the project have no data the response is [] in this case we should set the blankData - if (data.length === 0) { - setPlanningData(blankData); - setErrors(null); - return; - } - setPlanningData(data[0]); - setErrors(null); - } else { - console.error("Failed to fetch planning data"); - setPlanningData(blankData) - setErrors(errors); + const handleProjectChange = (e) => { + if (e.target.value === '') { + setSelectedProject({}); + return; } - }; - useEffect(() => { - fetchPlanningData(); - }, [selectedProject]); + const project = projects.find(project => project.id == e.target.value); + setSelectedProject(project); + } const fetchTypePresences = async () => { - const { isSuccess, errors, data } = await fetchRequest('/type-presences/'); + const {isSuccess, errors, data} = await fetchRequest('/type-presences/'); if (isSuccess) { setTypePresences(data); setErrors(null); @@ -82,169 +58,79 @@ const PlanningPage = () => { fetchTypePresences() }, []); - const handleTypePresenceChange = (weekIndex, dayIndex, value) => { - const updatedData = { ...planningData }; - // get the presence type by id (value is a string convert it to int - const typePresence = typePresences.find(typePresence => typePresence.id === parseInt(value)); - console.log(typePresence) - - // the value I want to add should be {"id": 1, "nom": "Travail à Temps Partiel"} and not just the id - updatedData.planning_data[weekIndex].days[dayIndex] = typePresence; - setPlanningData(updatedData); - console.log(value) - console.log(updatedData); - }; - - const handleSave = async () => { - if (!selectedProject) { - toggleNotification({ - visible: true, - message: 'Veuillez sélectionner un projet', - type: 'error' - }); - return; - } - // verify if planing data has an empty value - const emptyValue = planningData.planning_data.some(week => week.days.some(day => day.nom === '')); - if (emptyValue) { - toggleNotification({ - visible: true, - message: 'Veuillez remplir toutes les valeurs de planification', - type: 'error' - }); - return; - } - setLoading(true); - const requestBody = { id_project: selectedProject, planning_data: planningData.planning_data }; - console.log({ id_project: selectedProject, planning_data: planningData.planning_data }) - const { isSuccess, errors } = await fetchRequest(`/plannings/`, { - method: 'POST', - body: JSON.stringify(requestBody) - }); - setLoading(false); - if (isSuccess) { - toggleNotification({ - visible: true, - message: 'Données de planification enregistrées avec succès', - type: 'success' - }); - fetchPlanningData() - } else { - setErrors(errors); - toggleNotification({ - visible: true, - message: 'Échec de lenregistrement des données de planification', - type: 'error' - }); - } - }; - - const handleUpdate = async () => { - setLoading(true); - const requestBody = {id_project: selectedProject, planning_data: planningData.planning_data}; - const {isSuccess, errors} = await fetchRequest(`/plannings/${planningData.id}/`, { - method: 'PUT', - body: JSON.stringify(requestBody) - }); - setLoading(false); - if (isSuccess) { - toggleNotification({ - visible: true, - message: 'Données de planification mises à jour avec succès', - type: 'success' - }); - } else { - setErrors(errors); - toggleNotification({ - visible: true, - message: 'Échec de la mise à jour des données de planification', - type: 'error' - }); - } - } - - const handleDelete = async () => { - setLoading(true); - // dlete by planning id not project id - const { isSuccess, errors } = await fetchRequest(`/plannings/${planningData.id}/`, { - method: 'DELETE' - }); - setLoading(false); - if (isSuccess) { - setPlanningData(blankData); - toggleNotification({ - visible: true, - message: 'Données de planification supprimées avec succès', - type: 'success' - }); - } else { - setErrors(errors); - toggleNotification({ - visible: true, - message: 'Échec de la suppression des données de planification', - type: 'error' - }); - } - }; - const handleDeleteClick = () => { - setModalOpen(true); - }; - - const handleConfirmDelete = () => { - handleDelete(); - setModalOpen(false); - }; - return ( -
-

Planning

- -
- + <> +
+

Recherche

+
+
+ + +
+
+
+ + +
- {/* crud buttons*/} -
- - {planningData.id ? - <> - - - : - - } - +
+
+

Planning

+ +
+ {/*iterate for projects */} +
+ {selectedProject.id ? ( +
+ +
+ +
+
+
+ ) : projects.length === 0 ? ( +
+

Pas encore des projets

+
+ ) : ( + projects.map(project => ( +
+ +
+ +
+
+
+ )) + )} +
- setModalOpen(false)} - onConfirm={handleConfirmDelete} - message={`Etes-vous sûr de vouloir supprimer ces données de planification ?`} - /> -
+ {openPopup && } + ); }; diff --git a/src/app/(dashboard)/planning/type-presence/EntityForm.jsx b/src/app/(dashboard)/planning/type-presence/EntityForm.jsx index 408e3a41c2afc4009945d83e6f1d7db4930c3f42..3397d1e53fd1c9984f3e2bba89b07027c2a1f333 100644 --- a/src/app/(dashboard)/planning/type-presence/EntityForm.jsx +++ b/src/app/(dashboard)/planning/type-presence/EntityForm.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; import fetchRequest from "@/app/lib/fetchRequest"; import {useNotification} from "@/context/NotificationContext"; @@ -44,31 +44,32 @@ const EntityForm = ({ entity, id, onSaved, onCancel }) => { }; return ( -
+
- + setNom(e.target.value)} - className="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md" + className="w-full rounded-md px-3 duration-150 delay-75 focus:ring ring-offset-1 ring-sushi-200 border h-10 border-neutral-300 outline-none" required />
-
+
diff --git a/src/app/(dashboard)/planning/type-presence/EntityList.jsx b/src/app/(dashboard)/planning/type-presence/EntityList.jsx index d2f5d9e4b0f588a3eb15f1e4ce221f9ccf4b3d99..4086efa4e0971c1943b5a5fdfe964e44304408a9 100644 --- a/src/app/(dashboard)/planning/type-presence/EntityList.jsx +++ b/src/app/(dashboard)/planning/type-presence/EntityList.jsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import ConfirmationModal from '@/app/ui/ConfirmationModal'; import EditIcon from "@/static/image/svg/edit.svg"; -import DeleteIcon from "@/static/image/svg/delete.svg"; +import DeleteIcon from "@/static/image/svg/trash-bold.svg" const EntityList = ({ title, items, setState, handleDelete, handleEdit }) => { const [isModalOpen, setModalOpen] = useState(false); @@ -20,38 +20,35 @@ const EntityList = ({ title, items, setState, handleDelete, handleEdit }) => { return (
- - - - - - - - - {items.map(item => ( - - - - - ))} - -
NomActions
{item.nom} - - -
+
+ {items.map((item, index) => ( +
+

{item.nom}

+
+ {item.nom === 'Presentiel' || item.nom ==="Teletravail" ? <>: + } + +
+
+ ))} +
setModalOpen(false)} diff --git a/src/app/(dashboard)/planning/type-presence/TypePresence.jsx b/src/app/(dashboard)/planning/type-presence/TypePresence.jsx new file mode 100644 index 0000000000000000000000000000000000000000..6a2cdc312d5713536fa56466844735ef2526bdc6 --- /dev/null +++ b/src/app/(dashboard)/planning/type-presence/TypePresence.jsx @@ -0,0 +1,71 @@ +"use client"; + +import React, {useEffect, useState} from 'react'; +import fetchRequest from "@/app/lib/fetchRequest"; +import EntityList from "@/app/(dashboard)/planning/type-presence/EntityList"; +import EntityForm from "@/app/(dashboard)/planning/type-presence/EntityForm"; +import {useNotification} from "@/context/NotificationContext"; +import CancelIcon from "@/static/image/svg/cancel.svg"; + +const TypePresence = ({setIsOpen}) => { + const [typePresences, setTypePresences] = useState([]); + const [editingEntity, setEditingEntity] = useState({entity: null, id: null}); + const {toggleNotification} = useNotification() + + const fetchData = async () => { + const typePresencesResponse = await fetchRequest('/type-presences/'); + + if (typePresencesResponse.isSuccess) setTypePresences(typePresencesResponse.data); + }; + + useEffect(() => { + fetchData(); + }, []); + + const handleDelete = async (endpoint, id, setState, currentState) => { + const response = await fetchRequest(`/${endpoint}/${id}/`, {method: 'DELETE'}); + if (response.isSuccess) { + setState(currentState.filter(item => item.id !== id)); + toggleNotification({ + visible: true, + message: `${currentState.find(item => item.id === id).nom} a été supprimé avec succès`, + type: "success" + }) + } + await fetchData(); + }; + + return ( +
+
+ { + setEditingEntity({entity: null, id: null}); + setIsOpen(false)}} + className="h-8 w-8 cursor-pointer md:absolute fixed top-2 right-2 fill-neutral-600"/> +
+
+
+

Type de présence

+ setEditingEntity({entity: null, id: null})} + /> + setEditingEntity({entity: 'type-presences', id})} + /> +
+
+
+
+
+ ); +}; + +export default TypePresence; diff --git a/src/app/(dashboard)/projects/ProjectForm.jsx b/src/app/(dashboard)/projects/ProjectForm.jsx index 912651519a5eb757983d682cf6365a2e7dcc5af6..40e29d1d391b85bcc183b16971c8b802108a1446 100644 --- a/src/app/(dashboard)/projects/ProjectForm.jsx +++ b/src/app/(dashboard)/projects/ProjectForm.jsx @@ -177,7 +177,7 @@ const ProjectForm = ({setIsOpen, onAddProject, onEditProject, editingProject, se
  • !isUserChosen(user) && handleUserSelect(user)} - className={`px-3 py-2 border-b border-chicago-200 ${isUserChosen(user) ? 'bg-neutral-200 cursor-not-allowed' : 'hover:bg-sushi-200/40 cursor-pointer'}`} + className={`px-3 py-2 border-b border-chicago-200 ${isUserChosen(user) ? 'bg-neutral-100 cursor-not-allowed' : 'hover:bg-sushi-200/40 cursor-pointer'}`} > {/*add tick is chosen*/} {isUserChosen(user) && '✔ '} @@ -187,7 +187,7 @@ const ProjectForm = ({setIsOpen, onAddProject, onEditProject, editingProject, se )}
    + className="flex flex-wrap items-center justify-start content-start gap-x-2 gap-y-1 mt-2 p-2 h-40 bg-sushi-100 rounded-md border-2 border-sushi-100 overflow-y-auto"> {userIds.map((user, index) => (

    @@ -205,19 +205,21 @@ const ProjectForm = ({setIsOpen, onAddProject, onEditProject, editingProject, se

    {errors.userIds &&

    {errors.userIds}

    }
    - + )} + - {/* cancel*/} - {editingProject && ( - - )} +
  • diff --git a/src/app/(dashboard)/projects/ProjectsFilter.jsx b/src/app/(dashboard)/projects/ProjectsFilter.jsx index 1820453c1eb74b3cea214beb45bf4e3b94c2c44d..13f6c1b5ab745b133d33a6cb0c4d18cd00ca766d 100644 --- a/src/app/(dashboard)/projects/ProjectsFilter.jsx +++ b/src/app/(dashboard)/projects/ProjectsFilter.jsx @@ -47,7 +47,16 @@ const ProjectsFilter = ({setFilter}) => {
    diff --git a/src/app/(dashboard)/projects/page.jsx b/src/app/(dashboard)/projects/page.jsx index 463f4a2e1e8d3cb4e483c1af0b46907f3654144e..abdaafc760abb68f5abb39a8901e4e23e39a27f8 100644 --- a/src/app/(dashboard)/projects/page.jsx +++ b/src/app/(dashboard)/projects/page.jsx @@ -35,7 +35,7 @@ const Projects = () => { const fetchProjects = async (pageNumber = 1, signal) => { setLoading(true); - const URLQuery = `?page=${pageNumber}&?nom=${(filter.name)}&user_count=${(filter.user_count)}&nomClient=${(filter.name_client)}&dateDebut=${(filter.date_debut)}&dateFin=${(filter.date_fin)}` + const URLQuery = `?page=${pageNumber}&nom=${(filter.name)}&user_count=${(filter.user_count)}&nomClient=${(filter.name_client)}&dateDebut=${(filter.date_debut)}&dateFin=${(filter.date_fin)}` const {isSuccess, errors, data} = await fetchRequest(`/projects/pagination/${URLQuery}`, {signal: signal}) setLoading(false); diff --git a/src/app/auth/change-password/page.jsx b/src/app/auth/change-password/page.jsx index 6bc46caa9f1a0aa71d39d43d249f2fb50b0bbafd..235d187eb54de5f548100a684bbd91bbf4e76883 100644 --- a/src/app/auth/change-password/page.jsx +++ b/src/app/auth/change-password/page.jsx @@ -2,11 +2,12 @@ import fetchRequest from '@/app/lib/fetchRequest' import Image from 'next/image' import Link from 'next/link' -import React, { useEffect, useState } from 'react' -import { useSearchParams } from 'next/navigation' +import React, {useEffect, useState} from 'react' +import {useSearchParams} from 'next/navigation' import Loader from '@/components/Loader/Loader' -import { PASSWORD_REGEX } from '@/app/lib/constants' -import { useNotification } from '@/context/NotificationContext' +import {PASSWORD_REGEX} from '@/app/lib/constants' +import {useNotification} from '@/context/NotificationContext' +import TeambookVertical from "@/static/image/teambook-vertical.png"; const ChangePassword = () => { const [isSuccess, setIsSuccess] = useState(false) @@ -15,11 +16,11 @@ const ChangePassword = () => { const [confirmPassword, setConfirmPassword] = useState("") const [isLoading, setIsLoading] = useState(false) const params = useSearchParams(); - const { toggleNotification } = useNotification() + const {toggleNotification} = useNotification() const handleChangePassword = async (event) => { event.preventDefault() setIsLoading(true) - const { isSuccess, data, errors } = await fetchRequest(`/password_reset/confirm/?token=${params.get("token")}`, { + const {isSuccess, data, errors} = await fetchRequest(`/password_reset/confirm/?token=${params.get("token")}`, { method: "POST", body: JSON.stringify({ password, @@ -28,21 +29,18 @@ const ChangePassword = () => { }) if (isSuccess) { setIsSuccess(true) - } - else { + } else { console.log(errors) setIsLoading(false) if (errors.type === "ValidationError") { if (errors.detail.token) { setFormErrors(["Le lien que vous avez utilisé pour réinitialiser votre mot de passe est invalide"]) - } - else { + } else { setFormErrors(["Le Mot de passe est invalide"]) } } else if (errors?.detail?.detail?.startsWith("The OTP password entered is not valid")) { setFormErrors(["Le lien que vous avez utilisé pour réinitialiser votre mot de passe est déja utilisé"]) - } - else { + } else { toggleNotification({ type: "error", message: "Internal Server Error", @@ -66,60 +64,61 @@ const ChangePassword = () => { setFormErrors(currentErrors) }, [password, confirmPassword]) return ( -
    -
    -
    -
    - teamwill -
    - {(!isSuccess) &&
    -

    Change your password. -

    -
    -
      -
    • Le mot de passe doit contenir au moins 8 caractères
    • -
    • Le mot de passe doit contenir au moins un chiffre
    • -
    • Le mot de passe doit contenir au moins un caractère spécial
    • -
    +
    +
    + {TeambookVertical} +
    + {(!isSuccess) && +
    +

    Changer votre Mot de passe +

    +
    +
    + + setPassword(event.target.value)} type="password" + name="new_password1" id="new_password1" + className="w-full py-6 rounded-md px-3 duration-150 delay-75 focus:ring ring-offset-1 ring-sushi-200 border h-10 border-neutral-300 outline-none"/> +
    +
    + + setConfirmPassword(event.target.value)} type="password" + name="new_password2" id="new_password2" + className="w-full py-6 rounded-md px-3 duration-150 delay-75 focus:ring ring-offset-1 ring-sushi-200 border h-10 border-neutral-300 outline-none"/>
    - -
    - - setPassword(event.target.value)} type="password" name="new_password1" id="new_password1" - className="rounded-md px-3 w-full duration-150 delay-75 focus:ring ring-offset-1 ring-sushi-200 border h-10 border-neutral-300 outline-none" /> -
    -
    - - setConfirmPassword(event.target.value)} type="password" name="new_password2" id="new_password2" - className="rounded-md px-3 duration-150 delay-75 w-full focus:ring ring-offset-1 ring-sushi-200 border h-10 border-neutral-300 outline-none" /> -
    -
      0 && !isEmptyFields ? "bg-red-100 border border-red-300" : ""} min-h-10 px-3 text-xs py-3 rounded relative mt-9 mb-6 list-inside list-disc`} role="alert"> - {!isEmptyFields && formErrors.map((error, index) => { - return
    • {error}
    • - })} +
      +
        +
      • Le mot de passe doit contenir au moins 8 caractères
      • +
      • Le mot de passe doit contenir au moins un chiffre
      • +
      • Le mot de passe doit contenir au moins un caractère spécial
      -
      - -
      - -
      } - {(isSuccess) && ( -
      -

      The password has been changed!

      - log in again?
      - )} + +
        0 && !isEmptyFields ? "bg-red-100 border border-red-300 min-h-10" : ""} w-full px-3 text-xs py-3 rounded relative mt-9 list-inside list-disc`} + role="alert"> + {!isEmptyFields && formErrors.map((error, index) => { + return
      • {error}
      • + })} +
      + +
    } + {(isSuccess) && ( +
    +

    The password has been changed!

    + log in + again?
    -
    -
    + )} +
    ) } diff --git a/src/app/auth/forgot-password/page.jsx b/src/app/auth/forgot-password/page.jsx index d74d5db9ab4b3f3bd45be27257ce6fead4b790b3..434c4237f9113dcd94ce2b11265a98e151ca0e4a 100644 --- a/src/app/auth/forgot-password/page.jsx +++ b/src/app/auth/forgot-password/page.jsx @@ -5,6 +5,8 @@ import { useNotification } from '@/context/NotificationContext' import Image from 'next/image' import Link from 'next/link' import React, { useState } from 'react' +import TeambookVertical from "@/static/image/teambook-vertical.png"; +import UserIcon from "@/static/image/svg/perm_identity.svg"; const ForgotPassword = () => { const [email, setEmail] = useState("") @@ -40,39 +42,31 @@ const ForgotPassword = () => { } } return ( -
    -
    -
    - teamwill - {(!isSuccess) &&
    -
    -

    Forgot your password!! No Problem - Reset it here +
    + {TeambookVertical} + {(!isSuccess) &&
    +
    +

    Mot de passe oublié!! Aucun problème + Réinitialisez-le ici

    -
    -
    -
    -
    - setEmail(e.target.value)} autocomplete="off" id="email" name="email" type="text" - className="peer placeholder-transparent h-10 w-full border-b-2 border-gray-300 text-gray-900 focus:outline-none focus:borer-rose-600" - placeholder="Email address" /> - + +
    + +
    + setEmail(e.target.value)} id="email" name="email" type="text" + placeholder="Email address" + /> + +
    - - {(requestErrors.length !== 0) &&
    - -
    -
    } {(isSuccess) &&
    - - + +

    Email Sent!

    Check your email and open the link we sent to continue

    } -
    -

    ) } diff --git a/src/app/auth/layout.jsx b/src/app/auth/layout.jsx new file mode 100644 index 0000000000000000000000000000000000000000..27b0f05df286f35aa68a77ba58383b0f6aef9cbe --- /dev/null +++ b/src/app/auth/layout.jsx @@ -0,0 +1,48 @@ +import React from "react"; +import Link from "next/link"; +import Image from "next/image"; +import Hero from "@/static/image/pc_phone.png"; +import LineWhite from "@/static/image/svg/line-white4.svg"; +import LineSushi from "@/static/image/svg/line-sushi.svg"; + +const layout = ({children}) => { + return ( + <> +
    +
    + {children} + +
    + + Aide + + + Vos questions + +
    +
    +
    +
    + +
    +
    + Hero +
    +
    + + Teambook 2024 © + + + Politique de confidentialité + + + Termes et conditions + +
    +
    +
    + + ) +} + +export default layout \ No newline at end of file diff --git a/src/app/auth/login/page.jsx b/src/app/auth/login/page.jsx index 2bdb9a070baaa5983da96f4d5185e8c7919b7d6e..6ed5663b2f57400336156142df10496db5f2ef34 100644 --- a/src/app/auth/login/page.jsx +++ b/src/app/auth/login/page.jsx @@ -6,6 +6,10 @@ import Cookies from 'js-cookie'; import { createSession } from "@/app/lib/session"; import { useRouter } from 'next/navigation' import Loader from '@/components/Loader/Loader'; +import UserIcon from "@/static/image/svg/perm_identity.svg"; +import VisibilityIcon from "@/static/image/svg/visibility_off.svg"; +import Image from "next/image"; +import TeambookVertical from "@/static/image/teambook-vertical.png"; const LoginPage = () => { const [username, setUsername] = useState(''); @@ -48,17 +52,16 @@ const LoginPage = () => { }; return ( <> -
    -
    - teamwill + +
    + {TeambookVertical} +

    Connectez-vous à notre plateforme

    +

    Vous n avez pas de compte ? Créer un compte

    - + +
    { value={username} onChange={(e) => setUsername(e.target.value)} /> + +
    - + +
    { value={password} onChange={(e) => setPassword(e.target.value)} /> + +
    -

    Mot de passe oublié?

    +

    Mot de passe oublié?

    - {messages && ( -

    - {messages} -

    - )} + {messages && ( +

    + {messages} +

    + )}
    diff --git a/src/app/lib/constants.js b/src/app/lib/constants.js index 9d34296b9316dac3c714bc0476dcfa97e05dda4d..18d2248eca10d6ac984cee828b3f8bc180176dea 100644 --- a/src/app/lib/constants.js +++ b/src/app/lib/constants.js @@ -1,3 +1,28 @@ export const PASSWORD_REGEX = /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[^a-zA-Z\d\s]).*$/; export const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@teamwillgroup\.com$/ -export const PAGINATION_SIZE = 3 \ No newline at end of file +export const PAGINATION_SIZE = 3 + +export const blankData = { + planning_data: [ + { + week: 1, + days: [{}, {}, {}, {}, {}] + }, + { + week: 2, + days: [{}, {}, {}, {}, {}] + }, + { + week: 3, + days: [{}, {}, {}, {}, {}] + }, + { + week: 4, + days: [{}, {}, {}, {}, {}] + }, + { + week: 5, + days: [{}, {}, {}, {}, {}] + }, + ] +} \ No newline at end of file diff --git a/src/app/ui/Accordion.jsx b/src/app/ui/Accordion.jsx new file mode 100644 index 0000000000000000000000000000000000000000..9d46cd956fc650987ae0c96bf616e63b6458c2aa --- /dev/null +++ b/src/app/ui/Accordion.jsx @@ -0,0 +1,34 @@ +import { useState } from 'react'; + +const Accordion = ({ title, children }) => { + const [isOpen, setIsOpen] = useState(false); + + return ( +
    + + {isOpen && ( +
    + {children} +
    + )} +
    + ); +}; + +export default Accordion; diff --git a/src/app/ui/LogoutButton.js b/src/app/ui/LogoutButton.jsx similarity index 100% rename from src/app/ui/LogoutButton.js rename to src/app/ui/LogoutButton.jsx diff --git a/src/components/Notification/Error.jsx b/src/components/Notification/Error.jsx index 6329a934072ae8e2b30be32c6703019794afd7f4..d5e5efe98bad76471fb44dad5c97933c38e9f59b 100644 --- a/src/components/Notification/Error.jsx +++ b/src/components/Notification/Error.jsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef } from 'react' -import WarningIcon from "@/static/image/svg/WarningIcon.svg" +import WarningIcon from "@/static/image/svg/warningIcon.svg" import CrossIcon from "@/static/image/svg/cross.svg" import "./Notification.css" diff --git a/src/middleware.js b/src/middleware.js index 98b344147d38c058b220ed5bec6e4ec559c982eb..c746b7621632da09b58f027f315ceedec13ef4ce 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -28,10 +28,10 @@ export default async function middleware(req) { const hasPrivileges = userPrivileges.some(privilege => path.startsWith(`/${privilege}`)); console.log('hasPrivileges', hasPrivileges) - if (isProtectedRoute && !hasPrivileges) { - console.log('.') - return NextResponse.redirect(new URL('/no-access', req.nextUrl)); - } + // if (isProtectedRoute && !hasPrivileges) { + // console.log('.') + // return NextResponse.redirect(new URL('/no-access', req.nextUrl)); + // } // 7. Redirect to /dashboard if the user is authenticated if ( diff --git a/src/static/image/pc_phone.png b/src/static/image/pc_phone.png new file mode 100644 index 0000000000000000000000000000000000000000..959f6fd1b3b3b0897507029262a6860f64af46eb Binary files /dev/null and b/src/static/image/pc_phone.png differ diff --git a/src/static/image/svg/addchart.svg b/src/static/image/svg/addchart.svg new file mode 100644 index 0000000000000000000000000000000000000000..7ae7662c3831ff5fab55cf06ffa0b6df5d117acc --- /dev/null +++ b/src/static/image/svg/addchart.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/static/image/svg/line-sushi.svg b/src/static/image/svg/line-sushi.svg new file mode 100644 index 0000000000000000000000000000000000000000..c65c942599d8926f413a438c1edce9c957f42256 --- /dev/null +++ b/src/static/image/svg/line-sushi.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/static/image/svg/line-white4.svg b/src/static/image/svg/line-white4.svg new file mode 100644 index 0000000000000000000000000000000000000000..7451059151003e0f310414ca8e5ba8d645609f1c --- /dev/null +++ b/src/static/image/svg/line-white4.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/static/image/svg/perm_identity.svg b/src/static/image/svg/perm_identity.svg new file mode 100644 index 0000000000000000000000000000000000000000..c2d199ac1c7dcf4c2ac8d36c1f5671749dd576b2 --- /dev/null +++ b/src/static/image/svg/perm_identity.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/static/image/svg/visibility_off.svg b/src/static/image/svg/visibility_off.svg new file mode 100644 index 0000000000000000000000000000000000000000..ceec114e0af5bc13765cdff7667a78700b5360dc --- /dev/null +++ b/src/static/image/svg/visibility_off.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/static/image/teambook-vertical.png b/src/static/image/teambook-vertical.png new file mode 100644 index 0000000000000000000000000000000000000000..f4be112fc362181936e03ac429004a71e76a54a1 Binary files /dev/null and b/src/static/image/teambook-vertical.png differ diff --git a/tailwind.config.js b/tailwind.config.js index 76042c9bf2c5886f9e45ba9adf69768fc9d9a36a..3bbb6b72085fcbdfd21e91adecef10316559650b 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -10,7 +10,7 @@ module.exports = { colors: { 'sushi': { '50': '#f7f8ed', - '100': '#ebefd8', + '100': '#EFF1E9', '200': '#d9e1b5', '300': '#bfcc8a', '400': '#a6b764',