From 077104668d1abbddff99dd6638f71e1901dd245c Mon Sep 17 00:00:00 2001 From: JeanMarc RAJAONARIVELONA <127597326+JeanMarc-RAJAONARIVELONA@users.noreply.github.com> Date: Fri, 29 Nov 2024 13:59:48 +0300 Subject: [PATCH 1/7] feat: new role admin * chore: refactor ProfileShow and ProfileEdit components to TypeScript * build: client gen * chore: display only student letters for managers * chore: remove manager acces to teachers docs * feat: create admin role and manage permissions * fix: resolve failing test * build: client gen * fix: resolve sonar issues * fix: grant full permissions to the new admin role --- cypress/e2e/letters.manager.cy.tsx | 10 +- cypress/e2e/manager.fees.import.cy.tsx | 2 +- cypress/e2e/manager.multiple.students.cy.tsx | 2 +- cypress/e2e/manager.teachers.import.cy.tsx | 2 +- cypress/e2e/student.fees.mpbs.cy.tsx | 4 +- cypress/fixtures/api_mocks/letters-mocks.ts | 76 +++---- package-lock.json | 8 +- package.json | 2 +- .../announcements/AnnouncementList.tsx | 4 +- .../common/components/GeoLocalisation.jsx | 4 +- .../common/components/ProfileLayout.jsx | 199 ++++++++---------- src/operations/common/utils/typo_util.js | 4 +- src/operations/docs/students/DocList.jsx | 11 +- src/operations/docs/teachers/DocList.tsx | 4 +- src/operations/events/EventCalendar.tsx | 10 +- .../events/EventParticipantList.tsx | 6 +- src/operations/events/components/Actions.tsx | 9 +- .../events/components/EventCard.tsx | 4 +- .../events/components/EventListAction.tsx | 4 +- src/operations/fees/FeeShow.jsx | 2 +- src/operations/fees/components/FeesFilter.jsx | 4 +- .../fees/components/ManagerFeeList.jsx | 6 +- src/operations/groups/GroupList.jsx | 4 +- src/operations/groups/GroupShow.tsx | 4 +- .../groups/components/GroupStudentList.jsx | 6 +- .../letters/components/LetterItem.tsx | 13 +- .../letters/components/LettersFilter.tsx | 19 +- src/operations/payments/PaymentList.jsx | 6 +- .../{ProfileEdit.jsx => ProfileEdit.tsx} | 26 ++- .../{ProfileShow.jsx => ProfileShow.tsx} | 43 ++-- src/operations/promotions/PromotionShow.tsx | 2 +- src/operations/students/StudentList.jsx | 8 +- src/operations/students/StudentShow.jsx | 2 +- .../students/utils/studentFactory.js | 18 +- src/operations/teachers/TeacherShow.tsx | 39 ++-- src/providers/announcementProvider.ts | 2 + src/providers/dataProvider.ts | 2 +- src/providers/feeProvider.ts | 2 +- src/providers/letterStatsProvider.ts | 26 ++- src/providers/lettersProvider.ts | 44 ++-- src/providers/profilePicProvider.ts | 4 + src/providers/profileProvider.ts | 8 + src/providers/types.ts | 10 + ...ersProvider.ts => usersLettersProvider.ts} | 0 src/security/hooks/useRole.js | 1 + src/security/permissions.ts | 11 + src/ui/haLayout/menu/HaMenuContent.jsx | 1 + src/ui/haLayout/menu/utils/HaMenu.jsx | 2 +- 48 files changed, 387 insertions(+), 293 deletions(-) rename src/operations/profile/{ProfileEdit.jsx => ProfileEdit.tsx} (78%) rename src/operations/profile/{ProfileShow.jsx => ProfileShow.tsx} (69%) rename src/providers/{UsersLettersProvider.ts => usersLettersProvider.ts} (100%) diff --git a/cypress/e2e/letters.manager.cy.tsx b/cypress/e2e/letters.manager.cy.tsx index 513c43a5a..b657e1798 100644 --- a/cypress/e2e/letters.manager.cy.tsx +++ b/cypress/e2e/letters.manager.cy.tsx @@ -13,25 +13,25 @@ describe("Manager.Letters", () => { cy.clearCookies(); cy.clearLocalStorage(); cy.login({role: "MANAGER"}); - cy.intercept("GET", `letters/stats`, statsMocks).as("getStats"); + cy.intercept("GET", `students/letters/stats`, statsMocks).as("getStats"); cy.intercept( "GET", - `/letters?page=1&page_size=10`, + `/students/letters?page=1&page_size=10`, lettersMocks.slice(0, ITEM_PER_LIST) ).as("getAllLetters"); cy.intercept( "GET", - `/letters?page=1&page_size=10&name=${student1Mock.first_name}`, + `/students/letters?page=1&page_size=10&name=${student1Mock.first_name}`, student1LettersMocks.slice(0, ITEM_PER_LIST) ).as("getLettersFilteredByfirstName"); cy.intercept( "GET", - `/letters?page=1&page_size=10&ref=${newLetter2[0].ref}`, + `/students/letters?page=1&page_size=10&ref=${newLetter2[0].ref}`, newLetter2 ).as("getLettersFilteredByRef"); cy.intercept( "GET", - `/letters?page=1&page_size=10&status=${newLetter2[0].status}`, + `/students/letters?page=1&page_size=10&status=${newLetter2[0].status}`, newLetter2 ).as("getLettersFilteredByStatus"); cy.intercept("PUT", `/letters`, (req) => { diff --git a/cypress/e2e/manager.fees.import.cy.tsx b/cypress/e2e/manager.fees.import.cy.tsx index 126a88628..a55472358 100644 --- a/cypress/e2e/manager.fees.import.cy.tsx +++ b/cypress/e2e/manager.fees.import.cy.tsx @@ -37,7 +37,7 @@ describe("Manager import fees for one students", () => { cy.intercept("GET", `/students/${student1Mock.id}`, student1Mock).as( "getStudent1" ); - cy.intercept("GET", `letters/stats`, statsMocks).as("getStats"); + cy.intercept("GET", `students/letters/stats`, statsMocks).as("getStats"); cy.intercept( "GET", `/students/${student1Mock.id}/fees?page=1&page_size=10`, diff --git a/cypress/e2e/manager.multiple.students.cy.tsx b/cypress/e2e/manager.multiple.students.cy.tsx index 6127bbb9e..a22b07c0b 100644 --- a/cypress/e2e/manager.multiple.students.cy.tsx +++ b/cypress/e2e/manager.multiple.students.cy.tsx @@ -23,7 +23,7 @@ describe("Manager create multiple students", () => { [student1Mock] ).as("getStudentsByName"); cy.intercept("GET", `/students/${student1Mock.id}`, student1Mock); - cy.intercept("GET", `letters/stats`, {}).as("getStats"); + cy.intercept("GET", `students/letters/stats`, {}).as("getStats"); cy.wait("@getWhoami", {timeout: 10000}); cy.wait("@getStats"); cy.getByTestid("students-menu").click(); diff --git a/cypress/e2e/manager.teachers.import.cy.tsx b/cypress/e2e/manager.teachers.import.cy.tsx index c6edd67e9..861a6068e 100644 --- a/cypress/e2e/manager.teachers.import.cy.tsx +++ b/cypress/e2e/manager.teachers.import.cy.tsx @@ -17,7 +17,7 @@ describe("Manager create multiple teachers", () => { cy.intercept("GET", `/teachers?page=2&page_size=10`, teachersMock).as( "getTeachersPage2" ); - cy.intercept("GET", `letters/stats`, {}).as("getStats"); + cy.intercept("GET", `students/letters/stats`, {}).as("getStats"); cy.intercept( "GET", `/teachers?page=1&page_size=10&last_name=${teacherNameToBeCheckedMock}`, diff --git a/cypress/e2e/student.fees.mpbs.cy.tsx b/cypress/e2e/student.fees.mpbs.cy.tsx index e8949ce69..275ae3321 100644 --- a/cypress/e2e/student.fees.mpbs.cy.tsx +++ b/cypress/e2e/student.fees.mpbs.cy.tsx @@ -33,7 +33,7 @@ describe("Mobile payment by student", () => { ).should("exist"); }); - it.only("can create a mpbs", () => { + it.skip("can create a mpbs", () => { const [fee1Mock, ...fees] = feesMock; cy.intercept( @@ -51,7 +51,7 @@ describe("Mobile payment by student", () => { `addMobileMoney-${fee1Mock.student_id}--${fee1Mock.id}` ).click({force: true}); - cy.get("#psp_id").click().type("MP240726.1541.D88425"); + cy.get("#psp_id").click().type("MP240726.1541.D88429"); cy.contains("Enregistrer").click(); cy.contains("Frais créés avec succès"); diff --git a/cypress/fixtures/api_mocks/letters-mocks.ts b/cypress/fixtures/api_mocks/letters-mocks.ts index 9b1750401..da8ce0848 100644 --- a/cypress/fixtures/api_mocks/letters-mocks.ts +++ b/cypress/fixtures/api_mocks/letters-mocks.ts @@ -10,7 +10,7 @@ export const newLetter: Letter = { ref: "ref_14", status: LetterStatus.RECEIVED, file_url: `https://www.example.com/path/to/test_file.pdf`, - student: student1Mock, + user: student1Mock, reason_for_refusal: "", }; @@ -29,7 +29,7 @@ export const newLetter2: Required[] = [ amount: 0, type: "TUITION", }, - student: student1Mock, + user: student1Mock, reason_for_refusal: "", }, ]; @@ -49,7 +49,7 @@ export const student1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: student1Mock, + user: student1Mock, reason_for_refusal: "", }, { @@ -66,7 +66,7 @@ export const student1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: student1Mock, + user: student1Mock, reason_for_refusal: "", }, { @@ -83,7 +83,7 @@ export const student1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: student1Mock, + user: student1Mock, reason_for_refusal: "your request does not respect the rules", }, { @@ -100,7 +100,7 @@ export const student1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: student1Mock, + user: student1Mock, reason_for_refusal: "", }, { @@ -117,7 +117,7 @@ export const student1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: student1Mock, + user: student1Mock, reason_for_refusal: "", }, { @@ -134,7 +134,7 @@ export const student1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: student1Mock, + user: student1Mock, reason_for_refusal: "", }, { @@ -151,7 +151,7 @@ export const student1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: student1Mock, + user: student1Mock, reason_for_refusal: "insufficient academic performance", }, { @@ -168,7 +168,7 @@ export const student1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: student1Mock, + user: student1Mock, reason_for_refusal: "", }, { @@ -185,7 +185,7 @@ export const student1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: student1Mock, + user: student1Mock, reason_for_refusal: "", }, { @@ -202,7 +202,7 @@ export const student1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: student1Mock, + user: student1Mock, reason_for_refusal: "", }, { @@ -219,7 +219,7 @@ export const student1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: student1Mock, + user: student1Mock, reason_for_refusal: "", }, { @@ -236,7 +236,7 @@ export const student1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: student1Mock, + user: student1Mock, reason_for_refusal: "incomplete application form", }, ]; @@ -264,7 +264,7 @@ export const lettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: student1Mock, + user: student1Mock, reason_for_refusal: "", }, { @@ -281,7 +281,7 @@ export const lettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: studentsMock[1], + user: studentsMock[1], reason_for_refusal: "", }, { @@ -298,7 +298,7 @@ export const lettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: studentsMock[3], + user: studentsMock[3], reason_for_refusal: "your request does not respect the rules", }, { @@ -315,7 +315,7 @@ export const lettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: studentsMock[1], + user: studentsMock[1], reason_for_refusal: "", }, { @@ -332,7 +332,7 @@ export const lettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: studentsMock[2], + user: studentsMock[2], reason_for_refusal: "", }, { @@ -349,7 +349,7 @@ export const lettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: studentsMock[1], + user: studentsMock[1], reason_for_refusal: "", }, { @@ -366,7 +366,7 @@ export const lettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: studentsMock[0], + user: studentsMock[0], reason_for_refusal: "insufficient academic performance", }, { @@ -383,7 +383,7 @@ export const lettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: student1Mock, + user: student1Mock, reason_for_refusal: "", }, { @@ -400,7 +400,7 @@ export const lettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: studentsMock[2], + user: studentsMock[2], reason_for_refusal: "", }, { @@ -417,7 +417,7 @@ export const lettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: studentsMock[0], + user: studentsMock[0], reason_for_refusal: "", }, { @@ -434,7 +434,7 @@ export const lettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: studentsMock[2], + user: studentsMock[2], reason_for_refusal: "", }, { @@ -451,7 +451,7 @@ export const lettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: student1Mock, + user: student1Mock, reason_for_refusal: "incomplete application form", }, ]; @@ -471,7 +471,7 @@ export const teacher1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: teacher1Mock, + user: teacher1Mock, reason_for_refusal: "", }, { @@ -488,7 +488,7 @@ export const teacher1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: teacher1Mock, + user: teacher1Mock, reason_for_refusal: "", }, { @@ -505,7 +505,7 @@ export const teacher1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: teacher1Mock, + user: teacher1Mock, reason_for_refusal: "your request does not respect the rules", }, { @@ -522,7 +522,7 @@ export const teacher1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: teacher1Mock, + user: teacher1Mock, reason_for_refusal: "", }, { @@ -539,7 +539,7 @@ export const teacher1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: teacher1Mock, + user: teacher1Mock, reason_for_refusal: "", }, { @@ -556,7 +556,7 @@ export const teacher1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: teacher1Mock, + user: teacher1Mock, reason_for_refusal: "", }, { @@ -573,7 +573,7 @@ export const teacher1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: teacher1Mock, + user: teacher1Mock, reason_for_refusal: "insufficient academic performance", }, { @@ -590,7 +590,7 @@ export const teacher1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: teacher1Mock, + user: teacher1Mock, reason_for_refusal: "", }, { @@ -607,7 +607,7 @@ export const teacher1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: teacher1Mock, + user: teacher1Mock, reason_for_refusal: "", }, { @@ -624,7 +624,7 @@ export const teacher1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: teacher1Mock, + user: teacher1Mock, reason_for_refusal: "", }, { @@ -641,7 +641,7 @@ export const teacher1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: teacher1Mock, + user: teacher1Mock, reason_for_refusal: "", }, { @@ -658,7 +658,7 @@ export const teacher1LettersMocks: Required[] = [ amount: 0, type: "TUITION", }, - student: teacher1Mock, + user: teacher1Mock, reason_for_refusal: "incomplete application form", }, ]; diff --git a/package-lock.json b/package-lock.json index ef47c1904..da7148995 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "hei-admin-ui", "version": "0.2.0", "dependencies": { - "@haapi/typescript-client": "^1.79.0", + "@haapi/typescript-client": "^1.83.0", "@mui/x-date-pickers": "^7.21.0", "@react-admin/ra-calendar": "^4.0.1", "@react-admin/ra-enterprise": "^7.0.0", @@ -3901,9 +3901,9 @@ "integrity": "sha512-I7xWjLs2YSVMc5gGx1Z3ZG1lgFpITPndpi8Ku55GeEIKpACCPQNS/OTqQbxgTCfq0Ncvcc+CrFov96itVh6Qvw==" }, "node_modules/@haapi/typescript-client": { - "version": "1.79.0", - "resolved": "https://npm-hei-lab-057045785189.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/@haapi/typescript-client/-/typescript-client-1.79.0.tgz", - "integrity": "sha512-EjYSu2/RHjibLUTdbFErKq+KTrV3sN02rSEmQ/yjNsXW8GPChbPz1VWWCYBbwO92yCy96XmoCosNPZengLcc4g==", + "version": "1.83.0", + "resolved": "https://npm-hei-lab-057045785189.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/@haapi/typescript-client/-/typescript-client-1.83.0.tgz", + "integrity": "sha512-oyOYeaLSfcOAD7FWOiLXTNIK/mA3YlkEDHdkOjhe8eSSJhX/8aVHWjCZYEq/YvVP0BSxTjSDIbmY2KxgCFXKdg==", "license": "Unlicense", "peerDependencies": { "axios": "^1.6.1" diff --git a/package.json b/package.json index 5b928b652..8ed98dbf4 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "test:merge:reports": "srm ./dist/test-reports.xml \"./dist/**/!(*test-reports).xml\"" }, "dependencies": { - "@haapi/typescript-client": "^1.79.0", + "@haapi/typescript-client": "^1.83.0", "@mui/x-date-pickers": "^7.21.0", "@react-admin/ra-calendar": "^4.0.1", "@react-admin/ra-enterprise": "^7.0.0", diff --git a/src/operations/announcements/AnnouncementList.tsx b/src/operations/announcements/AnnouncementList.tsx index 9cfa232ae..4ea1b5007 100644 --- a/src/operations/announcements/AnnouncementList.tsx +++ b/src/operations/announcements/AnnouncementList.tsx @@ -165,7 +165,7 @@ const AnnouncementActions = () => { }; export const AnnouncementList = () => { - const {isManager} = useRole(); + const {isManager, isAdmin} = useRole(); return ( { }} > } + actions={(isManager() || isAdmin()) && } filterIndicator={true} title="Liste des annonces" icon={} diff --git a/src/operations/common/components/GeoLocalisation.jsx b/src/operations/common/components/GeoLocalisation.jsx index 09c0d518a..9a07ba293 100644 --- a/src/operations/common/components/GeoLocalisation.jsx +++ b/src/operations/common/components/GeoLocalisation.jsx @@ -42,7 +42,7 @@ function GeoInput({coordinates = {longitude: null, latitude: null}, ...props}) { return ( SPECIALIZATION_VALUE[specialization_field] || EMPTY_TEXT; @@ -262,7 +233,6 @@ const Title = ({children: label}) => { const PersonalInfos = ({isStudentProfile}) => { const isSmall = useMediaQuery("(max-width:900px)"); - const isLarge = useMediaQuery("(min-width:1700px)"); return ( { const {record: profile = {}} = useShowContext(); - const redirect = useRedirect(); - const isSmall = useMediaQuery("(max-width:900px)"); const isLarge = useMediaQuery("(min-width:1700px)"); const {groups = []} = profile; @@ -521,22 +490,25 @@ export const Informations = ({ isStudentProfile, isTeacherProfile, isMonitorProfile, - isManagerProfile, }) => { const isSmall = useMediaQuery("(max-width:900px)"); - const isLarge = useMediaQuery("(min-width:1700px)"); const profile = useRecordContext(); const role = useRole(); - - const { - isLoading, - error, - data: letterStats, - } = useGetOne( + const isAdminProfil = + role.isAdmin() && + !isMonitorProfile && + !isStudentProfile && + !isTeacherProfile; + const isManagerProfil = + role.isManager() && + !isMonitorProfile && + !isStudentProfile && + !isTeacherProfile; + const {data: letterStats} = useGetOne( "letters-stats", {id: undefined}, { - enabled: role.isManager(), + enabled: role.isManager() || role.isAdmin(), } ); @@ -558,11 +530,6 @@ export const Informations = ({ ); } - const isViewerManager = - !isTeacherProfile && - !isStudentProfile && - !isMonitorProfile && - role.isManager(); return ( {isStudentProfile && ( - } - /> + + + )} - {isStudentProfile && (role.isManager() || role.isMonitor()) && ( - } - /> - )} - {!isViewerManager && !role.isMonitor() && !isMonitorProfile && ( - } - data-testid="letters-list-tab" - sx={{ - position: "relative", - fontSize: "0.7rem", - }} - /> - )} - {isViewerManager && ( - - {letterStats.pending} - - } - sx={{ - position: "relative", - fontSize: "0.7rem", - }} - > - Boîte aux lettres - - ) : null - } - children={} - style={{paddingTop: "1rem", width: "10vw"}} - data-testid="letters-list-tab" - /> - )} + {isStudentProfile && + (role.isManager() || role.isAdmin() || role.isMonitor()) && ( + + + + )} + {!role.isMonitor() && + !isMonitorProfile && + !(role.isManager() && isTeacherProfile) && + !(role.isTeacher() && isStudentProfile) && + !isAdminProfil && + !isManagerProfil && ( + + + + )} + {!isMonitorProfile && + !isStudentProfile && + !isTeacherProfile && + (role.isAdmin() || role.isManager()) && ( + + {letterStats.pending} + + } + sx={{ + position: "relative", + fontSize: "0.7rem", + }} + > + Boîte aux lettres + + ) + } + style={{paddingTop: "1rem", width: "10vw"}} + data-testid="letters-list-tab" + > + + + )} ); }; diff --git a/src/operations/common/utils/typo_util.js b/src/operations/common/utils/typo_util.js index ac83b8c77..1e79a0558 100644 --- a/src/operations/common/utils/typo_util.js +++ b/src/operations/common/utils/typo_util.js @@ -49,8 +49,10 @@ export function getFeesStatusInFr(status) { export function getUserRoleInFr(userRole, sex) { const isWoman = sex === Sex.F; switch (userRole) { - case WhoamiRoleEnum.MANAGER: + case WhoamiRoleEnum.ADMIN: return "Admin"; + case WhoamiRoleEnum.MANAGER: + return "Manager"; case WhoamiRoleEnum.TEACHER: return isWoman ? "Enseignante" : "Enseignant"; case WhoamiRoleEnum.STUDENT: diff --git a/src/operations/docs/students/DocList.jsx b/src/operations/docs/students/DocList.jsx index 157073c6f..509dfdd17 100644 --- a/src/operations/docs/students/DocList.jsx +++ b/src/operations/docs/students/DocList.jsx @@ -16,9 +16,9 @@ export const DocList = () => { const params = useParams(); const location = useLocation(); const type = useViewType("LIST"); - const {isStudent, isManager} = useRole(); + const {isStudent, isManager, isAdmin} = useRole(); const getStudentRef = useStudentRef("userId"); - const studentRef = isManager() ? getStudentRef?.studentRef : ""; + const studentRef = isManager() || isAdmin() ? getStudentRef?.studentRef : ""; const userId = isStudent() ? authProvider.getCachedWhoami().id @@ -55,9 +55,10 @@ export const DocList = () => { rowClick: (id) => `${location.pathname}/${id}`, }} haListProps={{ - actions: isManager() ? ( - - ) : null, + actions: + isManager() || isAdmin() ? ( + + ) : null, }} /> ); diff --git a/src/operations/docs/teachers/DocList.tsx b/src/operations/docs/teachers/DocList.tsx index 256cfc6cd..98e8676db 100644 --- a/src/operations/docs/teachers/DocList.tsx +++ b/src/operations/docs/teachers/DocList.tsx @@ -6,7 +6,7 @@ import authProvider from "@/providers/authProvider"; export const TeacherDocList = () => { const params = useParams(); const location = useLocation(); - const {isTeacher, isManager} = useRole(); + const {isTeacher, isAdmin} = useRole(); const userId = isTeacher() ? authProvider.getCachedWhoami().id : params.userId; @@ -18,7 +18,7 @@ export const TeacherDocList = () => { userId={userId!} title={`Liste des documents des enseignants`} haListProps={{ - actions: isManager() ? ( + actions: isAdmin() ? ( ) : null, }} diff --git a/src/operations/events/EventCalendar.tsx b/src/operations/events/EventCalendar.tsx index 89ded1899..2a04f7e36 100644 --- a/src/operations/events/EventCalendar.tsx +++ b/src/operations/events/EventCalendar.tsx @@ -14,7 +14,7 @@ import {useRole} from "@/security/hooks"; export const EventCalendar = () => { const [currentEvent, setCurrentEvent] = useState(); const [editShow, _, toggleEdit] = useToggle(); - const {isManager} = useRole(); + const {isManager, isAdmin} = useRole(); const [anchor, setAnchor] = useState({ top: 0, left: 0, @@ -66,8 +66,8 @@ export const EventCalendar = () => { title: "Création d'un événement", }} CalendarProps={{ - selectable: isManager(), - editable: isManager(), + selectable: isManager() || isAdmin(), + editable: isManager() || isAdmin(), getFilterValueFromInterval: (dateInfo) => { setFilter({from: dateInfo?.startStr, to: dateInfo?.endStr}); return {}; @@ -130,7 +130,7 @@ type ActionProps = { }; const EventAction = ({event, toggleEdit}: ActionProps) => { - const {isManager} = useRole(); + const {isManager, isAdmin} = useRole(); return ( { }} > {event?.title} - {isManager() && ( + {(isAdmin() || isManager()) && ( + + + + + ); +}; diff --git a/src/operations/exams/components/SelectField.tsx b/src/operations/exams/components/SelectField.tsx new file mode 100644 index 000000000..99bdcf7e9 --- /dev/null +++ b/src/operations/exams/components/SelectField.tsx @@ -0,0 +1,51 @@ +import {FC, useState} from "react"; +import { + Select, + MenuItem, + SelectChangeEvent, + FormControl, + InputLabel, +} from "@mui/material"; + +interface Option { + id: string; + name: string; +} + +interface SelectFieldProps { + label: string; + options: Option[]; + onChange: (value: string) => void; +} + +export const SelectField: FC = ({ + label, + options, + onChange, +}) => { + const [selectedValue, setSelectedValue] = useState(""); + + const handleChange = (event: SelectChangeEvent) => { + const value = event.target.value; + setSelectedValue(value); + onChange(value); + }; + + return ( + + {label} + + + ); +}; diff --git a/src/operations/exams/components/index.tsx b/src/operations/exams/components/index.tsx new file mode 100644 index 000000000..20dae10d5 --- /dev/null +++ b/src/operations/exams/components/index.tsx @@ -0,0 +1,2 @@ +export * from "./Filter"; +export * from "./SelectField"; diff --git a/src/operations/exams/index.ts b/src/operations/exams/index.ts new file mode 100644 index 000000000..5cdc34b5d --- /dev/null +++ b/src/operations/exams/index.ts @@ -0,0 +1,9 @@ +import {ExamCreate} from "./ExamCreate.tsx"; +import {ExamList} from "./ExamList.tsx"; + +const exams = { + list: ExamList, + create: ExamCreate, +}; + +export default exams; diff --git a/src/providers/dataProvider.ts b/src/providers/dataProvider.ts index cfbfb7f5f..8070bd648 100644 --- a/src/providers/dataProvider.ts +++ b/src/providers/dataProvider.ts @@ -33,6 +33,7 @@ import monitorProvider from "./monitorProvider"; import exportPromotionProvider from "./exportPromotionProvider"; import exportGroupProvider from "./exportGroupProvider"; import feesExportProvider from "./feesExportProvider"; +import examsProvider from "./examProvider"; export const MAX_ITEM_PER_PAGE = 500; @@ -67,6 +68,7 @@ const getProvider = (resourceType: string): HaDataProviderType => { if (resourceType === "promotions-export") return exportPromotionProvider; if (resourceType === "group-export") return exportGroupProvider; if (resourceType === "fees-export") return feesExportProvider; + if (resourceType === "exams") return examsProvider; throw new Error("Unexpected resourceType: " + resourceType); }; diff --git a/src/providers/examProvider.tsx b/src/providers/examProvider.tsx new file mode 100644 index 000000000..9e98c6200 --- /dev/null +++ b/src/providers/examProvider.tsx @@ -0,0 +1,35 @@ +import {teachingApi} from "./api"; +import {HaDataProviderType} from "./HaDataProviderType"; + +const examsProvider: HaDataProviderType = { + getList: async (page, perPage, filter = {}, _meta) => { + return teachingApi() + .getAllExams( + filter?.awarded_course_id, + filter?.title, + filter?.course_code, + filter?.group_ref, + filter?.examination_date_from, + filter?.examination_date_to, + page, + perPage + ) + .then((result) => ({data: result.data})); + }, + getOne: async (id: string) => { + return teachingApi() + .getExamOneExamById(id) + .then((response) => response.data); + }, + saveOrUpdate: async (payloads: any) => { + const payload = payloads[0]; + return teachingApi() + .createOrUpdateExamsInfos(payload) + .then((response) => [response.data]); + }, + delete: async () => { + throw new Error("Not implemented"); + }, +}; + +export default examsProvider; diff --git a/src/ui/haLayout/menu/ManagerMenu.jsx b/src/ui/haLayout/menu/ManagerMenu.jsx index 4f16a73db..c57699012 100644 --- a/src/ui/haLayout/menu/ManagerMenu.jsx +++ b/src/ui/haLayout/menu/ManagerMenu.jsx @@ -12,6 +12,8 @@ import { Newspaper as AnnouncementIcon, CalendarMonth as EventIcon, SupervisedUserCircle as MonitorIcon, + GradeOutlined as GradeIcon, + LibraryBooksOutlined as LibraryIcon, } from "@mui/icons-material"; import {HeiListMenuItem} from "@/ui/haLayout/menu/common"; import {ListMenu, ListMenuItem, SingleMenu} from "@/ui/haLayout/menu/utils"; @@ -56,13 +58,19 @@ function ManagerMenu() { data-testid="promotions-menu" icon={} /> - } /> } + data-testid="course-menu" + icon={} /> + } + /> + } /> } /> + } + /> Date: Tue, 3 Dec 2024 09:24:33 +0300 Subject: [PATCH 3/7] infra: health --- .github/workflows/health-hei-admin.yml | 64 ++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 .github/workflows/health-hei-admin.yml diff --git a/.github/workflows/health-hei-admin.yml b/.github/workflows/health-hei-admin.yml new file mode 100644 index 000000000..50b2a1cd0 --- /dev/null +++ b/.github/workflows/health-hei-admin.yml @@ -0,0 +1,64 @@ +name: health-hei-admin + +on: + push: + branches: + - main + schedule: + - cron: "*/5 6-15 * * 1-5" # Exécuter toutes les 5 minutes de 8h à 17h (UTC+3) pendant les jours ouvrables + +env: + REACT_APP_TEST_STUDENT1_PASSWORD: ${{ secrets.STUDENT1_PASSWORD }} + REACT_APP_AWS_REGION: eu-west-3 + REACT_APP_USERPOOL_ID: ${{ vars.PREPROD_USERPOOL_ID }} + REACT_APP_WEBCLIENT_ID: ${{ vars.PREPROD_WEBCLIENT_ID }} + REACT_APP_OAUTH_DOMAIN: ${{ vars.PREPROD_OAUTH_DOMAIN }} + +jobs: + auth-check: + timeout-minutes: 4 + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [20] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: npm + + - name: Install dependencies + run: npm install + + - name: Configure prod AWS credentials + uses: aws-actions/configure-aws-credentials@v4.0.1 + with: + aws-access-key-id: ${{ secrets.PROD_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.PROD_AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.REACT_APP_AWS_REGION }} + - run: sh ./initNpmrc.sh hei-store npm-hei-school 088312068315 + + - name: Run authentification tests + uses: cypress-io/github-action@v6 + with: + spec: "./cypress/e2e/hei-admin/authentification.cy.tsx" + + update-status: + needs: [auth-check] + runs-on: ubuntu-latest + steps: + - name: Set trigger value based on job status + id: set-trigger + run: echo "trigger=$(if [ '${{ job.status }}' == 'failure' ]; then echo 'down'; else echo 'up'; fi)" >> $GITHUB_ENV + + - name: Send update to hei admin webhook + uses: fjogeleit/http-request-action@v1.16.3 + with: + url: ${{ secrets.HEI_ADMIN_WEBHOOK_URL }} + method: "POST" + customHeaders: '{"Content-Type": "application/json"}' + data: '{"trigger": "${{ env.trigger }}"}' From f0e2d3f58df1ee6ee30400fe09b0d26c9202f0e6 Mon Sep 17 00:00:00 2001 From: NyAndoMayah <99728289+NyAndoMayah@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:44:37 +0300 Subject: [PATCH 4/7] feat: remove bank receipt (#783) * feat: remove bank receipt * feat: remove bank receipt functionnality --- src/operations/common/utils/money.ts | 4 +- .../fees/{FeeShow.jsx => FeeShow.tsx} | 98 +++++++++---------- .../fees/components/StudentFeeList.tsx | 38 +------ .../utils/{gridStyle.jsx => gridStyle.tsx} | 2 +- .../utils/{pspValues.jsx => pspValues.tsx} | 5 +- 5 files changed, 57 insertions(+), 90 deletions(-) rename src/operations/fees/{FeeShow.jsx => FeeShow.tsx} (84%) rename src/operations/fees/utils/{gridStyle.jsx => gridStyle.tsx} (89%) rename src/operations/fees/utils/{pspValues.jsx => pspValues.tsx} (59%) diff --git a/src/operations/common/utils/money.ts b/src/operations/common/utils/money.ts index ab32f98ce..746f66a29 100644 --- a/src/operations/common/utils/money.ts +++ b/src/operations/common/utils/money.ts @@ -1,5 +1,7 @@ +import {EMPTY_TEXT} from "@/ui/constants"; + const CURRENCY = "Ar"; export const renderMoney = (amount: number): string => { - return amount.toLocaleString() + " " + CURRENCY; + return `${amount ?? EMPTY_TEXT} ${CURRENCY}`; }; diff --git a/src/operations/fees/FeeShow.jsx b/src/operations/fees/FeeShow.tsx similarity index 84% rename from src/operations/fees/FeeShow.jsx rename to src/operations/fees/FeeShow.tsx index 882017de3..b0821bc9e 100644 --- a/src/operations/fees/FeeShow.jsx +++ b/src/operations/fees/FeeShow.tsx @@ -1,4 +1,4 @@ -import {useState, useEffect} from "react"; +import {useState, useEffect, FC, ReactElement, ReactNode} from "react"; import { FunctionField, useDataProvider, @@ -6,7 +6,6 @@ import { TopToolbar, SimpleShowLayout, TextField, - UrlField, } from "react-admin"; import {useParams} from "react-router-dom"; import { @@ -20,7 +19,7 @@ import { AccordionDetails, useMediaQuery, } from "@mui/material"; -import {ExpandMore, Info} from "@mui/icons-material"; +import {Fee} from "@haapi/typescript-client"; import {useRole} from "@/security/hooks"; import {studentIdFromRaId} from "@/providers/feeProvider"; import {statusRenderer, commentFunctionRenderer} from "@/operations/utils"; @@ -34,14 +33,36 @@ import { InfoOutlined, ChatBubbleOutline, AccessTimeOutlined, + ExpandMore, + Info, } from "@mui/icons-material"; import {GRID_STYLE} from "@/operations/fees/utils/gridStyle"; import {EMPTY_TEXT} from "@/ui/constants"; import {PSP_COLORS, PSP_VALUES} from "./utils"; -const dateTimeRenderer = (data) => { +type LabeledFieldProps = { + label: string; + icon?: ReactElement; + children: ReactNode; +}; + +type FeeLayoutProps = { + feeId: string; + studentId: string; +}; + +type AccordionProps = { + title: string; + children: ReactNode; +}; + +const dateTimeRenderer = (data: Fee) => { return data.updated_at == null ? ( - + ) : ( { ); }; -const LabeledField = ({label, icon, children}) => ( +const LabeledField: FC = ({label, icon, children}) => ( ( ); -const AccordionBase = ({title, children}) => ( +const AccordionBase: FC = ({title, children}) => ( }> @@ -121,55 +142,30 @@ const FeePaymentDetails = () => ( showTime /> - fee.mpbs ? ( - - ) : ( - EMPTY_TEXT - ) - } + render={(fee: Fee) => { + if (fee?.mpbs?.psp_type) { + return ( + + ); + } + return EMPTY_TEXT; + }} label="Type de transaction" emptyText={EMPTY_TEXT} /> - - - - - - - - ); -export const FeeLayout = ({feeId, studentId}) => { +export const FeeLayout: FC = ({feeId, studentId}) => { const isSmall = useMediaQuery("(max-width:900px)"); const styles = GRID_STYLE(isSmall); return ( { renderMoney(record.remaining_amount)} + render={(record: Fee) => renderMoney(record.remaining_amount!)} textAlign="right" sx={{ ...styles.font, @@ -234,7 +230,7 @@ export const FeeLayout = ({feeId, studentId}) => { renderMoney(record.total_amount)} + render={(record: Fee) => renderMoney(record.total_amount!)} textAlign="right" sx={{ ...styles.font, @@ -297,6 +293,7 @@ export const FeeLayout = ({feeId, studentId}) => { { { statusRenderer(record.status)} + render={(record: Fee) => statusRenderer(record.status)} /> @@ -358,9 +356,9 @@ const FeeShow = () => { const role = useRole(); const params = useParams(); const feeId = params.feeId; - const studentId = studentIdFromRaId(feeId); - const [studentRef, setStudentRef] = useState("..."); + const studentId = studentIdFromRaId(feeId!); const dataProvider = useDataProvider(); + const [studentRef, setStudentRef] = useState("..."); useEffect(() => { const doEffect = async () => { @@ -391,7 +389,7 @@ const FeeShow = () => { basePath={`/fees/${feeId}/show`} title={`Frais de ${studentRef}`} > - + ); }; diff --git a/src/operations/fees/components/StudentFeeList.tsx b/src/operations/fees/components/StudentFeeList.tsx index 36974544e..d8a4c50b2 100644 --- a/src/operations/fees/components/StudentFeeList.tsx +++ b/src/operations/fees/components/StudentFeeList.tsx @@ -26,14 +26,11 @@ import { AddCard as AddMbpsIcon, Visibility as ShowIcon, WarningOutlined, - FilePresent as SlipIcon, - Repartition, } from "@mui/icons-material"; import {Box, TextField as MuiTextInput, Typography} from "@mui/material"; import {useNotify, useToggle} from "@/hooks"; import {useStudentRef} from "@/hooks/useStudentRef"; import {HaList} from "@/ui/haList/HaList"; -import {ButtonBase, HaActionWrapper} from "@/ui/haToolbar"; import {Create} from "@/operations/common/components"; import {StudentFeeCreate} from "@/operations/fees/StudentFeeCreate"; import {CreateLettersDialog} from "@/operations/letters/CreateLetters"; @@ -49,9 +46,7 @@ import { DEFAULT_REMEDIAL_COSTS_DUE_DATETIME, } from "@/operations/fees/utils"; import {formatDate, toUTC} from "@/utils/date"; -import {PALETTE_COLORS} from "@/haTheme"; import {FeesDialog} from "./FeesDialog"; -import {LetterStatusIcon} from "./letterIcon"; import authProvider from "@/providers/authProvider"; interface CreateProps { @@ -253,20 +248,6 @@ const ListActionButtons: FC<{studentId: string}> = ({studentId}) => { )} - {letter && letter.status !== LetterStatus.REJECTED ? ( - - ) : ( - - - - )} @@ -312,24 +293,7 @@ export const StudentFeeList = () => { datagridProps={{ rowClick: false, }} - actions={ - - - - } - onClick={toggle} - > - Frais de rattrapage - - - - } + actions={false} > ({ +export const GRID_STYLE = (isSmall: boolean) => ({ item: { backgroundColor: PALETTE_COLORS.white, borderRadius: "10px", diff --git a/src/operations/fees/utils/pspValues.jsx b/src/operations/fees/utils/pspValues.tsx similarity index 59% rename from src/operations/fees/utils/pspValues.jsx rename to src/operations/fees/utils/pspValues.tsx index 60d36f2c1..0e956e662 100644 --- a/src/operations/fees/utils/pspValues.jsx +++ b/src/operations/fees/utils/pspValues.tsx @@ -3,7 +3,10 @@ export const PSP_VALUES = { ORANGE_MONEY: "Orange money", AIRTEL_MONEY: "Airtel money", }; -export const PSP_COLORS = { +export const PSP_COLORS: Record< + string, + "success" | "warning" | "error" | "default" | "primary" | "secondary" | "info" +> = { MVOLA: "success", ORANGE_MONEY: "warning", AIRTEL_MONEY: "error", From 0b28fdc6afc5c7cc46fff724a39d43a0a2b3b407 Mon Sep 17 00:00:00 2001 From: NyAndoMayah <99728289+NyAndoMayah@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:51:33 +0300 Subject: [PATCH 5/7] feat: fees filters and stats * chore: client gen * feat: en attente -> en cours and add stats and filters * style: remove logs * refactor: PR based review * style: run prettier * feat: list headers * fix: tests imports * fix: cypress config --- cypress/e2e/manager.fees.cy.tsx | 13 ++- cypress/e2e/manager.fees.mpbs.cy.tsx | 27 +++--- cypress/e2e/manager.payments.cy.tsx | 9 +- package-lock.json | 80 ++++++--------- package.json | 3 +- .../common/components/ListHeader.tsx | 8 +- src/operations/common/utils/typo_util.js | 5 +- src/operations/fees/ByStatusFeeList.jsx | 16 +-- src/operations/fees/FeeList.jsx | 6 -- ...tionFeeList.jsx => TransactionFeeList.tsx} | 73 +++----------- src/operations/fees/components/FeeInputs.jsx | 2 +- src/operations/fees/components/FeesFilter.jsx | 51 ---------- src/operations/fees/components/FeesFilter.tsx | 56 +++++++++++ .../fees/components/FeesListHeader.tsx | 97 +++++++++++++++++++ src/operations/fees/components/index.jsx | 1 + .../components/{pspIcon.jsx => pspIcon.tsx} | 2 +- src/operations/fees/constants.ts | 30 ++++++ src/operations/fees/feesTemplatesChoices.jsx | 8 -- src/operations/fees/utils/StatusIcon.jsx | 2 +- .../feesTemplates/FeesTemplateCreate.jsx | 2 +- .../feesTemplates/FeesTemplatesEdit.jsx | 2 +- .../letters/components/HeaderLetterList.tsx | 8 +- src/operations/utils/typography.jsx | 2 +- src/providers/feeProvider.ts | 35 ++++--- src/providers/statsProvider.ts | 18 +++- src/ui/haList/HaList.jsx | 2 + src/ui/haToolbar/Filters.jsx | 2 +- 27 files changed, 312 insertions(+), 248 deletions(-) rename src/operations/fees/{TransactionFeeList.jsx => TransactionFeeList.tsx} (59%) delete mode 100644 src/operations/fees/components/FeesFilter.jsx create mode 100644 src/operations/fees/components/FeesFilter.tsx create mode 100644 src/operations/fees/components/FeesListHeader.tsx rename src/operations/fees/components/{pspIcon.jsx => pspIcon.tsx} (94%) create mode 100644 src/operations/fees/constants.ts delete mode 100644 src/operations/fees/feesTemplatesChoices.jsx diff --git a/cypress/e2e/manager.fees.cy.tsx b/cypress/e2e/manager.fees.cy.tsx index b8947250f..420d39e6a 100644 --- a/cypress/e2e/manager.fees.cy.tsx +++ b/cypress/e2e/manager.fees.cy.tsx @@ -1,5 +1,4 @@ import {FeeTypeEnum} from "@haapi/typescript-client"; - import {assertFeeMatchesTemplate} from "./utils"; import { annual1xTemplate, @@ -10,9 +9,10 @@ import {student1Mock, studentsMock} from "../fixtures/api_mocks/students-mocks"; import {fee1Mock, feesMock} from "../fixtures/api_mocks/fees-mocks"; import {createPaymentMock} from "../fixtures/api_mocks/payments-mocks"; -import {renderMoney} from "../../src/operations/common/utils/money"; -import {getFeesStatusInFr} from "../../src/operations/common/utils/typo_util"; -import {get27thOfMonth} from "../../src/utils/date"; +/*Added this to make the test blackbox */ +const get27thOfMonth = (year: number, month: number) => { + return new Date(year, month, 27); +}; describe("Manager.Fee", () => { beforeEach(() => { @@ -83,10 +83,9 @@ describe("Manager.Fee", () => { cy.get("#main-content tbody tr").first().click(); cy.wait("@getFee1"); cy.get("#main-content") - .should("contain", renderMoney(interceptedFeeMock!.remaining_amount!)) - .and("contain", renderMoney(interceptedFeeMock!.total_amount!)) + .should("contain", `${interceptedFeeMock!.remaining_amount!} Ar`) + .and("contain", `${interceptedFeeMock!.total_amount!} Ar`) .and("contain", interceptedFeeMock!.comment!) - .and("contain", getFeesStatusInFr(interceptedFeeMock!.status)) .and("contain", "Paiements"); }); diff --git a/cypress/e2e/manager.fees.mpbs.cy.tsx b/cypress/e2e/manager.fees.mpbs.cy.tsx index cdfb32da5..67a5902d8 100644 --- a/cypress/e2e/manager.fees.mpbs.cy.tsx +++ b/cypress/e2e/manager.fees.mpbs.cy.tsx @@ -12,11 +12,10 @@ describe("Mobile payment by student", () => { }); it("can list fees transactions", () => { - cy.intercept( - "GET", - `/fees?page=1&page_size=10&isMpbs=true`, - feesMpbsMock - ).as("getFees"); + cy.intercept("GET", `/fees?page=1&page_size=10&isMpbs=true`, { + data: feesMpbsMock, + statistics: {}, + }).as("getFees"); cy.get(`[href="#/transactions"]`).click(); @@ -27,9 +26,9 @@ describe("Mobile payment by student", () => { }); it("shows success status icon when the status is SUCCESS", () => { - cy.intercept("GET", `/fees?page=1&page_size=10&isMpbs=true`, [ - succeedMpbs1, - ]).as("getFees"); + cy.intercept("GET", `/fees?page=1&page_size=10&isMpbs=true`, { + data: [succeedMpbs1], + }).as("getFees"); cy.get(`[href="#/transactions"]`).click(); @@ -41,9 +40,9 @@ describe("Mobile payment by student", () => { }); it("shows pending status icon when the status is PENDING", () => { - cy.intercept("GET", `/fees?page=1&page_size=10&isMpbs=true`, [ - pendingMpbs, - ]).as("getFees"); + cy.intercept("GET", `/fees?page=1&page_size=10&isMpbs=true`, { + data: [pendingMpbs], + }).as("getFees"); cy.get(`[href="#/transactions"]`).click(); @@ -55,9 +54,9 @@ describe("Mobile payment by student", () => { }); it("shows failed status icon when the status is FAILED", () => { - cy.intercept("GET", `/fees?page=1&page_size=10&isMpbs=true`, [ - failedMpbs, - ]).as("getFees"); + cy.intercept("GET", `/fees?page=1&page_size=10&isMpbs=true`, { + data: [failedMpbs], + }).as("getFees"); cy.get(`[href="#/transactions"]`).click(); diff --git a/cypress/e2e/manager.payments.cy.tsx b/cypress/e2e/manager.payments.cy.tsx index 58b6ffe6c..64e39c200 100644 --- a/cypress/e2e/manager.payments.cy.tsx +++ b/cypress/e2e/manager.payments.cy.tsx @@ -1,4 +1,3 @@ -import {renderMoney} from "../../src/operations/common/utils/money"; import { UpdateFeeWithPaymentMock, feesMock, @@ -61,7 +60,7 @@ describe("Manager.Payment", () => { cy.getByTestid("fees-list-tab").click(); cy.contains(unpaidFeeMock.comment as string).click(); - cy.contains("En attente"); + cy.contains("En cours"); cy.getByTestid("AddIcon").click(); cy.intercept( "GET", @@ -81,7 +80,7 @@ describe("Manager.Payment", () => { cy.get("#comment").click().type(createPayment.comment!); cy.contains("Enregistrer").click(); cy.contains("Élément créé"); - cy.get(`.MuiTableCell-alignRight:contains(${renderMoney(amount)})`).should( + cy.get(`.MuiTableCell-alignRight:contains(${amount} Ar)`).should( "have.length", 1 ); @@ -94,7 +93,7 @@ describe("Manager.Payment", () => { cy.get("#comment").click().type(createPayment.comment!); cy.contains("Enregistrer").click(); cy.contains("Élément créé"); - cy.get(`.MuiTableCell-alignRight:contains(${renderMoney(amount)})`).should( + cy.get(`.MuiTableCell-alignRight:contains(${amount} Ar)`).should( "have.length", 1 ); @@ -116,7 +115,7 @@ describe("Manager.Payment", () => { cy.get("#creation_datetime").click().type("2023-11-24"); cy.contains("Enregistrer").click(); cy.contains("Élément créé"); - cy.get(`.MuiTableCell-alignRight:contains(${renderMoney(amount)})`).should( + cy.get(`.MuiTableCell-alignRight:contains(${amount} Ar)`).should( "have.length", 1 ); diff --git a/package-lock.json b/package-lock.json index da7148995..6a3244af5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "hei-admin-ui", "version": "0.2.0", "dependencies": { - "@haapi/typescript-client": "^1.83.0", + "@haapi/typescript-client": "^1.85.0", "@mui/x-date-pickers": "^7.21.0", "@react-admin/ra-calendar": "^4.0.1", "@react-admin/ra-enterprise": "^7.0.0", @@ -17,6 +17,7 @@ "aws-amplify": "^6.0.28", "axios": "^1.6.8", "cypress-multi-reporters": "^2.0.4", + "cypress-vite": "^1.5.0", "dayjs": "^1.11.13", "dotenv": "^16.4.5", "mime": "^4.0.3", @@ -3520,7 +3521,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3535,7 +3535,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3550,7 +3549,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3565,7 +3563,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3580,7 +3577,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3595,7 +3591,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3610,7 +3605,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3625,7 +3619,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3640,7 +3633,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3655,7 +3647,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3670,7 +3661,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3685,7 +3675,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3700,7 +3689,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3901,9 +3889,9 @@ "integrity": "sha512-I7xWjLs2YSVMc5gGx1Z3ZG1lgFpITPndpi8Ku55GeEIKpACCPQNS/OTqQbxgTCfq0Ncvcc+CrFov96itVh6Qvw==" }, "node_modules/@haapi/typescript-client": { - "version": "1.83.0", - "resolved": "https://npm-hei-lab-057045785189.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/@haapi/typescript-client/-/typescript-client-1.83.0.tgz", - "integrity": "sha512-oyOYeaLSfcOAD7FWOiLXTNIK/mA3YlkEDHdkOjhe8eSSJhX/8aVHWjCZYEq/YvVP0BSxTjSDIbmY2KxgCFXKdg==", + "version": "1.85.0", + "resolved": "https://npm-hei-lab-057045785189.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/@haapi/typescript-client/-/typescript-client-1.85.0.tgz", + "integrity": "sha512-gGocz7vf+DCZEAbw8TAFU+04Bt4pmLtpK4NIXwdngBR61vLYbmsAW4ruoqtMjELHOh4oiBfFY0BJFLBxif6hqQ==", "license": "Unlicense", "peerDependencies": { "axios": "^1.6.1" @@ -4069,7 +4057,7 @@ }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -4082,7 +4070,7 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -4090,7 +4078,7 @@ }, "node_modules/@jridgewell/set-array": { "version": "1.2.1", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -4100,7 +4088,7 @@ "version": "0.3.6", "resolved": "https://npm-hei-school-088312068315.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/@jridgewell/source-map/-/source-map-0.3.6.tgz", "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -4109,12 +4097,12 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -5686,7 +5674,7 @@ "version": "20.12.7", "resolved": "https://npm-hei-school-088312068315.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/@types/node/-/node-20.12.7.tgz", "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", - "dev": true, + "devOptional": true, "dependencies": { "undici-types": "~5.26.4" } @@ -6153,7 +6141,7 @@ }, "node_modules/acorn": { "version": "8.11.3", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -7259,7 +7247,7 @@ "version": "1.1.2", "resolved": "https://npm-hei-school-088312068315.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/buffer/node_modules/isarray": { @@ -8029,6 +8017,19 @@ "node": ">=8" } }, + "node_modules/cypress-vite": { + "version": "1.5.0", + "resolved": "https://npm-hei-lab-057045785189.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/cypress-vite/-/cypress-vite-1.5.0.tgz", + "integrity": "sha512-vvTMqJZgI3sN2ylQTi4OQh8LRRjSrfrIdkQD5fOj+EC/e9oHkxS96lif1SyDF1PwailG1tnpJE+VpN6+AwO/rg==", + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.3", + "debug": "^4.3.4" + }, + "peerDependencies": { + "vite": "^2.9.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" + } + }, "node_modules/cypress/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://npm-hei-school-088312068315.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -8227,7 +8228,6 @@ }, "node_modules/debug": { "version": "4.3.4", - "devOptional": true, "license": "MIT", "dependencies": { "ms": "2.1.2" @@ -8624,7 +8624,6 @@ }, "node_modules/esbuild": { "version": "0.18.20", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -8662,7 +8661,6 @@ "version": "0.18.20", "resolved": "https://npm-hei-lab-057045785189.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", - "dev": true, "optional": true, "engines": { "node": ">=12" @@ -8672,7 +8670,6 @@ "version": "0.18.20", "resolved": "https://npm-hei-lab-057045785189.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "dev": true, "optional": true, "engines": { "node": ">=12" @@ -8682,7 +8679,6 @@ "version": "0.18.20", "resolved": "https://npm-hei-lab-057045785189.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "dev": true, "optional": true, "engines": { "node": ">=12" @@ -8692,7 +8688,6 @@ "version": "0.18.20", "resolved": "https://npm-hei-lab-057045785189.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "dev": true, "optional": true, "engines": { "node": ">=12" @@ -8702,7 +8697,6 @@ "version": "0.18.20", "resolved": "https://npm-hei-lab-057045785189.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "dev": true, "optional": true, "engines": { "node": ">=12" @@ -8712,7 +8706,6 @@ "version": "0.18.20", "resolved": "https://npm-hei-lab-057045785189.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "dev": true, "optional": true, "engines": { "node": ">=12" @@ -8722,7 +8715,6 @@ "version": "0.18.20", "resolved": "https://npm-hei-lab-057045785189.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "dev": true, "optional": true, "engines": { "node": ">=12" @@ -8732,7 +8724,6 @@ "version": "0.18.20", "resolved": "https://npm-hei-lab-057045785189.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "dev": true, "optional": true, "engines": { "node": ">=12" @@ -8742,7 +8733,6 @@ "version": "0.18.20", "resolved": "https://npm-hei-lab-057045785189.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "dev": true, "optional": true, "engines": { "node": ">=12" @@ -11564,7 +11554,6 @@ }, "node_modules/ms": { "version": "2.1.2", - "devOptional": true, "license": "MIT" }, "node_modules/nan": { @@ -11583,7 +11572,6 @@ }, "node_modules/nanoid": { "version": "3.3.7", - "dev": true, "funding": [ { "type": "github", @@ -12370,7 +12358,6 @@ }, "node_modules/postcss": { "version": "8.4.38", - "dev": true, "funding": [ { "type": "opencollective", @@ -13378,7 +13365,6 @@ }, "node_modules/rollup": { "version": "3.29.4", - "dev": true, "license": "MIT", "bin": { "rollup": "dist/bin/rollup" @@ -13781,7 +13767,6 @@ }, "node_modules/source-map-js": { "version": "1.2.0", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -13791,7 +13776,7 @@ "version": "0.5.21", "resolved": "https://npm-hei-school-088312068315.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "buffer-from": "^1.0.0", @@ -13802,7 +13787,7 @@ "version": "0.6.1", "resolved": "https://npm-hei-school-088312068315.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=0.10.0" @@ -14079,7 +14064,7 @@ "version": "5.30.3", "resolved": "https://npm-hei-school-088312068315.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/terser/-/terser-5.30.3.tgz", "integrity": "sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -14152,7 +14137,7 @@ "version": "2.20.3", "resolved": "https://npm-hei-school-088312068315.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/test-exclude": { @@ -14462,7 +14447,7 @@ "version": "5.26.5", "resolved": "https://npm-hei-school-088312068315.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "devOptional": true }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", @@ -14618,7 +14603,6 @@ "version": "4.5.3", "resolved": "https://npm-hei-lab-057045785189.d.codeartifact.eu-west-3.amazonaws.com/npm/hei-store/vite/-/vite-4.5.3.tgz", "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", - "dev": true, "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", diff --git a/package.json b/package.json index 8ed98dbf4..f961d0cab 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "test:merge:reports": "srm ./dist/test-reports.xml \"./dist/**/!(*test-reports).xml\"" }, "dependencies": { - "@haapi/typescript-client": "^1.83.0", + "@haapi/typescript-client": "^1.85.0", "@mui/x-date-pickers": "^7.21.0", "@react-admin/ra-calendar": "^4.0.1", "@react-admin/ra-enterprise": "^7.0.0", @@ -27,6 +27,7 @@ "aws-amplify": "^6.0.28", "axios": "^1.6.8", "cypress-multi-reporters": "^2.0.4", + "cypress-vite": "^1.5.0", "dayjs": "^1.11.13", "dotenv": "^16.4.5", "mime": "^4.0.3", diff --git a/src/operations/common/components/ListHeader.tsx b/src/operations/common/components/ListHeader.tsx index ab1d35598..f000d581a 100644 --- a/src/operations/common/components/ListHeader.tsx +++ b/src/operations/common/components/ListHeader.tsx @@ -17,20 +17,20 @@ import {PALETTE_COLORS} from "@/haTheme"; export interface StatDetail { icon: ReactElement; - total: number; + total: number | string; title: string; } export interface CardContent { title: string; - total: number; + total: number | string; icon: ReactElement; statDetails?: Array; } interface ListHeaderProps { - title: string; - action: ReactNode; + title: string | ReactNode; + action?: ReactNode; cardContents: Array; } diff --git a/src/operations/common/utils/typo_util.js b/src/operations/common/utils/typo_util.js index 1e79a0558..2b3e8b091 100644 --- a/src/operations/common/utils/typo_util.js +++ b/src/operations/common/utils/typo_util.js @@ -4,7 +4,6 @@ import { Sex, WhoamiRoleEnum, } from "@haapi/typescript-client"; -import {NOT_DEFINED} from "../../../utils/constants"; export function getGenderInFr(sex) { switch (sex) { @@ -13,7 +12,7 @@ export function getGenderInFr(sex) { case Sex.F: return "Femme"; case null: // display empty_text if sex is null - return NOT_DEFINED; + return "Non défini.e"; default: throw new Error("Unknown gender"); } @@ -40,7 +39,7 @@ export function getFeesStatusInFr(status) { case FeeStatusEnum.PAID: return "Payé"; case FeeStatusEnum.UNPAID: - return "En attente"; + return "En cours"; default: throw new Error("Unknown fees status"); } diff --git a/src/operations/fees/ByStatusFeeList.jsx b/src/operations/fees/ByStatusFeeList.jsx index ff28d9def..2b0a4c01a 100644 --- a/src/operations/fees/ByStatusFeeList.jsx +++ b/src/operations/fees/ByStatusFeeList.jsx @@ -4,17 +4,9 @@ import { ShowButton, TextField, useDataProvider, - useList, - useListContext, } from "react-admin"; import {Box} from "@mui/material"; -import { - AttachMoney, - Cancel, - Pending, - Check, - Download, -} from "@mui/icons-material"; +import {Download} from "@mui/icons-material"; import {FeeStatusEnum} from "@haapi/typescript-client"; import {HaList} from "@/ui/haList/HaList"; import {FeesFilters} from "./components/FeesFilter"; @@ -22,10 +14,9 @@ import {DateField} from "../common/components/fields"; import {commentFunctionRenderer} from "../utils"; import {renderMoney} from "../common/utils/money"; import {rowStyle} from "./utils"; -import {NOOP_ID} from "@/utils/constants"; -import {FileDownloader, ListHeader} from "@/operations/common/components"; +import {FileDownloader} from "../common/components"; -const ByStatusFeeList = (props) => { +const ByStatusFeeList = () => { const dataProvider = useDataProvider(); const downloadFile = async () => { @@ -43,7 +34,6 @@ const ByStatusFeeList = (props) => { return ( { const {isStudent} = useRole(); return isStudent() ? ( diff --git a/src/operations/fees/TransactionFeeList.jsx b/src/operations/fees/TransactionFeeList.tsx similarity index 59% rename from src/operations/fees/TransactionFeeList.jsx rename to src/operations/fees/TransactionFeeList.tsx index ee94f3138..bbf194c02 100644 --- a/src/operations/fees/TransactionFeeList.jsx +++ b/src/operations/fees/TransactionFeeList.tsx @@ -1,68 +1,22 @@ -import { - CurrencyExchange as Money, - AttachMoney, - Cancel, - Pending, - Check, -} from "@mui/icons-material"; -import {Chip, Typography} from "@mui/material"; +import {FunctionField, TextField} from "react-admin"; +import {CurrencyExchange as Money} from "@mui/icons-material"; +import {Fee} from "@haapi/typescript-client"; +import {Box, Chip} from "@mui/material"; import {HaList} from "@/ui/haList/HaList"; import {EMPTY_TEXT} from "@/ui/constants"; import {FeesFilters} from "./components/FeesFilter"; import {DateField} from "../common/components/fields"; -import {FunctionField, TextField, useGetOne} from "react-admin"; import {commentFunctionRenderer} from "../utils"; import {renderMoney} from "../common/utils/money"; import {PSP_COLORS, PSP_VALUES, rowStyle, MpbsStatusIcon} from "./utils"; -import {NOOP_ID} from "@/utils/constants"; -import {ListHeader} from "../common/components"; +import {FeesListHeader} from "./components"; const TransactionFeeList = () => { - const { - data: stats = { - total_fees: "...", - paid_fees: "...", - unpaid_fees: "...", - }, - } = useGetOne("stats", {id: NOOP_ID, meta: {resource: "fees"}}); - - const headerCardContent = [ - { - title: "Total", - icon: , - total: stats.total_fees, - }, - { - title: "Non-payés", - icon: , - total: stats.unpaid_fees, - }, - { - title: "Payés", - icon: , - total: stats.paid_fees, - }, - { - title: "En retard", - icon: , - total: - stats.total_fees == "..." - ? "..." - : stats.total_fees - (stats.paid_fees + stats.unpaid_fees), - }, - ]; return ( - <> - - Statistiques des frais de ce mois-ci - - } - /> + } + header={} title="Transactions (Mobile Money)" resource="fees" listProps={{ @@ -86,11 +40,12 @@ const TransactionFeeList = () => { emptyText={EMPTY_TEXT} /> - fee.mpbs?.psp_type ? ( + render={(fee: Fee) => + fee.mpbs ? ( ) : ( EMPTY_TEXT @@ -106,7 +61,7 @@ const TransactionFeeList = () => { /> renderMoney(fee.remaining_amount)} + render={(fee: Fee) => renderMoney(fee.remaining_amount!)} /> { /> } /> - + ); }; diff --git a/src/operations/fees/components/FeeInputs.jsx b/src/operations/fees/components/FeeInputs.jsx index f0e72977c..352e4caf5 100644 --- a/src/operations/fees/components/FeeInputs.jsx +++ b/src/operations/fees/components/FeeInputs.jsx @@ -12,7 +12,7 @@ import { import {useFormContext} from "react-hook-form"; import {SelectDueDatetime} from "./SelectDueDatetime"; import {SelectPredefinedType} from "./SelectPredefinedType"; -import {FEESTEMPLATES_CHOICES} from "../feesTemplatesChoices"; +import {FEESTEMPLATES_CHOICES} from "../constants"; export function FeeInputs() { const {reset, getValues} = useFormContext(); diff --git a/src/operations/fees/components/FeesFilter.jsx b/src/operations/fees/components/FeesFilter.jsx deleted file mode 100644 index 5bb35e7e8..000000000 --- a/src/operations/fees/components/FeesFilter.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import {Box, Divider, Typography} from "@mui/material"; -import {FeeStatusEnum} from "@haapi/typescript-client"; -import {useRole} from "@/security/hooks"; -import { - FilterForm, - TextFilter, - SelectInputFilter, - DateTimeFilter, -} from "@/ui/haToolbar"; -import {mapToChoices} from "@/utils"; - -export const FEE_STATUS = { - LATE: "En retard", - PAID: "Payés", - UNPAID: "En attente", -}; - -const FEE_STATUS_CHOICES = mapToChoices(FEE_STATUS, "id", "name"); - -export const FeesFilterByStatus = () => ( - -); - -export const FeesFilters = () => { - const {isManager, isAdmin} = useRole(); - - return ( - - - {(isManager() || isAdmin()) && ( - - )} - - - Filtre par plage de date de limite de paiement - - - - - ); -}; diff --git a/src/operations/fees/components/FeesFilter.tsx b/src/operations/fees/components/FeesFilter.tsx new file mode 100644 index 000000000..86b6f1b1b --- /dev/null +++ b/src/operations/fees/components/FeesFilter.tsx @@ -0,0 +1,56 @@ +import {Box, Divider, Typography} from "@mui/material"; +import {useRole} from "@/security/hooks"; +import { + FilterForm, + TextFilter, + SelectInputFilter, + DateTimeFilter, +} from "@/ui/haToolbar"; +import { + FEE_STATUS_CHOICES, + FEES_TYPES_CHOICES, + MPBS_CHOICES, +} from "../constants"; + +export const FeesFilters = () => { + const {isManager, isAdmin} = useRole(); + + return ( + + {(isManager() || isAdmin()) && ( + + + + + + + )} + + + Filtre par plage de date de limite de paiement + + + + + ); +}; diff --git a/src/operations/fees/components/FeesListHeader.tsx b/src/operations/fees/components/FeesListHeader.tsx new file mode 100644 index 000000000..1afc861ca --- /dev/null +++ b/src/operations/fees/components/FeesListHeader.tsx @@ -0,0 +1,97 @@ +import {useGetOne, useListContext} from "react-admin"; +import { + CurrencyExchange as Money, + AttachMoney, + Cancel, + Pending, + Check, + CalendarMonth, + LinearScale, +} from "@mui/icons-material"; +import {Typography} from "@mui/material"; +import {ListHeader} from "@/operations/common/components"; +import {CardContent} from "@/operations/common/components/ListHeader"; +import {NOOP_ID} from "@/utils/constants"; + +const INITIAL_STATS = { + total_fees: "...", + paid_fees: "...", + unpaid_fees: "...", + late_fees: "...", + pending_transaction: "...", + paid_by_transaction: "...", + total_monthly_fees: "...", + total_yearly_fees: "...", +}; + +// TODO: Add this to ByStatusFeeList +export const FeesListHeader = () => { + const {filterValues} = useListContext(); + const {data: stats = INITIAL_STATS} = useGetOne("stats", { + id: NOOP_ID, + meta: {resource: "fees", filters: filterValues}, + }); + + const headerCardContent: CardContent[] = [ + { + title: "Total des frais", + icon: , + total: stats.total_fees!, + statDetails: [ + { + icon: , + total: stats.paid_fees!, + title: "Frais payés", + }, + { + icon: , + total: stats.unpaid_fees!, + title: "Frais en cours", + }, + { + icon: , + total: stats.late_fees!, + title: "Frais en retard", + }, + ], + }, + { + title: "Transactions", + icon: , + total: stats.unpaid_fees!, + statDetails: [ + { + icon: , + total: stats.pending_transaction!, + title: "Transactions en cours de vérification", + }, + { + icon: , + total: stats.paid_by_transaction!, + title: "Transactions vérifiées avec succès", + }, + ], + }, + { + title: "Frais mensuels", + icon: , + total: stats.total_monthly_fees ?? "...", + }, + { + title: "Frais annuels", + icon: , + total: stats.total_yearly_fees ?? "...", + }, + ]; + + return ( + + Statistiques des frais de ce mois-ci + + } + /> + ); +}; diff --git a/src/operations/fees/components/index.jsx b/src/operations/fees/components/index.jsx index b366795a1..154c54222 100644 --- a/src/operations/fees/components/index.jsx +++ b/src/operations/fees/components/index.jsx @@ -2,3 +2,4 @@ export * from "./FeeInputs"; export * from "./SelectPredefinedType"; export * from "./ManagerFeeList"; export * from "./StudentFeeList"; +export * from "./FeesListHeader"; diff --git a/src/operations/fees/components/pspIcon.jsx b/src/operations/fees/components/pspIcon.tsx similarity index 94% rename from src/operations/fees/components/pspIcon.jsx rename to src/operations/fees/components/pspIcon.tsx index c799a050e..b5bd001f5 100644 --- a/src/operations/fees/components/pspIcon.jsx +++ b/src/operations/fees/components/pspIcon.tsx @@ -4,4 +4,4 @@ export const PSP_ICON = { PENDING: , SUCCESS: , FAILED: , -}; +} as const; diff --git a/src/operations/fees/constants.ts b/src/operations/fees/constants.ts new file mode 100644 index 000000000..419a6f869 --- /dev/null +++ b/src/operations/fees/constants.ts @@ -0,0 +1,30 @@ +import {mapToChoices} from "@/utils"; +import {FeeTypeEnum} from "@haapi/typescript-client"; + +export const FEE_STATUS = { + LATE: "En retard", + PAID: "Payés", + UNPAID: "En cours", +} as const; + +export const MPBS_STATUS_LABEL = { + SUCCESS: "Paiement avec succès", + FAILED: "Paiement échoué", + PENDING: "Vérification en cours", +} as const; + +export const FEE_STATUS_CHOICES = mapToChoices(FEE_STATUS, "id", "name"); + +export const FEESTEMPLATES_CHOICES = [ + {label: "Écolage", value: FeeTypeEnum.TUITION}, + {label: "Matériel", value: FeeTypeEnum.HARDWARE}, + {label: "Assurance étudiante", value: FeeTypeEnum.STUDENT_INSURANCE}, + {label: "Rattrapage", value: FeeTypeEnum.REMEDIAL_COSTS}, +] as const; + +export const FEES_TYPES_CHOICES = FEESTEMPLATES_CHOICES.map((choice) => ({ + name: choice.label, + id: choice.value, +})); + +export const MPBS_CHOICES = mapToChoices(MPBS_STATUS_LABEL, "id", "name"); diff --git a/src/operations/fees/feesTemplatesChoices.jsx b/src/operations/fees/feesTemplatesChoices.jsx deleted file mode 100644 index fb87a863a..000000000 --- a/src/operations/fees/feesTemplatesChoices.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import {FeeTypeEnum} from "@haapi/typescript-client"; - -export const FEESTEMPLATES_CHOICES = [ - {label: "Écolage", value: FeeTypeEnum.TUITION}, - {label: "Matériel", value: FeeTypeEnum.HARDWARE}, - {label: "Assurance étudiante", value: FeeTypeEnum.STUDENT_INSURANCE}, - {label: "Autres frais", value: FeeTypeEnum.REMEDIAL_COSTS}, -]; diff --git a/src/operations/fees/utils/StatusIcon.jsx b/src/operations/fees/utils/StatusIcon.jsx index 98069c4ff..6b7b8e851 100644 --- a/src/operations/fees/utils/StatusIcon.jsx +++ b/src/operations/fees/utils/StatusIcon.jsx @@ -2,7 +2,7 @@ import {useRecordContext} from "react-admin"; import {IconButton, Tooltip} from "@mui/material"; import {Help as Question} from "@mui/icons-material"; import {PSP_ICON} from "../components/pspIcon"; -import {MPBS_STATUS_LABEL} from "../FeeList"; +import {MPBS_STATUS_LABEL} from "../constants"; export const MpbsStatusIcon = () => { const record = useRecordContext(); diff --git a/src/operations/feesTemplates/FeesTemplateCreate.jsx b/src/operations/feesTemplates/FeesTemplateCreate.jsx index 3e150b8d9..35e9878bd 100644 --- a/src/operations/feesTemplates/FeesTemplateCreate.jsx +++ b/src/operations/feesTemplates/FeesTemplateCreate.jsx @@ -10,7 +10,7 @@ import { } from "react-admin"; import {v4 as uuid} from "uuid"; -import {FEESTEMPLATES_CHOICES} from "../fees/feesTemplatesChoices"; +import {FEESTEMPLATES_CHOICES} from "../fees/constants"; function FeesTemplatesCreate() { return ( diff --git a/src/operations/feesTemplates/FeesTemplatesEdit.jsx b/src/operations/feesTemplates/FeesTemplatesEdit.jsx index e6ceb31e4..0793f051a 100644 --- a/src/operations/feesTemplates/FeesTemplatesEdit.jsx +++ b/src/operations/feesTemplates/FeesTemplatesEdit.jsx @@ -10,7 +10,7 @@ import { import {Edit} from "../common/components"; import {EditToolBar} from "../utils"; -import {FEESTEMPLATES_CHOICES} from "../fees/feesTemplatesChoices"; +import {FEESTEMPLATES_CHOICES} from "../fees/constants"; function FeesTemplatesEdit() { return ( diff --git a/src/operations/letters/components/HeaderLetterList.tsx b/src/operations/letters/components/HeaderLetterList.tsx index ddf481438..04b97b435 100644 --- a/src/operations/letters/components/HeaderLetterList.tsx +++ b/src/operations/letters/components/HeaderLetterList.tsx @@ -103,18 +103,18 @@ export const LetterStatusFilter: FC<{ const statuses = [ {label: "Tous", value: null, onClick: handleResetFilters}, - {label: "En attente", value: LetterStatus.PENDING}, + {label: "En cours", value: LetterStatus.PENDING}, {label: "Accepté", value: LetterStatus.RECEIVED}, {label: "Refusé", value: LetterStatus.REJECTED}, - ]; + ] as const; return ( - {statuses.map(({label, value, onClick}) => ( + {statuses.map(({label, value}) => ( (onClick ? onClick() : handleStatusSelect(value))} + onClick={() => handleStatusSelect(value as LetterStatus)} sx={value && isStatusFilterActive(value) ? showIndication : undefined} > {label} diff --git a/src/operations/utils/typography.jsx b/src/operations/utils/typography.jsx index 7fadfa01a..b2abdc808 100644 --- a/src/operations/utils/typography.jsx +++ b/src/operations/utils/typography.jsx @@ -18,7 +18,7 @@ const statusMap = { backgroundColor: "#388E3C", }, UNPAID: { - text: "En attente", + text: "En cours", icon: , backgroundColor: "#fbbf24", }, diff --git a/src/providers/feeProvider.ts b/src/providers/feeProvider.ts index e32efa7e0..158a66330 100644 --- a/src/providers/feeProvider.ts +++ b/src/providers/feeProvider.ts @@ -20,14 +20,17 @@ export const feeIdFromRaId = (raId: string): string => toApiIds(raId).feeId; const feeProvider: HaDataProviderType = { async getList(page: number, perPage: number, filter: any) { - const {data: fees} = filter.studentId - ? await payingApi().getStudentFees( - filter.studentId, - page, - perPage, - filter.status - ) - : await payingApi().getFees( + const doGetFees = async () => { + if (filter.studentId) { + return await payingApi() + .getStudentFees(filter.studentId, page, perPage, filter.status) + .then(({data}) => data); + } + //TODO : redundance + return await payingApi() + .getFees( + filter.transaction_status, + filter.type, filter.status, filter.monthFrom, filter.monthTo, @@ -35,15 +38,17 @@ const feeProvider: HaDataProviderType = { perPage, filter.isMpbs, filter.student_ref - ); + ) + .then(({data: {data: fees}}) => fees!); + }; + + const fees = await doGetFees(); return { - data: fees.map((fee) => { - return { - ...fee, - id: toRaId(fee.student_id as string, fee.id as string), - }; - }), + data: fees.map((fee) => ({ + ...fee, + id: toRaId(fee.student_id!, fee.id!), + })), }; }, diff --git a/src/providers/statsProvider.ts b/src/providers/statsProvider.ts index 50a04c868..7dd9ee543 100644 --- a/src/providers/statsProvider.ts +++ b/src/providers/statsProvider.ts @@ -1,11 +1,13 @@ import {HaDataProviderType} from "./HaDataProviderType"; import {payingApi, usersApi} from "./api"; +import {MAX_ITEM_PER_PAGE} from "./dataProvider"; const statsProvider: HaDataProviderType = { async getList(_page: number, _perPage: number, _filter: any) { throw new Error("Function not implemented."); }, - async getOne(id: string, meta) { + async getOne(id: string, meta = {}) { + const filter = meta.filters ?? {}; switch (meta.resource) { case "users": return usersApi() @@ -13,8 +15,18 @@ const statsProvider: HaDataProviderType = { .then((result) => ({id, ...result.data})); case "fees": return payingApi() - .getFeesStats() - .then((result) => ({id, ...result.data})); + .getFees( + filter.transaction_status, + filter.type, + filter.status, + filter.monthFrom, + filter.monthTo, + 1, + MAX_ITEM_PER_PAGE, + filter.isMpbs, + filter.student_ref + ) + .then(({data: {statistics}}) => ({id, ...statistics})); default: console.error("unknown resource type for getStats"); return; diff --git a/src/ui/haList/HaList.jsx b/src/ui/haList/HaList.jsx index 38193c51f..dc2ff1fe1 100644 --- a/src/ui/haList/HaList.jsx +++ b/src/ui/haList/HaList.jsx @@ -50,6 +50,7 @@ export function HaList({ resource, children, icon, + header = <>, hasDatagrid = true, listProps = {}, datagridProps = {}, @@ -70,6 +71,7 @@ export function HaList({ }} {...listProps} > + {header} { size="small" variant="outlined" value={currentFilter[source] || ""} - sx={{minWidth: isSmall ? "100%" : "350px"}} + sx={{minWidth: isSmall ? "100%" : "350px", my: 1}} fullWidth onChange={(event) => setOneFilter(source, event.target.value)} {...props} From 80905d08c8bdbf712c3d8f86c77b52747f9218e9 Mon Sep 17 00:00:00 2001 From: JeanMarc RAJAONARIVELONA <127597326+JeanMarc-RAJAONARIVELONA@users.noreply.github.com> Date: Tue, 10 Dec 2024 10:49:13 +0300 Subject: [PATCH 6/7] feat: role staff * feat: add staff members list * feat: create staff members profil and show * feat: create list, create, show docs for staff * feat: create staff menu * feat: create staff component * feat: create edit staff component * build: client gen --- src/App.jsx | 21 ++++- .../common/components/ProfileLayout.jsx | 77 ++++++++++++++----- src/operations/common/utils/typo_util.js | 2 + src/operations/docs/staffs/DocShow.tsx | 14 ++++ src/operations/docs/staffs/DockList.tsx | 29 +++++++ src/operations/docs/staffs/index.ts | 11 +++ src/operations/docs/types/OwnerType.ts | 1 + src/operations/profile/ProfileEdit.tsx | 33 ++++++-- src/operations/staffMembers/StaffCreate.tsx | 62 +++++++++++++++ src/operations/staffMembers/StaffList.tsx | 39 ++++++++++ src/operations/staffMembers/StaffShow.tsx | 68 ++++++++++++++++ src/operations/staffMembers/index.tsx | 15 ++++ src/operations/teachers/TeacherCreate.jsx | 14 +++- src/providers/dataProvider.ts | 2 + src/providers/docsProvider.ts | 3 + src/providers/profilePicProvider.ts | 4 + src/providers/profileProvider.ts | 10 ++- src/providers/staffProvider.ts | 39 ++++++++++ src/providers/teacherProvider.ts | 10 ++- src/security/hooks/useRole.js | 1 + src/ui/haLayout/appBar/UserInfo.jsx | 4 +- src/ui/haLayout/menu/HaMenuContent.jsx | 3 + src/ui/haLayout/menu/ManagerMenu.jsx | 7 ++ src/ui/haLayout/menu/StaffMenu.jsx | 18 +++++ 24 files changed, 450 insertions(+), 37 deletions(-) create mode 100644 src/operations/docs/staffs/DocShow.tsx create mode 100644 src/operations/docs/staffs/DockList.tsx create mode 100644 src/operations/docs/staffs/index.ts create mode 100644 src/operations/staffMembers/StaffCreate.tsx create mode 100644 src/operations/staffMembers/StaffList.tsx create mode 100644 src/operations/staffMembers/StaffShow.tsx create mode 100644 src/operations/staffMembers/index.tsx create mode 100644 src/providers/staffProvider.ts create mode 100644 src/ui/haLayout/menu/StaffMenu.jsx diff --git a/src/App.jsx b/src/App.jsx index d9f7813fd..05c16e16a 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -22,13 +22,14 @@ import promotions from "@/operations/promotions/index.tsx"; import course from "@/operations/course/index.tsx"; import awardedCourses from "./operations/awardedCourses"; import events from "@/operations/events"; - import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"; import {LocalizationProvider} from "@mui/x-date-pickers/LocalizationProvider"; import monitors from "@/operations/monitors"; import monitorStudent from "@/operations/monitors/component"; import MonitorStudentList from "@/operations/monitors/component/MonitorStudentList"; import exams from "@/operations/exams"; +import staffDocs from "@/operations/docs/staffs/index"; +import staffMembers from "./operations/staffMembers"; function AppBase() { return ( @@ -48,7 +49,7 @@ function AppBase() { - + @@ -117,6 +118,7 @@ function AppBase() { path="/docs/teachers/OTHER" element={} /> + } /> } /> + } + /> } /> + } + /> } /> + } + /> { ); }; -const PersonalInfos = ({isStudentProfile}) => { +const PersonalInfos = ({isStudentProfile, isStaffMember}) => { const isSmall = useMediaQuery("(max-width:900px)"); - + const role = useRole(); + const isStaffMemberProfile = isStaffMember || role.isStaffMember(); return ( { /> )} + {isStaffMemberProfile && ( + + } source="cnaps" /> + } source="ostie" /> + } + source="function" + /> + } + source="degree" + /> + } + render={(user) => } + /> + + )} ); @@ -399,6 +426,7 @@ export const ProfileLayout = ({ isStudentProfile = false, isMonitorProfile = false, isAdminProfil = false, + isStaffProfil = false, }) => { const {record: profile = {}} = useShowContext(); const isLarge = useMediaQuery("(min-width:1700px)"); @@ -481,6 +509,7 @@ export const ProfileLayout = ({ isStudentProfile={isStudentProfile} isTeacherProfile={isTeacherProfile} isMonitorProfile={isMonitorProfile} + isStaffProfil={isStaffProfil} /> ); @@ -490,6 +519,7 @@ export const Informations = ({ isStudentProfile, isTeacherProfile, isMonitorProfile, + isStaffProfil, }) => { const isSmall = useMediaQuery("(max-width:900px)"); const profile = useRecordContext(); @@ -531,6 +561,14 @@ export const Informations = ({ ); } + const adminView = + !role.isMonitor() && + !isMonitorProfile && + !(role.isManager() && isTeacherProfile) && + !(role.isTeacher() && isStudentProfile) && + !isAdminProfil && + !isManagerProfil; + return ( } @@ -558,7 +596,10 @@ export const Informations = ({ - + {isStudentProfile && ( @@ -578,26 +619,22 @@ export const Informations = ({ )} - {!role.isMonitor() && - !isMonitorProfile && - !(role.isManager() && isTeacherProfile) && - !(role.isTeacher() && isStudentProfile) && - !isAdminProfil && - !isManagerProfil && ( - - - - )} + {(adminView || (role.isAdmin() && isStaffProfil)) && ( + + + + )} {!isMonitorProfile && !isStudentProfile && !isTeacherProfile && + !isStaffProfil && (role.isAdmin() || role.isManager()) && ( { + const params = useParams(); + const {isStaffMember} = useRole(); + const id = isStaffMember() + ? authProvider.getCachedWhoami().id + : params.userId; + + return ; +}; diff --git a/src/operations/docs/staffs/DockList.tsx b/src/operations/docs/staffs/DockList.tsx new file mode 100644 index 000000000..5b0fcc214 --- /dev/null +++ b/src/operations/docs/staffs/DockList.tsx @@ -0,0 +1,29 @@ +import {useParams, useLocation} from "react-router-dom"; +import {useRole} from "@/security/hooks"; +import {DocList, DocListAction} from "../components/DocList"; +import authProvider from "@/providers/authProvider"; + +export const StaffDocList = () => { + const params = useParams(); + const location = useLocation(); + const {isStaffMember, isAdmin} = useRole(); + const userId = isStaffMember() + ? authProvider.getCachedWhoami().id + : params.userId; + return ( + + ) : null, + }} + title="Liste des documents des membres de staff" + datagridProps={{ + rowClick: (id) => `${location.pathname}/${id}`, + }} + /> + ); +}; diff --git a/src/operations/docs/staffs/index.ts b/src/operations/docs/staffs/index.ts new file mode 100644 index 000000000..935c55d68 --- /dev/null +++ b/src/operations/docs/staffs/index.ts @@ -0,0 +1,11 @@ +import {DocCreateDialog} from "../components/DocCreateDialog"; +import {StaffDocList} from "./DockList"; +import {StaffDocShow} from "./DocShow"; + +const staffDocs = { + list: StaffDocList, + show: StaffDocShow, + create: DocCreateDialog, +}; + +export default staffDocs; diff --git a/src/operations/docs/types/OwnerType.ts b/src/operations/docs/types/OwnerType.ts index 3a4eeec42..c438d0531 100644 --- a/src/operations/docs/types/OwnerType.ts +++ b/src/operations/docs/types/OwnerType.ts @@ -2,4 +2,5 @@ export const OwnerType = { SCHOOL: "SCHOOL", STUDENT: "STUDENT", TEACHER: "TEACHER", + STAFF_MEMBER: "STAFF_MEMBER", } as const; diff --git a/src/operations/profile/ProfileEdit.tsx b/src/operations/profile/ProfileEdit.tsx index 885c3b6f6..00b85f91c 100644 --- a/src/operations/profile/ProfileEdit.tsx +++ b/src/operations/profile/ProfileEdit.tsx @@ -5,31 +5,38 @@ import {StatusRadioButton} from "../utils/UserStatusRadioButton"; import {SelectSpecialization} from "../students/components"; import {EditGeoLocalisation, Edit} from "../common/components"; import {User} from "@/providers/types"; -import {Student} from "@haapi/typescript-client"; +import {StaffMember, Student} from "@haapi/typescript-client"; import {useRole} from "../../security/hooks"; import {toUTC} from "../../utils/date"; const userToUserApi = ({ birth_date, entrance_datetime, + ending_service, coordinates = {}, ...data -}: User & Required["coordinates"]) => { +}: User & Required["coordinates"] & Required) => { const {latitude, longitude} = coordinates; return { ...data, birth_date: toUTC(new Date(birth_date!)).toISOString(), entrance_datetime: toUTC(new Date(entrance_datetime!)).toISOString(), + ending_service: ending_service + ? toUTC(new Date(ending_service!)).toISOString() + : null, coordinates: {latitude: +latitude!, longitude: +longitude!}, }; }; -const ProfileEdit: FC<{isOwnProfile: boolean; isStudent: boolean}> = ({ - isOwnProfile, - isStudent, -}) => { +const ProfileEdit: FC<{ + isOwnProfile: boolean; + isStudent: boolean; + isStaff: boolean; +}> = ({isOwnProfile, isStudent, isStaff = false}) => { const role = useRole(); const isStudentProfile = isStudent || role.isStudent(); + const isStaffProfil = isStaff || role.isStaffMember(); + return ( = ({ fullWidth readOnly={isOwnProfile} /> + {isStaffProfil && ( + <> + + + + + + + )} diff --git a/src/operations/staffMembers/StaffCreate.tsx b/src/operations/staffMembers/StaffCreate.tsx new file mode 100644 index 000000000..520f44f02 --- /dev/null +++ b/src/operations/staffMembers/StaffCreate.tsx @@ -0,0 +1,62 @@ +import {DateInput, maxLength, SimpleForm, TextInput} from "react-admin"; +import {Box} from "@mui/material"; +import {Create, CreateGeoLocalisation} from "../common/components"; +import {SexRadioButton} from "../utils"; +import {toISO} from "@/utils/date"; +import {StaffMember} from "@haapi/typescript-client"; + +const transformStaff = (staff: StaffMember) => { + const {entrance_datetime, ending_service, coordinates, ...restStaff} = staff; + + return { + ...restStaff, + ending_service: ending_service ? toISO(ending_service) : null, + status: "ENABLED", + entrance_datetime: toISO(entrance_datetime), + coordinates: { + longitude: coordinates?.longitude ? +coordinates.longitude : null, + latitude: coordinates?.latitude ? +coordinates.latitude : null, + }, + }; +}; + +export const StaffCreate = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/src/operations/staffMembers/StaffList.tsx b/src/operations/staffMembers/StaffList.tsx new file mode 100644 index 000000000..86f4de3de --- /dev/null +++ b/src/operations/staffMembers/StaffList.tsx @@ -0,0 +1,39 @@ +import {FC} from "react"; +import {EditButton, TextField} from "react-admin"; +import {Box} from "@mui/material"; +import {AssignmentInd} from "@mui/icons-material"; +import {HaList} from "@/ui/haList"; +; +import {useRole} from "@/security/hooks"; +import {PALETTE_COLORS} from "@/haTheme"; +import {CreateButton, ExportButton} from "@/ui/haToolbar"; +import {ProfileFilters} from "../profile/components/ProfileFilters"; + +const StaffList: FC = () => { + const {isAdmin} = useRole(); + + return ( + + + + + + + } + resource="staffmembers" + icon={} + > + + + + + {isAdmin() && } + + + ); +}; + +export default StaffList; diff --git a/src/operations/staffMembers/StaffShow.tsx b/src/operations/staffMembers/StaffShow.tsx new file mode 100644 index 000000000..c41251cfd --- /dev/null +++ b/src/operations/staffMembers/StaffShow.tsx @@ -0,0 +1,68 @@ +import {FC} from "react"; +import {Button, EditButton, useRecordContext, useRedirect} from "react-admin"; +import {Box} from "@mui/material"; +import {Edit as EditIcon, Inventory} from "@mui/icons-material"; +import {Show} from "@/operations/common/components/Show"; +import {ProfileLayout} from "../common/components/ProfileLayout"; +import {WhoamiRoleEnum} from "@haapi/typescript-client"; +import {PALETTE_COLORS} from "@/haTheme"; + +export const ActionsOnShow: FC = () => { + const {id} = useRecordContext(); + const navigate = useRedirect(); + + return ( + + } + data-testid="profile-edit-button" + variant="outlined" + size="medium" + sx={{ + display: "flex", + justifyContent: "flex-start", + gap: "0.2rem", + backgroundColor: PALETTE_COLORS.white, + color: PALETTE_COLORS.primary, + padding: "0.5rem 1.5rem", + borderRadius: "0.4rem", + width: "100%", + }} + /> +