diff --git a/CLAUDE.md b/CLAUDE.md index be40938..b34dab0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -124,7 +124,7 @@ Import direction: `API → Business → Data`. Never skip layers. Cross-cutting ## Working Principles -1. **Check docs first**: Read relevant docs before starting (see Documentation Entry Points below) +1. **Check docs first**: Read relevant docs before starting (see Documentation Entry Points below). **Before any new model/column or DB manipulation (migration/seed), consult `backend/docs/data-model-guide.md`** — reuse/wire an existing slice or reserved table instead of duplicating. 2. **Minimal changes**: Only update necessary files, prefer simple robust solutions 3. **TypeScript strict mode**: No `any` types, never disable linter/TypeScript, no type casting 4. **Concise comments**: Explain "why" not "what" @@ -139,7 +139,8 @@ Import direction: `API → Business → Data`. Never skip layers. Cross-cutting - Frontend architecture: `frontend/docs/frontend-architecture.md` - Backend architecture: `backend/docs/backend-architecture.md` -- Database schema: `backend/docs/database-schema.md` (regenerate after schema changes) +- **Data model guide: `backend/docs/data-model-guide.md`** — model purpose, status, and reuse guidance. **Consult before creating any new model/column or doing DB work** (migration/seed) so existing built slices or reserved SIS tables are wired/reused instead of duplicated. +- Database schema (column reference): `backend/docs/database-schema.md` (regenerate after schema changes) - **Backlog / remaining work: `docs/backlog.md`** — the single source for deferred work and open gaps (endpoint wiring, RBAC residuals, design-gated UIs, Phase 4/5 items, file/test/a11y). Consult it before starting work or closing gaps, and keep it updated. (The former sequenced integration plan is retired; its history is in git.) - VM deployment: `docs/deployment-vm.md` - Docker deployment: `docs/deployment-docker.md` diff --git a/backend/docs/data-model-guide.md b/backend/docs/data-model-guide.md new file mode 100644 index 0000000..63889fb --- /dev/null +++ b/backend/docs/data-model-guide.md @@ -0,0 +1,82 @@ +# Data Model Guide — purpose, status & reuse + +> **Read this before creating a new model, adding a column, or any DB +> manipulation (migration/seed).** Its job is to prevent reinventing tables that +> already exist. Many "new feature" needs are already covered by a built slice or +> by a **reserved** generated table that should be *wired*, not duplicated. +> Column-level detail lives in [`database-schema.md`](database-schema.md); this +> file is the **semantic / decision** layer. + +## Before you add a model — checklist + +1. Does a **built product slice** already cover it? (see "Built slices" below) → reuse it. +2. Does a **reserved SIS table** already model it? (see "Reserved SIS cluster") → wire it, don't recreate. +3. Is the data **per-user self-state**? → it likely belongs in `user_progress` (e.g. the zone check-in reuses it), not a new table. +4. Is it **per-campus configuration / aggregate**? → look at `content_catalog` (global content) or the `campus_*` tables. +5. Only if none fit: add a model, following the per-slice template, and **update `database-schema.md` + this guide**. + +Also: every tenant-owned table carries `organizationId` (+ optional `campusId`) and is scoped in `db/api` via `findOwnedByPk`/`tenantWhere`; most are `paranoid` (soft delete). New tables must follow the same conventions. + +## Built slices — already implemented (do NOT reinvent these) + +| Need | Use | Notes | +|---|---|---| +| Staff acknowledge a policy/safety doc | **`policy_documents` + `policy_acknowledgments`** | per `userId × documentId × version`, idempotent. **Do not** repurpose `assessments` for acknowledgment. | +| Classroom-timer sounds (file/url/recipe) | **`audio_files`** | `kind` discriminator; recipe = synth params. | +| Campus daily attendance (working `/attendance`) | **`campus_attendance_config` + `campus_attendance_summaries`** | manual **aggregate** entry by `office_manager` (`FILL_ATTENDANCE`). NOT per-student rows. | +| Staff attendance | **`staff_attendance_records`** | the staff-attendance slice. | +| Weekly F.R.A.M.E. entry | **`frame_entries`** | `week_of` is the canonical Sunday-start ISO (`shared/constants/week.ts`). | +| Per-user progress / daily self-state | **`user_progress`** | `progress_type` + `item_id` + `value`. Backs sign-learned **and** the daily zone check-in (`item_id` = campus-local date). | +| Backend-owned editable content | **`content_catalog`** | global JSONB payloads by `content_type`. | +| File upload/download | the **file subsystem** + `file` table | see `file.md`; downloads enforce per-file ownership. | + +## Reserved SIS cluster — kept but **not yet wired** + +These generated tables have **no product UI yet**. They are retained for future +development (owner-approved). When a feature needs one, **wire the existing +table** (build the service/route/UI) — do not create a parallel model. They form +a coherent academic/SIS graph: + +### Academic structure + +- **`academic_years`** — the school year (`name`, `start_date`, `end_date`, `current`). Anchors `classes` and `timetables` to a period. *Plug-in:* set/seed years, mark one `current`. +- **`grades`** — grade **levels** (Grade 1, K…: `name`, `code`, `sort_order`). NOT marks. A `class` belongs to a grade. +- **`subjects`** — the reusable **subject catalog** (Math, English: `name`, `code`, `description`). +- **`class_subjects`** — a **subject taught in a class by a teacher** (`classId` + `subjectId` + `teacherId`). The many-to-many junction class↔subject; `assessments`, `attendance_sessions`, and `timetable_periods` hang off it. (So `subjects` = "what"; `class_subjects` = "this offering".) +- **`classes`** — a class/group (`name`, `section`, `capacity`, `grade`, `homeroom_teacher`→`staff`, `academic_year`, `campus`). The grouping unit relating teachers (via `class_subjects`), students (via `class_enrollments`), and guardians (via their student). +- **`class_enrollments`** — **student↔class membership** (`classId` + `studentId`, `enrolled_on`, `ended_on`, `status`). + +### Assessments (header/detail pair) + +- **`assessments`** — the **definition** of a task/exam (`name`, `assessment_type`, `max_score`, `due_at`, `instructions`), belongs to a `class_subject`. +- **`assessment_results`** — a **student's outcome** (`score`, `grade_letter`, `remarks`) per `assessment` + `studentId`. +- Two tables = one-to-many (one assessment → many student results). **Academic grading only** — not a general "acknowledgment" store (use `policy_acknowledgments`). + +### Attendance (header/detail pair — student-level) + +- **`attendance_sessions`** — one roll-call **event** for a class (`session_date`, `session_type`, `taken_by`→`staff`, `class`/`class_subject`). +- **`attendance_records`** — a **student's status** in a session (`status` present/absent/late, `minutes_late`) per `studentId`. +- Two tables = one session → many student rows. **This is the per-student model and is currently unwired.** The working `/attendance` page uses the **aggregate** `campus_attendance_*` instead, and staff use `staff_attendance_records`. If per-student attendance is needed, wire these; do not fold student + staff + aggregate into one model without a deliberate decision. + +### Scheduling (header/detail pair) + +- **`timetables`** — a schedule **version** for a campus/year (`effective_from`, `effective_to`, `status`). +- **`timetable_periods`** — individual **slots** (`day_of_week`, `starts_at`, `ends_at`, `room`) for a `class_subject` within a timetable. + +### People + +- **`staff`** — the **employment/HR profile** of a user (`employee_number`, `job_title`, `staff_type`, `hire_date`, `status`, `campus`), linked by `userId`. Distinct from `users` (the **account/identity**: login, email, role, password). One user ↔ one staff profile; students/guardians are users **without** a staff record. Already used by the staff-management and staff-attendance slices. + +## Pruned — do NOT re-add + +`students`, `guardians`, `fee_plans`, `invoices`, `payments`, and the old generic +`documents` entity were **removed** (this is not a SIS; students/guardians are +**roles** in `roles`/`users`, the finance cluster was unused, and the handbook +moved to `policy_documents`). Don't recreate them — see `docs/backlog.md`. + +## Header/detail rationale + +`assessments`/`results`, `attendance_sessions`/`records`, and +`timetables`/`periods` are all the same shape: a **parent** describes the event +and **children** hold per-subject rows. Keep that split when wiring — it is what +makes grouping, reporting, and scoping clean. diff --git a/backend/docs/file.md b/backend/docs/file.md index 2253429..a3266cc 100644 --- a/backend/docs/file.md +++ b/backend/docs/file.md @@ -7,6 +7,23 @@ local disk (development) or in Google Cloud Storage (production), selected at re `file` table records file metadata (name, URLs, owning relation) and is written indirectly by the file DAL when entity relations are persisted, not by the upload endpoint itself. +## Upload client contract + +When a typed frontend upload client is built, it follows the same multipart +contract the reference frontend used (preserved here so it isn't lost): + +1. Generate a unique filename: `${uuid}.${ext}` (extension from the picked file). +2. `POST /api/file/upload/:table/:field` as `multipart/form-data` with fields + **`file`** (the binary) and **`filename`** (the generated name). +3. The stored `privateUrl` is `${table}/${field}/${filename}`; the playable/ + download URL is `${API_BASE_URL}/file/download?privateUrl=` (works + for both the local-disk dev backend and the GCloud prod backend). + +**Open blocker:** `assertCanDownloadFile` denies any `privateUrl` with no tracked +`file` row, but the standalone `/file/upload/:table/:field` path does not create +one — so a non-global user would 403 on download until the upload flow also +records a `file` row (or the path is exempted). See `audio-files.md`. + ## Slice Files (by layer) - Route: `src/routes/file.ts` (thin wiring; `GET /download`, `POST /upload/:table/:field`). diff --git a/backend/docs/index.md b/backend/docs/index.md index 5dea5e2..0748e9d 100644 --- a/backend/docs/index.md +++ b/backend/docs/index.md @@ -19,6 +19,7 @@ Tenant Scope / Data Contract / Behavior / Tests / Related). - [`shared-crud-factories.md`](shared-crud-factories.md): the CRUD service/controller/router factories, repository helpers, and shared service helpers every generic-CRUD slice is built from. - [`database-schema.md`](database-schema.md): generated table/column/relation reference. +- [`data-model-guide.md`](data-model-guide.md): **read before adding any model/column or DB change** — purpose, status, and reuse guidance (which slice/reserved table already covers a need, so you wire instead of duplicating). - [`migrations-and-seeders.md`](migrations-and-seeders.md): the Umzug runner, file conventions, and how to author a migration/seeder. - [`error-handling.md`](error-handling.md): centralized `AppError` pipeline and error body shape. diff --git a/backend/src/shared/constants/pagination.ts b/backend/src/shared/constants/pagination.ts index 728ef1d..48abc3a 100644 --- a/backend/src/shared/constants/pagination.ts +++ b/backend/src/shared/constants/pagination.ts @@ -1,4 +1,4 @@ -/** Shared list pagination defaults (page size matches the ref-frontend grids). */ +/** Shared list pagination defaults (default grid page size). */ export const DEFAULT_PAGE_SIZE = 10; export const MAX_PAGE_SIZE = 100; diff --git a/docs/backlog.md b/docs/backlog.md index 4f52b1b..46586c5 100644 --- a/docs/backlog.md +++ b/docs/backlog.md @@ -7,7 +7,6 @@ Persistent list of deferred work and known gaps so they are not forgotten. **Thi - ⛔ **Design-gated (need a customer design decision):** the generic-CRUD management UIs (`users`/`roles`/`permissions` + the other groups), the roles/permissions admin UI, applying `` to specific create/edit/delete affordances, the `MANAGE_*` permissions that depend on it, and the director-creates-classrooms UI (needs a first-class `classrooms` entity, which the backend can build independently). - **Unblocked, backend-only:** the self-editable-vs-privileged profile-field split; the `classrooms` entity backend; the manager acknowledgment-status report (pending an audience decision); the binary `file` audio-upload path (needs the file-download ownership fix); AI sound generation (swap the `generateSoundRecipe` stub). - **Dev-machine runs / verification:** `npm install` (sync the OAuth dependency change), `npm run db:reset` (apply the Phase 4 migrations), `npm test`, `npm run test:e2e:content` (incl. the accessibility suite — zero WCAG 2/2.1 A/AA violations across 19 pages), `npm run lint`. -- **Last:** delete `ref-frontend/` once the generic-CRUD UIs (it is their reference) are built. ## Endpoint wiring @@ -44,6 +43,3 @@ Files: Phase 4 product UIs: - **Audio library — remaining:** AI sound generation (swap the `generateSoundRecipe` stub for a real model call); the binary `file` upload UI — needs a typed upload client **and** the download-ownership fix (`assertCanDownloadFile` denies any `privateUrl` with no tracked `file` row, but the standalone `/file/upload/:table/:field` path doesn't create one; `recipe`/`url` rows are unaffected). - **Manager acknowledgment-status report** — backend addition pending the report-audience decision. - -Phase 5 — operations & cleanup: -- **`ref-frontend/` removal** — last; after the generic-CRUD UIs are built (it is their reference). diff --git a/docs/dependency-baseline.md b/docs/dependency-baseline.md deleted file mode 100644 index 19d56ec..0000000 --- a/docs/dependency-baseline.md +++ /dev/null @@ -1,69 +0,0 @@ -# Dependency Baseline - -## Purpose - -This document records the active dependency baseline for the project after upgrading runtime and tooling packages. - -## Active Applications - -The active applications are: - -- `frontend/` -- `backend/` - -Both active applications use npm lockfiles: - -- `frontend/package-lock.json` -- `backend/package-lock.json` - -The root production scripts use npm commands. Do not add Yarn lockfiles back to the active apps unless the package-manager decision is explicitly changed. - -## Frontend Baseline - -The frontend dependency baseline has been updated to current stable npm versions for the active Vite app. - -Key tooling/runtime updates: - -- React 19 -- Vite 8 -- TypeScript 6 -- Tailwind 4 with `@tailwindcss/postcss` -- Vitest 4 -- ESLint 10 -- `@vitejs/plugin-react` -- Playwright for frontend smoke tests - -Verification: - -- `npm run lint` passes. -- `npm run test` passes. -- `npm run test:e2e` passes when a local browser install is available. -- `npm run build` passes and runs typecheck before Vite. -- `npm audit --audit-level=low` reports 0 vulnerabilities. -- `npm outdated` reports no outdated stable dependencies. - -## Backend Baseline - -The backend dependency baseline has been updated to current stable npm versions for the active Express app. - -Key tooling/runtime updates: - -- Express 5 -- bcrypt 6 -- helmet 8 -- jsonwebtoken 9 -- Sequelize 6.37 -- ESLint 10 flat config -- `eslint-plugin-import-x` for unresolved import checks with ESLint 10 - -The backend uses an npm `overrides` entry for `uuid` so transitive dependency trees resolve to the patched stable line. - -Verification: - -- `npm audit --audit-level=low` reports 0 vulnerabilities. -- `npm outdated` reports only `json2csv@6.0.0-alpha.2` above the installed stable `5.0.7`; prerelease packages are not part of the stable baseline. -- `npm run lint` still fails on existing generated/template code debt. The ESLint 10 `.eslintignore` warning is resolved, and the remaining lint failures should be fixed as backend cleanup instead of hidden with broad ignores. - -## Reference Frontend - -`ref-frontend/` is a temporary reference artifact, not the active runtime frontend. Keep it frozen until integration work no longer needs it, then delete it. diff --git a/frontend/README.md b/frontend/README.md index 7d857d3..3e21fb5 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -2,8 +2,6 @@ This is the customer-approved product frontend that will be developed going forward. -The generated Flatlogic/Next.js frontend has been moved to `../ref-frontend/` and should be used only as a temporary reference for backend API contracts, authentication flow, generated CRUD behavior, roles, and permissions. - ## Stack - React diff --git a/ref-frontend/.eslintignore b/ref-frontend/.eslintignore deleted file mode 100644 index f4ef79d..0000000 --- a/ref-frontend/.eslintignore +++ /dev/null @@ -1,3 +0,0 @@ -# Ignore generated files -build/* -next-env.d.ts diff --git a/ref-frontend/.eslintrc.cjs b/ref-frontend/.eslintrc.cjs deleted file mode 100644 index 590e61f..0000000 --- a/ref-frontend/.eslintrc.cjs +++ /dev/null @@ -1,25 +0,0 @@ -module.exports = { - extends: [ - 'next/core-web-vitals', - 'plugin:@typescript-eslint/recommended', - 'eslint-config-prettier', - ], - plugins: ['import'], - settings: { - 'import/resolver': { - typescript: { - project: ['./tsconfig.json'], - }, - }, - }, - rules: { - 'react/no-children-prop': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-unused-vars': 'off', // Turned off to reduce noise - '@typescript-eslint/ban-types': 'off', - 'react-hooks/exhaustive-deps': 'off', // Turned off to reduce noise - 'import/named': 'error', - 'import/no-duplicates': 'error', - 'import/no-unresolved': 'error', - }, -}; diff --git a/ref-frontend/.gitignore b/ref-frontend/.gitignore deleted file mode 100644 index fdc0491..0000000 --- a/ref-frontend/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local.js env files -.env.local -.env.development.local -.env.test.local -.env.production.local - -# vercel -.vercel -/.idea/ diff --git a/ref-frontend/.prettierrc b/ref-frontend/.prettierrc deleted file mode 100644 index cedf9c7..0000000 --- a/ref-frontend/.prettierrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "singleQuote": true, - "tabWidth": 2, - "trailingComma": "all", - "quoteProps": "as-needed", - "jsxSingleQuote": true, - "bracketSpacing": true, - "bracketSameLine": false, - "arrowParens": "always" -} diff --git a/ref-frontend/Dockerfile b/ref-frontend/Dockerfile deleted file mode 100644 index 56e10d0..0000000 --- a/ref-frontend/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM node:20.15.1-alpine - -# Create app directory -WORKDIR /usr/src/app - -# Install app dependencies -# A wildcard is used to ensure both package.json AND package-lock.json are copied -# where available (npm@5+) -COPY package*.json ./ - -RUN yarn install -# If you are building your code for production -# RUN npm ci --only=production - -# Bundle app source -COPY . . - -EXPOSE 3000 -CMD [ "yarn", "dev" ] \ No newline at end of file diff --git a/ref-frontend/LICENSE-justboil b/ref-frontend/LICENSE-justboil deleted file mode 100644 index 798238d..0000000 --- a/ref-frontend/LICENSE-justboil +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2019-current JustBoil.me (https://justboil.me) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/ref-frontend/README.md b/ref-frontend/README.md deleted file mode 100644 index 66c4e8c..0000000 --- a/ref-frontend/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# School Chain Manager - -## This project was generated by Flatlogic Platform. -## Install - -`cd` to project's dir and run `npm install` - -### Builds - -Build are handled by Next.js CLI — [Info](https://nextjs.org/docs/api-reference/cli) - -### Hot-reloads for development - -``` -npm run dev -``` - -### Builds and minifies for production - -``` -npm run build -``` - -### Exports build for static hosts - -``` -npm run export -``` - -### Lint - -``` -npm run lint -``` - -### Format with prettier - -``` -npm run format -``` - -## Support -For any additional information please refer to [Flatlogic homepage](https://flatlogic.com). - - -## To start the project with Docker: -### Description: - -The project contains the **docker folder** and the `Dockerfile`. - -The `Dockerfile` is used to Deploy the project to Google Cloud. - -The **docker folder** contains a couple of helper scripts: - -- `docker-compose.yml` (all our services: web, backend, db are described here) -- `start-backend.sh` (starts backend, but only after the database) -- `wait-for-it.sh` (imported from https://github.com/vishnubob/wait-for-it) - - > To avoid breaking the application, we recommend you don't edit the following files: everything that includes the **docker folder** and `Dokerfile`. - - - -### Run services: - -1. Install docker compose (https://docs.docker.com/compose/install/) - -2. Move to `docker` folder. All next steps should be done from this folder. - - ``` cd docker ``` - -3. Make executables from `wait-for-it.sh` and `start-backend.sh`: - - ``` chmod +x start-backend.sh && chmod +x wait-for-it.sh ``` - -4. Download dependend projects for services. - -5. Review the docker-compose.yml file. Make sure that all services have Dockerfiles. Only db service doesn't require a Dockerfile. - -6. Make sure you have needed ports (see them in `ports`) available on your local machine. - -7. Start services: - - 7.1. With an empty database `rm -rf data && docker-compose up` - - 7.2. With a stored (from previus runs) database data `docker-compose up` - -8. Check http://localhost:3000 - -9. Stop services: - - 9.1. Just press `Ctr+C` - diff --git a/ref-frontend/next-env.d.ts b/ref-frontend/next-env.d.ts deleted file mode 100644 index 00a8785..0000000 --- a/ref-frontend/next-env.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -/// -/// -/// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. diff --git a/ref-frontend/next.config.mjs b/ref-frontend/next.config.mjs deleted file mode 100644 index 89767ec..0000000 --- a/ref-frontend/next.config.mjs +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @type {import('next').NextConfig} - */ - -const output = process.env.NODE_ENV === 'production' ? 'export' : 'standalone'; - const nextConfig = { -trailingSlash: true, - distDir: 'build', - output, - basePath: "", - devIndicators: { - position: 'bottom-left', - }, - typescript: { - ignoreBuildErrors: true, - }, - eslint: { - ignoreDuringBuilds: true, - }, - images: { - unoptimized: true, - remotePatterns: [ - { - protocol: 'https', - hostname: '**', - }, - ], - }, - -} - -export default nextConfig \ No newline at end of file diff --git a/ref-frontend/package.json b/ref-frontend/package.json deleted file mode 100644 index 7eb7de1..0000000 --- a/ref-frontend/package.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "private": true, - "scripts": { - "dev": "cross-env PORT=${FRONT_PORT:-3000} next dev --turbopack", - "build": "next build", - "start": "next start", - "lint": "eslint . --ext .ts,.tsx", - "format": "prettier '{components,pages,src,interfaces,hooks}/**/*.{tsx,ts,js}' --write" - }, - "dependencies": { - "@emotion/react": "^11.11.3", - "@emotion/styled": "^11.11.0", - "@mdi/js": "^7.4.47", - "@mui/material": "^6.3.0", - "@mui/x-data-grid": "^6.19.2", - "@reduxjs/toolkit": "^2.1.0", - "@tailwindcss/typography": "^0.5.13", - "@tinymce/tinymce-react": "^4.3.2", - "apexcharts": "^3.45.2", - "axios": "^1.8.4", - "chart.js": "^4.4.1", - "chroma-js": "^2.4.2", - "dayjs": "^1.11.10", - "file-saver": "^2.0.5", - "formik": "^2.4.5", - "html2canvas": "^1.4.1", - "i18next": "^25.1.2", - "i18next-browser-languagedetector": "^8.1.0", - "i18next-http-backend": "^3.0.2", - "intro.js": "^7.2.0", - "intro.js-react": "^1.0.0", - "jsonwebtoken": "^9.0.2", - "jwt-decode": "^3.1.2", - "lodash": "^4.17.21", - "moment": "^2.30.1", - "next": "15.5.12", - "next-i18next": "15.4.3", - "numeral": "^2.0.6", - "query-string": "^8.1.0", - "react": "19.0.0", - "react-apexcharts": "^1.4.1", - "react-big-calendar": "^1.10.3", - "react-chartjs-2": "^4.3.1", - "react-datepicker": "^4.10.0", - "react-dnd": "^16.0.1", - "react-dnd-html5-backend": "^16.0.1", - "react-dom": "19.0.0", - "react-i18next": "^15.5.1", - "react-redux": "^8.0.2", - "react-select": "^5.7.0", - "react-select-async-paginate": "^0.7.9", - "react-switch": "^7.0.0", - "react-toastify": "^11.0.2", - "swr": "1.3.0", - "uuid": "^9.0.0" - }, - "devDependencies": { - "@tailwindcss/forms": "^0.5.7", - "@tailwindcss/line-clamp": "^0.4.4", - "@types/node": "18.7.16", - "@types/numeral": "^2.0.2", - "@types/react-big-calendar": "^1.8.8", - "@types/react-redux": "^7.1.24", - "@typescript-eslint/eslint-plugin": "^5.37.0", - "@typescript-eslint/parser": "^5.37.0", - "autoprefixer": "^10.4.0", - "cross-env": "^7.0.3", - "eslint": "^8.23.1", - "eslint-config-next": "^13.0.4", - "eslint-config-prettier": "^8.5.0", - "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-import": "^2.29.1", - "postcss": "^8.4.4", - "postcss-import": "^14.1.0", - "prettier": "^3.2.4", - "tailwindcss": "^3.4.1", - "typescript": "^5.4.5" - } -} diff --git a/ref-frontend/postcss.config.js b/ref-frontend/postcss.config.js deleted file mode 100644 index 36f40f3..0000000 --- a/ref-frontend/postcss.config.js +++ /dev/null @@ -1,9 +0,0 @@ -/* eslint-env node */ -module.exports = { - plugins: { - 'postcss-import': {}, - 'tailwindcss/nesting': {}, - tailwindcss: {}, - autoprefixer: {}, - } -} \ No newline at end of file diff --git a/ref-frontend/prettier.config.js b/ref-frontend/prettier.config.js deleted file mode 100644 index ecde36c..0000000 --- a/ref-frontend/prettier.config.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = { - semi: false, - singleQuote: true, - printWidth: 100, - trailingComma: 'es5', - arrowParens: 'always', - tabWidth: 2, - useTabs: false, - quoteProps: 'as-needed', - jsxSingleQuote: false, - bracketSpacing: true, - bracketSameLine: false, -} \ No newline at end of file diff --git a/ref-frontend/public/data-sources/clients.json b/ref-frontend/public/data-sources/clients.json deleted file mode 100644 index 297ec7b..0000000 --- a/ref-frontend/public/data-sources/clients.json +++ /dev/null @@ -1 +0,0 @@ -{"data":[{"id":19,"avatar":"https:\/\/avatars.dicebear.com\/v2\/gridy\/Howell-Hand.svg","login":"percy64","name":"Howell Hand","company":"Kiehn-Green","city":"Emelyside","progress":70,"created":"Mar 3, 2022","created_mm_dd_yyyy":"03-03-2022"},{"id":11,"avatar":"https:\/\/avatars.dicebear.com\/v2\/gridy\/Hope-Howe.svg","login":"dare.concepcion","name":"Hope Howe","company":"Nolan Inc","city":"Paristown","progress":68,"created":"Dec 1, 2022","created_mm_dd_yyyy":"12-01-2022"},{"id":32,"avatar":"https:\/\/avatars.dicebear.com\/v2\/gridy\/Nelson-Jerde.svg","login":"geovanni.kessler","name":"Nelson Jerde","company":"Nitzsche LLC","city":"Jailynbury","progress":49,"created":"May 18, 2022","created_mm_dd_yyyy":"05-18-2022"},{"id":22,"avatar":"https:\/\/avatars.dicebear.com\/v2\/gridy\/Kim-Weimann.svg","login":"macejkovic.dashawn","name":"Kim Weimann","company":"Brown-Lueilwitz","city":"New Emie","progress":38,"created":"May 4, 2022","created_mm_dd_yyyy":"05-04-2022"},{"id":34,"avatar":"https:\/\/avatars.dicebear.com\/v2\/gridy\/Justice-OReilly.svg","login":"hilpert.leora","name":"Justice O'Reilly","company":"Lakin-Muller","city":"New Kacie","progress":38,"created":"Mar 27, 2022","created_mm_dd_yyyy":"03-27-2022"},{"id":48,"avatar":"https:\/\/avatars.dicebear.com\/v2\/gridy\/Adrienne-Mayer-III.svg","login":"ferry.sophia","name":"Adrienne Mayer III","company":"Kozey, McLaughlin and Kuhn","city":"Howardbury","progress":39,"created":"Mar 29, 2022","created_mm_dd_yyyy":"03-29-2022"},{"id":20,"avatar":"https:\/\/avatars.dicebear.com\/v2\/gridy\/Mr.-Julien-Ebert.svg","login":"gokuneva","name":"Mr. Julien Ebert","company":"Cormier LLC","city":"South Serenaburgh","progress":29,"created":"Jun 25, 2022","created_mm_dd_yyyy":"06-25-2022"},{"id":47,"avatar":"https:\/\/avatars.dicebear.com\/v2\/gridy\/Lenna-Smitham.svg","login":"paolo.walter","name":"Lenna Smitham","company":"King Inc","city":"McCulloughfort","progress":59,"created":"Oct 8, 2022","created_mm_dd_yyyy":"10-08-2022"},{"id":24,"avatar":"https:\/\/avatars.dicebear.com\/v2\/gridy\/Travis-Davis.svg","login":"lkessler","name":"Travis Davis","company":"Leannon and Sons","city":"West Frankton","progress":52,"created":"Oct 20, 2022","created_mm_dd_yyyy":"10-20-2022"},{"id":49,"avatar":"https:\/\/avatars.dicebear.com\/v2\/gridy\/Prof.-Esteban-Steuber.svg","login":"shana.lang","name":"Prof. Esteban Steuber","company":"Langosh-Ernser","city":"East Sedrick","progress":34,"created":"May 16, 2022","created_mm_dd_yyyy":"05-16-2022"},{"id":36,"avatar":"https:\/\/avatars.dicebear.com\/v2\/gridy\/Russell-Goodwin-V.svg","login":"jewel07","name":"Russell Goodwin V","company":"Nolan-Stracke","city":"Williamsonmouth","progress":55,"created":"Apr 22, 2022","created_mm_dd_yyyy":"04-22-2022"},{"id":33,"avatar":"https:\/\/avatars.dicebear.com\/v2\/gridy\/Ms.-Cassidy-Wiegand-DVM.svg","login":"burnice.okuneva","name":"Ms. Cassidy Wiegand DVM","company":"Kuhlman-Hahn","city":"New Ruthiehaven","progress":76,"created":"Sep 16, 2022","created_mm_dd_yyyy":"09-16-2022"},{"id":44,"avatar":"https:\/\/avatars.dicebear.com\/v2\/gridy\/Mr.-Watson-Brakus-PhD.svg","login":"oconnell.juanita","name":"Mr. Watson Brakus PhD","company":"Osinski, Bins and Kuhn","city":"Lake Gloria","progress":58,"created":"Jun 22, 2022","created_mm_dd_yyyy":"06-22-2022"},{"id":46,"avatar":"https:\/\/avatars.dicebear.com\/v2\/gridy\/Mr.-Garrison-Friesen-V.svg","login":"vgutmann","name":"Mr. Garrison Friesen V","company":"VonRueden, Rippin and Pfeffer","city":"Port Cieloport","progress":39,"created":"Oct 19, 2022","created_mm_dd_yyyy":"10-19-2022"},{"id":14,"avatar":"https:\/\/avatars.dicebear.com\/v2\/gridy\/Ms.-Sister-Morar.svg","login":"veum.lucio","name":"Ms. Sister Morar","company":"Gusikowski, Altenwerth and Abbott","city":"Lake Macville","progress":34,"created":"Jun 11, 2022","created_mm_dd_yyyy":"06-11-2022"},{"id":40,"avatar":"https:\/\/avatars.dicebear.com\/v2\/gridy\/Ms.-Laisha-Reinger.svg","login":"edietrich","name":"Ms. Laisha Reinger","company":"Boehm PLC","city":"West Alexiemouth","progress":73,"created":"Nov 2, 2022","created_mm_dd_yyyy":"11-02-2022"},{"id":5,"avatar":"https:\/\/avatars.dicebear.com\/v2\/gridy\/Cameron-Lind.svg","login":"mose44","name":"Cameron Lind","company":"Tremblay, Padberg and Pouros","city":"Naderview","progress":59,"created":"Sep 14, 2022","created_mm_dd_yyyy":"09-14-2022"},{"id":43,"avatar":"https:\/\/avatars.dicebear.com\/v2\/gridy\/Sarai-Little.svg","login":"rau.abelardo","name":"Sarai Little","company":"Deckow LLC","city":"Jeanieborough","progress":49,"created":"Jun 13, 2022","created_mm_dd_yyyy":"06-13-2022"},{"id":2,"avatar":"https:\/\/avatars.dicebear.com\/v2\/gridy\/Shyann-Kautzer.svg","login":"imurazik","name":"Shyann Kautzer","company":"Osinski, Boehm and Kihn","city":"New Alvera","progress":41,"created":"Feb 15, 2022","created_mm_dd_yyyy":"02-15-2022"},{"id":15,"avatar":"https:\/\/avatars.dicebear.com\/v2\/gridy\/Lorna-Christiansen.svg","login":"annalise97","name":"Lorna Christiansen","company":"Altenwerth-Friesen","city":"Port Elbertland","progress":36,"created":"Mar 9, 2022","created_mm_dd_yyyy":"03-09-2022"}]} diff --git a/ref-frontend/public/data-sources/history.json b/ref-frontend/public/data-sources/history.json deleted file mode 100644 index 1cc0c82..0000000 --- a/ref-frontend/public/data-sources/history.json +++ /dev/null @@ -1 +0,0 @@ -{"data":[{"id":1,"amount":375.53,"account":"45721474","name":"Home Loan Account","date":"3 days ago","type":"deposit","business":"Turcotte"},{"id":2,"amount":470.26,"account":"94486537","name":"Savings Account","date":"3 days ago","type":"payment","business":"Murazik - Graham"},{"id":3,"amount":971.34,"account":"63189893","name":"Checking Account","date":"5 days ago","type":"invoice","business":"Fahey - Keebler"},{"id":4,"amount":374.63,"account":"74828780","name":"Auto Loan Account","date":"7 days ago","type":"withdraw","business":"Collier - Hintz"}]} \ No newline at end of file diff --git a/ref-frontend/public/favicon.svg b/ref-frontend/public/favicon.svg deleted file mode 100644 index c8c4e3e..0000000 --- a/ref-frontend/public/favicon.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ref-frontend/public/locales/de/common.json b/ref-frontend/public/locales/de/common.json deleted file mode 100644 index 7f2d578..0000000 --- a/ref-frontend/public/locales/de/common.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "pages": { - "dashboard": { - "pageTitle": "Dashboard", - "overview": "Übersicht", - "loadingWidgets": "Widgets werden geladen...", - "loading": "Laden..." - }, - "login": { - "pageTitle": "Anmeldung", - - "sampleCredentialsAdmin": "Verwenden Sie {{email}} / {{password}}, um sich als Administrator anzumelden", - "sampleCredentialsUser": "Verwenden Sie {{email}} / {{password}}, um sich als Benutzer anzumelden", - - "form": { - "loginLabel": "Login", - "loginHelp": "Bitte geben Sie Ihren Login ein", - "passwordLabel": "Passwort", - "passwordHelp": "Bitte geben Sie Ihr Passwort ein", - "remember": "Angemeldet bleiben", - "forgotPassword": "Passwort vergessen?", - "loginButton": "Anmelden", - "loading": "Wird geladen...", - "noAccountYet": "Noch kein Konto?", - "newAccount": "Neues Konto" - }, - - "pexels": { - "photoCredit": "Foto von {{photographer}} auf Pexels", - "videoCredit": "Video von {{name}} auf Pexels", - "videoUnsupported": "Ihr Browser unterstützt das Video-Tag nicht." - }, - - "footer": { - "copyright": "© {{year}} {{title}}. Alle Rechte vorbehalten", - "privacy": "Datenschutzrichtlinie" - } - } - }, - "components": { - "widgetCreator": { - "title": "Diagramm oder Widget erstellen", - "helpText": "Beschreiben Sie Ihr neues Widget oder Diagramm in natürlicher Sprache. Zum Beispiel: \"Anzahl der Admin-Benutzer\" ODER \"rotes Diagramm mit der Anzahl geschlossener Verträge gruppiert nach Monat\"", - "settingsTitle": "Einstellungen für Widget Creator", - "settingsDescription": "Für welche Rolle zeigen wir Widgets an und erstellen sie?", - "doneButton": "Fertig", - "loading": "Laden..." - }, - "search": { - "placeholder": "Suche", - "required": "Pflichtfeld", - "minLength": "Mindestlänge: {{count}} Zeichen" - } - } -} diff --git a/ref-frontend/public/locales/en/common.json b/ref-frontend/public/locales/en/common.json deleted file mode 100644 index 8d45685..0000000 --- a/ref-frontend/public/locales/en/common.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "pages": { - "dashboard": { - "pageTitle": "Dashboard", - "overview": "Overview", - "loadingWidgets": "Loading widgets...", - "loading": "Loading..." - }, - "login": { - "pageTitle": "Login", - - "form": { - "loginLabel": "Login", - "loginHelp": "Please enter your login", - "passwordLabel": "Password", - "passwordHelp": "Please enter your password", - "remember": "Remember", - "forgotPassword": "Forgot password?", - "loginButton": "Login", - "loading": "Loading...", - "noAccountYet": "Don’t have an account yet?", - "newAccount": "New Account" - }, - - "pexels": { - "photoCredit": "Photo by {{photographer}} on Pexels", - "videoCredit": "Video by {{name}} on Pexels", - "videoUnsupported": "Your browser does not support the video tag." - }, - - "footer": { - "copyright": "© {{year}} {{title}}. All rights reserved", - "privacy": "Privacy Policy" - } - } - }, - "components": { - "widgetCreator": { - "title": "Create Chart or Widget", - "helpText": "Describe your new widget or chart in natural language. For example: \"Number of admin users\" OR \"red chart with number of closed contracts grouped by month\"", - "settingsTitle": "Widget Creator Settings", - "settingsDescription": "What role are we showing and creating widgets for?", - "doneButton": "Done", - "loading": "Loading..." - }, - "search": { - "placeholder": "Search", - "required": "Required", - "minLength": "Minimum length: {{count}} characters" - } - } -} diff --git a/ref-frontend/public/locales/es/common.json b/ref-frontend/public/locales/es/common.json deleted file mode 100644 index f3421b1..0000000 --- a/ref-frontend/public/locales/es/common.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "pages": { - "dashboard": { - "pageTitle": "Tablero", - "overview": "Resumen", - "loadingWidgets": "Cargando widgets...", - "loading": "Cargando..." - }, - "login": { - "pageTitle": "Inicio de sesión", - - "sampleCredentialsAdmin": "Use {{email}} / {{password}} para iniciar sesión como Administrador", - "sampleCredentialsUser": "Use {{email}} / {{password}} para iniciar sesión como Usuario", - - "form": { - "loginLabel": "Usuario", - "loginHelp": "Introduzca su usuario", - "passwordLabel": "Contraseña", - "passwordHelp": "Introduzca su contraseña", - "remember": "Recuérdame", - "forgotPassword": "¿Olvidó su contraseña?", - "loginButton": "Acceder", - "loading": "Cargando...", - "noAccountYet": "¿Aún no tiene una cuenta?", - "newAccount": "Crear cuenta" - }, - - "pexels": { - "photoCredit": "Foto de {{photographer}} en Pexels", - "videoCredit": "Vídeo de {{name}} en Pexels", - "videoUnsupported": "Su navegador no admite la etiqueta de vídeo." - }, - - "footer": { - "copyright": "© {{year}} {{title}}. Todos los derechos reservados", - "privacy": "Política de privacidad" - } - } - }, - "components": { - "widgetCreator": { - "title": "Crear gráfico o widget", - "helpText": "Describe tu nuevo widget o gráfico en lenguaje natural. Por ejemplo: \"Número de usuarios administradores\" O \"gráfico rojo con el número de contratos cerrados agrupados por mes\"", - "settingsTitle": "Configuración del creador de widgets", - "settingsDescription": "¿Para qué rol estamos mostrando y creando widgets?", - "doneButton": "Listo", - "loading": "Cargando..." - }, - "search": { - "placeholder": "Buscar", - "required": "Obligatorio", - "minLength": "Longitud mínima: {{count}} caracteres" - } - } -} diff --git a/ref-frontend/public/locales/fr/common.json b/ref-frontend/public/locales/fr/common.json deleted file mode 100644 index ecf531b..0000000 --- a/ref-frontend/public/locales/fr/common.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "pages": { - "dashboard": { - "pageTitle": "Tableau de bord", - "overview": "Vue d'ensemble", - "loadingWidgets": "Chargement des widgets...", - "loading": "Chargement..." - }, - "login": { - "pageTitle": "Connexion", - - "sampleCredentialsAdmin": "Utilisez {{email}} / {{password}} pour vous connecter en tant qu’administrateur", - "sampleCredentialsUser": "Utilisez {{email}} / {{password}} pour vous connecter en tant qu’utilisateur", - - "form": { - "loginLabel": "Identifiant", - "loginHelp": "Veuillez saisir votre identifiant", - "passwordLabel": "Mot de passe", - "passwordHelp": "Veuillez saisir votre mot de passe", - "remember": "Se souvenir de moi", - "forgotPassword": "Mot de passe oublié ?", - "loginButton": "Se connecter", - "loading": "Chargement…", - "noAccountYet": "Vous n’avez pas encore de compte ?", - "newAccount": "Créer un compte" - }, - - "pexels": { - "photoCredit": "Photo de {{photographer}} sur Pexels", - "videoCredit": "Vidéo de {{name}} sur Pexels", - "videoUnsupported": "Votre navigateur ne prend pas en charge la balise vidéo." - }, - - "footer": { - "copyright": "© {{year}} {{title}}. Tous droits réservés", - "privacy": "Politique de confidentialité" - } - } - }, - "components": { - "widgetCreator": { - "title": "Créer un graphique ou un widget", - "helpText": "Décrivez votre nouveau widget ou graphique en langage naturel. Par exemple : \"Nombre d'utilisateurs administrateurs\" OU \"graphique rouge avec le nombre de contrats clôturés regroupés par mois\"", - "settingsTitle": "Paramètres du créateur de widget", - "settingsDescription": "Pour quel rôle affichons-nous et créons-nous des widgets ?", - "doneButton": "Terminé", - "loading": "Chargement..." - }, - "search": { - "placeholder": "Rechercher", - "required": "Champ requis", - "minLength": "Longueur minimale : {{count}} caractères" - } - } -} diff --git a/ref-frontend/src/colors.ts b/ref-frontend/src/colors.ts deleted file mode 100644 index 71e116a..0000000 --- a/ref-frontend/src/colors.ts +++ /dev/null @@ -1,138 +0,0 @@ -import type { ColorButtonKey } from './interfaces' - -export const gradientBgBase = 'bg-gradient-to-tr' -export const colorBgBase = "bg-violet-50/50" -export const gradientBgPurplePink = `${gradientBgBase} from-purple-400 via-pink-500 to-red-500` -export const gradientBgViolet = `${gradientBgBase} ${colorBgBase}` -export const gradientBgDark = `${gradientBgBase} from-dark-700 via-dark-900 to-dark-800`; -export const gradientBgPinkRed = `${gradientBgBase} from-pink-400 via-red-500 to-yellow-500` - -export const colorsBgLight = { - white: 'bg-white text-black', - light: ' bg-white text-black text-black dark:bg-dark-900 dark:text-white', - contrast: 'bg-gray-800 text-white dark:bg-white dark:text-black', - success: 'bg-emerald-500 border-emerald-500 dark:bg-pavitra-blue dark:border-pavitra-blue text-white', - danger: 'bg-red-500 border-red-500 text-white', - warning: 'bg-yellow-500 border-yellow-500 text-white', - info: 'bg-blue-500 border-blue-500 dark:bg-pavitra-blue dark:border-pavitra-blue text-white', -} - -export const colorsText = { - white: 'text-black dark:text-slate-100', - light: 'text-gray-700 dark:text-slate-400', - contrast: 'dark:text-white', - success: 'text-emerald-500', - danger: 'text-red-500', - warning: 'text-yellow-500', - info: 'text-blue-500', -}; - -export const colorsOutline = { - white: [colorsText.white, 'border-gray-100'].join(' '), - light: [colorsText.light, 'border-gray-100'].join(' '), - contrast: [colorsText.contrast, 'border-gray-900 dark:border-slate-100'].join(' '), - success: [colorsText.success, 'border-emerald-500'].join(' '), - danger: [colorsText.danger, 'border-red-500'].join(' '), - warning: [colorsText.warning, 'border-yellow-500'].join(' '), - info: [colorsText.info, 'border-blue-500'].join(' '), -}; - -export const getButtonColor = ( - color: ColorButtonKey, - isOutlined: boolean, - hasHover: boolean, - isActive = false -) => { - if (color === 'void') { - return '' - } - - const colors = { - ring: { - white: 'ring-gray-200 dark:ring-gray-500', - whiteDark: 'ring-gray-200 dark:ring-dark-500', - lightDark: 'ring-gray-200 dark:ring-gray-500', - contrast: 'ring-gray-300 dark:ring-gray-400', - success: 'ring-emerald-300 dark:ring-pavitra-blue', - danger: 'ring-red-300 dark:ring-red-700', - warning: 'ring-yellow-300 dark:ring-yellow-700', - info: "ring-blue-300 dark:ring-pavitra-blue", - }, - active: { - white: 'bg-gray-100', - whiteDark: 'bg-gray-100 dark:bg-dark-800', - lightDark: 'bg-gray-200 dark:bg-slate-700', - contrast: 'bg-gray-700 dark:bg-slate-100', - success: 'bg-emerald-700 dark:bg-pavitra-blue', - danger: 'bg-red-700 dark:bg-red-600', - warning: 'bg-yellow-700 dark:bg-yellow-600', - info: 'bg-blue-700 dark:bg-pavitra-blue', - }, - bg: { - white: 'bg-white text-black', - whiteDark: 'bg-white text-black dark:bg-dark-900 dark:text-white', - lightDark: 'bg-gray-100 text-black dark:bg-slate-800 dark:text-white', - contrast: 'bg-gray-800 text-white dark:bg-white dark:text-black', - success: 'bg-emerald-600 dark:bg-pavitra-blue text-white', - danger: 'bg-red-600 text-white dark:bg-red-500 ', - warning: 'bg-yellow-600 dark:bg-yellow-500 text-white', - info: " bg-blue-600 dark:bg-pavitra-blue text-white ", - }, - bgHover: { - white: 'hover:bg-gray-100', - whiteDark: 'hover:bg-gray-100 hover:dark:bg-dark-800', - lightDark: 'hover:bg-gray-200 hover:dark:bg-slate-700', - contrast: 'hover:bg-gray-700 hover:dark:bg-slate-100', - success: - 'hover:bg-emerald-700 hover:border-emerald-700 hover:dark:bg-pavitra-blue hover:dark:border-pavitra-blue', - danger: - 'hover:bg-red-700 hover:border-red-700 hover:dark:bg-red-600 hover:dark:border-red-600', - warning: - 'hover:bg-yellow-700 hover:border-yellow-700 hover:dark:bg-yellow-600 hover:dark:border-yellow-600', - info: "hover:bg-blue-700 hover:border-blue-700 hover:dark:bg-pavitra-blue/80 hover:dark:border-pavitra-blue/80", - }, - borders: { - white: 'border-white', - whiteDark: 'border-white dark:border-dark-900', - lightDark: 'border-gray-100 dark:border-slate-800', - contrast: 'border-gray-800 dark:border-white', - success: 'border-emerald-600 dark:border-pavitra-blue', - danger: 'border-red-600 dark:border-red-500', - warning: 'border-yellow-600 dark:border-yellow-500', - info: "border-blue-600 border-blue-600 dark:border-pavitra-blue", - }, - text: { - contrast: 'dark:text-slate-100', - success: 'text-emerald-600 dark:text-pavitra-blue', - danger: 'text-red-600 dark:text-red-500', - warning: 'text-yellow-600 dark:text-yellow-500', - info: 'text-blue-600 dark:text-pavitra-blue', - }, - outlineHover: { - contrast: - 'hover:bg-gray-800 hover:text-gray-100 hover:dark:bg-slate-100 hover:dark:text-black', - success: 'hover:bg-emerald-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-pavitra-blue', - danger: - 'hover:bg-red-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-red-600', - warning: - 'hover:bg-yellow-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-yellow-600', - info: "hover:bg-blue-600 hover:bg-blue-600 hover:text-white hover:dark:text-white hover:dark:border-pavitra-blue", - }, - } - - const isOutlinedProcessed = isOutlined && ['white', 'whiteDark', 'lightDark'].indexOf(color) < 0 - - const base = [colors.borders[color], colors.ring[color]] - - if (isActive) { - base.push(colors.active[color]) - } else { - base.push(isOutlinedProcessed ? colors.text[color] : colors.bg[color]) - } - - if (hasHover) { - base.push(isOutlinedProcessed ? colors.outlineHover[color] : colors.bgHover[color]) - } - - return base.join(' ') -} diff --git a/ref-frontend/src/components/Academic_years/CardAcademic_years.tsx b/ref-frontend/src/components/Academic_years/CardAcademic_years.tsx deleted file mode 100644 index 083096f..0000000 --- a/ref-frontend/src/components/Academic_years/CardAcademic_years.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - academic_years: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardAcademic_years = ({ - academic_years, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ACADEMIC_YEARS') - - - return ( -
- {loading && } -
    - {!loading && academic_years.map((item, index) => ( -
  • - -
    - - - {item.name} - - - -
    - -
    -
    -
    - - -
    -
    Organization
    -
    -
    - { dataFormatter.organizationsOneListFormatter(item.organization) } -
    -
    -
    - - - - -
    -
    AcademicYear
    -
    -
    - { item.name } -
    -
    -
    - - - - -
    -
    StartDate
    -
    -
    - { dataFormatter.dateTimeFormatter(item.start_date) } -
    -
    -
    - - - - -
    -
    EndDate
    -
    -
    - { dataFormatter.dateTimeFormatter(item.end_date) } -
    -
    -
    - - - - -
    -
    Current
    -
    -
    - { dataFormatter.booleanFormatter(item.current) } -
    -
    -
    - - - -
    -
  • - ))} - {!loading && academic_years.length === 0 && ( -
    -

    No data to display

    -
    - )} -
-
- -
-
- ); -}; - -export default CardAcademic_years; diff --git a/ref-frontend/src/components/Academic_years/ListAcademic_years.tsx b/ref-frontend/src/components/Academic_years/ListAcademic_years.tsx deleted file mode 100644 index 63bf432..0000000 --- a/ref-frontend/src/components/Academic_years/ListAcademic_years.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - academic_years: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListAcademic_years = ({ academic_years, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ACADEMIC_YEARS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
- {loading && } - {!loading && academic_years.map((item) => ( -
- -
- - dark:divide-dark-700 overflow-x-auto' - } - > - - -
-

Organization

-

{ dataFormatter.organizationsOneListFormatter(item.organization) }

-
- - - - -
-

AcademicYear

-

{ item.name }

-
- - - - -
-

StartDate

-

{ dataFormatter.dateTimeFormatter(item.start_date) }

-
- - - - -
-

EndDate

-

{ dataFormatter.dateTimeFormatter(item.end_date) }

-
- - - - -
-

Current

-

{ dataFormatter.booleanFormatter(item.current) }

-
- - - - - -
-
-
- ))} - {!loading && academic_years.length === 0 && ( -
-

No data to display

-
- )} -
-
- -
- - ) -}; - -export default ListAcademic_years \ No newline at end of file diff --git a/ref-frontend/src/components/Academic_years/TableAcademic_years.tsx b/ref-frontend/src/components/Academic_years/TableAcademic_years.tsx deleted file mode 100644 index 017c05d..0000000 --- a/ref-frontend/src/components/Academic_years/TableAcademic_years.tsx +++ /dev/null @@ -1,489 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/academic_years/academic_yearsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; -import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureAcademic_yearsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - -import BigCalendar from "../BigCalendar"; -import { SlotInfo } from 'react-big-calendar'; - - -const perPage = 100 - -const TableSampleAcademic_years = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { academic_years, loading, count, notify: academic_yearsNotify, refetch } = useAppSelector((state) => state.academic_years) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (academic_yearsNotify.showNotification) { - notify(academic_yearsNotify.typeNotification, academic_yearsNotify.textNotification); - } - }, [academic_yearsNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - const handleCreateEventAction = ({ start, end }: SlotInfo) => { - router.push( - `/academic_years/academic_years-new?dateRangeStart=${start.toISOString()}&dateRangeEnd=${end.toISOString()}`, - ); - }; - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `academic_years`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
- `datagrid--row`} - rows={academic_years ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
- ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
- <> - {filterItems && filterItems.map((filterItem) => { - return ( -
-
-
Filter
- - {filters.map((selectOption) => ( - - ))} - -
- {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
-
- Value -
- - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
- ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
-
-
From
- -
-
-
To
- -
-
- ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
-
-
- From -
- -
-
-
To
- -
-
- ) : ( -
-
Contains
- -
- )} -
-
Action
- { - deleteFilter(filterItem.id) - }} - /> -
-
- ) - })} -
- - -
- -
-
-
: null - } - -

Are you sure you want to delete this item?

-
- - - {!showGrid && ( - { - loadData(0,`&calendarStart=${range.start}&calendarEnd=${range.end}`); - }} - entityName={'academic_years'} - /> - )} - - - - {showGrid && dataGrid} - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) -} - -export default TableSampleAcademic_years diff --git a/ref-frontend/src/components/Academic_years/configureAcademic_yearsCols.tsx b/ref-frontend/src/components/Academic_years/configureAcademic_yearsCols.tsx deleted file mode 100644 index 6aa82db..0000000 --- a/ref-frontend/src/components/Academic_years/configureAcademic_yearsCols.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; -import ListActionsPopover from '../ListActionsPopover'; - -import {hasPermission} from "../../helpers/userPermissions"; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - -) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_ACADEMIC_YEARS') - - return [ - - { - field: 'organization', - headerName: 'Organization', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('organizations'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'name', - headerName: 'AcademicYear', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'start_date', - headerName: 'StartDate', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.start_date), - - }, - - { - field: 'end_date', - headerName: 'EndDate', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.end_date), - - }, - - { - field: 'current', - headerName: 'Current', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
- -
, - ] - }, - }, - ]; -}; diff --git a/ref-frontend/src/components/AsideMenu.tsx b/ref-frontend/src/components/AsideMenu.tsx deleted file mode 100644 index 442dfac..0000000 --- a/ref-frontend/src/components/AsideMenu.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react' -import { MenuAsideItem } from '../interfaces' -import AsideMenuLayer from './AsideMenuLayer' -import OverlayLayer from './OverlayLayer' - -type Props = { - menu: MenuAsideItem[] - isAsideMobileExpanded: boolean - isAsideLgActive: boolean - onAsideLgClose: () => void -} - -export default function AsideMenu({ - isAsideMobileExpanded = false, - isAsideLgActive = false, - ...props -}: Props) { - return ( - <> - - {isAsideLgActive && } - - ) -} diff --git a/ref-frontend/src/components/AsideMenuItem.tsx b/ref-frontend/src/components/AsideMenuItem.tsx deleted file mode 100644 index dbb09b2..0000000 --- a/ref-frontend/src/components/AsideMenuItem.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React, { useEffect, useState } from 'react' -import { mdiMinus, mdiPlus } from '@mdi/js' -import BaseIcon from './BaseIcon' -import Link from 'next/link' -import { getButtonColor } from '../colors' -import AsideMenuList from './AsideMenuList' -import { MenuAsideItem } from '../interfaces' -import { useAppSelector } from '../stores/hooks' -import { useRouter } from 'next/router' - -type Props = { - item: MenuAsideItem - isDropdownList?: boolean -} - -const AsideMenuItem = ({ item, isDropdownList = false }: Props) => { - const [isLinkActive, setIsLinkActive] = useState(false) - const [isDropdownActive, setIsDropdownActive] = useState(false) - - const asideMenuItemStyle = useAppSelector((state) => state.style.asideMenuItemStyle) - const asideMenuDropdownStyle = useAppSelector((state) => state.style.asideMenuDropdownStyle) - const asideMenuItemActiveStyle = useAppSelector((state) => state.style.asideMenuItemActiveStyle) - const borders = useAppSelector((state) => state.style.borders); - const activeLinkColor = useAppSelector( - (state) => state.style.activeLinkColor, - ); - const activeClassAddon = !item.color && isLinkActive ? asideMenuItemActiveStyle : '' - - const { asPath, isReady } = useRouter() - - useEffect(() => { - if (item.href && isReady) { - const linkPathName = new URL(item.href, location.href).pathname + '/'; - const activePathname = new URL(asPath, location.href).pathname - - const activeView = activePathname.split('/')[1]; - const linkPathNameView = linkPathName.split('/')[1]; - - setIsLinkActive(linkPathNameView === activeView); - } - }, [item.href, isReady, asPath]) - - const asideMenuItemInnerContents = ( - <> - {item.icon && ( - - )} - - {item.label} - - {item.menu && ( - - )} - - ) - - const componentClass = [ - 'flex cursor-pointer py-1.5 ', - isDropdownList ? 'px-6 text-sm' : '', - item.color - ? getButtonColor(item.color, false, true) - : `${asideMenuItemStyle}`, - isLinkActive - ? `text-black ${activeLinkColor} dark:text-white dark:bg-dark-800` - : '', - ].join(' '); - - return ( -
  • - {item.withDevider &&
    } - {item.href && ( - - {asideMenuItemInnerContents} - - )} - {!item.href && ( -
    setIsDropdownActive(!isDropdownActive)}> - {asideMenuItemInnerContents} -
    - )} - {item.menu && ( - - )} -
  • - ) -} - -export default AsideMenuItem diff --git a/ref-frontend/src/components/AsideMenuLayer.tsx b/ref-frontend/src/components/AsideMenuLayer.tsx deleted file mode 100644 index 143bcfb..0000000 --- a/ref-frontend/src/components/AsideMenuLayer.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React from 'react' -import { mdiLogout, mdiClose } from '@mdi/js' -import BaseIcon from './BaseIcon' -import AsideMenuList from './AsideMenuList' -import { MenuAsideItem } from '../interfaces' -import { useAppSelector } from '../stores/hooks' -import Link from 'next/link'; - -import { useAppDispatch } from '../stores/hooks'; -import { createAsyncThunk } from '@reduxjs/toolkit'; -import axios from 'axios'; - - -type Props = { - menu: MenuAsideItem[] - className?: string - onAsideLgCloseClick: () => void -} - -export default function AsideMenuLayer({ menu, className = '', ...props }: Props) { - const corners = useAppSelector((state) => state.style.corners); - const asideStyle = useAppSelector((state) => state.style.asideStyle) - const asideBrandStyle = useAppSelector((state) => state.style.asideBrandStyle) - const asideScrollbarsStyle = useAppSelector((state) => state.style.asideScrollbarsStyle) - const darkMode = useAppSelector((state) => state.style.darkMode) - - const handleAsideLgCloseClick = (e: React.MouseEvent) => { - e.preventDefault() - props.onAsideLgCloseClick() - } - - const dispatch = useAppDispatch(); - const { currentUser } = useAppSelector((state) => state.auth); - const organizationsId = currentUser?.organizations?.id; - const [organizations, setOrganizations] = React.useState(null); - - const fetchOrganizations = createAsyncThunk('/org-for-auth', async () => { - try { - const response = await axios.get('/org-for-auth'); - setOrganizations(response.data); - return response.data; - } catch (error) { - console.error(error.response); - throw error; - } - }); - - React.useEffect(() => { - dispatch(fetchOrganizations()); - }, [dispatch]); - - let organizationName = organizations?.find(item => item.id === organizationsId)?.name; - if(organizationName?.length > 25){ - organizationName = organizationName?.substring(0, 25) + '...'; - } - - - return ( - - ) -} diff --git a/ref-frontend/src/components/AsideMenuList.tsx b/ref-frontend/src/components/AsideMenuList.tsx deleted file mode 100644 index 9e33ea1..0000000 --- a/ref-frontend/src/components/AsideMenuList.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react' -import { MenuAsideItem } from '../interfaces' -import AsideMenuItem from './AsideMenuItem' -import {useAppSelector} from "../stores/hooks"; -import {hasPermission} from "../helpers/userPermissions"; - -type Props = { - menu: MenuAsideItem[] - isDropdownList?: boolean - className?: string -} - -export default function AsideMenuList({ menu, isDropdownList = false, className = '' }: Props) { - const { currentUser } = useAppSelector((state) => state.auth); - - if (!currentUser) return null; - - return ( -
      - {menu.map((item, index) => { - - if (!hasPermission(currentUser, item.permissions)) return null; - - return ( -
      - -
      - ) - })} -
    - ) -} diff --git a/ref-frontend/src/components/Assessment_results/CardAssessment_results.tsx b/ref-frontend/src/components/Assessment_results/CardAssessment_results.tsx deleted file mode 100644 index 7431ce1..0000000 --- a/ref-frontend/src/components/Assessment_results/CardAssessment_results.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - assessment_results: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardAssessment_results = ({ - assessment_results, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ASSESSMENT_RESULTS') - - - return ( -
    - {loading && } -
      - {!loading && assessment_results.map((item, index) => ( -
    • - -
      - - - {item.grade_letter} - - - -
      - -
      -
      -
      - - -
      -
      Organization
      -
      -
      - { dataFormatter.organizationsOneListFormatter(item.organization) } -
      -
      -
      - - - - -
      -
      Assessment
      -
      -
      - { dataFormatter.assessmentsOneListFormatter(item.assessment) } -
      -
      -
      - - - - -
      -
      Student
      -
      -
      - { dataFormatter.studentsOneListFormatter(item.student) } -
      -
      -
      - - - - -
      -
      Score
      -
      -
      - { item.score } -
      -
      -
      - - - - -
      -
      GradeLetter
      -
      -
      - { item.grade_letter } -
      -
      -
      - - - - -
      -
      Remarks
      -
      -
      - { item.remarks } -
      -
      -
      - - - -
      -
    • - ))} - {!loading && assessment_results.length === 0 && ( -
      -

      No data to display

      -
      - )} -
    -
    - -
    -
    - ); -}; - -export default CardAssessment_results; diff --git a/ref-frontend/src/components/Assessment_results/ListAssessment_results.tsx b/ref-frontend/src/components/Assessment_results/ListAssessment_results.tsx deleted file mode 100644 index f7d3590..0000000 --- a/ref-frontend/src/components/Assessment_results/ListAssessment_results.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - assessment_results: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListAssessment_results = ({ assessment_results, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ASSESSMENT_RESULTS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
    - {loading && } - {!loading && assessment_results.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Organization

    -

    { dataFormatter.organizationsOneListFormatter(item.organization) }

    -
    - - - - -
    -

    Assessment

    -

    { dataFormatter.assessmentsOneListFormatter(item.assessment) }

    -
    - - - - -
    -

    Student

    -

    { dataFormatter.studentsOneListFormatter(item.student) }

    -
    - - - - -
    -

    Score

    -

    { item.score }

    -
    - - - - -
    -

    GradeLetter

    -

    { item.grade_letter }

    -
    - - - - -
    -

    Remarks

    -

    { item.remarks }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && assessment_results.length === 0 && ( -
    -

    No data to display

    -
    - )} -
    -
    - -
    - - ) -}; - -export default ListAssessment_results \ No newline at end of file diff --git a/ref-frontend/src/components/Assessment_results/TableAssessment_results.tsx b/ref-frontend/src/components/Assessment_results/TableAssessment_results.tsx deleted file mode 100644 index 1304070..0000000 --- a/ref-frontend/src/components/Assessment_results/TableAssessment_results.tsx +++ /dev/null @@ -1,463 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/assessment_results/assessment_resultsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; -import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureAssessment_resultsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - - -const perPage = 10 - -const TableSampleAssessment_results = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { assessment_results, loading, count, notify: assessment_resultsNotify, refetch } = useAppSelector((state) => state.assessment_results) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (assessment_resultsNotify.showNotification) { - notify(assessment_resultsNotify.typeNotification, assessment_resultsNotify.textNotification); - } - }, [assessment_resultsNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `assessment_results`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={assessment_results ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) -} - -export default TableSampleAssessment_results diff --git a/ref-frontend/src/components/Assessment_results/configureAssessment_resultsCols.tsx b/ref-frontend/src/components/Assessment_results/configureAssessment_resultsCols.tsx deleted file mode 100644 index 56a831d..0000000 --- a/ref-frontend/src/components/Assessment_results/configureAssessment_resultsCols.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; -import ListActionsPopover from '../ListActionsPopover'; - -import {hasPermission} from "../../helpers/userPermissions"; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - -) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_ASSESSMENT_RESULTS') - - return [ - - { - field: 'organization', - headerName: 'Organization', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('organizations'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'assessment', - headerName: 'Assessment', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('assessments'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'student', - headerName: 'Student', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('students'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'score', - headerName: 'Score', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'grade_letter', - headerName: 'GradeLetter', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'remarks', - headerName: 'Remarks', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; -}; diff --git a/ref-frontend/src/components/Assessments/CardAssessments.tsx b/ref-frontend/src/components/Assessments/CardAssessments.tsx deleted file mode 100644 index 827a16a..0000000 --- a/ref-frontend/src/components/Assessments/CardAssessments.tsx +++ /dev/null @@ -1,226 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - assessments: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardAssessments = ({ - assessments, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ASSESSMENTS') - - - return ( -
    - {loading && } -
      - {!loading && assessments.map((item, index) => ( -
    • - -
      - - - {item.name} - - - -
      - -
      -
      -
      - - -
      -
      Organization
      -
      -
      - { dataFormatter.organizationsOneListFormatter(item.organization) } -
      -
      -
      - - - - -
      -
      ClassSubject
      -
      -
      - { dataFormatter.class_subjectsOneListFormatter(item.class_subject) } -
      -
      -
      - - - - -
      -
      AssessmentName
      -
      -
      - { item.name } -
      -
      -
      - - - - -
      -
      AssessmentType
      -
      -
      - { item.assessment_type } -
      -
      -
      - - - - -
      -
      AssignedAt
      -
      -
      - { dataFormatter.dateTimeFormatter(item.assigned_at) } -
      -
      -
      - - - - -
      -
      DueAt
      -
      -
      - { dataFormatter.dateTimeFormatter(item.due_at) } -
      -
      -
      - - - - -
      -
      MaxScore
      -
      -
      - { item.max_score } -
      -
      -
      - - - - -
      -
      Status
      -
      -
      - { item.status } -
      -
      -
      - - - - -
      -
      Instructions
      -
      -
      - { item.instructions } -
      -
      -
      - - - - -
      -
      Attachments
      -
      -
      - {dataFormatter.filesFormatter(item.attachments).map(link => ( - - ))} -
      -
      -
      - - - -
      -
    • - ))} - {!loading && assessments.length === 0 && ( -
      -

      No data to display

      -
      - )} -
    -
    - -
    -
    - ); -}; - -export default CardAssessments; diff --git a/ref-frontend/src/components/Assessments/ListAssessments.tsx b/ref-frontend/src/components/Assessments/ListAssessments.tsx deleted file mode 100644 index c1896fa..0000000 --- a/ref-frontend/src/components/Assessments/ListAssessments.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - assessments: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListAssessments = ({ assessments, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ASSESSMENTS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
    - {loading && } - {!loading && assessments.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Organization

    -

    { dataFormatter.organizationsOneListFormatter(item.organization) }

    -
    - - - - -
    -

    ClassSubject

    -

    { dataFormatter.class_subjectsOneListFormatter(item.class_subject) }

    -
    - - - - -
    -

    AssessmentName

    -

    { item.name }

    -
    - - - - -
    -

    AssessmentType

    -

    { item.assessment_type }

    -
    - - - - -
    -

    AssignedAt

    -

    { dataFormatter.dateTimeFormatter(item.assigned_at) }

    -
    - - - - -
    -

    DueAt

    -

    { dataFormatter.dateTimeFormatter(item.due_at) }

    -
    - - - - -
    -

    MaxScore

    -

    { item.max_score }

    -
    - - - - -
    -

    Status

    -

    { item.status }

    -
    - - - - -
    -

    Instructions

    -

    { item.instructions }

    -
    - - - - -
    -

    Attachments

    - {dataFormatter.filesFormatter(item.attachments).map(link => ( - - ))} -
    - - - - - -
    -
    -
    - ))} - {!loading && assessments.length === 0 && ( -
    -

    No data to display

    -
    - )} -
    -
    - -
    - - ) -}; - -export default ListAssessments \ No newline at end of file diff --git a/ref-frontend/src/components/Assessments/TableAssessments.tsx b/ref-frontend/src/components/Assessments/TableAssessments.tsx deleted file mode 100644 index ef61b2f..0000000 --- a/ref-frontend/src/components/Assessments/TableAssessments.tsx +++ /dev/null @@ -1,489 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/assessments/assessmentsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; -import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureAssessmentsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - -import BigCalendar from "../BigCalendar"; -import { SlotInfo } from 'react-big-calendar'; - - -const perPage = 100 - -const TableSampleAssessments = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { assessments, loading, count, notify: assessmentsNotify, refetch } = useAppSelector((state) => state.assessments) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (assessmentsNotify.showNotification) { - notify(assessmentsNotify.typeNotification, assessmentsNotify.textNotification); - } - }, [assessmentsNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - const handleCreateEventAction = ({ start, end }: SlotInfo) => { - router.push( - `/assessments/assessments-new?dateRangeStart=${start.toISOString()}&dateRangeEnd=${end.toISOString()}`, - ); - }; - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `assessments`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={assessments ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {!showGrid && ( - { - loadData(0,`&calendarStart=${range.start}&calendarEnd=${range.end}`); - }} - entityName={'assessments'} - /> - )} - - - - {showGrid && dataGrid} - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) -} - -export default TableSampleAssessments diff --git a/ref-frontend/src/components/Assessments/configureAssessmentsCols.tsx b/ref-frontend/src/components/Assessments/configureAssessmentsCols.tsx deleted file mode 100644 index c3be6a0..0000000 --- a/ref-frontend/src/components/Assessments/configureAssessmentsCols.tsx +++ /dev/null @@ -1,250 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; -import ListActionsPopover from '../ListActionsPopover'; - -import {hasPermission} from "../../helpers/userPermissions"; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - -) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_ASSESSMENTS') - - return [ - - { - field: 'organization', - headerName: 'Organization', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('organizations'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'class_subject', - headerName: 'ClassSubject', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('class_subjects'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'name', - headerName: 'AssessmentName', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'assessment_type', - headerName: 'AssessmentType', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'assigned_at', - headerName: 'AssignedAt', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.assigned_at), - - }, - - { - field: 'due_at', - headerName: 'DueAt', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.due_at), - - }, - - { - field: 'max_score', - headerName: 'MaxScore', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'status', - headerName: 'Status', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'instructions', - headerName: 'Instructions', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'attachments', - headerName: 'Attachments', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: false, - sortable: false, - renderCell: (params: GridValueGetterParams) => ( - <> - {dataFormatter.filesFormatter(params.row.attachments).map(link => ( - - ))} - - ), - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; -}; diff --git a/ref-frontend/src/components/Attendance_records/CardAttendance_records.tsx b/ref-frontend/src/components/Attendance_records/CardAttendance_records.tsx deleted file mode 100644 index 174614c..0000000 --- a/ref-frontend/src/components/Attendance_records/CardAttendance_records.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - attendance_records: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardAttendance_records = ({ - attendance_records, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ATTENDANCE_RECORDS') - - - return ( -
    - {loading && } -
      - {!loading && attendance_records.map((item, index) => ( -
    • - -
      - - - {item.status} - - - -
      - -
      -
      -
      - - -
      -
      Organization
      -
      -
      - { dataFormatter.organizationsOneListFormatter(item.organization) } -
      -
      -
      - - - - -
      -
      AttendanceSession
      -
      -
      - { dataFormatter.attendance_sessionsOneListFormatter(item.attendance_session) } -
      -
      -
      - - - - -
      -
      Student
      -
      -
      - { dataFormatter.studentsOneListFormatter(item.student) } -
      -
      -
      - - - - -
      -
      Status
      -
      -
      - { item.status } -
      -
      -
      - - - - -
      -
      MinutesLate
      -
      -
      - { item.minutes_late } -
      -
      -
      - - - - -
      -
      Remarks
      -
      -
      - { item.remarks } -
      -
      -
      - - - -
      -
    • - ))} - {!loading && attendance_records.length === 0 && ( -
      -

      No data to display

      -
      - )} -
    -
    - -
    -
    - ); -}; - -export default CardAttendance_records; diff --git a/ref-frontend/src/components/Attendance_records/ListAttendance_records.tsx b/ref-frontend/src/components/Attendance_records/ListAttendance_records.tsx deleted file mode 100644 index 2902a4e..0000000 --- a/ref-frontend/src/components/Attendance_records/ListAttendance_records.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - attendance_records: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListAttendance_records = ({ attendance_records, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ATTENDANCE_RECORDS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
    - {loading && } - {!loading && attendance_records.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Organization

    -

    { dataFormatter.organizationsOneListFormatter(item.organization) }

    -
    - - - - -
    -

    AttendanceSession

    -

    { dataFormatter.attendance_sessionsOneListFormatter(item.attendance_session) }

    -
    - - - - -
    -

    Student

    -

    { dataFormatter.studentsOneListFormatter(item.student) }

    -
    - - - - -
    -

    Status

    -

    { item.status }

    -
    - - - - -
    -

    MinutesLate

    -

    { item.minutes_late }

    -
    - - - - -
    -

    Remarks

    -

    { item.remarks }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && attendance_records.length === 0 && ( -
    -

    No data to display

    -
    - )} -
    -
    - -
    - - ) -}; - -export default ListAttendance_records \ No newline at end of file diff --git a/ref-frontend/src/components/Attendance_records/TableAttendance_records.tsx b/ref-frontend/src/components/Attendance_records/TableAttendance_records.tsx deleted file mode 100644 index bfde854..0000000 --- a/ref-frontend/src/components/Attendance_records/TableAttendance_records.tsx +++ /dev/null @@ -1,463 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/attendance_records/attendance_recordsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; -import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureAttendance_recordsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - - -const perPage = 10 - -const TableSampleAttendance_records = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { attendance_records, loading, count, notify: attendance_recordsNotify, refetch } = useAppSelector((state) => state.attendance_records) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (attendance_recordsNotify.showNotification) { - notify(attendance_recordsNotify.typeNotification, attendance_recordsNotify.textNotification); - } - }, [attendance_recordsNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `attendance_records`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={attendance_records ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) -} - -export default TableSampleAttendance_records diff --git a/ref-frontend/src/components/Attendance_records/configureAttendance_recordsCols.tsx b/ref-frontend/src/components/Attendance_records/configureAttendance_recordsCols.tsx deleted file mode 100644 index 45826d1..0000000 --- a/ref-frontend/src/components/Attendance_records/configureAttendance_recordsCols.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; -import ListActionsPopover from '../ListActionsPopover'; - -import {hasPermission} from "../../helpers/userPermissions"; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - -) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_ATTENDANCE_RECORDS') - - return [ - - { - field: 'organization', - headerName: 'Organization', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('organizations'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'attendance_session', - headerName: 'AttendanceSession', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('attendance_sessions'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'student', - headerName: 'Student', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('students'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'status', - headerName: 'Status', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'minutes_late', - headerName: 'MinutesLate', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'remarks', - headerName: 'Remarks', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; -}; diff --git a/ref-frontend/src/components/Attendance_sessions/CardAttendance_sessions.tsx b/ref-frontend/src/components/Attendance_sessions/CardAttendance_sessions.tsx deleted file mode 100644 index ea17c07..0000000 --- a/ref-frontend/src/components/Attendance_sessions/CardAttendance_sessions.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - attendance_sessions: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardAttendance_sessions = ({ - attendance_sessions, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ATTENDANCE_SESSIONS') - - - return ( -
    - {loading && } -
      - {!loading && attendance_sessions.map((item, index) => ( -
    • - -
      - - - {item.session_type} - - - -
      - -
      -
      -
      - - -
      -
      Organization
      -
      -
      - { dataFormatter.organizationsOneListFormatter(item.organization) } -
      -
      -
      - - - - -
      -
      Campus
      -
      -
      - { dataFormatter.campusesOneListFormatter(item.campus) } -
      -
      -
      - - - - -
      -
      Class
      -
      -
      - { dataFormatter.classesOneListFormatter(item.class) } -
      -
      -
      - - - - -
      -
      ClassSubject
      -
      -
      - { dataFormatter.class_subjectsOneListFormatter(item.class_subject) } -
      -
      -
      - - - - -
      -
      TakenBy
      -
      -
      - { dataFormatter.staffOneListFormatter(item.taken_by) } -
      -
      -
      - - - - -
      -
      SessionDate
      -
      -
      - { dataFormatter.dateTimeFormatter(item.session_date) } -
      -
      -
      - - - - -
      -
      SessionType
      -
      -
      - { item.session_type } -
      -
      -
      - - - - -
      -
      Notes
      -
      -
      - { item.notes } -
      -
      -
      - - - -
      -
    • - ))} - {!loading && attendance_sessions.length === 0 && ( -
      -

      No data to display

      -
      - )} -
    -
    - -
    -
    - ); -}; - -export default CardAttendance_sessions; diff --git a/ref-frontend/src/components/Attendance_sessions/ListAttendance_sessions.tsx b/ref-frontend/src/components/Attendance_sessions/ListAttendance_sessions.tsx deleted file mode 100644 index 102f632..0000000 --- a/ref-frontend/src/components/Attendance_sessions/ListAttendance_sessions.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - attendance_sessions: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListAttendance_sessions = ({ attendance_sessions, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ATTENDANCE_SESSIONS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
    - {loading && } - {!loading && attendance_sessions.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Organization

    -

    { dataFormatter.organizationsOneListFormatter(item.organization) }

    -
    - - - - -
    -

    Campus

    -

    { dataFormatter.campusesOneListFormatter(item.campus) }

    -
    - - - - -
    -

    Class

    -

    { dataFormatter.classesOneListFormatter(item.class) }

    -
    - - - - -
    -

    ClassSubject

    -

    { dataFormatter.class_subjectsOneListFormatter(item.class_subject) }

    -
    - - - - -
    -

    TakenBy

    -

    { dataFormatter.staffOneListFormatter(item.taken_by) }

    -
    - - - - -
    -

    SessionDate

    -

    { dataFormatter.dateTimeFormatter(item.session_date) }

    -
    - - - - -
    -

    SessionType

    -

    { item.session_type }

    -
    - - - - -
    -

    Notes

    -

    { item.notes }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && attendance_sessions.length === 0 && ( -
    -

    No data to display

    -
    - )} -
    -
    - -
    - - ) -}; - -export default ListAttendance_sessions \ No newline at end of file diff --git a/ref-frontend/src/components/Attendance_sessions/TableAttendance_sessions.tsx b/ref-frontend/src/components/Attendance_sessions/TableAttendance_sessions.tsx deleted file mode 100644 index 3386d01..0000000 --- a/ref-frontend/src/components/Attendance_sessions/TableAttendance_sessions.tsx +++ /dev/null @@ -1,463 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/attendance_sessions/attendance_sessionsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; -import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureAttendance_sessionsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - - -const perPage = 10 - -const TableSampleAttendance_sessions = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { attendance_sessions, loading, count, notify: attendance_sessionsNotify, refetch } = useAppSelector((state) => state.attendance_sessions) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (attendance_sessionsNotify.showNotification) { - notify(attendance_sessionsNotify.typeNotification, attendance_sessionsNotify.textNotification); - } - }, [attendance_sessionsNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `attendance_sessions`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={attendance_sessions ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) -} - -export default TableSampleAttendance_sessions diff --git a/ref-frontend/src/components/Attendance_sessions/configureAttendance_sessionsCols.tsx b/ref-frontend/src/components/Attendance_sessions/configureAttendance_sessionsCols.tsx deleted file mode 100644 index 145188c..0000000 --- a/ref-frontend/src/components/Attendance_sessions/configureAttendance_sessionsCols.tsx +++ /dev/null @@ -1,226 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; -import ListActionsPopover from '../ListActionsPopover'; - -import {hasPermission} from "../../helpers/userPermissions"; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - -) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_ATTENDANCE_SESSIONS') - - return [ - - { - field: 'organization', - headerName: 'Organization', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('organizations'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'campus', - headerName: 'Campus', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('campuses'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'class', - headerName: 'Class', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('classes'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'class_subject', - headerName: 'ClassSubject', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('class_subjects'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'taken_by', - headerName: 'TakenBy', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('staff'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'session_date', - headerName: 'SessionDate', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.session_date), - - }, - - { - field: 'session_type', - headerName: 'SessionType', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'notes', - headerName: 'Notes', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; -}; diff --git a/ref-frontend/src/components/BaseButton.tsx b/ref-frontend/src/components/BaseButton.tsx deleted file mode 100644 index a137dc2..0000000 --- a/ref-frontend/src/components/BaseButton.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React from 'react' -import Link from 'next/link' -import { getButtonColor } from '../colors' -import BaseIcon from './BaseIcon' -import type { ColorButtonKey } from '../interfaces' -import { useAppSelector } from '../stores/hooks'; - -type Props = { - label?: string - icon?: string - iconSize?: string | number - href?: string - target?: string - type?: string - color?: ColorButtonKey - className?: string - iconClassName?: string - asAnchor?: boolean - small?: boolean - outline?: boolean - active?: boolean - disabled?: boolean - roundedFull?: boolean - onClick?: (e: React.MouseEvent) => void -} - -export default function BaseButton({ - label, - icon, - iconSize, - href, - target, - type, - color = 'white', - className = '', - iconClassName = '', - asAnchor = false, - small = false, - outline = false, - active = false, - disabled = false, - roundedFull = false, - onClick, -}: Props) { - const corners = useAppSelector((state) => state.style.corners); - const componentClass = [ - 'inline-flex', - 'justify-center', - 'items-center', - 'whitespace-nowrap', - 'focus:outline-none', - 'transition-colors', - 'focus:ring', - 'duration-150', - 'border', - disabled ? 'cursor-not-allowed' : 'cursor-pointer', - roundedFull ? 'rounded-full' : `${corners}`, - getButtonColor(color, outline, !disabled, active), - className, - ] - - if (!label && icon) { - componentClass.push('p-1') - } else if (small) { - componentClass.push('text-sm', roundedFull ? 'px-3 py-1' : 'p-1') - } else { - componentClass.push('py-2', roundedFull ? 'px-6' : 'px-3') - } - - if (disabled) { - componentClass.push(outline ? 'opacity-50' : 'opacity-70') - } - - const componentClassString = componentClass.join(' ') - - const componentChildren = ( - <> - {icon && } - {label && {label}} - - ) - - if (href && !disabled) { - return ( - - {componentChildren} - - ) - } - - return React.createElement( - asAnchor ? 'a' : 'button', - { className: componentClassString, type: type ?? 'button', target, disabled, onClick }, - componentChildren - ) -} diff --git a/ref-frontend/src/components/BaseButtons.tsx b/ref-frontend/src/components/BaseButtons.tsx deleted file mode 100644 index e81a102..0000000 --- a/ref-frontend/src/components/BaseButtons.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Children, cloneElement, ReactElement } from 'react'; -import type { ReactNode } from 'react'; - -type Props = { - type?: string; - mb?: string; - noWrap?: boolean; - classAddon?: string; - children?: ReactNode; - className?: string; -}; - -const BaseButtons = ({ - type = 'justify-end', - mb = '-mb-3', - classAddon = 'mr-3 last:mr-0 mb-3', - noWrap = false, - children, - className, - }: Props) => { - return ( -
    - {Children.map(children, (child: ReactElement) => - child - ? cloneElement(child as ReactElement<{ className?: string }>, { - className: `${classAddon} ${(child.props as { className?: string }).className || ''}`, - }) - : null, - )} -
    - ); -}; - -export default BaseButtons; diff --git a/ref-frontend/src/components/BaseDivider.tsx b/ref-frontend/src/components/BaseDivider.tsx deleted file mode 100644 index ac56fa6..0000000 --- a/ref-frontend/src/components/BaseDivider.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react' -import { useAppSelector } from '../stores/hooks'; -type Props = { - navBar?: boolean -} - -export default function BaseDivider({ navBar = false }: Props) { - const borders = useAppSelector((state) => state.style.borders); - const classAddon = navBar - ? 'hidden lg:block lg:my-0.5 dark:border-dark-700' - : 'my-6 -mx-6 dark:border-dark-800' - - return
    -} diff --git a/ref-frontend/src/components/BaseIcon.tsx b/ref-frontend/src/components/BaseIcon.tsx deleted file mode 100644 index 98ec891..0000000 --- a/ref-frontend/src/components/BaseIcon.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React, { ReactNode } from 'react' - -type Props = { - path: string - w?: string - h?: string - fill?: string; - size?: string | number | null - className?: string - children?: ReactNode -} - -export default function BaseIcon({ - path, - fill, - w = 'w-6', - h = 'h-6', - size = null, - className = '', - children, -}: Props) { - const iconSize = size ?? 16 - - return ( - - - - - {children} - - ) -} diff --git a/ref-frontend/src/components/BigCalendar.tsx b/ref-frontend/src/components/BigCalendar.tsx deleted file mode 100644 index a457be6..0000000 --- a/ref-frontend/src/components/BigCalendar.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import React, { useEffect, useMemo, useState, useRef } from 'react'; -import { - Calendar, - Views, - momentLocalizer, - SlotInfo, - EventProps, -} from 'react-big-calendar'; -import moment from 'moment'; -import 'react-big-calendar/lib/css/react-big-calendar.css'; -import ListActionsPopover from './ListActionsPopover'; -import Link from 'next/link'; - -import { useAppSelector } from '../stores/hooks'; -import { hasPermission } from '../helpers/userPermissions'; - - -const localizer = momentLocalizer(moment); - -type TEvent = { - id: string; - title: string; - start: Date; - end: Date; -}; - -type Props = { - events: any[]; - handleDeleteAction: (id: string) => void; - handleCreateEventAction: (slotInfo: SlotInfo) => void; - onDateRangeChange: (range: { start: string; end: string }) => void; - entityName: string; - showField: string; - pathEdit?: string; - pathView?: string; - 'start-data-key': string; - 'end-data-key': string; -}; - -const BigCalendar = ({ - events, - handleDeleteAction, - handleCreateEventAction, - onDateRangeChange, - entityName, - showField, - pathEdit, - pathView, - 'start-data-key': startDataKey, - 'end-data-key': endDataKey, - }: Props) => { - const [myEvents, setMyEvents] = useState([]); - const prevRange = useRef<{ start: string; end: string } | null>(null); - - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = - currentUser && - hasPermission(currentUser, `UPDATE_${entityName.toUpperCase()}`); - const hasCreatePermission = - currentUser && - hasPermission(currentUser, `CREATE_${entityName.toUpperCase()}`); - - - const { defaultDate, scrollToTime } = useMemo( - () => ({ - defaultDate: new Date(), - scrollToTime: new Date(1970, 1, 1, 6), - }), - [], - ); - - useEffect(() => { - if (!events || !Array.isArray(events) || !events?.length) return; - - const formattedEvents = events.map((event) => ({ - ...event, - start: new Date(event[startDataKey]), - end: new Date(event[endDataKey]), - title: event[showField], - })); - - setMyEvents(formattedEvents); - }, [endDataKey, events, startDataKey, showField]); - - const onRangeChange = ( - range: Date[] | { start: Date; end: Date }, - ) => { - const newRange = { start: '', end: '' }; - const format = 'YYYY-MM-DDTHH:mm'; - - if (Array.isArray(range)) { - newRange.start = moment(range[0]).format(format); - newRange.end = moment(range[range.length - 1]).format(format); - } else { - newRange.start = moment(range.start).format(format); - newRange.end = moment(range.end).format(format); - } - - if (newRange.start === newRange.end) { - newRange.end = moment(newRange.end).add(1, 'days').format(format); - } - - // check if the range fits in the previous range - if ( - prevRange.current && - prevRange.current.start <= newRange.start && - prevRange.current.end >= newRange.end - ) { - return; - } - - prevRange.current = { start: newRange.start, end: newRange.end }; - onDateRangeChange(newRange); - }; - - return ( -
    - ( - - ), - }} - /> -
    - ); -}; - -const MyCustomEvent = ( - props: { - onDelete: (id: string) => void; - hasUpdatePermission: boolean; - pathEdit?: string; - pathView?: string; - } & EventProps, -) => { - const { onDelete, hasUpdatePermission, title, event, pathEdit, pathView } = props; - - return ( -
    - - {title} - - -
    - ); -}; - -export default BigCalendar; diff --git a/ref-frontend/src/components/Campuses/CardCampuses.tsx b/ref-frontend/src/components/Campuses/CardCampuses.tsx deleted file mode 100644 index 7df06b7..0000000 --- a/ref-frontend/src/components/Campuses/CardCampuses.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - campuses: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardCampuses = ({ - campuses, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_CAMPUSES') - - - return ( -
    - {loading && } -
      - {!loading && campuses.map((item, index) => ( -
    • - -
      - - - {item.name} - - - -
      - -
      -
      -
      - - -
      -
      Organization
      -
      -
      - { dataFormatter.organizationsOneListFormatter(item.organization) } -
      -
      -
      - - - - -
      -
      CampusName
      -
      -
      - { item.name } -
      -
      -
      - - - - -
      -
      CampusCode
      -
      -
      - { item.code } -
      -
      -
      - - - - -
      -
      Address
      -
      -
      - { item.address } -
      -
      -
      - - - - -
      -
      Phone
      -
      -
      - { item.phone } -
      -
      -
      - - - - -
      -
      Email
      -
      -
      - { item.email } -
      -
      -
      - - - - -
      -
      Active
      -
      -
      - { dataFormatter.booleanFormatter(item.active) } -
      -
      -
      - - - -
      -
    • - ))} - {!loading && campuses.length === 0 && ( -
      -

      No data to display

      -
      - )} -
    -
    - -
    -
    - ); -}; - -export default CardCampuses; diff --git a/ref-frontend/src/components/Campuses/ListCampuses.tsx b/ref-frontend/src/components/Campuses/ListCampuses.tsx deleted file mode 100644 index 5a65796..0000000 --- a/ref-frontend/src/components/Campuses/ListCampuses.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - campuses: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListCampuses = ({ campuses, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_CAMPUSES') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
    - {loading && } - {!loading && campuses.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Organization

    -

    { dataFormatter.organizationsOneListFormatter(item.organization) }

    -
    - - - - -
    -

    CampusName

    -

    { item.name }

    -
    - - - - -
    -

    CampusCode

    -

    { item.code }

    -
    - - - - -
    -

    Address

    -

    { item.address }

    -
    - - - - -
    -

    Phone

    -

    { item.phone }

    -
    - - - - -
    -

    Email

    -

    { item.email }

    -
    - - - - -
    -

    Active

    -

    { dataFormatter.booleanFormatter(item.active) }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && campuses.length === 0 && ( -
    -

    No data to display

    -
    - )} -
    -
    - -
    - - ) -}; - -export default ListCampuses \ No newline at end of file diff --git a/ref-frontend/src/components/Campuses/TableCampuses.tsx b/ref-frontend/src/components/Campuses/TableCampuses.tsx deleted file mode 100644 index ac2785d..0000000 --- a/ref-frontend/src/components/Campuses/TableCampuses.tsx +++ /dev/null @@ -1,463 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/campuses/campusesSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; -import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureCampusesCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - - -const perPage = 10 - -const TableSampleCampuses = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { campuses, loading, count, notify: campusesNotify, refetch } = useAppSelector((state) => state.campuses) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (campusesNotify.showNotification) { - notify(campusesNotify.typeNotification, campusesNotify.textNotification); - } - }, [campusesNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `campuses`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={campuses ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) -} - -export default TableSampleCampuses diff --git a/ref-frontend/src/components/Campuses/configureCampusesCols.tsx b/ref-frontend/src/components/Campuses/configureCampusesCols.tsx deleted file mode 100644 index a45a92b..0000000 --- a/ref-frontend/src/components/Campuses/configureCampusesCols.tsx +++ /dev/null @@ -1,181 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; -import ListActionsPopover from '../ListActionsPopover'; - -import {hasPermission} from "../../helpers/userPermissions"; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - -) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_CAMPUSES') - - return [ - - { - field: 'organization', - headerName: 'Organization', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('organizations'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'name', - headerName: 'CampusName', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'code', - headerName: 'CampusCode', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'address', - headerName: 'Address', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'phone', - headerName: 'Phone', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'email', - headerName: 'Email', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'active', - headerName: 'Active', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; -}; diff --git a/ref-frontend/src/components/CardBox.tsx b/ref-frontend/src/components/CardBox.tsx deleted file mode 100644 index 988f162..0000000 --- a/ref-frontend/src/components/CardBox.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React, { ReactNode } from 'react' -import CardBoxComponentBody from './CardBoxComponentBody' -import CardBoxComponentFooter from './CardBoxComponentFooter' -import { useAppSelector } from '../stores/hooks'; - -type Props = { - rounded?: string - flex?: string - className?: string - hasComponentLayout?: boolean - cardBoxClassName?: string - hasTable?: boolean - isHoverable?: boolean - isModal?: boolean - children?: ReactNode - footer?: ReactNode - isList?:boolean - id?: string; - onClick?: (e: React.MouseEvent) => void -} - -export default function CardBox({ - rounded = 'rounded', - flex = 'flex-col', - className = '', - hasComponentLayout = false, - cardBoxClassName = '', - hasTable = false, - isHoverable = false, - isList = false, - isModal = false, - children, - footer, - id ='', - onClick, -}: Props) { - const corners = useAppSelector((state) => state.style.corners); - const cardsStyle = useAppSelector((state) => state.style.cardsStyle); - const componentClass = [ - `flex dark:border-dark-700 dark:bg-dark-900`, - className, - corners !== 'rounded-full'? corners : 'rounded-3xl', - flex, - isList ? '' : `${cardsStyle}`, - hasTable ? '' : `border-dark-700 dark:border-dark-700`, - ] - - if (isHoverable) { - componentClass.push('hover:shadow-lg transition-shadow duration-500') - } - - return React.createElement( - 'div', - { className: componentClass.join(' '), onClick }, - hasComponentLayout ? ( - children - ) : ( - <> - {children} - {footer && {footer}} - - ) - ) -} diff --git a/ref-frontend/src/components/CardBoxComponentBody.tsx b/ref-frontend/src/components/CardBoxComponentBody.tsx deleted file mode 100644 index 4c89896..0000000 --- a/ref-frontend/src/components/CardBoxComponentBody.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React, { ReactNode } from 'react' - -type Props = { - noPadding?: boolean - className?: string - children?: ReactNode - id?: string -} - -export default function CardBoxComponentBody({ noPadding = false, className, children, id }: Props) { - return
    {children}
    -} diff --git a/ref-frontend/src/components/CardBoxComponentEmpty.tsx b/ref-frontend/src/components/CardBoxComponentEmpty.tsx deleted file mode 100644 index c71413e..0000000 --- a/ref-frontend/src/components/CardBoxComponentEmpty.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' - -const CardBoxComponentEmpty = () => { - return ( -
    -

    Nothing's here…

    -
    - ) -} - -export default CardBoxComponentEmpty diff --git a/ref-frontend/src/components/CardBoxComponentFooter.tsx b/ref-frontend/src/components/CardBoxComponentFooter.tsx deleted file mode 100644 index 4b41bba..0000000 --- a/ref-frontend/src/components/CardBoxComponentFooter.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React, { ReactNode } from 'react' - -type Props = { - className?: string - children?: ReactNode -} - -export default function CardBoxComponentFooter({ className, children }: Props) { - return
    {children}
    -} diff --git a/ref-frontend/src/components/CardBoxComponentTitle.tsx b/ref-frontend/src/components/CardBoxComponentTitle.tsx deleted file mode 100644 index 4f92def..0000000 --- a/ref-frontend/src/components/CardBoxComponentTitle.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { ReactNode } from 'react' - -type Props = { - title: string - children?: ReactNode -} - -const CardBoxComponentTitle = ({ title, children }: Props) => { - return ( -
    -

    {title}

    - {children} -
    - ) -} - -export default CardBoxComponentTitle diff --git a/ref-frontend/src/components/CardBoxModal.tsx b/ref-frontend/src/components/CardBoxModal.tsx deleted file mode 100644 index 27d5676..0000000 --- a/ref-frontend/src/components/CardBoxModal.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { mdiClose } from '@mdi/js' -import { ReactNode } from 'react' -import type { ColorButtonKey } from '../interfaces' -import BaseButton from './BaseButton' -import BaseButtons from './BaseButtons' -import CardBox from './CardBox' -import CardBoxComponentTitle from './CardBoxComponentTitle' -import OverlayLayer from './OverlayLayer' - -type Props = { - title: string - buttonColor: ColorButtonKey - buttonLabel: string - isActive: boolean - children?: ReactNode - onConfirm: () => void - onCancel?: () => void -} - -const CardBoxModal = ({ - title, - buttonColor, - buttonLabel, - isActive, - children, - onConfirm, - onCancel, -}: Props) => { - if (!isActive) { - return null - } - - const footer = ( - - - {!!onCancel && } - - ) - - return ( - - - - {!!onCancel && ( - - )} - - -
    {children}
    -
    -
    - ) -} - -export default CardBoxModal diff --git a/ref-frontend/src/components/ChartLineSample/config.ts b/ref-frontend/src/components/ChartLineSample/config.ts deleted file mode 100644 index cacce92..0000000 --- a/ref-frontend/src/components/ChartLineSample/config.ts +++ /dev/null @@ -1,54 +0,0 @@ -export const chartColors = { - default: { - primary: '#00D1B2', - info: '#209CEE', - danger: '#FF3860', - }, -} - -const randomChartData = (n: number) => { - const data = [] - - for (let i = 0; i < n; i++) { - data.push(Math.round(Math.random() * 200)) - } - - return data -} - -const datasetObject = (color: string, points: number) => { - return { - fill: false, - borderColor: chartColors.default[color], - borderWidth: 2, - borderDash: [], - borderDashOffset: 0.0, - pointBackgroundColor: chartColors.default[color], - pointBorderColor: 'rgba(255,255,255,0)', - pointHoverBackgroundColor: chartColors.default[color], - pointBorderWidth: 20, - pointHoverRadius: 4, - pointHoverBorderWidth: 15, - pointRadius: 4, - data: randomChartData(points), - tension: 0.5, - cubicInterpolationMode: 'default', - } -} - -export const sampleChartData = (points = 9) => { - const labels = [] - - for (let i = 1; i <= points; i++) { - labels.push(`0${i}`) - } - - return { - labels, - datasets: [ - datasetObject('primary', points), - datasetObject('info', points), - datasetObject('danger', points), - ], - } -} diff --git a/ref-frontend/src/components/ChartLineSample/index.tsx b/ref-frontend/src/components/ChartLineSample/index.tsx deleted file mode 100644 index c7f417b..0000000 --- a/ref-frontend/src/components/ChartLineSample/index.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react' -import { - Chart, - LineElement, - PointElement, - LineController, - LinearScale, - CategoryScale, - Tooltip, -} from 'chart.js' -import { Line } from 'react-chartjs-2' - -Chart.register(LineElement, PointElement, LineController, LinearScale, CategoryScale, Tooltip) - -const options = { - responsive: true, - maintainAspectRatio: false, - scales: { - y: { - display: false, - }, - x: { - display: true, - }, - }, - plugins: { - legend: { - display: false, - }, - }, -} - -const ChartLineSample = ({ data }) => { - return -} - -export default ChartLineSample diff --git a/ref-frontend/src/components/Class_enrollments/CardClass_enrollments.tsx b/ref-frontend/src/components/Class_enrollments/CardClass_enrollments.tsx deleted file mode 100644 index 62e9861..0000000 --- a/ref-frontend/src/components/Class_enrollments/CardClass_enrollments.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - class_enrollments: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardClass_enrollments = ({ - class_enrollments, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_CLASS_ENROLLMENTS') - - - return ( -
    - {loading && } -
      - {!loading && class_enrollments.map((item, index) => ( -
    • - -
      - - - {item.status} - - - -
      - -
      -
      -
      - - -
      -
      Organization
      -
      -
      - { dataFormatter.organizationsOneListFormatter(item.organization) } -
      -
      -
      - - - - -
      -
      Class
      -
      -
      - { dataFormatter.classesOneListFormatter(item.class) } -
      -
      -
      - - - - -
      -
      Student
      -
      -
      - { dataFormatter.studentsOneListFormatter(item.student) } -
      -
      -
      - - - - -
      -
      EnrolledOn
      -
      -
      - { dataFormatter.dateTimeFormatter(item.enrolled_on) } -
      -
      -
      - - - - -
      -
      EndedOn
      -
      -
      - { dataFormatter.dateTimeFormatter(item.ended_on) } -
      -
      -
      - - - - -
      -
      Status
      -
      -
      - { item.status } -
      -
      -
      - - - -
      -
    • - ))} - {!loading && class_enrollments.length === 0 && ( -
      -

      No data to display

      -
      - )} -
    -
    - -
    -
    - ); -}; - -export default CardClass_enrollments; diff --git a/ref-frontend/src/components/Class_enrollments/ListClass_enrollments.tsx b/ref-frontend/src/components/Class_enrollments/ListClass_enrollments.tsx deleted file mode 100644 index 8e0898b..0000000 --- a/ref-frontend/src/components/Class_enrollments/ListClass_enrollments.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - class_enrollments: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListClass_enrollments = ({ class_enrollments, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_CLASS_ENROLLMENTS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
    - {loading && } - {!loading && class_enrollments.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Organization

    -

    { dataFormatter.organizationsOneListFormatter(item.organization) }

    -
    - - - - -
    -

    Class

    -

    { dataFormatter.classesOneListFormatter(item.class) }

    -
    - - - - -
    -

    Student

    -

    { dataFormatter.studentsOneListFormatter(item.student) }

    -
    - - - - -
    -

    EnrolledOn

    -

    { dataFormatter.dateTimeFormatter(item.enrolled_on) }

    -
    - - - - -
    -

    EndedOn

    -

    { dataFormatter.dateTimeFormatter(item.ended_on) }

    -
    - - - - -
    -

    Status

    -

    { item.status }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && class_enrollments.length === 0 && ( -
    -

    No data to display

    -
    - )} -
    -
    - -
    - - ) -}; - -export default ListClass_enrollments \ No newline at end of file diff --git a/ref-frontend/src/components/Class_enrollments/TableClass_enrollments.tsx b/ref-frontend/src/components/Class_enrollments/TableClass_enrollments.tsx deleted file mode 100644 index 99ee9f0..0000000 --- a/ref-frontend/src/components/Class_enrollments/TableClass_enrollments.tsx +++ /dev/null @@ -1,463 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/class_enrollments/class_enrollmentsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; -import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureClass_enrollmentsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - - -const perPage = 10 - -const TableSampleClass_enrollments = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { class_enrollments, loading, count, notify: class_enrollmentsNotify, refetch } = useAppSelector((state) => state.class_enrollments) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (class_enrollmentsNotify.showNotification) { - notify(class_enrollmentsNotify.typeNotification, class_enrollmentsNotify.textNotification); - } - }, [class_enrollmentsNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `class_enrollments`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={class_enrollments ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) -} - -export default TableSampleClass_enrollments diff --git a/ref-frontend/src/components/Class_enrollments/configureClass_enrollmentsCols.tsx b/ref-frontend/src/components/Class_enrollments/configureClass_enrollmentsCols.tsx deleted file mode 100644 index d0288b7..0000000 --- a/ref-frontend/src/components/Class_enrollments/configureClass_enrollmentsCols.tsx +++ /dev/null @@ -1,185 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; -import ListActionsPopover from '../ListActionsPopover'; - -import {hasPermission} from "../../helpers/userPermissions"; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - -) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_CLASS_ENROLLMENTS') - - return [ - - { - field: 'organization', - headerName: 'Organization', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('organizations'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'class', - headerName: 'Class', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('classes'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'student', - headerName: 'Student', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('students'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'enrolled_on', - headerName: 'EnrolledOn', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.enrolled_on), - - }, - - { - field: 'ended_on', - headerName: 'EndedOn', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.ended_on), - - }, - - { - field: 'status', - headerName: 'Status', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; -}; diff --git a/ref-frontend/src/components/Class_subjects/CardClass_subjects.tsx b/ref-frontend/src/components/Class_subjects/CardClass_subjects.tsx deleted file mode 100644 index e5dea2b..0000000 --- a/ref-frontend/src/components/Class_subjects/CardClass_subjects.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - class_subjects: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardClass_subjects = ({ - class_subjects, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_CLASS_SUBJECTS') - - - return ( -
    - {loading && } -
      - {!loading && class_subjects.map((item, index) => ( -
    • - -
      - - - {item.status} - - - -
      - -
      -
      -
      - - -
      -
      Organization
      -
      -
      - { dataFormatter.organizationsOneListFormatter(item.organization) } -
      -
      -
      - - - - -
      -
      Class
      -
      -
      - { dataFormatter.classesOneListFormatter(item.class) } -
      -
      -
      - - - - -
      -
      Subject
      -
      -
      - { dataFormatter.subjectsOneListFormatter(item.subject) } -
      -
      -
      - - - - -
      -
      Teacher
      -
      -
      - { dataFormatter.staffOneListFormatter(item.teacher) } -
      -
      -
      - - - - -
      -
      Status
      -
      -
      - { item.status } -
      -
      -
      - - - -
      -
    • - ))} - {!loading && class_subjects.length === 0 && ( -
      -

      No data to display

      -
      - )} -
    -
    - -
    -
    - ); -}; - -export default CardClass_subjects; diff --git a/ref-frontend/src/components/Class_subjects/ListClass_subjects.tsx b/ref-frontend/src/components/Class_subjects/ListClass_subjects.tsx deleted file mode 100644 index 576c3be..0000000 --- a/ref-frontend/src/components/Class_subjects/ListClass_subjects.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - class_subjects: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListClass_subjects = ({ class_subjects, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_CLASS_SUBJECTS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
    - {loading && } - {!loading && class_subjects.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Organization

    -

    { dataFormatter.organizationsOneListFormatter(item.organization) }

    -
    - - - - -
    -

    Class

    -

    { dataFormatter.classesOneListFormatter(item.class) }

    -
    - - - - -
    -

    Subject

    -

    { dataFormatter.subjectsOneListFormatter(item.subject) }

    -
    - - - - -
    -

    Teacher

    -

    { dataFormatter.staffOneListFormatter(item.teacher) }

    -
    - - - - -
    -

    Status

    -

    { item.status }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && class_subjects.length === 0 && ( -
    -

    No data to display

    -
    - )} -
    -
    - -
    - - ) -}; - -export default ListClass_subjects \ No newline at end of file diff --git a/ref-frontend/src/components/Class_subjects/TableClass_subjects.tsx b/ref-frontend/src/components/Class_subjects/TableClass_subjects.tsx deleted file mode 100644 index 05f9253..0000000 --- a/ref-frontend/src/components/Class_subjects/TableClass_subjects.tsx +++ /dev/null @@ -1,463 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/class_subjects/class_subjectsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; -import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureClass_subjectsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - - -const perPage = 10 - -const TableSampleClass_subjects = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { class_subjects, loading, count, notify: class_subjectsNotify, refetch } = useAppSelector((state) => state.class_subjects) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (class_subjectsNotify.showNotification) { - notify(class_subjectsNotify.typeNotification, class_subjectsNotify.textNotification); - } - }, [class_subjectsNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `class_subjects`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={class_subjects ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) -} - -export default TableSampleClass_subjects diff --git a/ref-frontend/src/components/Class_subjects/configureClass_subjectsCols.tsx b/ref-frontend/src/components/Class_subjects/configureClass_subjectsCols.tsx deleted file mode 100644 index a5d2c29..0000000 --- a/ref-frontend/src/components/Class_subjects/configureClass_subjectsCols.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; -import ListActionsPopover from '../ListActionsPopover'; - -import {hasPermission} from "../../helpers/userPermissions"; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - -) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_CLASS_SUBJECTS') - - return [ - - { - field: 'organization', - headerName: 'Organization', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('organizations'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'class', - headerName: 'Class', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('classes'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'subject', - headerName: 'Subject', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('subjects'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'teacher', - headerName: 'Teacher', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('staff'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'status', - headerName: 'Status', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; -}; diff --git a/ref-frontend/src/components/Classes/CardClasses.tsx b/ref-frontend/src/components/Classes/CardClasses.tsx deleted file mode 100644 index 8d21173..0000000 --- a/ref-frontend/src/components/Classes/CardClasses.tsx +++ /dev/null @@ -1,207 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - classes: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardClasses = ({ - classes, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_CLASSES') - - - return ( -
    - {loading && } -
      - {!loading && classes.map((item, index) => ( -
    • - -
      - - - {item.name} - - - -
      - -
      -
      -
      - - -
      -
      Organization
      -
      -
      - { dataFormatter.organizationsOneListFormatter(item.organization) } -
      -
      -
      - - - - -
      -
      Campus
      -
      -
      - { dataFormatter.campusesOneListFormatter(item.campus) } -
      -
      -
      - - - - -
      -
      AcademicYear
      -
      -
      - { dataFormatter.academic_yearsOneListFormatter(item.academic_year) } -
      -
      -
      - - - - -
      -
      Grade
      -
      -
      - { dataFormatter.gradesOneListFormatter(item.grade) } -
      -
      -
      - - - - -
      -
      ClassName
      -
      -
      - { item.name } -
      -
      -
      - - - - -
      -
      Section
      -
      -
      - { item.section } -
      -
      -
      - - - - -
      -
      HomeroomTeacher
      -
      -
      - { dataFormatter.staffOneListFormatter(item.homeroom_teacher) } -
      -
      -
      - - - - -
      -
      Capacity
      -
      -
      - { item.capacity } -
      -
      -
      - - - - -
      -
      Status
      -
      -
      - { item.status } -
      -
      -
      - - - -
      -
    • - ))} - {!loading && classes.length === 0 && ( -
      -

      No data to display

      -
      - )} -
    -
    - -
    -
    - ); -}; - -export default CardClasses; diff --git a/ref-frontend/src/components/Classes/ListClasses.tsx b/ref-frontend/src/components/Classes/ListClasses.tsx deleted file mode 100644 index 7730d94..0000000 --- a/ref-frontend/src/components/Classes/ListClasses.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - classes: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListClasses = ({ classes, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_CLASSES') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
    - {loading && } - {!loading && classes.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Organization

    -

    { dataFormatter.organizationsOneListFormatter(item.organization) }

    -
    - - - - -
    -

    Campus

    -

    { dataFormatter.campusesOneListFormatter(item.campus) }

    -
    - - - - -
    -

    AcademicYear

    -

    { dataFormatter.academic_yearsOneListFormatter(item.academic_year) }

    -
    - - - - -
    -

    Grade

    -

    { dataFormatter.gradesOneListFormatter(item.grade) }

    -
    - - - - -
    -

    ClassName

    -

    { item.name }

    -
    - - - - -
    -

    Section

    -

    { item.section }

    -
    - - - - -
    -

    HomeroomTeacher

    -

    { dataFormatter.staffOneListFormatter(item.homeroom_teacher) }

    -
    - - - - -
    -

    Capacity

    -

    { item.capacity }

    -
    - - - - -
    -

    Status

    -

    { item.status }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && classes.length === 0 && ( -
    -

    No data to display

    -
    - )} -
    -
    - -
    - - ) -}; - -export default ListClasses \ No newline at end of file diff --git a/ref-frontend/src/components/Classes/TableClasses.tsx b/ref-frontend/src/components/Classes/TableClasses.tsx deleted file mode 100644 index 6a3404f..0000000 --- a/ref-frontend/src/components/Classes/TableClasses.tsx +++ /dev/null @@ -1,463 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/classes/classesSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; -import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureClassesCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - - -const perPage = 10 - -const TableSampleClasses = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { classes, loading, count, notify: classesNotify, refetch } = useAppSelector((state) => state.classes) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (classesNotify.showNotification) { - notify(classesNotify.typeNotification, classesNotify.textNotification); - } - }, [classesNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `classes`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={classes ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) -} - -export default TableSampleClasses diff --git a/ref-frontend/src/components/Classes/configureClassesCols.tsx b/ref-frontend/src/components/Classes/configureClassesCols.tsx deleted file mode 100644 index b826bf9..0000000 --- a/ref-frontend/src/components/Classes/configureClassesCols.tsx +++ /dev/null @@ -1,239 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; -import ListActionsPopover from '../ListActionsPopover'; - -import {hasPermission} from "../../helpers/userPermissions"; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - -) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_CLASSES') - - return [ - - { - field: 'organization', - headerName: 'Organization', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('organizations'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'campus', - headerName: 'Campus', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('campuses'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'academic_year', - headerName: 'AcademicYear', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('academic_years'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'grade', - headerName: 'Grade', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('grades'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'name', - headerName: 'ClassName', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'section', - headerName: 'Section', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'homeroom_teacher', - headerName: 'HomeroomTeacher', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('staff'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'capacity', - headerName: 'Capacity', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'status', - headerName: 'Status', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; -}; diff --git a/ref-frontend/src/components/ClickOutside.tsx b/ref-frontend/src/components/ClickOutside.tsx deleted file mode 100644 index 1673e29..0000000 --- a/ref-frontend/src/components/ClickOutside.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React, { useCallback, useEffect, useRef, ReactNode, MutableRefObject } from 'react'; - -interface ClickOutsideProps { - children?: ReactNode; - onClickOutside: () => void; - excludedElements: MutableRefObject[]; -} - -const ClickOutside = ({ children, onClickOutside, excludedElements }: ClickOutsideProps) => { - const wrapperRef = useRef(null); - - const handleClickOutside = useCallback( - (event) => { - if ( - wrapperRef.current && - !wrapperRef.current.contains(event.target) && - !excludedElements.some((el) => el.current.contains(event.target)) - ) { - onClickOutside(); - } - }, - [wrapperRef, onClickOutside, ...excludedElements], - ); - - useEffect(() => { - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [handleClickOutside]); - - return
    {children}
    ; -}; - -export default ClickOutside; diff --git a/ref-frontend/src/components/DataGridMultiSelect.tsx b/ref-frontend/src/components/DataGridMultiSelect.tsx deleted file mode 100644 index bb82434..0000000 --- a/ref-frontend/src/components/DataGridMultiSelect.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { GridRenderEditCellParams, useGridApiContext } from '@mui/x-data-grid'; -import React, { useEffect, useState } from 'react'; -import axios from 'axios'; -import { MenuItem, Select } from '@mui/material'; - -interface Props { - entityName: string; -} - -const DataGridMultiSelect = (props: GridRenderEditCellParams & Props) => { - const { id, value, field, entityName } = props; - const apiRef = useGridApiContext(); - const [options, setOptions] = useState([]); - - async function callApi(entityName: string) { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } - - useEffect(() => { - callApi(entityName).then((data) => { - setOptions(data); - }); - }, []); - - const handleChange = (event) => { - const eventValue = event.target.value; // The new value entered by the user - - const newValue = - typeof eventValue === 'string' ? value.split(',') : eventValue; - - apiRef.current.setEditCellValue({ - id, - field, - value: newValue.filter((x) => x !== ''), - }); - }; - - return ( - - ); -}; - -export default DataGridMultiSelect; diff --git a/ref-frontend/src/components/DevModeBadge.tsx b/ref-frontend/src/components/DevModeBadge.tsx deleted file mode 100644 index bc238c4..0000000 --- a/ref-frontend/src/components/DevModeBadge.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import useDevCompilationStatus from '../hooks/useDevCompilationStatus'; -const DevModeBadge: React.FC = () => { - const [isVisible, setIsVisible] = useState(false); - const [isCollapsed, setIsCollapsed] = useState(true); - const compilationStatus = useDevCompilationStatus(); - - const [badgeStyles, setBadgeStyles] = useState({ - position: 'fixed', - bottom: '20px', - left: '70px', - background: 'rgba(0, 0, 0, 0.85)', - color: 'white', - padding: '15px', - borderRadius: '8px', - fontFamily: 'sans-serif', - fontSize: '14px', - lineHeight: '1.5', - textAlign: 'left', - zIndex: 2147483647, - boxShadow: '0 4px 10px rgba(0, 0, 0, 0.3)', - whiteSpace: 'pre-wrap', - transition: 'width 0.3s cubic-bezier(0.25, 0.1, 0.25, 1), padding 0.3s ease-in-out, opacity 0.3s ease-in-out, background-color 0.3s ease-in-out', // Improved transition for width - opacity: 0, - pointerEvents: 'none', - width: '340px', - maxWidth: '340px', - height: 'auto', - overflow: 'hidden', - cursor: 'pointer', - }); - - const fullText = `🚧 Your app is running in development mode. -Current request is compiling and may take a few moments. - -💡 Tip: Set up a stable environment to run your app in production mode—pages will load instantly without compilation delays.`; - - const collapsedText = '🚧 DEV stage'; - - useEffect(() => { - if (compilationStatus === 'ready') { - setIsCollapsed(true); - } else { - setIsCollapsed(false); - } - - }, [compilationStatus]); - - useEffect(() => { - if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'dev_stage') { - setIsVisible(true); - - setBadgeStyles(prev => ({ - ...prev, - opacity: 1, - width: '120px', - maxWidth: '120px', - padding: '6px 10px', - borderRadius: '18px', - whiteSpace: 'nowrap', - fontSize: '12px', - cursor: 'pointer', - pointerEvents: 'auto', - })); - - } else { - setIsVisible(false); - setBadgeStyles(prev => ({ ...prev, opacity: 0 })); - } - }, []); - - useEffect(() => { - if (!isVisible) return; - - if (isCollapsed) { - setBadgeStyles(prev => ({ - ...prev, - width: '140px', - maxWidth: '160px', - padding: '6px 20px', - borderRadius: '18px', - whiteSpace: 'nowrap', - fontSize: '12px', - })); - } else { - setBadgeStyles(prev => ({ - ...prev, - width: '340px', - maxWidth: '340px', - padding: '15px', - borderRadius: '8px', - whiteSpace: 'pre-wrap', - fontSize: '14px', - })); - } - }, [isCollapsed, isVisible]); - - const handleToggleCollapse = (e: React.MouseEvent) => { - e.stopPropagation(); - setIsCollapsed(prev => !prev); - }; - - if (!isVisible) { - return null; - } - - return ( -
    - - - {!isCollapsed && ( -
    - {fullText} -
    - )} - {isCollapsed && ( -
    - {collapsedText} -
    - )} -
    - ); -}; - -export default DevModeBadge; diff --git a/ref-frontend/src/components/Documents/CardDocuments.tsx b/ref-frontend/src/components/Documents/CardDocuments.tsx deleted file mode 100644 index cd4a0de..0000000 --- a/ref-frontend/src/components/Documents/CardDocuments.tsx +++ /dev/null @@ -1,214 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - documents: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardDocuments = ({ - documents, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_DOCUMENTS') - - - return ( -
    - {loading && } -
      - {!loading && documents.map((item, index) => ( -
    • - -
      - - - {item.name} - - - -
      - -
      -
      -
      - - -
      -
      Organization
      -
      -
      - { dataFormatter.organizationsOneListFormatter(item.organization) } -
      -
      -
      - - - - -
      -
      Campus
      -
      -
      - { dataFormatter.campusesOneListFormatter(item.campus) } -
      -
      -
      - - - - -
      -
      EntityType
      -
      -
      - { item.entity_type } -
      -
      -
      - - - - -
      -
      EntityReference
      -
      -
      - { item.entity_reference } -
      -
      -
      - - - - -
      -
      DocumentName
      -
      -
      - { item.name } -
      -
      -
      - - - - -
      -
      Category
      -
      -
      - { item.category } -
      -
      -
      - - - - -
      -
      File
      -
      -
      - {dataFormatter.filesFormatter(item.file).map(link => ( - - ))} -
      -
      -
      - - - - -
      -
      UploadedAt
      -
      -
      - { dataFormatter.dateTimeFormatter(item.uploaded_at) } -
      -
      -
      - - - - -
      -
      Notes
      -
      -
      - { item.notes } -
      -
      -
      - - - -
      -
    • - ))} - {!loading && documents.length === 0 && ( -
      -

      No data to display

      -
      - )} -
    -
    - -
    -
    - ); -}; - -export default CardDocuments; diff --git a/ref-frontend/src/components/Documents/ListDocuments.tsx b/ref-frontend/src/components/Documents/ListDocuments.tsx deleted file mode 100644 index 35cc6cc..0000000 --- a/ref-frontend/src/components/Documents/ListDocuments.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - documents: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListDocuments = ({ documents, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_DOCUMENTS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
    - {loading && } - {!loading && documents.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Organization

    -

    { dataFormatter.organizationsOneListFormatter(item.organization) }

    -
    - - - - -
    -

    Campus

    -

    { dataFormatter.campusesOneListFormatter(item.campus) }

    -
    - - - - -
    -

    EntityType

    -

    { item.entity_type }

    -
    - - - - -
    -

    EntityReference

    -

    { item.entity_reference }

    -
    - - - - -
    -

    DocumentName

    -

    { item.name }

    -
    - - - - -
    -

    Category

    -

    { item.category }

    -
    - - - - -
    -

    File

    - {dataFormatter.filesFormatter(item.file).map(link => ( - - ))} -
    - - - - -
    -

    UploadedAt

    -

    { dataFormatter.dateTimeFormatter(item.uploaded_at) }

    -
    - - - - -
    -

    Notes

    -

    { item.notes }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && documents.length === 0 && ( -
    -

    No data to display

    -
    - )} -
    -
    - -
    - - ) -}; - -export default ListDocuments \ No newline at end of file diff --git a/ref-frontend/src/components/Documents/TableDocuments.tsx b/ref-frontend/src/components/Documents/TableDocuments.tsx deleted file mode 100644 index c70443a..0000000 --- a/ref-frontend/src/components/Documents/TableDocuments.tsx +++ /dev/null @@ -1,463 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/documents/documentsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; -import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureDocumentsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - - -const perPage = 10 - -const TableSampleDocuments = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { documents, loading, count, notify: documentsNotify, refetch } = useAppSelector((state) => state.documents) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (documentsNotify.showNotification) { - notify(documentsNotify.typeNotification, documentsNotify.textNotification); - } - }, [documentsNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `documents`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={documents ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) -} - -export default TableSampleDocuments diff --git a/ref-frontend/src/components/Documents/configureDocumentsCols.tsx b/ref-frontend/src/components/Documents/configureDocumentsCols.tsx deleted file mode 100644 index 1de8316..0000000 --- a/ref-frontend/src/components/Documents/configureDocumentsCols.tsx +++ /dev/null @@ -1,231 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; -import ListActionsPopover from '../ListActionsPopover'; - -import {hasPermission} from "../../helpers/userPermissions"; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - -) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_DOCUMENTS') - - return [ - - { - field: 'organization', - headerName: 'Organization', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('organizations'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'campus', - headerName: 'Campus', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('campuses'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'entity_type', - headerName: 'EntityType', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'entity_reference', - headerName: 'EntityReference', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'name', - headerName: 'DocumentName', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'category', - headerName: 'Category', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'file', - headerName: 'File', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: false, - sortable: false, - renderCell: (params: GridValueGetterParams) => ( - <> - {dataFormatter.filesFormatter(params.row.file).map(link => ( - - ))} - - ), - - }, - - { - field: 'uploaded_at', - headerName: 'UploadedAt', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.uploaded_at), - - }, - - { - field: 'notes', - headerName: 'Notes', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; -}; diff --git a/ref-frontend/src/components/DragDropFilePicker.tsx b/ref-frontend/src/components/DragDropFilePicker.tsx deleted file mode 100644 index 821570d..0000000 --- a/ref-frontend/src/components/DragDropFilePicker.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import React, { ChangeEvent, useEffect, useState } from 'react'; -import BaseIcon from './BaseIcon'; -import { mdiFileUploadOutline } from '@mdi/js'; - -type Props = { - file: File | null; - setFile: (file: File) => void; - formats?: string; -}; - -const DragDropFilePicker = ({ file, setFile, formats = '' }: Props) => { - const [highlight, setHighlight] = useState(false); - const [errorMessage, setErrorMessage] = useState(''); - const fileInput = React.createRef(); - - useEffect(() => { - if (!file && fileInput) fileInput.current.value = ''; - }, [file, fileInput]); - - function onFilesAdded(files: FileList | null) { - if (files && files[0]) { - const newFile = files[0]; - const fileExtension = newFile.name.split('.').pop().toLowerCase(); - - if (formats.includes(fileExtension) || !formats) { - setFile(newFile); - setErrorMessage(''); - } else { - setErrorMessage(`Allowed formats: ${formats}`); - } - } - } - - function onDragOver(e) { - e.preventDefault(); - setHighlight(true); - } - - function onDragLeave() { - setHighlight(false); - } - - function onDrop(e) { - e.preventDefault(); - - const files = e.dataTransfer.files; - - onFilesAdded(files); - setHighlight(false); - } - - const onClear = () => { - setFile(null); - setErrorMessage(''); - }; - - return ( -
    - -
    - ); -}; - -export default DragDropFilePicker; diff --git a/ref-frontend/src/components/ErrorBoundary.tsx b/ref-frontend/src/components/ErrorBoundary.tsx deleted file mode 100644 index 93b2833..0000000 --- a/ref-frontend/src/components/ErrorBoundary.tsx +++ /dev/null @@ -1,218 +0,0 @@ -import React, { Component, ErrorInfo, ReactNode } from 'react'; -import { mdiAlertCircle } from '@mdi/js'; -import BaseIcon from './BaseIcon'; - -// Define the props and state interfaces -interface ErrorBoundaryProps { - children: ReactNode; -} - -interface ErrorBoundaryState { - hasError: boolean; - error: Error | null; - errorInfo: ErrorInfo | null; - showStack: boolean; -} - -// Class-based ErrorBoundary Component -class ErrorBoundary extends Component { - constructor(props: ErrorBoundaryProps) { - super(props); - - // Define state variables - this.state = { - hasError: false, - error: null, - errorInfo: null, - showStack: false, - }; - } - - static getDerivedStateFromError(error: Error): Partial { - // Update state so the next render will show the fallback UI - return { - hasError: true, - error: error, - }; - } - - componentDidUpdate( - prevProps: Readonly, - prevState: Readonly, - snapshot?: any, - ) { - if (process.env.NODE_ENV !== 'production') { - console.log('componentDidUpdate'); - } - } - - async componentWillUnmount() { - if (process.env.NODE_ENV !== 'production') { - console.log('componentWillUnmount'); - const response = await fetch('/api/logError', { - method: 'DELETE', - }); - - const data = await response.json(); - console.log('Error logs cleared:', data); - } - } - - async componentDidCatch(error: Error, errorInfo: ErrorInfo) { - // Update state with error details (always needed for UI) - this.setState({ - errorInfo: errorInfo, - }); - - // Only perform logging in non-production environments - if (process.env.NODE_ENV !== 'production') { - console.log('Error caught in boundary:', error, errorInfo); - - // Function to log errors to the server - const logErrorToServer = async () => { - try { - const response = await fetch('/api/logError', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - message: error.message, - stack: errorInfo.componentStack, - }), - }); - - const data = await response.json(); - console.log('Error logged:', data); - } catch (err) { - console.error('Failed to log error:', err); - } - }; - - // Function to fetch logged errors (optional) - const fetchLoggedErrors = async () => { - try { - const response = await fetch('/api/logError'); - const data = await response.json(); - console.log('Fetched logs:', data); - } catch (err) { - console.error('Failed to fetch logs:', err); - } - }; - - await logErrorToServer(); - await fetchLoggedErrors(); - } - } - - toggleStack = () => { - this.setState((prevState) => ({ - showStack: !prevState.showStack, - })); - }; - - resetError = () => { - this.setState({ - hasError: false, - error: null, - errorInfo: null, - showStack: false, - }); - }; - - tryAgain = async () => { - // Only clear error logs in non-production environments - if (process.env.NODE_ENV !== 'production') { - try { - const response = await fetch('/api/logError', { - method: 'DELETE', - }); - - const data = await response.json(); - console.log('Error logs cleared:', data); - } catch (e) { - console.error('Failed to clear error logs:', e); - } - } - - // Always reset the error state (needed for UI recovery) - this.setState({ hasError: false }); - }; - - render() { - if (this.state.hasError) { - // Extract error details - const { error, errorInfo, showStack } = this.state; - const errorMessage = error?.message || 'An unexpected error occurred'; - const stackTrace = - errorInfo?.componentStack || error?.stack || 'No stack trace available'; - - return ( -
    -
    -
    -
    - -
    - -
    -

    - Something went wrong -

    -

    - We're sorry, but we encountered an unexpected error. -

    -
    - -
    -

    - {errorMessage} -

    - -
    - - - {showStack && ( -
    -                      {stackTrace}
    -                    
    - )} -
    -
    - -
    - - - -
    -
    -
    -
    - ); - } - - return this.props.children; - } -} - -export default ErrorBoundary; diff --git a/ref-frontend/src/components/Fee_plans/CardFee_plans.tsx b/ref-frontend/src/components/Fee_plans/CardFee_plans.tsx deleted file mode 100644 index c8b35cd..0000000 --- a/ref-frontend/src/components/Fee_plans/CardFee_plans.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - fee_plans: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardFee_plans = ({ - fee_plans, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_FEE_PLANS') - - - return ( -
    - {loading && } -
      - {!loading && fee_plans.map((item, index) => ( -
    • - -
      - - - {item.name} - - - -
      - -
      -
      -
      - - -
      -
      Organization
      -
      -
      - { dataFormatter.organizationsOneListFormatter(item.organization) } -
      -
      -
      - - - - -
      -
      AcademicYear
      -
      -
      - { dataFormatter.academic_yearsOneListFormatter(item.academic_year) } -
      -
      -
      - - - - -
      -
      Grade
      -
      -
      - { dataFormatter.gradesOneListFormatter(item.grade) } -
      -
      -
      - - - - -
      -
      FeePlanName
      -
      -
      - { item.name } -
      -
      -
      - - - - -
      -
      BillingCycle
      -
      -
      - { item.billing_cycle } -
      -
      -
      - - - - -
      -
      TotalAmount
      -
      -
      - { item.total_amount } -
      -
      -
      - - - - -
      -
      Active
      -
      -
      - { dataFormatter.booleanFormatter(item.active) } -
      -
      -
      - - - - -
      -
      Notes
      -
      -
      - { item.notes } -
      -
      -
      - - - -
      -
    • - ))} - {!loading && fee_plans.length === 0 && ( -
      -

      No data to display

      -
      - )} -
    -
    - -
    -
    - ); -}; - -export default CardFee_plans; diff --git a/ref-frontend/src/components/Fee_plans/ListFee_plans.tsx b/ref-frontend/src/components/Fee_plans/ListFee_plans.tsx deleted file mode 100644 index a09c56c..0000000 --- a/ref-frontend/src/components/Fee_plans/ListFee_plans.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - fee_plans: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListFee_plans = ({ fee_plans, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_FEE_PLANS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
    - {loading && } - {!loading && fee_plans.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Organization

    -

    { dataFormatter.organizationsOneListFormatter(item.organization) }

    -
    - - - - -
    -

    AcademicYear

    -

    { dataFormatter.academic_yearsOneListFormatter(item.academic_year) }

    -
    - - - - -
    -

    Grade

    -

    { dataFormatter.gradesOneListFormatter(item.grade) }

    -
    - - - - -
    -

    FeePlanName

    -

    { item.name }

    -
    - - - - -
    -

    BillingCycle

    -

    { item.billing_cycle }

    -
    - - - - -
    -

    TotalAmount

    -

    { item.total_amount }

    -
    - - - - -
    -

    Active

    -

    { dataFormatter.booleanFormatter(item.active) }

    -
    - - - - -
    -

    Notes

    -

    { item.notes }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && fee_plans.length === 0 && ( -
    -

    No data to display

    -
    - )} -
    -
    - -
    - - ) -}; - -export default ListFee_plans \ No newline at end of file diff --git a/ref-frontend/src/components/Fee_plans/TableFee_plans.tsx b/ref-frontend/src/components/Fee_plans/TableFee_plans.tsx deleted file mode 100644 index b975c5b..0000000 --- a/ref-frontend/src/components/Fee_plans/TableFee_plans.tsx +++ /dev/null @@ -1,463 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/fee_plans/fee_plansSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; -import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureFee_plansCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - - -const perPage = 10 - -const TableSampleFee_plans = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { fee_plans, loading, count, notify: fee_plansNotify, refetch } = useAppSelector((state) => state.fee_plans) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (fee_plansNotify.showNotification) { - notify(fee_plansNotify.typeNotification, fee_plansNotify.textNotification); - } - }, [fee_plansNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `fee_plans`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={fee_plans ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) -} - -export default TableSampleFee_plans diff --git a/ref-frontend/src/components/Fee_plans/configureFee_plansCols.tsx b/ref-frontend/src/components/Fee_plans/configureFee_plansCols.tsx deleted file mode 100644 index d038b1d..0000000 --- a/ref-frontend/src/components/Fee_plans/configureFee_plansCols.tsx +++ /dev/null @@ -1,211 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; -import ListActionsPopover from '../ListActionsPopover'; - -import {hasPermission} from "../../helpers/userPermissions"; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - -) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_FEE_PLANS') - - return [ - - { - field: 'organization', - headerName: 'Organization', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('organizations'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'academic_year', - headerName: 'AcademicYear', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('academic_years'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'grade', - headerName: 'Grade', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('grades'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'name', - headerName: 'FeePlanName', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'billing_cycle', - headerName: 'BillingCycle', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'total_amount', - headerName: 'TotalAmount', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'active', - headerName: 'Active', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - field: 'notes', - headerName: 'Notes', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; -}; diff --git a/ref-frontend/src/components/FooterBar.tsx b/ref-frontend/src/components/FooterBar.tsx deleted file mode 100644 index 0acc9c5..0000000 --- a/ref-frontend/src/components/FooterBar.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React, { ReactNode } from 'react' -import { containerMaxW } from '../config' -import Logo from './Logo' - -type Props = { - children?: ReactNode -} - -export default function FooterBar({ children }: Props) { - const year = new Date().getFullYear() - - return ( - - ) -} diff --git a/ref-frontend/src/components/FormCheckRadio.tsx b/ref-frontend/src/components/FormCheckRadio.tsx deleted file mode 100644 index 7a9bc31..0000000 --- a/ref-frontend/src/components/FormCheckRadio.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { ReactNode } from 'react' - -type Props = { - children: ReactNode - type: 'checkbox' | 'radio' | 'switch' - label?: string - className?: string -} - -const FormCheckRadio = (props: Props) => { - return ( - - ) -} - -export default FormCheckRadio diff --git a/ref-frontend/src/components/FormCheckRadioGroup.tsx b/ref-frontend/src/components/FormCheckRadioGroup.tsx deleted file mode 100644 index 90d98e0..0000000 --- a/ref-frontend/src/components/FormCheckRadioGroup.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Children, cloneElement, ReactElement, ReactNode } from 'react' - -type Props = { - isColumn?: boolean - children: ReactNode -} - -const FormCheckRadioGroup = (props: Props) => { - return ( -
    - {Children.map(props.children, (child: ReactElement) => - cloneElement(child as ReactElement<{ className?: string }>, { - className: `mr-6 mb-3 last:mr-0 ${(child.props as { className?: string }).className || ''}`, - }), - )} -
    - ) -} - -export default FormCheckRadioGroup diff --git a/ref-frontend/src/components/FormField.tsx b/ref-frontend/src/components/FormField.tsx deleted file mode 100644 index 988ac39..0000000 --- a/ref-frontend/src/components/FormField.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { Children, cloneElement, ReactElement, ReactNode } from 'react' -import BaseIcon from './BaseIcon' -import { useAppSelector } from '../stores/hooks'; - -type Props = { - label?: string - labelFor?: string - help?: string - icons?: string[] | null[] - isBorderless?: boolean - isTransparent?: boolean - hasTextareaHeight?: boolean - children: ReactNode - disabled?: boolean - borderButtom?: boolean - diversity?: boolean - websiteBg?: boolean -} - -const FormField = ({ icons = [], ...props }: Props) => { - const childrenCount = Children.count(props.children) - const bgColor = useAppSelector((state) => state.style.cardsColor); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const corners = useAppSelector((state) => state.style.corners); - const bgWebsiteColor = useAppSelector((state) => state.style.bgLayoutColor); - let elementWrapperClass = '' - - switch (childrenCount) { - case 2: - elementWrapperClass = 'grid grid-cols-1 gap-3 md:grid-cols-2' - break - case 3: - elementWrapperClass = 'grid grid-cols-1 gap-3 md:grid-cols-3' - } - - const controlClassName = [ - `px-3 py-2 max-w-full border-gray-300 dark:border-dark-700 ${corners} w-full dark:placeholder-gray-400`, - `${focusRing}`, - props.hasTextareaHeight ? 'h-24' : 'h-12', - props.isBorderless ? 'border-0' : 'border', - props.isTransparent ? 'bg-transparent' : `${props.websiteBg ? ` bg-white` : bgColor} dark:bg-dark-800`, - props.disabled ? 'bg-gray-200 text-gray-100 dark:bg-dark-900 disabled' : '', - props.borderButtom ? `border-0 border-b ${props.diversity ? "border-gray-400" : " placeholder-white border-gray-300/10 border-white "} rounded-none focus:ring-0` : '', - ].join(' '); - - return ( -
    - {props.label && ( - - )} -
    - {Children.map(props.children, (child: ReactElement, index) => ( -
    - {cloneElement(child as ReactElement<{ className?: string }>, { - className: `${controlClassName} ${icons[index] ? 'pl-10' : ''}`, - })} - {icons[index] && ( - - )} -
    - ))} -
    - {props.help && ( -
    {props.help}
    - )} -
    - ) -} - -export default FormField diff --git a/ref-frontend/src/components/FormFilePicker.tsx b/ref-frontend/src/components/FormFilePicker.tsx deleted file mode 100644 index 4302edd..0000000 --- a/ref-frontend/src/components/FormFilePicker.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import {useEffect, useState} from 'react'; -import { ColorButtonKey } from '../interfaces'; -import BaseButton from './BaseButton'; -import FileUploader from "./Uploaders/UploadService"; -import { mdiReload } from '@mdi/js'; -import { useAppSelector } from '../stores/hooks'; - -type Props = { - label?: string; - icon?: string; - accept?: string; - color: ColorButtonKey; - isRoundIcon?: boolean; - path: string; - schema: object; - field: any; - form: any; -}; - -const FormFilePicker = ({ label, icon, accept, color, isRoundIcon, path, - schema, - form, - field, }: Props) => { - const [file, setFile] = useState(null); - const [loading, setLoading] = useState(false); - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - let cornersRight; - if (corners === 'rounded'){ - cornersRight = 'rounded-r' - } else if (corners === 'rounded-lg'){ - cornersRight = 'rounded-r-lg' - }else if (corners === 'rounded-full'){ - cornersRight = 'rounded-r-3xl' - }else{ - cornersRight = '' - } - - useEffect(() => { - if (field.value) { - setFile(field.value[0]); - } - }, [field.value]); - const handleFileChange = async (event) => { - const file = event.currentTarget.files[0]; - setFile(file); - - FileUploader.validate(file, schema); - setLoading(true); - const remoteFile = await FileUploader.upload(path, file, schema); - - form.setFieldValue(field.name, [{ ...remoteFile }]); - setLoading(false); - }; - - const showFilename = !isRoundIcon && file; - - return ( -
    - - {showFilename && !loading && ( -
    - - {file.name} - -
    - )} -
    - ); -}; - -export default FormFilePicker; diff --git a/ref-frontend/src/components/FormImagePicker.tsx b/ref-frontend/src/components/FormImagePicker.tsx deleted file mode 100644 index 7bc34ae..0000000 --- a/ref-frontend/src/components/FormImagePicker.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { useState, useEffect } from 'react'; -import { ColorButtonKey } from '../interfaces'; -import BaseButton from './BaseButton'; -import ImagesUploader from "./Uploaders/ImagesUploader"; -import FileUploader from './Uploaders/UploadService'; -import { mdiReload } from '@mdi/js'; -import { useAppSelector } from '../stores/hooks'; - -type Props = { - label?: string; - icon?: string; - accept?: string; - color: ColorButtonKey; - isRoundIcon?: boolean; - path: string; - schema: object; - field: any, - form: any, -}; - -const FormImagePicker = ({ label, icon, accept, color, isRoundIcon, path, schema, form, field }: Props) => { - const [file, setFile] = useState(null); - const [loading, setLoading] = useState(false); - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - let cornersRight; - if (corners === 'rounded'){ - cornersRight = 'rounded-r' - } else if (corners === 'rounded-lg'){ - cornersRight = 'rounded-r-lg' - }else if (corners === 'rounded-full'){ - cornersRight = 'rounded-r-3xl' - }else{ - cornersRight = '' - } - - useEffect(() => { - if(field.value) { - setFile(field.value[0]) - } - }, [field.value]) - const handleFileChange = async (event) => { - const file = event.currentTarget.files[0] - setFile(file); - - FileUploader.validate(file, schema); - setLoading(true); - const remoteFile = await FileUploader.upload(path, file, schema); - - form.setFieldValue(field.name, [{...remoteFile}]); - setLoading(false); - }; - - const showFilename = !isRoundIcon && file; - - return ( -
    - - {showFilename && !loading && ( -
    - - {file.name} - -
    - )} -
    - ); -}; - -export default FormImagePicker; diff --git a/ref-frontend/src/components/Grades/CardGrades.tsx b/ref-frontend/src/components/Grades/CardGrades.tsx deleted file mode 100644 index 2379977..0000000 --- a/ref-frontend/src/components/Grades/CardGrades.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - grades: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardGrades = ({ - grades, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_GRADES') - - - return ( -
    - {loading && } -
      - {!loading && grades.map((item, index) => ( -
    • - -
      - - - {item.name} - - - -
      - -
      -
      -
      - - -
      -
      Organization
      -
      -
      - { dataFormatter.organizationsOneListFormatter(item.organization) } -
      -
      -
      - - - - -
      -
      GradeName
      -
      -
      - { item.name } -
      -
      -
      - - - - -
      -
      GradeCode
      -
      -
      - { item.code } -
      -
      -
      - - - - -
      -
      SortOrder
      -
      -
      - { item.sort_order } -
      -
      -
      - - - - -
      -
      Description
      -
      -
      - { item.description } -
      -
      -
      - - - -
      -
    • - ))} - {!loading && grades.length === 0 && ( -
      -

      No data to display

      -
      - )} -
    -
    - -
    -
    - ); -}; - -export default CardGrades; diff --git a/ref-frontend/src/components/Grades/ListGrades.tsx b/ref-frontend/src/components/Grades/ListGrades.tsx deleted file mode 100644 index b244430..0000000 --- a/ref-frontend/src/components/Grades/ListGrades.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - grades: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListGrades = ({ grades, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_GRADES') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
    - {loading && } - {!loading && grades.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Organization

    -

    { dataFormatter.organizationsOneListFormatter(item.organization) }

    -
    - - - - -
    -

    GradeName

    -

    { item.name }

    -
    - - - - -
    -

    GradeCode

    -

    { item.code }

    -
    - - - - -
    -

    SortOrder

    -

    { item.sort_order }

    -
    - - - - -
    -

    Description

    -

    { item.description }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && grades.length === 0 && ( -
    -

    No data to display

    -
    - )} -
    -
    - -
    - - ) -}; - -export default ListGrades \ No newline at end of file diff --git a/ref-frontend/src/components/Grades/TableGrades.tsx b/ref-frontend/src/components/Grades/TableGrades.tsx deleted file mode 100644 index c562ce2..0000000 --- a/ref-frontend/src/components/Grades/TableGrades.tsx +++ /dev/null @@ -1,463 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/grades/gradesSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; -import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureGradesCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - - -const perPage = 10 - -const TableSampleGrades = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { grades, loading, count, notify: gradesNotify, refetch } = useAppSelector((state) => state.grades) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (gradesNotify.showNotification) { - notify(gradesNotify.typeNotification, gradesNotify.textNotification); - } - }, [gradesNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `grades`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={grades ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) -} - -export default TableSampleGrades diff --git a/ref-frontend/src/components/Grades/configureGradesCols.tsx b/ref-frontend/src/components/Grades/configureGradesCols.tsx deleted file mode 100644 index 1e6adb2..0000000 --- a/ref-frontend/src/components/Grades/configureGradesCols.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; -import ListActionsPopover from '../ListActionsPopover'; - -import {hasPermission} from "../../helpers/userPermissions"; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - -) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_GRADES') - - return [ - - { - field: 'organization', - headerName: 'Organization', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('organizations'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'name', - headerName: 'GradeName', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'code', - headerName: 'GradeCode', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'sort_order', - headerName: 'SortOrder', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'description', - headerName: 'Description', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; -}; diff --git a/ref-frontend/src/components/Guardians/CardGuardians.tsx b/ref-frontend/src/components/Guardians/CardGuardians.tsx deleted file mode 100644 index 83df9d3..0000000 --- a/ref-frontend/src/components/Guardians/CardGuardians.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - guardians: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardGuardians = ({ - guardians, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_GUARDIANS') - - - return ( -
    - {loading && } -
      - {!loading && guardians.map((item, index) => ( -
    • - -
      - - - {item.full_name} - - - -
      - -
      -
      -
      - - -
      -
      Organization
      -
      -
      - { dataFormatter.organizationsOneListFormatter(item.organization) } -
      -
      -
      - - - - -
      -
      Student
      -
      -
      - { dataFormatter.studentsOneListFormatter(item.student) } -
      -
      -
      - - - - -
      -
      FullName
      -
      -
      - { item.full_name } -
      -
      -
      - - - - -
      -
      Relationship
      -
      -
      - { item.relationship } -
      -
      -
      - - - - -
      -
      Phone
      -
      -
      - { item.phone } -
      -
      -
      - - - - -
      -
      Email
      -
      -
      - { item.email } -
      -
      -
      - - - - -
      -
      Address
      -
      -
      - { item.address } -
      -
      -
      - - - - -
      -
      PrimaryContact
      -
      -
      - { dataFormatter.booleanFormatter(item.primary_contact) } -
      -
      -
      - - - -
      -
    • - ))} - {!loading && guardians.length === 0 && ( -
      -

      No data to display

      -
      - )} -
    -
    - -
    -
    - ); -}; - -export default CardGuardians; diff --git a/ref-frontend/src/components/Guardians/ListGuardians.tsx b/ref-frontend/src/components/Guardians/ListGuardians.tsx deleted file mode 100644 index 6ff8adc..0000000 --- a/ref-frontend/src/components/Guardians/ListGuardians.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - guardians: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListGuardians = ({ guardians, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_GUARDIANS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
    - {loading && } - {!loading && guardians.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Organization

    -

    { dataFormatter.organizationsOneListFormatter(item.organization) }

    -
    - - - - -
    -

    Student

    -

    { dataFormatter.studentsOneListFormatter(item.student) }

    -
    - - - - -
    -

    FullName

    -

    { item.full_name }

    -
    - - - - -
    -

    Relationship

    -

    { item.relationship }

    -
    - - - - -
    -

    Phone

    -

    { item.phone }

    -
    - - - - -
    -

    Email

    -

    { item.email }

    -
    - - - - -
    -

    Address

    -

    { item.address }

    -
    - - - - -
    -

    PrimaryContact

    -

    { dataFormatter.booleanFormatter(item.primary_contact) }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && guardians.length === 0 && ( -
    -

    No data to display

    -
    - )} -
    -
    - -
    - - ) -}; - -export default ListGuardians \ No newline at end of file diff --git a/ref-frontend/src/components/Guardians/TableGuardians.tsx b/ref-frontend/src/components/Guardians/TableGuardians.tsx deleted file mode 100644 index 29e0ad0..0000000 --- a/ref-frontend/src/components/Guardians/TableGuardians.tsx +++ /dev/null @@ -1,463 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/guardians/guardiansSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; -import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureGuardiansCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - - -const perPage = 10 - -const TableSampleGuardians = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { guardians, loading, count, notify: guardiansNotify, refetch } = useAppSelector((state) => state.guardians) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (guardiansNotify.showNotification) { - notify(guardiansNotify.typeNotification, guardiansNotify.textNotification); - } - }, [guardiansNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `guardians`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={guardians ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) -} - -export default TableSampleGuardians diff --git a/ref-frontend/src/components/Guardians/configureGuardiansCols.tsx b/ref-frontend/src/components/Guardians/configureGuardiansCols.tsx deleted file mode 100644 index 264a3dc..0000000 --- a/ref-frontend/src/components/Guardians/configureGuardiansCols.tsx +++ /dev/null @@ -1,203 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; -import ListActionsPopover from '../ListActionsPopover'; - -import {hasPermission} from "../../helpers/userPermissions"; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - -) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_GUARDIANS') - - return [ - - { - field: 'organization', - headerName: 'Organization', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('organizations'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'student', - headerName: 'Student', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('students'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'full_name', - headerName: 'FullName', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'relationship', - headerName: 'Relationship', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'phone', - headerName: 'Phone', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'email', - headerName: 'Email', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'address', - headerName: 'Address', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'primary_contact', - headerName: 'PrimaryContact', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; -}; diff --git a/ref-frontend/src/components/IconRounded.tsx b/ref-frontend/src/components/IconRounded.tsx deleted file mode 100644 index fc45023..0000000 --- a/ref-frontend/src/components/IconRounded.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react' -import { ColorKey } from '../interfaces' -import { colorsBgLight, colorsText } from '../colors' -import BaseIcon from './BaseIcon' - -type Props = { - icon: string - color: ColorKey - w?: string - h?: string - bg?: boolean - className?: string -} - -export default function IconRounded({ - icon, - color, - w = 'w-12', - h = 'h-12', - bg = false, - className = '', -}: Props) { - const classAddon = bg ? colorsBgLight[color] : `${colorsText[color]} bg-gray-50 dark:bg-slate-800` - - return ( - - ) -} diff --git a/ref-frontend/src/components/ImageField.tsx b/ref-frontend/src/components/ImageField.tsx deleted file mode 100644 index 7ad5b55..0000000 --- a/ref-frontend/src/components/ImageField.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* eslint-disable @next/next/no-img-element */ -// Why disabled: -// avatars.dicebear.com provides svg avatars -// next/image needs dangerouslyAllowSVG option for that - -import React, { ReactNode } from 'react'; -import { mdiImageOutline } from '@mdi/js'; -import BaseIcon from './BaseIcon'; - -type Props = { - name: string; - image?: object | null; - api?: string; - className?: string; - imageClassName?: string; - children?: ReactNode; -}; - -export default function ImageField({ - name, - image, - className = '', - imageClassName = '', - children, - }: Props) { - const imageSrc = image && image[0] ? `${image[0].publicUrl}` : ''; - - return ( -
    - {imageSrc ? ( - {name} - ) : ( -
    - -
    - )} - - {children} -
    - ); -} \ No newline at end of file diff --git a/ref-frontend/src/components/IntroGuide.tsx b/ref-frontend/src/components/IntroGuide.tsx deleted file mode 100644 index e19f6a9..0000000 --- a/ref-frontend/src/components/IntroGuide.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import { Steps, Hints } from 'intro.js-react'; -import { useRouter } from 'next/router'; -interface IntroGuideProps { - steps: Array<{ - element: string; - intro: string; - position?: string; - }>; - disableInteraction?: boolean; - stepsEnabled: boolean; - stepsName: string; - onExit: () => void; -} - -const IntroGuide: React.FC = ({ - steps, - stepsEnabled, - onExit, - stepsName -}) => { - const router = useRouter(); - const handleStepChange = (stepIndex: number ) => { - - if (stepIndex === 7 && stepsName === 'appSteps') { - onExit(); - router.push('/users/users-list/'); - } else if (stepIndex === 2 && stepsName === 'usersSteps') { - onExit(); - router.push('/roles/roles-list/'); - } - }; - - const handleExit = () => { - localStorage.setItem(`completed_${stepsName}`, 'true'); - onExit(); - }; - return ( - <> - - - ); -}; - -export default IntroGuide; \ No newline at end of file diff --git a/ref-frontend/src/components/Invoices/CardInvoices.tsx b/ref-frontend/src/components/Invoices/CardInvoices.tsx deleted file mode 100644 index bb013ba..0000000 --- a/ref-frontend/src/components/Invoices/CardInvoices.tsx +++ /dev/null @@ -1,286 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - invoices: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardInvoices = ({ - invoices, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_INVOICES') - - - return ( -
    - {loading && } -
      - {!loading && invoices.map((item, index) => ( -
    • - -
      - - - {item.invoice_number} - - - -
      - -
      -
      -
      - - -
      -
      Organization
      -
      -
      - { dataFormatter.organizationsOneListFormatter(item.organization) } -
      -
      -
      - - - - -
      -
      Campus
      -
      -
      - { dataFormatter.campusesOneListFormatter(item.campus) } -
      -
      -
      - - - - -
      -
      Student
      -
      -
      - { dataFormatter.studentsOneListFormatter(item.student) } -
      -
      -
      - - - - -
      -
      FeePlan
      -
      -
      - { dataFormatter.fee_plansOneListFormatter(item.fee_plan) } -
      -
      -
      - - - - -
      -
      InvoiceNumber
      -
      -
      - { item.invoice_number } -
      -
      -
      - - - - -
      -
      IssueDate
      -
      -
      - { dataFormatter.dateTimeFormatter(item.issue_date) } -
      -
      -
      - - - - -
      -
      DueDate
      -
      -
      - { dataFormatter.dateTimeFormatter(item.due_date) } -
      -
      -
      - - - - -
      -
      Subtotal
      -
      -
      - { item.subtotal } -
      -
      -
      - - - - -
      -
      DiscountAmount
      -
      -
      - { item.discount_amount } -
      -
      -
      - - - - -
      -
      TaxAmount
      -
      -
      - { item.tax_amount } -
      -
      -
      - - - - -
      -
      TotalAmount
      -
      -
      - { item.total_amount } -
      -
      -
      - - - - -
      -
      BalanceDue
      -
      -
      - { item.balance_due } -
      -
      -
      - - - - -
      -
      Status
      -
      -
      - { item.status } -
      -
      -
      - - - - -
      -
      Notes
      -
      -
      - { item.notes } -
      -
      -
      - - - - -
      -
      Attachments
      -
      -
      - {dataFormatter.filesFormatter(item.attachments).map(link => ( - - ))} -
      -
      -
      - - - -
      -
    • - ))} - {!loading && invoices.length === 0 && ( -
      -

      No data to display

      -
      - )} -
    -
    - -
    -
    - ); -}; - -export default CardInvoices; diff --git a/ref-frontend/src/components/Invoices/ListInvoices.tsx b/ref-frontend/src/components/Invoices/ListInvoices.tsx deleted file mode 100644 index 86b7dc0..0000000 --- a/ref-frontend/src/components/Invoices/ListInvoices.tsx +++ /dev/null @@ -1,207 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - invoices: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListInvoices = ({ invoices, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_INVOICES') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
    - {loading && } - {!loading && invoices.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Organization

    -

    { dataFormatter.organizationsOneListFormatter(item.organization) }

    -
    - - - - -
    -

    Campus

    -

    { dataFormatter.campusesOneListFormatter(item.campus) }

    -
    - - - - -
    -

    Student

    -

    { dataFormatter.studentsOneListFormatter(item.student) }

    -
    - - - - -
    -

    FeePlan

    -

    { dataFormatter.fee_plansOneListFormatter(item.fee_plan) }

    -
    - - - - -
    -

    InvoiceNumber

    -

    { item.invoice_number }

    -
    - - - - -
    -

    IssueDate

    -

    { dataFormatter.dateTimeFormatter(item.issue_date) }

    -
    - - - - -
    -

    DueDate

    -

    { dataFormatter.dateTimeFormatter(item.due_date) }

    -
    - - - - -
    -

    Subtotal

    -

    { item.subtotal }

    -
    - - - - -
    -

    DiscountAmount

    -

    { item.discount_amount }

    -
    - - - - -
    -

    TaxAmount

    -

    { item.tax_amount }

    -
    - - - - -
    -

    TotalAmount

    -

    { item.total_amount }

    -
    - - - - -
    -

    BalanceDue

    -

    { item.balance_due }

    -
    - - - - -
    -

    Status

    -

    { item.status }

    -
    - - - - -
    -

    Notes

    -

    { item.notes }

    -
    - - - - -
    -

    Attachments

    - {dataFormatter.filesFormatter(item.attachments).map(link => ( - - ))} -
    - - - - - -
    -
    -
    - ))} - {!loading && invoices.length === 0 && ( -
    -

    No data to display

    -
    - )} -
    -
    - -
    - - ) -}; - -export default ListInvoices \ No newline at end of file diff --git a/ref-frontend/src/components/Invoices/TableInvoices.tsx b/ref-frontend/src/components/Invoices/TableInvoices.tsx deleted file mode 100644 index 1917abe..0000000 --- a/ref-frontend/src/components/Invoices/TableInvoices.tsx +++ /dev/null @@ -1,511 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/invoices/invoicesSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; -import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureInvoicesCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - -import KanbanBoard from '../KanbanBoard/KanbanBoard'; -import axios from 'axios'; - - -const perPage = 10 - -const TableSampleInvoices = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const [kanbanColumns, setKanbanColumns] = useState | null>(null); - const [kanbanFilters, setKanbanFilters] = useState(''); - - const { invoices, loading, count, notify: invoicesNotify, refetch } = useAppSelector((state) => state.invoices) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (invoicesNotify.showNotification) { - notify(invoicesNotify.typeNotification, invoicesNotify.textNotification); - } - }, [invoicesNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - useEffect(() => { - - - - setKanbanColumns([ - - { id: "draft", label: "draft" }, - - { id: "issued", label: "issued" }, - - { id: "partially_paid", label: "partially_paid" }, - - { id: "paid", label: "paid" }, - - { id: "overdue", label: "overdue" }, - - { id: "void", label: "void" }, - - ]); - - - }, []); - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setKanbanFilters(''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - setKanbanFilters(generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - setKanbanFilters(''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `invoices`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={invoices ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - - {!showGrid && kanbanColumns && ( - - )} - - - - {showGrid && dataGrid} - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) -} - -export default TableSampleInvoices diff --git a/ref-frontend/src/components/Invoices/configureInvoicesCols.tsx b/ref-frontend/src/components/Invoices/configureInvoicesCols.tsx deleted file mode 100644 index 32da17c..0000000 --- a/ref-frontend/src/components/Invoices/configureInvoicesCols.tsx +++ /dev/null @@ -1,343 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; -import ListActionsPopover from '../ListActionsPopover'; - -import {hasPermission} from "../../helpers/userPermissions"; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - -) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_INVOICES') - - return [ - - { - field: 'organization', - headerName: 'Organization', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('organizations'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'campus', - headerName: 'Campus', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('campuses'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'student', - headerName: 'Student', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('students'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'fee_plan', - headerName: 'FeePlan', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('fee_plans'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'invoice_number', - headerName: 'InvoiceNumber', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'issue_date', - headerName: 'IssueDate', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.issue_date), - - }, - - { - field: 'due_date', - headerName: 'DueDate', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.due_date), - - }, - - { - field: 'subtotal', - headerName: 'Subtotal', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'discount_amount', - headerName: 'DiscountAmount', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'tax_amount', - headerName: 'TaxAmount', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'total_amount', - headerName: 'TotalAmount', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'balance_due', - headerName: 'BalanceDue', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'status', - headerName: 'Status', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'notes', - headerName: 'Notes', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'attachments', - headerName: 'Attachments', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: false, - sortable: false, - renderCell: (params: GridValueGetterParams) => ( - <> - {dataFormatter.filesFormatter(params.row.attachments).map(link => ( - - ))} - - ), - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; -}; diff --git a/ref-frontend/src/components/KanbanBoard/KanbanBoard.tsx b/ref-frontend/src/components/KanbanBoard/KanbanBoard.tsx deleted file mode 100644 index 84954b8..0000000 --- a/ref-frontend/src/components/KanbanBoard/KanbanBoard.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import KanbanColumn from './KanbanColumn'; -import { AsyncThunk } from '@reduxjs/toolkit'; -import { DndProvider } from 'react-dnd'; -import { HTML5Backend } from 'react-dnd-html5-backend'; - -type Props = { - columns: Array<{id: string, label: string}>; - filtersQuery: string; - entityName: string; - columnFieldName: string; - showFieldName: string; - deleteThunk: AsyncThunk; - updateThunk: AsyncThunk; -}; - -const KanbanBoard = ({ - columns, - entityName, - columnFieldName, - filtersQuery, - showFieldName, - deleteThunk, - updateThunk, -}: Props) => { - return ( -
    - - {columns.map((column) => ( -
    - -
    - ))} -
    -
    - ); -}; - -export default KanbanBoard; diff --git a/ref-frontend/src/components/KanbanBoard/KanbanCard.tsx b/ref-frontend/src/components/KanbanBoard/KanbanCard.tsx deleted file mode 100644 index 7655572..0000000 --- a/ref-frontend/src/components/KanbanBoard/KanbanCard.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react'; -import Link from 'next/link'; -import moment from 'moment'; -import ListActionsPopover from '../ListActionsPopover'; -import { DragSourceMonitor, useDrag } from 'react-dnd'; - -type Props = { - item: any; - column: { id: string; label: string }; - entityName: string; - showFieldName: string; - setItemIdToDelete: (id: string) => void; -}; - -const KanbanCard = ({ - item, - entityName, - showFieldName, - setItemIdToDelete, - column, - }: Props) => { - const [{ isDragging }, drag] = useDrag( - () => ({ - type: 'box', - item: { item, column }, - collect: (monitor: DragSourceMonitor) => ({ - isDragging: monitor.isDragging(), - }), - }), - [item], - ); - - return ( -
    -
    - - {item[showFieldName] ?? 'No data'} - -
    -
    -

    {moment(item.createdAt).format('MMM DD hh:mm a')}

    - setItemIdToDelete(id)} - hasUpdatePermission={true} - className={'w-2 h-2 text-white'} - iconClassName={'w-5'} - /> -
    -
    - ); -}; - -export default KanbanCard; diff --git a/ref-frontend/src/components/KanbanBoard/KanbanColumn.tsx b/ref-frontend/src/components/KanbanBoard/KanbanColumn.tsx deleted file mode 100644 index 425a0d3..0000000 --- a/ref-frontend/src/components/KanbanBoard/KanbanColumn.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import Axios from 'axios'; -import CardBox from '../CardBox'; -import CardBoxModal from '../CardBoxModal'; -import { AsyncThunk } from '@reduxjs/toolkit'; -import { useDrop } from 'react-dnd'; -import KanbanCard from './KanbanCard'; - -type Props = { - column: { id: string; label: string }; - entityName: string; - columnFieldName: string; - showFieldName: string; - filtersQuery: any; - deleteThunk: AsyncThunk; - updateThunk: AsyncThunk; -}; - -type DropResult = { - sourceColumn: { id: string; label: string }; - item: any; -}; - -const perPage = 10; - -const KanbanColumn = ({ - column, - entityName, - columnFieldName, - showFieldName, - filtersQuery, - deleteThunk, - updateThunk, - }: Props) => { - const [currentPage, setCurrentPage] = useState(0); - const [count, setCount] = useState(0); - const [data, setData] = useState(null); - const [loading, setLoading] = useState(false); - const [itemIdToDelete, setItemIdToDelete] = useState(''); - const currentUser = useAppSelector((state) => state.auth.currentUser); - const listInnerRef = useRef(null); - const dispatch = useAppDispatch(); - - const [{ dropResult }, drop] = useDrop< - { - item: any; - column: { - id: string; - label: string; - }; - }, - unknown, - { - dropResult: DropResult; - } - >( - () => ({ - accept: 'box', - drop: ({ - item, - column: sourceColumn, - }: { - item: any; - column: { id: string; label: string }; - }) => { - if (sourceColumn.id === column.id) return; - - dispatch( - updateThunk({ - id: item.id, - data: { - [columnFieldName]: column.id, - }, - }), - ).then((res) => { - setData((prevState) => (prevState ? [...prevState, item] : [item])); - setCount((prevState) => prevState + 1); - }); - - return { sourceColumn, item }; - }, - collect: (monitor) => ({ - dropResult: monitor.getDropResult(), - }), - }), - [], - ); - - const loadData = useCallback( - (page: number, filters = '') => { - const query = `?page=${page}&limit=${perPage}&field=createdAt&sort=desc&${columnFieldName}=${column.id}&${filters}`; - setLoading(true); - Axios.get(`${entityName}${query}`) - .then((res) => { - setData((prevState) => - page === 0 ? res.data.rows : [...prevState, ...res.data.rows], - ); - setCount(res.data.count); - setCurrentPage(page); - }) - .catch((err) => { - console.error(err); - }) - .finally(() => { - setLoading(false); - }); - }, - [currentUser, column], - ); - - useEffect(() => { - if (!currentUser) return; - loadData(0, filtersQuery); - }, [currentUser, loadData, filtersQuery]); - - useEffect(() => { - loadData(0, filtersQuery); - }, [loadData, filtersQuery]); - - useEffect(() => { - if (dropResult?.sourceColumn && dropResult.sourceColumn.id === column.id) { - setData((prevState) => - prevState.filter((item) => item.id !== dropResult.item.id), - ); - setCount((prevState) => prevState - 1); - } - }, [dropResult]); - - const onScroll = () => { - if (listInnerRef.current) { - const { scrollTop, scrollHeight, clientHeight } = listInnerRef.current; - if (Math.floor(scrollTop + clientHeight) === scrollHeight) { - if (data.length < count && !loading) { - loadData(currentPage + 1, filtersQuery); - } - } - } - }; - - const onDeleteConfirm = () => { - if (!itemIdToDelete) return; - - dispatch(deleteThunk(itemIdToDelete)) - .then((res) => { - if (res.meta.requestStatus === 'fulfilled') { - setItemIdToDelete(''); - loadData(0, filtersQuery); - } - }) - .catch((err) => { - console.error(err); - }) - .finally(() => { - setItemIdToDelete(''); - }); - }; - - return ( - <> - -
    -

    {column.label}

    -

    {count}

    -
    -
    { - drop(node); - listInnerRef.current = node; - }} - className={'p-3 space-y-3 flex-1 overflow-y-auto max-h-[400px]'} - onScroll={onScroll} - > - {data?.map((item) => ( -
    - -
    - ))} - {!data?.length && ( -

    No data

    - )} -
    -
    - setItemIdToDelete('')} - > -

    Are you sure you want to delete this item?

    -
    - - ); -}; - -export default KanbanColumn; diff --git a/ref-frontend/src/components/LanguageSwitcher.tsx b/ref-frontend/src/components/LanguageSwitcher.tsx deleted file mode 100644 index f2f373a..0000000 --- a/ref-frontend/src/components/LanguageSwitcher.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import Select, { components, SingleValueProps, OptionProps } from 'react-select'; - -type LanguageOption = { label: string; value: string }; - -const LANGS: LanguageOption[] = [ - { value: 'en', label: '🇬🇧 EN' }, - { value: 'fr', label: '🇫🇷 FR' }, - { value: 'es', label: '🇪🇸 ES' }, - { value: 'de', label: '🇩🇪 DE' }, -]; - -const Option = (props: OptionProps) => ( - - {props.data.label} - -); - -const SingleVal = (props: SingleValueProps) => ( - - {props.data.label} - -); - -const LanguageSwitcher: React.FC = () => { - const [mounted, setMounted] = useState(false); - const [selected, setSelected] = useState(LANGS[0]); - - useEffect(() => { - setMounted(true); - }, []); - - const handleChange = (opt: LanguageOption | null) => { - if (!opt) return; - setSelected(opt); - }; - - if (!mounted) return null; - - return ( -
    - - - ); - - return ( -
    - {readonly || (max && fileList().length >= max) - ? null - : uploadButton} - - {valuesArr() && valuesArr().length ? ( -
    - {valuesArr().map((item) => { - return ( -
    - - - {item.name} - - - {!readonly && ( - - )} -
    - ); - })} -
    - ) : null} -
    - ) -} - -FilesUploader.propTypes = { - readonly: PropTypes.bool, - path: PropTypes.string, - max: PropTypes.number, - schema: PropTypes.shape({ - image: PropTypes.bool, - size: PropTypes.number, - formats: PropTypes.arrayOf(PropTypes.string), - }), - value: PropTypes.any, - onChange: PropTypes.func, -}; - -export default FilesUploader; diff --git a/ref-frontend/src/components/Uploaders/ImagesUploader.js b/ref-frontend/src/components/Uploaders/ImagesUploader.js deleted file mode 100644 index 725abe7..0000000 --- a/ref-frontend/src/components/Uploaders/ImagesUploader.js +++ /dev/null @@ -1,237 +0,0 @@ -import React, { useState, useRef } from 'react'; -import PropTypes from 'prop-types'; -import Button from '@mui/material/Button'; -import CloseIcon from '@mui/icons-material/Close'; -import SearchIcon from '@mui/icons-material/Search'; -import Grid from '@mui/material/Grid'; -import Box from '@mui/material/Box'; -import Dialog from '@mui/material/Dialog'; -import FileUploader from 'components/FormItems/uploaders/UploadService'; -import Errors from '../../../components/FormItems/error/errors'; -import {makeStyles} from "@mui/styles"; - -const useStyles = makeStyles({ - actionButtonsWrapper: { - position: 'relative', - }, - previewContent: { - padding: '0px !important', - }, - imageItem: { - '&.MuiGrid-root': { - margin: 10, - boxShadow: '2px 2px 8px 0 rgb(0 0 0 / 40%)', - borderRadius: 10, - }, - height: '100px', - }, - actionButtons: { - position: 'absolute', - bottom: 5, - right: 4, - }, - previewContainer: { - '& button': { - position: 'absolute', - top: 10, - right: 10, - '& svg': { - height: 50, - width: 50, - fill: '#FFF', - stroke: '#909090', - strokeWidth: 0.5, - }, - }, - }, - button: { - padding: '0px !important', - minWidth: '45px !important', - '& svg': { - height: 36, - width: 36, - fill: '#FFF', - stroke: '#909090', - strokeWidth: 0.5, - }, - } -}); - -const ImagesUploader = (props) => { - const classes = useStyles(); - const { value, onChange, schema, path, max, readonly, name } = props; - - const [loading, setLoading] = useState(false); - const [showPreview, setShowPreview] = useState(false); - const [imageMeta, setImageMeta] = useState({ - imageSrc: null, - imageAlt: null, - }); - const inputElement = useRef(null); - - const valuesArr = () => { - if (!value) { - return []; - } - return Array.isArray(value) ? value : [value]; - }; - - const fileList = () => { - return valuesArr().map((item) => ({ - uid: item.id || undefined, - name: item.name, - status: 'done', - url: item.publicUrl, - })); - }; - - const handleRemove = (id) => { - onChange(valuesArr().filter((item) => item.id !== id)); - }; - - const handleChange = async (event) => { - try { - const files = event.target.files; - - if (!files || !files.length) { - return; - } - - let file = files[0]; - - FileUploader.validate(file, schema); - - setLoading(true); - - file = await FileUploader.upload(path, file, schema); - - inputElement.current.value = ''; - setLoading(false); - onChange([...valuesArr(), file]); - } catch (error) { - inputElement.current.value = ''; - console.log('error', error); - setLoading(false); - Errors.showMessage(error); - } - }; - - const doPreviewImage = (image) => { - setImageMeta({ - imageSrc: image.publicUrl, - imageAlt: image.name, - }); - setShowPreview(true); - }; - - const doCloseImageModal = () => { - setImageMeta({ - imageSrc: null, - imageAlt: null, - }); - setShowPreview(false); - }; - - const uploadButton = ( - - - - ); - - return ( - - {readonly || (max && fileList().length >= max) ? null : uploadButton} - - {valuesArr() && valuesArr().length ? ( - - {valuesArr().map((item) => { - return ( - - {item.name} - -
    -
    - - {!readonly && ( - - )} -
    -
    -
    - ); - })} -
    - ) : null} - - - {imageMeta.imageAlt} - -
    - ); -}; - -ImagesUploader.propTypes = { - readonly: PropTypes.bool, - path: PropTypes.string, - max: PropTypes.number, - schema: PropTypes.shape({ - image: PropTypes.bool, - size: PropTypes.number, - formats: PropTypes.arrayOf(PropTypes.string), - }), - value: PropTypes.any, - onChange: PropTypes.func, - name: PropTypes.string, -}; - -export default ImagesUploader; diff --git a/ref-frontend/src/components/Uploaders/UploadService.js b/ref-frontend/src/components/Uploaders/UploadService.js deleted file mode 100644 index 33620a6..0000000 --- a/ref-frontend/src/components/Uploaders/UploadService.js +++ /dev/null @@ -1,79 +0,0 @@ -import { v4 as uuidv4 } from 'uuid'; -import Axios from "axios"; -import {baseURLApi} from "../../config"; - -function extractExtensionFrom(filename) { - if (!filename) { - return null; - } - - const regex = /(?:\.([^.]+))?$/; - return regex.exec(filename)[1]; -} - -export default class FileUploader { - static validate(file, schema) { - if (!schema) { - return; - } - - if (schema.image) { - if (!file.type.startsWith("image")) { - throw new Error("You must upload an image"); - } - } - - if (schema.size && file.size > schema.size) { - throw new Error("File is too big."); - } - - const extension = extractExtensionFrom(file.name); - - if (schema.formats && !schema.formats.includes(extension)) { - throw new Error("Invalid format"); - } - } - - static async upload(path, file, schema) { - try { - this.validate(file, schema); - } catch (error) { - return Promise.reject(error); - } - - const extension = extractExtensionFrom(file.name); - const id = uuidv4(); - const filename = `${id}.${extension}`; - const privateUrl = `${path}/${filename}`; - - const publicUrl = await this.uploadToServer(file, path, filename); - - return { - id: id, - name: file.name, - sizeInBytes: file.size, - privateUrl, - publicUrl, - new: true, - }; - } - - static async uploadToServer(file, path, filename) { - const formData = new FormData(); - formData.append("file", file); - formData.append("filename", filename); - const uri = `/file/upload/${path}`; - await Axios.post(uri, formData, { - headers: { - "Content-Type": "multipart/form-data", - }, - }); - - const privateUrl = `${path}/${filename}`; - - console.log("process.env.NODE_ENV in uploadToServer function", process.env.NODE_ENV); - console.log("baseURLApi in uploadToServer function", baseURLApi); - - return `${baseURLApi}/file/download?privateUrl=${privateUrl}`; - } -} diff --git a/ref-frontend/src/components/UserAvatar.tsx b/ref-frontend/src/components/UserAvatar.tsx deleted file mode 100644 index 257ebdb..0000000 --- a/ref-frontend/src/components/UserAvatar.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/* eslint-disable @next/next/no-img-element */ -// Why disabled: -// avatars.dicebear.com provides svg avatars -// next/image needs dangerouslyAllowSVG option for that - -import React, { ReactNode } from 'react'; -import BaseIcon from "./BaseIcon"; -import {mdiAccountCircleOutline} from "@mdi/js"; - -type Props = { - username: string; - avatar?: string | null; - image?: object | null; - api?: string; - className?: string; - children?: ReactNode; -}; - -export default function UserAvatar({ - username, - image, - avatar, - className = '', - children, - }: Props) { - - const avatarImage = (image && image[0]) ? `${image[0].publicUrl}` : '#'; - - return ( -
    - {avatarImage === "#" - ? - : {username} - } - {children} -
    - ); -} diff --git a/ref-frontend/src/components/UserAvatarCurrentUser.tsx b/ref-frontend/src/components/UserAvatarCurrentUser.tsx deleted file mode 100644 index 7bfc768..0000000 --- a/ref-frontend/src/components/UserAvatarCurrentUser.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React, {ReactNode, useEffect, useState} from 'react'; -import { useAppSelector } from '../stores/hooks'; -import UserAvatar from './UserAvatar'; - -type Props = { - className?: string; - children?: ReactNode; -}; - -export default function UserAvatarCurrentUser({ - className = '', - children, - }: Props) { - const userName = useAppSelector((state) => state.main.userName); - const userAvatar = useAppSelector((state) => state.main.userAvatar); - const { currentUser, isFetching, token } = useAppSelector( - (state) => state.auth, - ); - const { users, loading } = useAppSelector((state) => state.users); - - const [ avatar, setAvatar ] = useState(null) - - useEffect(() => { - currentUserAvatarCheck() - }, []); - - useEffect(() => { - currentUserAvatarCheck() - }, [currentUser?.id, users]); - - const currentUserAvatarCheck = () => { - if (currentUser?.id) { - const image = currentUser?.avatar; - setAvatar(image); - } - } - - return ( - - {children} - - ); -} diff --git a/ref-frontend/src/components/UserCard.tsx b/ref-frontend/src/components/UserCard.tsx deleted file mode 100644 index a81e8a2..0000000 --- a/ref-frontend/src/components/UserCard.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { mdiCheckDecagram } from '@mdi/js' -import { Field, Form, Formik } from 'formik' -import { useAppSelector } from '../stores/hooks' -import CardBox from './CardBox' -import FormCheckRadio from './FormCheckRadio' -import UserAvatarCurrentUser from './UserAvatarCurrentUser' - -type Props = { - className?: string -} - -const UserCard = ({ className }: Props) => { - const userName = useAppSelector((state) => state.main.userName) - - return ( - -
    - -
    -
    - alert(JSON.stringify(values, null, 2))} - > -
    - - - -
    -
    -
    -

    - Howdy, {userName}! -

    -

    - Last login 12 mins ago from 127.0.0.1 -

    -
    - Verified -
    -
    -
    -
    - ) -} - -export default UserCard diff --git a/ref-frontend/src/components/Users/CardUsers.tsx b/ref-frontend/src/components/Users/CardUsers.tsx deleted file mode 100644 index d8c73cc..0000000 --- a/ref-frontend/src/components/Users/CardUsers.tsx +++ /dev/null @@ -1,220 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - users: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardUsers = ({ - users, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_USERS') - - - return ( -
    - {loading && } -
      - {!loading && users.map((item, index) => ( -
    • - -
      - - - -

      {item.firstName}

      - - - -
      - -
      -
      -
      - - -
      -
      First Name
      -
      -
      - { item.firstName } -
      -
      -
      - - - - -
      -
      Last Name
      -
      -
      - { item.lastName } -
      -
      -
      - - - - -
      -
      Phone Number
      -
      -
      - { item.phoneNumber } -
      -
      -
      - - - - -
      -
      E-Mail
      -
      -
      - { item.email } -
      -
      -
      - - - - -
      -
      Disabled
      -
      -
      - { dataFormatter.booleanFormatter(item.disabled) } -
      -
      -
      - - - - -
      -
      Avatar
      -
      -
      - -
      -
      -
      - - - - -
      -
      App Role
      -
      -
      - { dataFormatter.rolesOneListFormatter(item.app_role) } -
      -
      -
      - - - - -
      -
      Custom Permissions
      -
      -
      - { dataFormatter.permissionsManyListFormatter(item.custom_permissions).join(', ')} -
      -
      -
      - - - - -
      -
      Organizations
      -
      -
      - { dataFormatter.organizationsOneListFormatter(item.organizations) } -
      -
      -
      - - - -
      -
    • - ))} - {!loading && users.length === 0 && ( -
      -

      No data to display

      -
      - )} -
    -
    - -
    -
    - ); -}; - -export default CardUsers; diff --git a/ref-frontend/src/components/Users/ListUsers.tsx b/ref-frontend/src/components/Users/ListUsers.tsx deleted file mode 100644 index 377cca1..0000000 --- a/ref-frontend/src/components/Users/ListUsers.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; - - -type Props = { - users: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListUsers = ({ users, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_USERS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
    - {loading && } - {!loading && users.map((item) => ( -
    - -
    - - - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    First Name

    -

    { item.firstName }

    -
    - - - - -
    -

    Last Name

    -

    { item.lastName }

    -
    - - - - -
    -

    Phone Number

    -

    { item.phoneNumber }

    -
    - - - - -
    -

    E-Mail

    -

    { item.email }

    -
    - - - - -
    -

    Disabled

    -

    { dataFormatter.booleanFormatter(item.disabled) }

    -
    - - - - -
    -

    Avatar

    - -
    - - - - -
    -

    App Role

    -

    { dataFormatter.rolesOneListFormatter(item.app_role) }

    -
    - - - - -
    -

    Custom Permissions

    -

    { dataFormatter.permissionsManyListFormatter(item.custom_permissions).join(', ')}

    -
    - - - - -
    -

    Organizations

    -

    { dataFormatter.organizationsOneListFormatter(item.organizations) }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && users.length === 0 && ( -
    -

    No data to display

    -
    - )} -
    -
    - -
    - - ) -}; - -export default ListUsers \ No newline at end of file diff --git a/ref-frontend/src/components/Users/TableUsers.tsx b/ref-frontend/src/components/Users/TableUsers.tsx deleted file mode 100644 index bef1b7f..0000000 --- a/ref-frontend/src/components/Users/TableUsers.tsx +++ /dev/null @@ -1,463 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/users/usersSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; -import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureUsersCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - - -const perPage = 10 - -const TableSampleUsers = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { users, loading, count, notify: usersNotify, refetch } = useAppSelector((state) => state.users) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (usersNotify.showNotification) { - notify(usersNotify.typeNotification, usersNotify.textNotification); - } - }, [usersNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `users`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - delete data?.password; - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={users ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) -} - -export default TableSampleUsers diff --git a/ref-frontend/src/components/Users/configureUsersCols.tsx b/ref-frontend/src/components/Users/configureUsersCols.tsx deleted file mode 100644 index dc7e770..0000000 --- a/ref-frontend/src/components/Users/configureUsersCols.tsx +++ /dev/null @@ -1,229 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; -import ListActionsPopover from '../ListActionsPopover'; - -import {hasPermission} from "../../helpers/userPermissions"; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - -) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_USERS') - - return [ - - { - field: 'firstName', - headerName: 'First Name', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'lastName', - headerName: 'Last Name', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'phoneNumber', - headerName: 'Phone Number', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'email', - headerName: 'E-Mail', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'disabled', - headerName: 'Disabled', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - field: 'avatar', - headerName: 'Avatar', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: false, - sortable: false, - renderCell: (params: GridValueGetterParams) => ( - - ), - - }, - - { - field: 'app_role', - headerName: 'App Role', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('roles'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'custom_permissions', - headerName: 'Custom Permissions', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: false, - sortable: false, - type: 'singleSelect', - valueFormatter: ({ value }) => - dataFormatter.permissionsManyListFormatter(value).join(', '), - renderEditCell: (params) => ( - - ), - - }, - - { - field: 'organizations', - headerName: 'Organizations', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('organizations'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; -}; diff --git a/ref-frontend/src/components/WidgetCreator/RoleSelect.tsx b/ref-frontend/src/components/WidgetCreator/RoleSelect.tsx deleted file mode 100644 index 7cc095c..0000000 --- a/ref-frontend/src/components/WidgetCreator/RoleSelect.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useEffect, useId, useState } from 'react'; -import { AsyncPaginate } from 'react-select-async-paginate'; -import axios from 'axios'; - -export const RoleSelect = ({ options, field, form, itemRef, disabled, currentUser }) => { - const [value, setValue] = useState(null); - const PAGE_SIZE = 100; - - React.useEffect(() => { - if (currentUser.app_role.id) { - setValue({ value: currentUser.app_role.id, label: currentUser.app_role.name }); - } - }, [currentUser]); - - useEffect(() => { - if (options?.value && options?.label) { - setValue({ value: options.value, label: options.label }); - } - }, [options?.id, field?.value?.id]); - - const mapResponseToValuesAndLabels = (data) => ({ - value: data.id, - label: data.label, - }); - const handleChange = (option) => { - form.setFieldValue(field.name, option); - setValue(option); - }; - - async function callApi(inputValue: string, loadedOptions: any[]) { - const path = `/${itemRef}/autocomplete?limit=${PAGE_SIZE}&offset=${loadedOptions.length}${inputValue ? `&query=${inputValue}` : ''}`; - const { data } = await axios(path); - return { - options: data.map(mapResponseToValuesAndLabels), - hasMore: data.length === PAGE_SIZE, - } - } - return ( - 'px-1 py-2', - }} - classNamePrefix={'react-select'} - instanceId={useId()} - value={value} - debounceTimeout={1000} - loadOptions={callApi} - onChange={handleChange} - defaultOptions - isDisabled={disabled} - /> - ); -}; diff --git a/ref-frontend/src/components/WidgetCreator/WidgetCreator.tsx b/ref-frontend/src/components/WidgetCreator/WidgetCreator.tsx deleted file mode 100644 index 80b4999..0000000 --- a/ref-frontend/src/components/WidgetCreator/WidgetCreator.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import CardBox from '../CardBox'; -import { mdiCog } from '@mdi/js'; -import { Field, Form, Formik } from 'formik'; -import { ToastContainer, toast } from 'react-toastify'; -import FormField from '../FormField'; -import React from 'react'; -import { - aiPrompt, - setErrorNotification, - resetNotify, -} from '../../stores/openAiSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; - -import { fetchWidgets } from '../../stores/roles/rolesSlice'; - -import BaseButton from '../BaseButton'; -import CardBoxModal from '../CardBoxModal'; -import { RoleSelect } from './RoleSelect'; - -export const WidgetCreator = ({ - currentUser, - isFetchingQuery, - setWidgetsRole, - widgetsRole, - }) => { - const dispatch = useAppDispatch(); - const [isModalOpen, setIsModalOpen] = React.useState(false); - const { notify: openAiNotify } = useAppSelector((state) => state.openAi); - - const notify = (type, msg) => toast(msg, { type, position: 'bottom-center' }); - React.useEffect(() => { - if (openAiNotify.showNotification) { - notify(openAiNotify.typeNotification, openAiNotify.textNotification); - dispatch(resetNotify()); - } - }, [openAiNotify.showNotification]); - - const openModal = (): void => { - setIsModalOpen(true); - }; - - const handleCloseModal = (value = {}) => { - setWidgetsRole(value); - setIsModalOpen(false); - }; - - const getWidgets = async () => { - await dispatch(fetchWidgets(widgetsRole?.role?.value || '')); - }; - - const smartSearch = async (values: { description: string }, resetForm: any) => { - const description = values.description; - const projectId = ''; - - const payload = { - roleId: widgetsRole?.role?.value, - description, - projectId, - userId: currentUser?.id, - }; - const { payload: responcePayload, error }: any = await dispatch(aiPrompt(payload)); - - await getWidgets().then(); - - resetForm({ values: { description: '' } }); - if (responcePayload.data?.error || error) { - const errorMessage = - responcePayload.data?.error?.message || error?.message; - await dispatch( - setErrorNotification(errorMessage || 'Error with widget creation'), - ); - } - }; - - return ( - <> - - - smartSearch(values, resetForm)} - > -
    - - - -
    -
    -
    - handleCloseModal(values)} - > - {({ submitForm }) => ( - setIsModalOpen(false)} - > -

    What role are we showing and creating widgets for?

    - -
    - - - -
    -
    - )} -
    - - - ); -}; diff --git a/ref-frontend/src/config.ts b/ref-frontend/src/config.ts deleted file mode 100644 index a9783c8..0000000 --- a/ref-frontend/src/config.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const hostApi = process.env.NODE_ENV === 'development' && !process.env.NEXT_PUBLIC_BACK_API ? 'http://localhost' : '' -export const portApi = process.env.NODE_ENV === 'development' && !process.env.NEXT_PUBLIC_BACK_API ? 8080 : ''; -export const baseURLApi = `${hostApi}${portApi ? `:${portApi}` : ``}/api` - -export const localStorageDarkModeKey = 'darkMode' - -export const localStorageStyleKey = 'style' - -export const containerMaxW = 'xl:max-w-full xl:mx-auto 2xl:mx-20' - -export const appTitle = 'created by Flatlogic generator!' - -export const getPageTitle = (currentPageTitle: string) => `${currentPageTitle} — ${appTitle}` - -export const tinyKey = process.env.NEXT_PUBLIC_TINY_KEY || '' diff --git a/ref-frontend/src/css/_app.css b/ref-frontend/src/css/_app.css deleted file mode 100644 index affc655..0000000 --- a/ref-frontend/src/css/_app.css +++ /dev/null @@ -1,32 +0,0 @@ -html { - @apply h-full; -} - -body { - @apply pt-14 xl:pl-60 h-full; -} - -#app { - @apply w-screen transition-position lg:w-auto h-full flex flex-col; -} - -.dropdown { - @apply cursor-pointer; -} - -li.stack-item:not(:last-child):after { - content: '/'; - @apply inline-block pl-2; -} - -.m-clipped, .m-clipped body { - @apply overflow-hidden lg:overflow-visible; -} - -.full-screen body { - @apply p-0; -} - -.main-navbar, .app-sidebar-brand { - box-shadow: 0px -1px 40px rgba(112, 144, 176, 0.2); -} diff --git a/ref-frontend/src/css/_calendar.css b/ref-frontend/src/css/_calendar.css deleted file mode 100644 index e3945b4..0000000 --- a/ref-frontend/src/css/_calendar.css +++ /dev/null @@ -1,39 +0,0 @@ -.rbc-event { - @apply bg-blue-600 !important; -} - -.rbc-show-more { - @apply dark:text-white bg-transparent !important; -} - -.rbc-btn-group button { - @apply dark:text-white !important; -} - -.rbc-btn-group button:hover { - @apply text-white dark:bg-dark-700 !important; -} - -.rbc-btn-group button.rbc-active { - @apply text-black dark:bg-blue-600 !important; -} - -.rbc-btn-group button:focus { - @apply dark:bg-blue-600 !important; -} - -.rbc-day-bg.rbc-off-range-bg { - @apply dark:bg-dark-800 !important; -} -.rbc-current-time-indicator{ - @apply h-1 !important; -} -.rbc-today { - @apply dark:bg-dark-600/40 !important; -} - -.rbc-day-bg.rbc-selected-cell { - @apply dark:bg-dark-500 !important; -} - - diff --git a/ref-frontend/src/css/_checkbox-radio-switch.css b/ref-frontend/src/css/_checkbox-radio-switch.css deleted file mode 100644 index b2dab5c..0000000 --- a/ref-frontend/src/css/_checkbox-radio-switch.css +++ /dev/null @@ -1,59 +0,0 @@ -@layer components { - .checkbox, .radio, .switch { - @apply inline-flex items-center cursor-pointer relative; - } - - .checkbox input[type=checkbox], .radio input[type=radio], .switch input[type=checkbox] { - @apply absolute left-0 opacity-0 -z-1; - } - - .checkbox input[type=checkbox]+.check, .radio input[type=radio]+.check, .switch input[type=checkbox]+.check { - @apply border-gray-700 border transition-colors duration-200 dark:bg-slate-800; - } - - .checkbox input[type=checkbox]:focus+.check, .radio input[type=radio]:focus+.check, .switch input[type=checkbox]:focus+.check { - @apply ring ring-blue-700 ; - } - - .checkbox input[type=checkbox]+.check, .radio input[type=radio]+.check { - @apply block w-5 h-5; - } - - .checkbox input[type=checkbox]+.check { - @apply rounded; - } - - .switch input[type=checkbox]+.check { - @apply flex items-center shrink-0 w-12 h-6 p-0.5 bg-gray-200; - } - - .radio input[type=radio]+.check, .switch input[type=checkbox]+.check, .switch input[type=checkbox]+.check:before { - @apply rounded-full; - } - - .checkbox input[type=checkbox]:checked+.check, .radio input[type=radio]:checked+.check { - @apply bg-no-repeat bg-center border-4; - } - - .checkbox input[type=checkbox]:checked+.check { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Cpath style='fill:%23fff' d='M 0.04038059,0.6267767 0.14644661,0.52071068 0.42928932,0.80355339 0.3232233,0.90961941 z M 0.21715729,0.80355339 0.85355339,0.16715729 0.95961941,0.2732233 0.3232233,0.90961941 z'%3E%3C/path%3E%3C/svg%3E"); - } - - .radio input[type=radio]:checked+.check { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23fff' d='M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z' /%3E%3C/svg%3E"); - } - - .switch input[type=checkbox]:checked+.check, .checkbox input[type=checkbox]:checked+.check, .radio input[type=radio]:checked+.check { - @apply bg-blue-600 border-blue-600 ; - } - - .switch input[type=checkbox]+.check:before { - content: ''; - @apply block w-5 h-5 bg-white border border-gray-700; - } - - .switch input[type=checkbox]:checked+.check:before { - transform: translate3d(110%, 0 ,0); - @apply border-blue-600; - } -} \ No newline at end of file diff --git a/ref-frontend/src/css/_helper.css b/ref-frontend/src/css/_helper.css deleted file mode 100644 index 3444fa9..0000000 --- a/ref-frontend/src/css/_helper.css +++ /dev/null @@ -1,23 +0,0 @@ -.helper-container { - right: 0; - top: 70px; - transform: translateX(100%); - - .tab { - top: 0; - left: 0; - transform: translateX(-100%); - } - - .tab:hover { - @apply bg-gray-900 cursor-pointer; - } -} - -.helper-container.open { - transform: translateX(0); -} - -.react-datepicker-wrapper, .react-datepicker-popper { - z-index: 10 !important; -} diff --git a/ref-frontend/src/css/_progress.css b/ref-frontend/src/css/_progress.css deleted file mode 100644 index 65d6796..0000000 --- a/ref-frontend/src/css/_progress.css +++ /dev/null @@ -1,21 +0,0 @@ -@layer base { - progress { - @apply h-3 rounded-full overflow-hidden; - } - - progress::-webkit-progress-bar { - @apply bg-blue-200; - } - - progress::-webkit-progress-value { - @apply bg-blue-500; - } - - progress::-moz-progress-bar { - @apply bg-blue-500; - } - - progress::-ms-fill { - @apply bg-blue-500 border-0; - } -} diff --git a/ref-frontend/src/css/_rich-text.css b/ref-frontend/src/css/_rich-text.css deleted file mode 100644 index 8b13789..0000000 --- a/ref-frontend/src/css/_rich-text.css +++ /dev/null @@ -1 +0,0 @@ - diff --git a/ref-frontend/src/css/_scrollbars.css b/ref-frontend/src/css/_scrollbars.css deleted file mode 100644 index a181b84..0000000 --- a/ref-frontend/src/css/_scrollbars.css +++ /dev/null @@ -1,41 +0,0 @@ -@layer base { - html { - scrollbar-width: thin; - scrollbar-color: rgb(156, 163, 175) rgb(249, 250, 251); - } - - body::-webkit-scrollbar { - width: 8px; - height: 8px; - } - - body::-webkit-scrollbar-track { - @apply bg-gray-50; - } - - body::-webkit-scrollbar-thumb { - @apply bg-gray-400 rounded; - } - - body::-webkit-scrollbar-thumb:hover { - @apply bg-gray-500; - } -} - -@layer utilities { - .dark-scrollbars-compat { - scrollbar-color: rgb(71, 85, 105) rgb(30, 41, 59); - } - - .dark-scrollbars::-webkit-scrollbar-track { - @apply bg-slate-800; - } - - .dark-scrollbars::-webkit-scrollbar-thumb { - @apply bg-slate-600; - } - - .dark-scrollbars::-webkit-scrollbar-thumb:hover { - @apply bg-slate-500; - } -} diff --git a/ref-frontend/src/css/_select-dropdown.css b/ref-frontend/src/css/_select-dropdown.css deleted file mode 100644 index d610d8a..0000000 --- a/ref-frontend/src/css/_select-dropdown.css +++ /dev/null @@ -1,31 +0,0 @@ -.react-select__control { - @apply dark:bg-dark-800 dark:border-dark-700 !important; -} - -.react-select__single-value { - @apply dark:text-white !important; -} - -.react-select__menu { - @apply dark:border-dark-700 -} - -.react-select__menu-list { - @apply dark:bg-dark-800 dark:border-dark-700 dark:rounded !important; -} - -.react-select__option { - @apply cursor-pointer hover:bg-gray-200 dark:hover:bg-dark-700 !important; -} - -.react-select__option--is-focused { - @apply dark:bg-dark-800 dark:text-white hover:dark:bg-dark-700 hover:dark:text-white !important; -} - -.react-select__option--is-selected, .react-select__option--is-selected:hover { - @apply dark:bg-dark-600 !important; -} - -.react-select__multi-value__remove { - @apply dark:bg-dark-600 dark:text-white hover:dark:bg-red-300 hover:dark:text-red-600 !important; -} diff --git a/ref-frontend/src/css/_table.css b/ref-frontend/src/css/_table.css deleted file mode 100644 index 893dc89..0000000 --- a/ref-frontend/src/css/_table.css +++ /dev/null @@ -1,117 +0,0 @@ -@layer base { - table { - @apply w-full; - } - - thead { - @apply hidden lg:table-header-group; - } - - tr { - @apply max-w-full block relative border-b-4 border-gray-100 - lg:table-row lg:border-b-0 dark:border-slate-800; - } - - tr:last-child { - @apply border-b-0; - } - - td:not(:first-child) { - @apply lg:border-l lg:border-t-0 lg:border-r-0 lg:border-b-0 lg:border-gray-100 lg:dark:border-slate-700; - } - - th { - @apply lg:text-left lg:p-3 border-b; - } - - th.sortable { - cursor: pointer; - } - - th.sortable:hover:after { - transition: all 1s; - position: absolute; - - content: "↕"; - - margin-left: 1rem; - } - - th.sortable.asc:hover:after { - content: "↑"; - } - th.sortable.desc:hover:after { - content: "↓"; - } - - td { - @apply flex justify-between text-right py-3 px-4 align-top border-b border-gray-100 - lg:table-cell lg:text-left lg:p-3 lg:align-middle lg:border-b-0 dark:border-slate-800 dark:text-white; - } - - td:last-child { - @apply border-b-0; - } - - tbody tr, tbody tr:nth-child(odd) { - @apply lg:hover:bg-pavitra-300/70; - } - - tbody tr:nth-child(even) { - @apply lg:bg-pavitra-300 dark:bg-pavitra-300/70; - } - - td:before { - content: attr(data-label); - @apply font-semibold pr-3 text-left lg:hidden; - } - - tbody tr td { - @apply text-sm font-normal text-pavitra-900 dark:text-white; - } - - .datagrid--table, .MuiDataGrid-root { - @apply rounded border-none !important; - - } - - .datagrid--header { - @apply uppercase !important; - } - - .datagrid--header, - .datagrid--header .MuiIconButton-root, - .datagrid--cell, - .datagrid--cell .MuiIconButton-root { - @apply dark:text-white; - } - - .datagrid--cell .MuiDataGrid-booleanCell { - @apply dark:text-white !important; - } - - .datagrid--cell .MuiIconButton-root:hover { - @apply dark:text-white dark:bg-dark-700; - } - - .datagrid--row { - @apply even:bg-gray-100 dark:even:bg-[#1B1D22] dark:odd:bg-dark-900 !important; - } - - - .datagrid--table .MuiTablePagination-root { - @apply dark:text-white; - } - - .datagrid--table .MuiTablePagination-root .MuiButtonBase-root:disabled { - @apply dark:text-dark-700; - } - - .datagrid--table .MuiTablePagination-root .MuiButtonBase-root:hover { - @apply dark:bg-dark-700; - } - - .MuiButton-colorInherit { - @apply text-blue-600 dark:text-dark-700 !important; - } -} diff --git a/ref-frontend/src/css/_theme.css b/ref-frontend/src/css/_theme.css deleted file mode 100644 index d07e37a..0000000 --- a/ref-frontend/src/css/_theme.css +++ /dev/null @@ -1,103 +0,0 @@ - -.theme-pink { -.app-sidebar { - @apply bg-pavitra-900 text-white; - -.menu-title, -.menu-item-icon, -.menu-item-link { - @apply text-white; -} -} - -.app-sidebar-brand { - @apply bg-white; -} - -.bg-blue-600 { - @apply bg-pavitra-800; -} - -.border-blue-700 { - @apply border-pink-700; -} - -.checkbox input[type=checkbox]:checked + .check, .radio input[type=radio]:checked + .check { - @apply border-pavitra-800; -} - -.helper-container .tab { - @apply bg-pavitra-900; -} - -.focus\:ring:focus { - --tw-ring-color: #14142A; -} - -.checkbox input[type=checkbox]:focus + .check, .radio input[type=radio]:focus + .check, .switch input[type=checkbox]:focus + .check { - --tw-ring-color: #14142A; -} - -} - -.theme-green { - -.app-sidebar { - @apply bg-pavitra-800 text-white; - -.menu-title, -.menu-item-icon, -.menu-item-link { - @apply text-white; -} - -} - -.app-sidebar-brand { - @apply bg-white; -} - -.bg-blue-600 { - @apply bg-pavitra-800; -} - -.border-blue-700 { - @apply bg-pavitra-700; -} - -.hover\:bg-blue-700:hover { - @apply bg-pavitra-700; -} - -.text-blue-600 { - @apply text-pavitra-900; -} - -.checkbox input[type=checkbox]:checked + .check, .radio input[type=radio]:checked + .check { - @apply border-pavitra-800; -} - -.helper-container .tab { - @apply bg-pavitra-700; -} - -.focus\:ring:focus { - --tw-ring-color: #4E4B66; -} - -.checkbox input[type=checkbox]:focus + .check, .radio input[type=radio]:focus + .check, .switch input[type=checkbox]:focus + .check { - --tw-ring-color: #4E4B66; -} - -.text-blue-500 { - @apply text-pavitra-800; -} - -.hover\:text-blue-600:hover { - @apply text-pavitra-800; -} - -.active\:text-blue-700:active { - @apply text-pavitra-800; -} -} diff --git a/ref-frontend/src/css/main.css b/ref-frontend/src/css/main.css deleted file mode 100644 index f061e28..0000000 --- a/ref-frontend/src/css/main.css +++ /dev/null @@ -1,35 +0,0 @@ -@import "tailwind/_base.css"; -@import "tailwind/_components.css"; -@import "tailwind/_utilities.css"; -@import 'intro.js/introjs.css'; -@import "_checkbox-radio-switch.css"; -@import "_progress.css"; -@import "_scrollbars.css"; -@import "_table.css"; -@import "_helper.css"; -@import '_calendar.css'; -@import '_select-dropdown.css'; -@import "_theme.css"; -@import '_rich-text.css'; - - -.introjs-tooltip { - @apply min-w-[400px] max-w-[480px] p-2 !important; -} - -.good-img { - @apply -mt-96 !important; -} -.end-img { - @apply -mt-72 !important; -} -.introjs-button { - @apply bg-blue-600 text-white !important; - text-shadow: none !important; -} -.introjs-bullets ul li a.active { - @apply bg-blue-600 !important; -} -.introjs-prevbutton{ - @apply bg-transparent border border-blue-600 text-blue-600 !important; -} diff --git a/ref-frontend/src/css/tailwind/_base.css b/ref-frontend/src/css/tailwind/_base.css deleted file mode 100644 index 2f02db5..0000000 --- a/ref-frontend/src/css/tailwind/_base.css +++ /dev/null @@ -1 +0,0 @@ -@tailwind base; diff --git a/ref-frontend/src/css/tailwind/_components.css b/ref-frontend/src/css/tailwind/_components.css deleted file mode 100644 index 020aaba..0000000 --- a/ref-frontend/src/css/tailwind/_components.css +++ /dev/null @@ -1 +0,0 @@ -@tailwind components; diff --git a/ref-frontend/src/css/tailwind/_utilities.css b/ref-frontend/src/css/tailwind/_utilities.css deleted file mode 100644 index 65dd5f6..0000000 --- a/ref-frontend/src/css/tailwind/_utilities.css +++ /dev/null @@ -1 +0,0 @@ -@tailwind utilities; diff --git a/ref-frontend/src/helpers/dataFormatter.js b/ref-frontend/src/helpers/dataFormatter.js deleted file mode 100644 index 119e56a..0000000 --- a/ref-frontend/src/helpers/dataFormatter.js +++ /dev/null @@ -1,436 +0,0 @@ -import dayjs from 'dayjs'; -import _ from 'lodash'; - -export default { - filesFormatter(arr) { - if (!arr || !arr.length) return []; - return arr.map((item) => item); - }, - imageFormatter(arr) { - if (!arr || !arr.length) return [] - return arr.map(item => ({ - publicUrl: item.publicUrl || '' - })) - }, - oneImageFormatter(arr) { - if (!arr || !arr.length) return '' - return arr[0].publicUrl || '' - }, - dateFormatter(date) { - if (!date) return '' - return dayjs(date).format('YYYY-MM-DD') - }, - dateTimeFormatter(date) { - if (!date) return '' - return dayjs(date).format('YYYY-MM-DD HH:mm') - }, - booleanFormatter(val) { - return val ? 'Yes' : 'No' - }, - dataGridEditFormatter(obj) { - return _.transform(obj, (result, value, key) => { - if (_.isArray(value)) { - result[key] = _.map(value, 'id'); - } else if (_.isObject(value)) { - result[key] = value.id; - } else { - result[key] = value; - } - }); - }, - - - usersManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.firstName) - }, - usersOneListFormatter(val) { - if (!val) return '' - return val.firstName - }, - usersManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.firstName} - }); - }, - usersOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.firstName, id: val.id} - }, - - - - rolesManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.name) - }, - rolesOneListFormatter(val) { - if (!val) return '' - return val.name - }, - rolesManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.name} - }); - }, - rolesOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.name, id: val.id} - }, - - - - permissionsManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.name) - }, - permissionsOneListFormatter(val) { - if (!val) return '' - return val.name - }, - permissionsManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.name} - }); - }, - permissionsOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.name, id: val.id} - }, - - - - organizationsManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.name) - }, - organizationsOneListFormatter(val) { - if (!val) return '' - return val.name - }, - organizationsManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.name} - }); - }, - organizationsOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.name, id: val.id} - }, - - - - campusesManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.name) - }, - campusesOneListFormatter(val) { - if (!val) return '' - return val.name - }, - campusesManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.name} - }); - }, - campusesOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.name, id: val.id} - }, - - - - academic_yearsManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.name) - }, - academic_yearsOneListFormatter(val) { - if (!val) return '' - return val.name - }, - academic_yearsManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.name} - }); - }, - academic_yearsOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.name, id: val.id} - }, - - - - gradesManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.name) - }, - gradesOneListFormatter(val) { - if (!val) return '' - return val.name - }, - gradesManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.name} - }); - }, - gradesOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.name, id: val.id} - }, - - - - subjectsManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.name) - }, - subjectsOneListFormatter(val) { - if (!val) return '' - return val.name - }, - subjectsManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.name} - }); - }, - subjectsOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.name, id: val.id} - }, - - - - studentsManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.student_number) - }, - studentsOneListFormatter(val) { - if (!val) return '' - return val.student_number - }, - studentsManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.student_number} - }); - }, - studentsOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.student_number, id: val.id} - }, - - - - - - staffManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.employee_number) - }, - staffOneListFormatter(val) { - if (!val) return '' - return val.employee_number - }, - staffManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.employee_number} - }); - }, - staffOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.employee_number, id: val.id} - }, - - - - classesManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.name) - }, - classesOneListFormatter(val) { - if (!val) return '' - return val.name - }, - classesManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.name} - }); - }, - classesOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.name, id: val.id} - }, - - - - - - class_subjectsManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.status) - }, - class_subjectsOneListFormatter(val) { - if (!val) return '' - return val.status - }, - class_subjectsManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.status} - }); - }, - class_subjectsOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.status, id: val.id} - }, - - - - timetablesManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.name) - }, - timetablesOneListFormatter(val) { - if (!val) return '' - return val.name - }, - timetablesManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.name} - }); - }, - timetablesOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.name, id: val.id} - }, - - - - - - attendance_sessionsManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.session_type) - }, - attendance_sessionsOneListFormatter(val) { - if (!val) return '' - return val.session_type - }, - attendance_sessionsManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.session_type} - }); - }, - attendance_sessionsOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.session_type, id: val.id} - }, - - - - - - fee_plansManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.name) - }, - fee_plansOneListFormatter(val) { - if (!val) return '' - return val.name - }, - fee_plansManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.name} - }); - }, - fee_plansOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.name, id: val.id} - }, - - - - invoicesManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.invoice_number) - }, - invoicesOneListFormatter(val) { - if (!val) return '' - return val.invoice_number - }, - invoicesManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.invoice_number} - }); - }, - invoicesOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.invoice_number, id: val.id} - }, - - - - - - assessmentsManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.name) - }, - assessmentsOneListFormatter(val) { - if (!val) return '' - return val.name - }, - assessmentsManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.name} - }); - }, - assessmentsOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.name, id: val.id} - }, - - - - - - messagesManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.subject) - }, - messagesOneListFormatter(val) { - if (!val) return '' - return val.subject - }, - messagesManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.subject} - }); - }, - messagesOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.subject, id: val.id} - }, - - - - - - -} diff --git a/ref-frontend/src/helpers/fileSaver.ts b/ref-frontend/src/helpers/fileSaver.ts deleted file mode 100644 index eff647d..0000000 --- a/ref-frontend/src/helpers/fileSaver.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { saveAs } from "file-saver"; - -export const saveFile = (e, url: string, name: string) => { - e.stopPropagation(); - saveAs(url,name); -}; diff --git a/ref-frontend/src/helpers/humanize.ts b/ref-frontend/src/helpers/humanize.ts deleted file mode 100644 index ea88c06..0000000 --- a/ref-frontend/src/helpers/humanize.ts +++ /dev/null @@ -1,11 +0,0 @@ -export function humanize(str: string) { - if (!str) { - return ''; - } - return str.toString() - .replace(/^[\s_]+|[\s_]+$/g, '') - .replace(/[_\s]+/g, ' ') - .replace(/^[a-z]/, function (m) { - return m.toUpperCase(); - }); - } \ No newline at end of file diff --git a/ref-frontend/src/helpers/notifyStateHandler.ts b/ref-frontend/src/helpers/notifyStateHandler.ts deleted file mode 100644 index b1a247c..0000000 --- a/ref-frontend/src/helpers/notifyStateHandler.ts +++ /dev/null @@ -1,31 +0,0 @@ -export const resetNotify = (state) => { - state.notify.showNotification = false; - state.notify.typeNotification = ''; - state.notify.textNotification = ''; -}; -export const rejectNotify = (state, action) => { - if (typeof action.payload === 'string') { - state.notify.textNotification = action.payload; - } else if (typeof action === 'object') { - const obj = { ...action.payload?.errors }; - delete obj['_errors']; - - let msg = ''; - - for (const key in obj) { - msg += `${key}: ${obj[key]['_errors']}; \n `; - } - - state.notify.textNotification = msg; - } else { - state.notify.textNotification = 'Network error'; - } - state.notify.textNotification = state.notify.textNotification || 'Network error'; - state.notify.typeNotification = 'error'; - state.notify.showNotification = true; -}; -export const fulfilledNotify = (state, msg) => { - state.notify.textNotification = msg; - state.notify.typeNotification = 'success'; - state.notify.showNotification = true; -}; diff --git a/ref-frontend/src/helpers/pexels.ts b/ref-frontend/src/helpers/pexels.ts deleted file mode 100644 index 3789520..0000000 --- a/ref-frontend/src/helpers/pexels.ts +++ /dev/null @@ -1,76 +0,0 @@ -import axios from 'axios'; - -export async function getPexelsImage() { - try { - const response = await axios.get(`/pexels/image`); - return response.data; - } catch (error) { - console.error('Error fetching image:', error); - return null; - } -} - -export async function getPexelsVideo() { - try { - const response = await axios.get(`/pexels/video`); - return response.data; - } catch (error) { - console.error('Error fetching video:', error); - return null; - } -} - - -let localStorageLock = false; - -export async function getMultiplePexelsImages( - queries = ['home', 'apple', 'pizza', 'mountains', 'cat'], -) { - const normalizeQuery = (query) => - query.trim().toLowerCase().replace(/\s+/g, ''); - - while (localStorageLock) { - await new Promise((resolve) => setTimeout(resolve, 50)); - } - localStorageLock = true; - - const cachedImages = JSON.parse(localStorage.getItem('pexelsImagesCache')) || {}; - - - const isImageCached = (query) => { - const normalizedQuery = normalizeQuery(query); - const cached = cachedImages[normalizedQuery]; - const isCached = cached && cached.src && cached.photographer && cached.photographer_url; - return isCached; - }; - - const missingQueries = queries.filter((query) => !isImageCached(query)); - - if (missingQueries.length > 0) { - const queryString = missingQueries.join(','); - - try { - const response = await axios.get(`/pexels/multiple-images`, { - params: { queries: queryString }, - }); - - missingQueries.forEach((query, index) => { - const normalizedQuery = normalizeQuery(query); - if (!cachedImages[normalizedQuery]) { - cachedImages[normalizedQuery] = response.data[index]; - } - }); - - localStorage.setItem('pexelsImagesCache', JSON.stringify(cachedImages)); - - } catch (error) { - console.error(error); - } - } - - const result = queries.map((query) => cachedImages[normalizeQuery(query)]); - - localStorageLock = false; - - return result; -} \ No newline at end of file diff --git a/ref-frontend/src/helpers/userPermissions.ts b/ref-frontend/src/helpers/userPermissions.ts deleted file mode 100644 index 7818d38..0000000 --- a/ref-frontend/src/helpers/userPermissions.ts +++ /dev/null @@ -1,18 +0,0 @@ - -export function hasPermission(user, permission_name: string | string[]) { - if (!user?.app_role?.name) return false; - if (!permission_name) { - return true; - } - const permissions = new Set([ - ...(user?.custom_permissions ?? []).map((p) => p.name), - ...(user?.app_role_permissions ?? []).map((p) => p.name), - ]); - - if (typeof permission_name === 'string') { - return permissions.has(permission_name) || user.app_role.globalAccess - } else { - return permission_name.some((permission) => permissions.has(permission)); - } -} - diff --git a/ref-frontend/src/hooks/sampleData.ts b/ref-frontend/src/hooks/sampleData.ts deleted file mode 100644 index 7e2aeec..0000000 --- a/ref-frontend/src/hooks/sampleData.ts +++ /dev/null @@ -1,22 +0,0 @@ -import useSWR from 'swr' -const fetcher = (url: string) => fetch(url).then((res) => res.json()) - -export const useSampleClients = () => { - const { data, error } = useSWR('/data-sources/clients.json', fetcher) - - return { - clients: data?.data ?? [], - isLoading: !error && !data, - isError: error, - } -} - -export const useSampleTransactions = () => { - const { data, error } = useSWR('/data-sources/history.json', fetcher) - - return { - transactions: data?.data ?? [], - isLoading: !error && !data, - isError: error, - } -} diff --git a/ref-frontend/src/hooks/useDevCompilationStatus.ts b/ref-frontend/src/hooks/useDevCompilationStatus.ts deleted file mode 100644 index dd07dba..0000000 --- a/ref-frontend/src/hooks/useDevCompilationStatus.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { useState, useEffect } from 'react'; -import { useRouter } from 'next/router'; - -type CompilationStatus = 'ready' | 'compiling' | 'error' | 'initial'; - -const useDevCompilationStatus = (): CompilationStatus => { - const router = useRouter(); - const [status, setStatus] = useState('initial'); - - useEffect(() => { - if (process.env.NODE_ENV !== 'development') { - setStatus('ready'); - return; - } - - const handleRouteChangeStart = () => { - setStatus('compiling'); - }; - - const handleRouteChangeComplete = () => { - setTimeout(() => setStatus('ready'), 300); - }; - - const handleRouteChangeError = () => { - setTimeout(() => setStatus('error'), 300); - }; - - router.events.on('routeChangeStart', handleRouteChangeStart); - router.events.on('routeChangeComplete', handleRouteChangeComplete); - router.events.on('routeChangeError', handleRouteChangeError); - - setStatus('ready'); - - return () => { - router.events.off('routeChangeStart', handleRouteChangeStart); - router.events.off('routeChangeComplete', handleRouteChangeComplete); - router.events.off('routeChangeError', handleRouteChangeError); - }; - }, [router]); - - return status; -}; - -export default useDevCompilationStatus; \ No newline at end of file diff --git a/ref-frontend/src/i18n.ts b/ref-frontend/src/i18n.ts deleted file mode 100644 index 2bc1e8e..0000000 --- a/ref-frontend/src/i18n.ts +++ /dev/null @@ -1,21 +0,0 @@ -import i18n from 'i18next'; -import { initReactI18next } from 'react-i18next'; -import HttpApi from 'i18next-http-backend'; -import LanguageDetector from 'i18next-browser-languagedetector'; - -i18n - .use(HttpApi) - .use(LanguageDetector) - .use(initReactI18next) - .init({ - fallbackLng: 'en', - detection: { - order: ['localStorage', 'navigator'], - lookupLocalStorage: 'app_lang_', - caches: ['localStorage'], - }, - backend: { - loadPath: '/locales/{{lng}}/{{ns}}.json', - }, - interpolation: { escapeValue: false }, - }); \ No newline at end of file diff --git a/ref-frontend/src/interfaces/index.ts b/ref-frontend/src/interfaces/index.ts deleted file mode 100644 index 0c7dd74..0000000 --- a/ref-frontend/src/interfaces/index.ts +++ /dev/null @@ -1,109 +0,0 @@ -export type UserPayloadObject = { - name: string - email: string - avatar: string -} - -export type MenuAsideItem = { - label: string - icon?: string - href?: string - target?: string - color?: ColorButtonKey - isLogout?: boolean - withDevider?: boolean; - menu?: MenuAsideItem[] - permissions?: string | string[] -} - -export type MenuNavBarItem = { - label?: string - icon?: string - href?: string - target?: string - isDivider?: boolean - isLogout?: boolean - isDesktopNoLabel?: boolean - isToggleLightDark?: boolean - isCurrentUser?: boolean - menu?: MenuNavBarItem[] -} - -export type ColorKey = 'white' | 'light' | 'contrast' | 'success' | 'danger' | 'warning' | 'info' - -export type ColorButtonKey = - | 'white' - | 'whiteDark' - | 'lightDark' - | 'contrast' - | 'success' - | 'danger' - | 'warning' - | 'info' - | 'void' - -export type BgKey = 'purplePink' | 'pinkRed' | 'violet' - -export type TrendType = 'up' | 'down' | 'success' | 'danger' | 'warning' | 'info' - -export type TransactionType = 'withdraw' | 'deposit' | 'invoice' | 'payment' - -export type Transaction = { - id: number - amount: number - account: string - name: string - date: string - type: TransactionType - business: string -} - -export type Client = { - id: number - avatar: string - login: string - name: string - city: string, - company: string - firstName: string - lastName: string - phoneNumber: string - email: string - progress: number, - role: string - disabled: boolean - created: string - created_mm_dd_yyyy: string -} - -export interface User { - id: string; - firstName: string; - lastName?: any; - phoneNumber?: any; - email: string; - role: string; - disabled: boolean; - password: string; - emailVerified: boolean; - emailVerificationToken?: any; - emailVerificationTokenExpiresAt?: any; - passwordResetToken?: any; - passwordResetTokenExpiresAt?: any; - provider: string; - importHash?: any; - createdAt: Date; - updatedAt: Date; - deletedAt?: any; - createdById?: any; - updatedById?: any; - avatar: any[]; - notes: any[]; -} - -export type StyleKey = 'white' | 'basic' - -export type UserForm = { - name: string - email: string -} diff --git a/ref-frontend/src/layouts/Authenticated.tsx b/ref-frontend/src/layouts/Authenticated.tsx deleted file mode 100644 index 1b9907d..0000000 --- a/ref-frontend/src/layouts/Authenticated.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import React, { ReactNode, useEffect } from 'react' -import { useState } from 'react' -import jwt from 'jsonwebtoken'; -import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js' -import menuAside from '../menuAside' -import menuNavBar from '../menuNavBar' -import BaseIcon from '../components/BaseIcon' -import NavBar from '../components/NavBar' -import NavBarItemPlain from '../components/NavBarItemPlain' -import AsideMenu from '../components/AsideMenu' -import FooterBar from '../components/FooterBar' -import { useAppDispatch, useAppSelector } from '../stores/hooks' -import Search from '../components/Search'; -import { useRouter } from 'next/router' -import {findMe, logoutUser} from "../stores/authSlice"; - -import {hasPermission} from "../helpers/userPermissions"; - - -type Props = { - children: ReactNode - - permission?: string - -} - -export default function LayoutAuthenticated({ - children, - - permission - -}: Props) { - const dispatch = useAppDispatch() - const router = useRouter() - const { token, currentUser } = useAppSelector((state) => state.auth) - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - let localToken - if (typeof window !== 'undefined') { - // Perform localStorage action - localToken = localStorage.getItem('token') - } - - const isTokenValid = () => { - const token = localStorage.getItem('token'); - if (!token) return; - const date = new Date().getTime() / 1000; - const data = jwt.decode(token); - if (!data) return; - return date < data.exp; - }; - - useEffect(() => { - dispatch(findMe()); - if (!isTokenValid()) { - dispatch(logoutUser()); - router.push('/login'); - } - }, [token, localToken]); - - - useEffect(() => { - if (!permission || !currentUser) return; - - if (!hasPermission(currentUser, permission)) router.push('/error'); - }, [currentUser, permission]); - - - const darkMode = useAppSelector((state) => state.style.darkMode) - - const [isAsideMobileExpanded, setIsAsideMobileExpanded] = useState(false) - const [isAsideLgActive, setIsAsideLgActive] = useState(false) - - useEffect(() => { - const handleRouteChangeStart = () => { - setIsAsideMobileExpanded(false) - setIsAsideLgActive(false) - } - - router.events.on('routeChangeStart', handleRouteChangeStart) - - // If the component is unmounted, unsubscribe - // from the event with the `off` method: - return () => { - router.events.off('routeChangeStart', handleRouteChangeStart) - } - }, [router.events, dispatch]) - - - const layoutAsidePadding = 'xl:pl-60' - - return ( -
    -
    - - setIsAsideMobileExpanded(!isAsideMobileExpanded)} - > - - - setIsAsideLgActive(true)} - > - - - - - - - setIsAsideLgActive(false)} - /> - {children} - Hand-crafted & Made with ❤️ -
    -
    - ) -} diff --git a/ref-frontend/src/layouts/Guest.tsx b/ref-frontend/src/layouts/Guest.tsx deleted file mode 100644 index 657813c..0000000 --- a/ref-frontend/src/layouts/Guest.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { ReactNode } from 'react' -import { useAppSelector } from '../stores/hooks' - -type Props = { - children: ReactNode -} - -export default function LayoutGuest({ children }: Props) { - const darkMode = useAppSelector((state) => state.style.darkMode) - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - - return ( -
    -
    {children}
    -
    - ) -} diff --git a/ref-frontend/src/menuAside.ts b/ref-frontend/src/menuAside.ts deleted file mode 100644 index 40d03f9..0000000 --- a/ref-frontend/src/menuAside.ts +++ /dev/null @@ -1,235 +0,0 @@ -import * as icon from '@mdi/js'; -import { MenuAsideItem } from './interfaces' - -const menuAside: MenuAsideItem[] = [ - { - href: '/dashboard', - icon: icon.mdiViewDashboardOutline, - label: 'Dashboard', - }, - - { - href: '/users/users-list', - label: 'Users', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiAccountGroup ?? icon.mdiTable, - permissions: 'READ_USERS' - }, - { - href: '/roles/roles-list', - label: 'Roles', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable, - permissions: 'READ_ROLES' - }, - { - href: '/permissions/permissions-list', - label: 'Permissions', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiShieldAccountOutline ?? icon.mdiTable, - permissions: 'READ_PERMISSIONS' - }, - { - href: '/organizations/organizations-list', - label: 'Organizations', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_ORGANIZATIONS' - }, - { - href: '/campuses/campuses-list', - label: 'Campuses', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiSchool' in icon ? icon['mdiSchool' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_CAMPUSES' - }, - { - href: '/academic_years/academic_years-list', - label: 'Academic years', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiCalendarRange' in icon ? icon['mdiCalendarRange' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_ACADEMIC_YEARS' - }, - { - href: '/grades/grades-list', - label: 'Grades', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiFormatListNumbered' in icon ? icon['mdiFormatListNumbered' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_GRADES' - }, - { - href: '/subjects/subjects-list', - label: 'Subjects', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiBookOpenPageVariant' in icon ? icon['mdiBookOpenPageVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_SUBJECTS' - }, - { - href: '/students/students-list', - label: 'Students', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiAccountSchool' in icon ? icon['mdiAccountSchool' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_STUDENTS' - }, - { - href: '/guardians/guardians-list', - label: 'Guardians', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiAccountMultiple' in icon ? icon['mdiAccountMultiple' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_GUARDIANS' - }, - { - href: '/staff/staff-list', - label: 'Staff', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiBadgeAccount' in icon ? icon['mdiBadgeAccount' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_STAFF' - }, - { - href: '/classes/classes-list', - label: 'Classes', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiGoogleClassroom' in icon ? icon['mdiGoogleClassroom' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_CLASSES' - }, - { - href: '/class_enrollments/class_enrollments-list', - label: 'Class enrollments', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiAccountArrowRight' in icon ? icon['mdiAccountArrowRight' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_CLASS_ENROLLMENTS' - }, - { - href: '/class_subjects/class_subjects-list', - label: 'Class subjects', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiBookEducation' in icon ? icon['mdiBookEducation' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_CLASS_SUBJECTS' - }, - { - href: '/timetables/timetables-list', - label: 'Timetables', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiTimetable' in icon ? icon['mdiTimetable' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_TIMETABLES' - }, - { - href: '/timetable_periods/timetable_periods-list', - label: 'Timetable periods', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiClockOutline' in icon ? icon['mdiClockOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_TIMETABLE_PERIODS' - }, - { - href: '/attendance_sessions/attendance_sessions-list', - label: 'Attendance sessions', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiClipboardCheckOutline' in icon ? icon['mdiClipboardCheckOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_ATTENDANCE_SESSIONS' - }, - { - href: '/attendance_records/attendance_records-list', - label: 'Attendance records', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiAccountCheckOutline' in icon ? icon['mdiAccountCheckOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_ATTENDANCE_RECORDS' - }, - { - href: '/fee_plans/fee_plans-list', - label: 'Fee plans', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiCashMultiple' in icon ? icon['mdiCashMultiple' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_FEE_PLANS' - }, - { - href: '/invoices/invoices-list', - label: 'Invoices', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiFileDocumentMultiple' in icon ? icon['mdiFileDocumentMultiple' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_INVOICES' - }, - { - href: '/payments/payments-list', - label: 'Payments', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiCashCheck' in icon ? icon['mdiCashCheck' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_PAYMENTS' - }, - { - href: '/assessments/assessments-list', - label: 'Assessments', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiClipboardTextOutline' in icon ? icon['mdiClipboardTextOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_ASSESSMENTS' - }, - { - href: '/assessment_results/assessment_results-list', - label: 'Assessment results', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiChartLine' in icon ? icon['mdiChartLine' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_ASSESSMENT_RESULTS' - }, - { - href: '/messages/messages-list', - label: 'Messages', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiMessageTextOutline' in icon ? icon['mdiMessageTextOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_MESSAGES' - }, - { - href: '/message_recipients/message_recipients-list', - label: 'Message recipients', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiEmailMultipleOutline' in icon ? icon['mdiEmailMultipleOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_MESSAGE_RECIPIENTS' - }, - { - href: '/documents/documents-list', - label: 'Documents', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiFileUploadOutline' in icon ? icon['mdiFileUploadOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_DOCUMENTS' - }, - { - href: '/profile', - label: 'Profile', - icon: icon.mdiAccountCircle, - }, - - - { - href: '/api-docs', - target: '_blank', - label: 'Swagger API', - icon: icon.mdiFileCode, - permissions: 'READ_API_DOCS' - }, -] - -export default menuAside diff --git a/ref-frontend/src/menuNavBar.ts b/ref-frontend/src/menuNavBar.ts deleted file mode 100644 index a5dd956..0000000 --- a/ref-frontend/src/menuNavBar.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { - mdiMenu, - mdiClockOutline, - mdiCloud, - mdiCrop, - mdiAccount, - mdiCogOutline, - mdiEmail, - mdiLogout, - mdiThemeLightDark, - mdiGithub, - mdiVuejs, -} from '@mdi/js' -import { MenuNavBarItem } from './interfaces' - -const menuNavBar: MenuNavBarItem[] = [ - { - isCurrentUser: true, - menu: [ - { - icon: mdiAccount, - label: 'My Profile', - href: '/profile', - }, - { - isDivider: true, - }, - { - icon: mdiLogout, - label: 'Log Out', - isLogout: true, - }, - ], - }, - { - icon: mdiThemeLightDark, - label: 'Light/Dark', - isDesktopNoLabel: true, - isToggleLightDark: true, - }, - { - icon: mdiLogout, - label: 'Log out', - isDesktopNoLabel: true, - isLogout: true, - }, -] - -export const webPagesNavBar = [ - -]; - -export default menuNavBar diff --git a/ref-frontend/src/pages/_app.tsx b/ref-frontend/src/pages/_app.tsx deleted file mode 100644 index c97c870..0000000 --- a/ref-frontend/src/pages/_app.tsx +++ /dev/null @@ -1,201 +0,0 @@ -import React from 'react'; -import type { AppProps } from 'next/app'; -import type { ReactElement, ReactNode } from 'react'; -import type { NextPage } from 'next'; -import Head from 'next/head'; -import { store } from '../stores/store'; -import { Provider } from 'react-redux'; -import '../css/main.css'; -import axios from 'axios'; -import { baseURLApi } from '../config'; -import { useRouter } from 'next/router'; -import ErrorBoundary from "../components/ErrorBoundary"; -import DevModeBadge from '../components/DevModeBadge'; -import 'intro.js/introjs.css'; -import { appWithTranslation } from 'next-i18next'; -import '../i18n'; -import IntroGuide from '../components/IntroGuide'; -import { appSteps, loginSteps, usersSteps, rolesSteps } from '../stores/introSteps'; - -// Initialize axios -axios.defaults.baseURL = process.env.NEXT_PUBLIC_BACK_API - ? process.env.NEXT_PUBLIC_BACK_API - : baseURLApi; - -axios.defaults.headers.common['Content-Type'] = 'application/json'; - -export type NextPageWithLayout

    , IP = P> = NextPage & { - getLayout?: (page: ReactElement) => ReactNode -} - -type AppPropsWithLayout = AppProps & { - Component: NextPageWithLayout -} - -function MyApp({ Component, pageProps }: AppPropsWithLayout) { - // Use the layout defined at the page level, if available - const getLayout = Component.getLayout || ((page) => page); - const router = useRouter(); - const [stepsEnabled, setStepsEnabled] = React.useState(false); - const [stepName, setStepName] = React.useState(''); - const [steps, setSteps] = React.useState([]); - - axios.interceptors.request.use( - config => { - const token = localStorage.getItem('token'); - - if (token) { - config.headers.Authorization = `Bearer ${token}`; - } else { - delete config.headers.Authorization; - } - - return config; - }, - error => { - return Promise.reject(error); - } - ); - - // TODO: Remove this code in future releases - React.useEffect(() => { - const allowedOrigin = (() => { - if (!document.referrer) { - return null; - } - try { - return new URL(document.referrer).origin; - } catch (error) { - console.warn('[postMessage] Failed to parse parent origin from referrer', error); - return null; - } - })(); - - const handleMessage = async (event: MessageEvent) => { - if (event.data === 'getLocation') { - event.source?.postMessage( - { iframeLocation: window.location.pathname }, - event.origin, - ); - return; - } - - if (event.data === 'getAuthToken') { - if (allowedOrigin && event.origin !== allowedOrigin) { - console.warn('[postMessage] Blocked getAuthToken from origin', event.origin); - return; - } - const token = localStorage.getItem('token'); - const user = localStorage.getItem('user'); - event.source?.postMessage( - { iframeAuthToken: token, iframeAuthUser: user }, - event.origin, - ); - return; - } - - if (event.data === 'getScreenshot') { - try { - const html2canvas = (await import('html2canvas')).default; - const canvas = await html2canvas(document.body, { useCORS: true }); - const url = canvas.toDataURL('image/jpeg', 0.8); - event.source?.postMessage({ iframeScreenshot: url }, event.origin); - } catch (e) { - console.error('html2canvas failed', e); - event.source?.postMessage({ iframeScreenshot: null }, event.origin); - } - } - }; - - window.addEventListener('message', handleMessage); - return () => window.removeEventListener('message', handleMessage); - }, []); - - React.useEffect(() => { - // Tour is disabled by default in generated projects. - return; - const isCompleted = (stepKey: string) => { - return localStorage.getItem(`completed_${stepKey}`) === 'true'; - }; - if (router.pathname === '/login' && !isCompleted('loginSteps')) { - setSteps(loginSteps); - setStepName('loginSteps'); - setStepsEnabled(true); - }else if (router.pathname === '/dashboard' && !isCompleted('appSteps')) { - setTimeout(() => { - setSteps(appSteps); - setStepName('appSteps'); - setStepsEnabled(true); - }, 1000); - } else if (router.pathname === '/users/users-list' && !isCompleted('usersSteps')) { - setTimeout(() => { - setSteps(usersSteps); - setStepName('usersSteps'); - setStepsEnabled(true); - }, 1000); - } else if (router.pathname === '/roles/roles-list' && !isCompleted('rolesSteps')) { - setTimeout(() => { - setSteps(rolesSteps); - setStepName('rolesSteps'); - setStepsEnabled(true); - }, 1000); - } else { - setSteps([]); - setStepsEnabled(false); - } - }, [router.pathname]); - - const handleExit = () => { - setStepsEnabled(false); - }; - - const title = 'School Chain Manager' - const description = "Multi-tenant platform to manage campuses, students, staff, classes, attendance, fees, and reports in one place." - const url = "https://flatlogic.com/" - const image = "https://project-screens.s3.amazonaws.com/screenshots/40227/app-hero-20260608-063205.png" - const imageWidth = '1920' - const imageHeight = '960' - - return ( - - {getLayout( - <> - - - - - - - - - - - - - - - - - - - - - - - - - - - {(process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'dev_stage') && } - - )} - - ) -} - -export default appWithTranslation(MyApp); diff --git a/ref-frontend/src/pages/academic_years/[academic_yearsId].tsx b/ref-frontend/src/pages/academic_years/[academic_yearsId].tsx deleted file mode 100644 index ad4dd2a..0000000 --- a/ref-frontend/src/pages/academic_years/[academic_yearsId].tsx +++ /dev/null @@ -1,521 +0,0 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js' -import Head from 'next/head' -import React, { ReactElement, useEffect, useState } from 'react' -import DatePicker from "react-datepicker"; -import "react-datepicker/dist/react-datepicker.css"; -import dayjs from "dayjs"; - -import CardBox from '../../components/CardBox' -import LayoutAuthenticated from '../../layouts/Authenticated' -import SectionMain from '../../components/SectionMain' -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton' -import { getPageTitle } from '../../config' - -import { Field, Form, Formik } from 'formik' -import FormField from '../../components/FormField' -import BaseDivider from '../../components/BaseDivider' -import BaseButtons from '../../components/BaseButtons' -import BaseButton from '../../components/BaseButton' -import FormCheckRadio from '../../components/FormCheckRadio' -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup' -import FormFilePicker from '../../components/FormFilePicker' -import FormImagePicker from '../../components/FormImagePicker' -import { SelectField } from "../../components/SelectField"; -import { SelectFieldMany } from "../../components/SelectFieldMany"; -import { SwitchField } from '../../components/SwitchField' -import {RichTextField} from "../../components/RichTextField"; - -import { update, fetch } from '../../stores/academic_years/academic_yearsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from "../../components/ImageField"; - -import {hasPermission} from "../../helpers/userPermissions"; - - - -const EditAcademic_years = () => { - const router = useRouter() - const dispatch = useAppDispatch() - const initVals = { - - - - - - - - - - - - - - - - - - - - - - - - - organization: null, - - - - - - 'name': '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - start_date: new Date(), - - - - - - - - - - - - - - - - - - - - - - - - - - - - end_date: new Date(), - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - current: false, - - - - - - - - - - - - - - - - } - const [initialValues, setInitialValues] = useState(initVals) - - const { academic_years } = useAppSelector((state) => state.academic_years) - - const { currentUser } = useAppSelector((state) => state.auth); - - - const { academic_yearsId } = router.query - - useEffect(() => { - dispatch(fetch({ id: academic_yearsId })) - }, [academic_yearsId]) - - useEffect(() => { - if (typeof academic_years === 'object') { - setInitialValues(academic_years) - } - }, [academic_years]) - - useEffect(() => { - if (typeof academic_years === 'object') { - - const newInitialVal = {...initVals}; - - Object.keys(initVals).forEach(el => newInitialVal[el] = (academic_years)[el]) - - setInitialValues(newInitialVal); - } - }, [academic_years]) - - const handleSubmit = async (data) => { - await dispatch(update({ id: academic_yearsId, data })) - await router.push('/academic_years/academic_years-list') - } - - return ( - <> - - {getPageTitle('Edit academic_years')} - - - - {''} - - - handleSubmit(values)} - > -

    - - - - - - - - - - - - - - - - - - - - - - {hasPermission(currentUser, 'READ_ORGANIZATIONS') && - - - - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - setInitialValues({...initialValues, 'start_date': date})} - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - setInitialValues({...initialValues, 'end_date': date})} - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/academic_years/academic_years-list')}/> - - - - - - - ) -} - -EditAcademic_years.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ) -} - -export default EditAcademic_years diff --git a/ref-frontend/src/pages/academic_years/academic_years-edit.tsx b/ref-frontend/src/pages/academic_years/academic_years-edit.tsx deleted file mode 100644 index 67e04f9..0000000 --- a/ref-frontend/src/pages/academic_years/academic_years-edit.tsx +++ /dev/null @@ -1,518 +0,0 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js' -import Head from 'next/head' -import React, { ReactElement, useEffect, useState } from 'react' -import DatePicker from "react-datepicker"; -import "react-datepicker/dist/react-datepicker.css"; -import dayjs from "dayjs"; - -import CardBox from '../../components/CardBox' -import LayoutAuthenticated from '../../layouts/Authenticated' -import SectionMain from '../../components/SectionMain' -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton' -import { getPageTitle } from '../../config' - -import { Field, Form, Formik } from 'formik' -import FormField from '../../components/FormField' -import BaseDivider from '../../components/BaseDivider' -import BaseButtons from '../../components/BaseButtons' -import BaseButton from '../../components/BaseButton' -import FormCheckRadio from '../../components/FormCheckRadio' -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup' -import FormFilePicker from '../../components/FormFilePicker' -import FormImagePicker from '../../components/FormImagePicker' -import { SelectField } from "../../components/SelectField"; -import { SelectFieldMany } from "../../components/SelectFieldMany"; -import { SwitchField } from '../../components/SwitchField' -import {RichTextField} from "../../components/RichTextField"; - -import { update, fetch } from '../../stores/academic_years/academic_yearsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from "../../components/ImageField"; - -import {hasPermission} from "../../helpers/userPermissions"; - - - -const EditAcademic_yearsPage = () => { - const router = useRouter() - const dispatch = useAppDispatch() - const initVals = { - - - - - - - - - - - - - - - - - - - - - - - - - organization: null, - - - - - - 'name': '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - start_date: new Date(), - - - - - - - - - - - - - - - - - - - - - - - - - - - - end_date: new Date(), - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - current: false, - - - - - - - - - - - - - - - - } - const [initialValues, setInitialValues] = useState(initVals) - - const { academic_years } = useAppSelector((state) => state.academic_years) - - const { currentUser } = useAppSelector((state) => state.auth); - - - const { id } = router.query - - useEffect(() => { - dispatch(fetch({ id: id })) - }, [id]) - - useEffect(() => { - if (typeof academic_years === 'object') { - setInitialValues(academic_years) - } - }, [academic_years]) - - useEffect(() => { - if (typeof academic_years === 'object') { - const newInitialVal = {...initVals}; - Object.keys(initVals).forEach(el => newInitialVal[el] = (academic_years)[el]) - setInitialValues(newInitialVal); - } - }, [academic_years]) - - const handleSubmit = async (data) => { - await dispatch(update({ id: id, data })) - await router.push('/academic_years/academic_years-list') - } - - return ( - <> - - {getPageTitle('Edit academic_years')} - - - - {''} - - - handleSubmit(values)} - > -
    - - - - - - - - - - - - - - - - - - - - - - {hasPermission(currentUser, 'READ_ORGANIZATIONS') && - - - - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - setInitialValues({...initialValues, 'start_date': date})} - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - setInitialValues({...initialValues, 'end_date': date})} - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/academic_years/academic_years-list')}/> - - -
    -
    -
    - - ) -} - -EditAcademic_yearsPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ) -} - -export default EditAcademic_yearsPage diff --git a/ref-frontend/src/pages/academic_years/academic_years-list.tsx b/ref-frontend/src/pages/academic_years/academic_years-list.tsx deleted file mode 100644 index 04410e3..0000000 --- a/ref-frontend/src/pages/academic_years/academic_years-list.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { mdiChartTimelineVariant } from '@mdi/js' -import Head from 'next/head' -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react' -import CardBox from '../../components/CardBox' -import LayoutAuthenticated from '../../layouts/Authenticated' -import SectionMain from '../../components/SectionMain' -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton' -import { getPageTitle } from '../../config' -import TableAcademic_years from '../../components/Academic_years/TableAcademic_years' -import BaseButton from '../../components/BaseButton' -import axios from "axios"; -import Link from "next/link"; -import {useAppDispatch, useAppSelector} from "../../stores/hooks"; -import CardBoxModal from "../../components/CardBoxModal"; -import DragDropFilePicker from "../../components/DragDropFilePicker"; -import {setRefetch, uploadCsv} from '../../stores/academic_years/academic_yearsSlice'; - - -import {hasPermission} from "../../helpers/userPermissions"; - - - -const Academic_yearsTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); - const [csvFile, setCsvFile] = useState(null); - const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); - - - const { currentUser } = useAppSelector((state) => state.auth); - - - const dispatch = useAppDispatch(); - - - const [filters] = useState([{label: 'AcademicYear', title: 'name'}, - - - {label: 'StartDate', title: 'start_date', date: 'true'},{label: 'EndDate', title: 'end_date', date: 'true'}, - - - - - - ]); - - const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_ACADEMIC_YEARS'); - - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getAcademic_yearsCSV = async () => { - const response = await axios({url: '/academic_years?filetype=csv', method: 'GET',responseType: 'blob'}); - const type = response.headers['content-type'] - const blob = new Blob([response.data], { type: type }) - const link = document.createElement('a') - link.href = window.URL.createObjectURL(blob) - link.download = 'academic_yearsCSV.csv' - link.click() - }; - - const onModalConfirm = async () => { - if (!csvFile) return; - await dispatch(uploadCsv(csvFile)); - dispatch(setRefetch(true)); - setCsvFile(null); - setIsModalActive(false); - }; - - const onModalCancel = () => { - setCsvFile(null); - setIsModalActive(false); - }; - - return ( - <> - - {getPageTitle('Academic_years')} - - - - {''} - - - - {hasCreatePermission && } - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
    -
    -
    - -
    - Switch to Table -
    - -
    - - - - - -
    - - - - - ) -} - -Academic_yearsTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ) -} - -export default Academic_yearsTablesPage diff --git a/ref-frontend/src/pages/academic_years/academic_years-new.tsx b/ref-frontend/src/pages/academic_years/academic_years-new.tsx deleted file mode 100644 index 611c2fb..0000000 --- a/ref-frontend/src/pages/academic_years/academic_years-new.tsx +++ /dev/null @@ -1,353 +0,0 @@ -import { mdiAccount, mdiChartTimelineVariant, mdiMail, mdiUpload } from '@mdi/js' -import Head from 'next/head' -import React, { ReactElement } from 'react' -import CardBox from '../../components/CardBox' -import LayoutAuthenticated from '../../layouts/Authenticated' -import SectionMain from '../../components/SectionMain' -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton' -import { getPageTitle } from '../../config' - -import { Field, Form, Formik } from 'formik' -import FormField from '../../components/FormField' -import BaseDivider from '../../components/BaseDivider' -import BaseButtons from '../../components/BaseButtons' -import BaseButton from '../../components/BaseButton' -import FormCheckRadio from '../../components/FormCheckRadio' -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup' -import FormFilePicker from '../../components/FormFilePicker' -import FormImagePicker from '../../components/FormImagePicker' -import { SwitchField } from '../../components/SwitchField' - -import { SelectField } from '../../components/SelectField' -import { SelectFieldMany } from "../../components/SelectFieldMany"; -import {RichTextField} from "../../components/RichTextField"; - -import { create } from '../../stores/academic_years/academic_yearsSlice' -import { useAppDispatch } from '../../stores/hooks' -import { useRouter } from 'next/router' -import moment from 'moment'; - -const initialValues = { - - - - - - - - - - - - - - organization: '', - - - - - name: '', - - - - - - - - - - - - - - - - - - - - - start_date: '', - - - - - - - - - - - - - - - - end_date: '', - - - - - - - - - - - - - - - - - current: false, - - - - - - - - -} - - -const Academic_yearsNew = () => { - const router = useRouter() - const dispatch = useAppDispatch() - - - - // get from url params - const { dateRangeStart, dateRangeEnd } = router.query - - - const handleSubmit = async (data) => { - await dispatch(create(data)) - await router.push('/academic_years/academic_years-list') - } - return ( - <> - - {getPageTitle('New Item')} - - - - {''} - - - handleSubmit(values)} - > -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/academic_years/academic_years-list')}/> - - -
    -
    -
    - - ) -} - -Academic_yearsNew.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ) -} - -export default Academic_yearsNew diff --git a/ref-frontend/src/pages/academic_years/academic_years-table.tsx b/ref-frontend/src/pages/academic_years/academic_years-table.tsx deleted file mode 100644 index e41dcdf..0000000 --- a/ref-frontend/src/pages/academic_years/academic_years-table.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import { mdiChartTimelineVariant } from '@mdi/js' -import Head from 'next/head' -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react' -import CardBox from '../../components/CardBox' -import LayoutAuthenticated from '../../layouts/Authenticated' -import SectionMain from '../../components/SectionMain' -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton' -import { getPageTitle } from '../../config' -import TableAcademic_years from '../../components/Academic_years/TableAcademic_years' -import BaseButton from '../../components/BaseButton' -import axios from "axios"; -import Link from "next/link"; -import {useAppDispatch, useAppSelector} from "../../stores/hooks"; -import CardBoxModal from "../../components/CardBoxModal"; -import DragDropFilePicker from "../../components/DragDropFilePicker"; -import {setRefetch, uploadCsv} from '../../stores/academic_years/academic_yearsSlice'; - - -import {hasPermission} from "../../helpers/userPermissions"; - - - -const Academic_yearsTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); - const [csvFile, setCsvFile] = useState(null); - const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); - - - const { currentUser } = useAppSelector((state) => state.auth); - - - const dispatch = useAppDispatch(); - - - const [filters] = useState([{label: 'AcademicYear', title: 'name'}, - - - {label: 'StartDate', title: 'start_date', date: 'true'},{label: 'EndDate', title: 'end_date', date: 'true'}, - - - - - - ]); - - const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_ACADEMIC_YEARS'); - - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getAcademic_yearsCSV = async () => { - const response = await axios({url: '/academic_years?filetype=csv', method: 'GET',responseType: 'blob'}); - const type = response.headers['content-type'] - const blob = new Blob([response.data], { type: type }) - const link = document.createElement('a') - link.href = window.URL.createObjectURL(blob) - link.download = 'academic_yearsCSV.csv' - link.click() - }; - - const onModalConfirm = async () => { - if (!csvFile) return; - await dispatch(uploadCsv(csvFile)); - dispatch(setRefetch(true)); - setCsvFile(null); - setIsModalActive(false); - }; - - const onModalCancel = () => { - setCsvFile(null); - setIsModalActive(false); - }; - - return ( - <> - - {getPageTitle('Academic_years')} - - - - {''} - - - - {hasCreatePermission && } - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
    -
    - - - Back to calendar - - -
    -
    - - - -
    - - - - - ) -} - -Academic_yearsTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ) -} - -export default Academic_yearsTablesPage diff --git a/ref-frontend/src/pages/academic_years/academic_years-view.tsx b/ref-frontend/src/pages/academic_years/academic_years-view.tsx deleted file mode 100644 index 0f89e13..0000000 --- a/ref-frontend/src/pages/academic_years/academic_years-view.tsx +++ /dev/null @@ -1,605 +0,0 @@ -import React, { ReactElement, useEffect } from 'react'; -import Head from 'next/head' -import DatePicker from "react-datepicker"; -import "react-datepicker/dist/react-datepicker.css"; -import dayjs from "dayjs"; -import {useAppDispatch, useAppSelector} from "../../stores/hooks"; -import {useRouter} from "next/router"; -import { fetch } from '../../stores/academic_years/academic_yearsSlice' -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from "../../components/ImageField"; -import LayoutAuthenticated from "../../layouts/Authenticated"; -import {getPageTitle} from "../../config"; -import SectionTitleLineWithButton from "../../components/SectionTitleLineWithButton"; -import SectionMain from "../../components/SectionMain"; -import CardBox from "../../components/CardBox"; -import BaseButton from "../../components/BaseButton"; -import BaseDivider from "../../components/BaseDivider"; -import {mdiChartTimelineVariant} from "@mdi/js"; -import {SwitchField} from "../../components/SwitchField"; -import FormField from "../../components/FormField"; - -import {hasPermission} from "../../helpers/userPermissions"; - - -const Academic_yearsView = () => { - const router = useRouter() - const dispatch = useAppDispatch() - const { academic_years } = useAppSelector((state) => state.academic_years) - - const { currentUser } = useAppSelector((state) => state.auth); - - - const { id } = router.query; - - function removeLastCharacter(str) { - console.log(str,`str`) - return str.slice(0, -1); - } - - useEffect(() => { - dispatch(fetch({ id })); - }, [dispatch, id]); - - - return ( - <> - - {getPageTitle('View academic_years')} - - - - - - - - - - - - - - - - - - - - - - - - - - - - {hasPermission(currentUser, 'READ_ORGANIZATIONS') && -
    -

    Organization

    - - - - - - - - -

    {academic_years?.organization?.name ?? 'No data'}

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - } - - - - - - - - - - -
    -

    AcademicYear

    -

    {academic_years?.name}

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {academic_years.start_date ? :

    No StartDate

    } -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {academic_years.end_date ? :

    No EndDate

    } -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - null}} - disabled - /> - - - - - - - - - - - - - - - - - - - - - - - - - <> -

    Classes AcademicYear

    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {academic_years.classes_academic_year && Array.isArray(academic_years.classes_academic_year) && - academic_years.classes_academic_year.map((item: any) => ( - router.push(`/classes/classes-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ))} - -
    ClassNameSectionCapacityStatus
    - { item.name } - - { item.section } - - { item.capacity } - - { item.status } -
    -
    - {!academic_years?.classes_academic_year?.length &&
    No data
    } -
    - - - - - - <> -

    Timetables AcademicYear

    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {academic_years.timetables_academic_year && Array.isArray(academic_years.timetables_academic_year) && - academic_years.timetables_academic_year.map((item: any) => ( - router.push(`/timetables/timetables-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - - - - - - - - - - - ))} - -
    TimetableNameEffectiveFromEffectiveToStatus
    - { item.name } - - { dataFormatter.dateTimeFormatter(item.effective_from) } - - { dataFormatter.dateTimeFormatter(item.effective_to) } - - { item.status } -
    -
    - {!academic_years?.timetables_academic_year?.length &&
    No data
    } -
    - - - - - - - <> -

    Fee_plans AcademicYear

    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {academic_years.fee_plans_academic_year && Array.isArray(academic_years.fee_plans_academic_year) && - academic_years.fee_plans_academic_year.map((item: any) => ( - router.push(`/fee_plans/fee_plans-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ))} - -
    FeePlanNameBillingCycleTotalAmountActiveNotes
    - { item.name } - - { item.billing_cycle } - - { item.total_amount } - - { dataFormatter.booleanFormatter(item.active) } - - { item.notes } -
    -
    - {!academic_years?.fee_plans_academic_year?.length &&
    No data
    } -
    - - - - - - - - - - - - - - router.push('/academic_years/academic_years-list')} - /> -
    -
    - - ); -}; - -Academic_yearsView.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ) -} - -export default Academic_yearsView; \ No newline at end of file diff --git a/ref-frontend/src/pages/api/hello.js b/ref-frontend/src/pages/api/hello.js deleted file mode 100644 index f163396..0000000 --- a/ref-frontend/src/pages/api/hello.js +++ /dev/null @@ -1,5 +0,0 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction - -export default function helloAPI(req, res) { - res.status(200).json({ name: 'John Doe' }) -} diff --git a/ref-frontend/src/pages/api/logError.ts b/ref-frontend/src/pages/api/logError.ts deleted file mode 100644 index d72b3fd..0000000 --- a/ref-frontend/src/pages/api/logError.ts +++ /dev/null @@ -1,83 +0,0 @@ -import fsPromises from 'fs/promises'; -import path from 'path'; - -const dataFilePath = path.join(process.cwd(), 'json/runtimeError.json'); - -export default async function handler(req, res) { - // Ensure directory exists - try { - await fsPromises.mkdir(path.dirname(dataFilePath), { recursive: true }); - } catch (error) { - // Ignore if directory already exists - } - - if (req.method === 'GET') { - try { - // Check if file exists - try { - await fsPromises.access(dataFilePath); - } catch (error) { - // File doesn't exist, return empty object - return res.status(200).json({}); - } - - // Read the existing data from the JSON file - const jsonData = await fsPromises.readFile(dataFilePath, 'utf-8'); - - // Handle empty file - if (!jsonData || jsonData.trim() === '') { - // Write empty JSON object to file - await fsPromises.writeFile(dataFilePath, '{}', 'utf-8'); - return res.status(200).json({}); - } - - // Parse JSON data - try { - const objectData = JSON.parse(jsonData); - return res.status(200).json(objectData); - } catch (parseError) { - console.error('Error parsing JSON from file:', parseError); - // Reset the file with valid JSON if parsing fails - await fsPromises.writeFile(dataFilePath, '{}', 'utf-8'); - return res.status(200).json({}); - } - } catch (error) { - console.error('Error in GET handler:', error); - return res.status(200).json({}); // Return empty object instead of error - } - } else if (req.method === 'POST') { - try { - const updatedData = JSON.stringify(req.body); - - // Create directory if it doesn't exist - await fsPromises.mkdir(path.dirname(dataFilePath), { recursive: true }); - - // Write the updated data to the JSON file - await fsPromises.writeFile(dataFilePath, updatedData); - - // Send a success response - res.status(200).json({ message: 'Data stored successfully' }); - } catch (error) { - console.error('Error in POST handler:', error); - // Send an error response - res.status(500).json({ message: 'Error storing data' }); - } - } else if (req.method === 'DELETE') { - try { - // Create directory if it doesn't exist - await fsPromises.mkdir(path.dirname(dataFilePath), { recursive: true }); - - // Write empty JSON object to file - await fsPromises.writeFile(dataFilePath, '{}'); - - // Send a success response - res.status(200).json({message: 'Data deleted successfully'}); - } catch (error) { - console.error('Error in DELETE handler:', error); - // Send an error response - res.status(500).json({message: 'Error deleting data'}); - } - } else { - res.status(405).json({ message: 'Method not allowed' }); - } -} diff --git a/ref-frontend/src/pages/assessment_results/[assessment_resultsId].tsx b/ref-frontend/src/pages/assessment_results/[assessment_resultsId].tsx deleted file mode 100644 index f733492..0000000 --- a/ref-frontend/src/pages/assessment_results/[assessment_resultsId].tsx +++ /dev/null @@ -1,702 +0,0 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js' -import Head from 'next/head' -import React, { ReactElement, useEffect, useState } from 'react' -import DatePicker from "react-datepicker"; -import "react-datepicker/dist/react-datepicker.css"; -import dayjs from "dayjs"; - -import CardBox from '../../components/CardBox' -import LayoutAuthenticated from '../../layouts/Authenticated' -import SectionMain from '../../components/SectionMain' -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton' -import { getPageTitle } from '../../config' - -import { Field, Form, Formik } from 'formik' -import FormField from '../../components/FormField' -import BaseDivider from '../../components/BaseDivider' -import BaseButtons from '../../components/BaseButtons' -import BaseButton from '../../components/BaseButton' -import FormCheckRadio from '../../components/FormCheckRadio' -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup' -import FormFilePicker from '../../components/FormFilePicker' -import FormImagePicker from '../../components/FormImagePicker' -import { SelectField } from "../../components/SelectField"; -import { SelectFieldMany } from "../../components/SelectFieldMany"; -import { SwitchField } from '../../components/SwitchField' -import {RichTextField} from "../../components/RichTextField"; - -import { update, fetch } from '../../stores/assessment_results/assessment_resultsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from "../../components/ImageField"; - -import {hasPermission} from "../../helpers/userPermissions"; - - - -const EditAssessment_results = () => { - const router = useRouter() - const dispatch = useAppDispatch() - const initVals = { - - - - - - - - - - - - - - - - - - - - - - - - - organization: null, - - - - - - - - - - - - - - - - - - - - - - - - - - - - assessment: null, - - - - - - - - - - - - - - - - - - - - - - - - - - - - student: null, - - - - - - 'score': '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - grade_letter: '', - - - - - - - - - - - - - - remarks: '', - - - - - - - - - - - - - - - - - - - - - - - - - - } - const [initialValues, setInitialValues] = useState(initVals) - - const { assessment_results } = useAppSelector((state) => state.assessment_results) - - const { currentUser } = useAppSelector((state) => state.auth); - - - const { assessment_resultsId } = router.query - - useEffect(() => { - dispatch(fetch({ id: assessment_resultsId })) - }, [assessment_resultsId]) - - useEffect(() => { - if (typeof assessment_results === 'object') { - setInitialValues(assessment_results) - } - }, [assessment_results]) - - useEffect(() => { - if (typeof assessment_results === 'object') { - - const newInitialVal = {...initVals}; - - Object.keys(initVals).forEach(el => newInitialVal[el] = (assessment_results)[el]) - - setInitialValues(newInitialVal); - } - }, [assessment_results]) - - const handleSubmit = async (data) => { - await dispatch(update({ id: assessment_resultsId, data })) - await router.push('/assessment_results/assessment_results-list') - } - - return ( - <> - - {getPageTitle('Edit assessment_results')} - - - - {''} - - - handleSubmit(values)} - > -
    - - - - - - - - - - - - - - - - - - - - - - {hasPermission(currentUser, 'READ_ORGANIZATIONS') && - - - - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/assessment_results/assessment_results-list')}/> - - -
    -
    -
    - - ) -} - -EditAssessment_results.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ) -} - -export default EditAssessment_results diff --git a/ref-frontend/src/pages/assessment_results/assessment_results-edit.tsx b/ref-frontend/src/pages/assessment_results/assessment_results-edit.tsx deleted file mode 100644 index e168e09..0000000 --- a/ref-frontend/src/pages/assessment_results/assessment_results-edit.tsx +++ /dev/null @@ -1,699 +0,0 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js' -import Head from 'next/head' -import React, { ReactElement, useEffect, useState } from 'react' -import DatePicker from "react-datepicker"; -import "react-datepicker/dist/react-datepicker.css"; -import dayjs from "dayjs"; - -import CardBox from '../../components/CardBox' -import LayoutAuthenticated from '../../layouts/Authenticated' -import SectionMain from '../../components/SectionMain' -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton' -import { getPageTitle } from '../../config' - -import { Field, Form, Formik } from 'formik' -import FormField from '../../components/FormField' -import BaseDivider from '../../components/BaseDivider' -import BaseButtons from '../../components/BaseButtons' -import BaseButton from '../../components/BaseButton' -import FormCheckRadio from '../../components/FormCheckRadio' -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup' -import FormFilePicker from '../../components/FormFilePicker' -import FormImagePicker from '../../components/FormImagePicker' -import { SelectField } from "../../components/SelectField"; -import { SelectFieldMany } from "../../components/SelectFieldMany"; -import { SwitchField } from '../../components/SwitchField' -import {RichTextField} from "../../components/RichTextField"; - -import { update, fetch } from '../../stores/assessment_results/assessment_resultsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from "../../components/ImageField"; - -import {hasPermission} from "../../helpers/userPermissions"; - - - -const EditAssessment_resultsPage = () => { - const router = useRouter() - const dispatch = useAppDispatch() - const initVals = { - - - - - - - - - - - - - - - - - - - - - - - - - organization: null, - - - - - - - - - - - - - - - - - - - - - - - - - - - - assessment: null, - - - - - - - - - - - - - - - - - - - - - - - - - - - - student: null, - - - - - - 'score': '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - grade_letter: '', - - - - - - - - - - - - - - remarks: '', - - - - - - - - - - - - - - - - - - - - - - - - - - } - const [initialValues, setInitialValues] = useState(initVals) - - const { assessment_results } = useAppSelector((state) => state.assessment_results) - - const { currentUser } = useAppSelector((state) => state.auth); - - - const { id } = router.query - - useEffect(() => { - dispatch(fetch({ id: id })) - }, [id]) - - useEffect(() => { - if (typeof assessment_results === 'object') { - setInitialValues(assessment_results) - } - }, [assessment_results]) - - useEffect(() => { - if (typeof assessment_results === 'object') { - const newInitialVal = {...initVals}; - Object.keys(initVals).forEach(el => newInitialVal[el] = (assessment_results)[el]) - setInitialValues(newInitialVal); - } - }, [assessment_results]) - - const handleSubmit = async (data) => { - await dispatch(update({ id: id, data })) - await router.push('/assessment_results/assessment_results-list') - } - - return ( - <> - - {getPageTitle('Edit assessment_results')} - - - - {''} - - - handleSubmit(values)} - > -
    - - - - - - - - - - - - - - - - - - - - - - {hasPermission(currentUser, 'READ_ORGANIZATIONS') && - - - - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/assessment_results/assessment_results-list')}/> - - -
    -
    -
    - - ) -} - -EditAssessment_resultsPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ) -} - -export default EditAssessment_resultsPage diff --git a/ref-frontend/src/pages/assessment_results/assessment_results-list.tsx b/ref-frontend/src/pages/assessment_results/assessment_results-list.tsx deleted file mode 100644 index 8d36151..0000000 --- a/ref-frontend/src/pages/assessment_results/assessment_results-list.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { mdiChartTimelineVariant } from '@mdi/js' -import Head from 'next/head' -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react' -import CardBox from '../../components/CardBox' -import LayoutAuthenticated from '../../layouts/Authenticated' -import SectionMain from '../../components/SectionMain' -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton' -import { getPageTitle } from '../../config' -import TableAssessment_results from '../../components/Assessment_results/TableAssessment_results' -import BaseButton from '../../components/BaseButton' -import axios from "axios"; -import Link from "next/link"; -import {useAppDispatch, useAppSelector} from "../../stores/hooks"; -import CardBoxModal from "../../components/CardBoxModal"; -import DragDropFilePicker from "../../components/DragDropFilePicker"; -import {setRefetch, uploadCsv} from '../../stores/assessment_results/assessment_resultsSlice'; - - -import {hasPermission} from "../../helpers/userPermissions"; - - - -const Assessment_resultsTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); - const [csvFile, setCsvFile] = useState(null); - const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); - - - const { currentUser } = useAppSelector((state) => state.auth); - - - const dispatch = useAppDispatch(); - - - const [filters] = useState([{label: 'Remarks', title: 'remarks'}, - - {label: 'Score', title: 'score', number: 'true'}, - - - - - - {label: 'Assessment', title: 'assessment'}, - - - - {label: 'Student', title: 'student'}, - - - - {label: 'GradeLetter', title: 'grade_letter', type: 'enum', options: ['A','B','C','D','E','F','P','N']}, - ]); - - const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_ASSESSMENT_RESULTS'); - - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getAssessment_resultsCSV = async () => { - const response = await axios({url: '/assessment_results?filetype=csv', method: 'GET',responseType: 'blob'}); - const type = response.headers['content-type'] - const blob = new Blob([response.data], { type: type }) - const link = document.createElement('a') - link.href = window.URL.createObjectURL(blob) - link.download = 'assessment_resultsCSV.csv' - link.click() - }; - - const onModalConfirm = async () => { - if (!csvFile) return; - await dispatch(uploadCsv(csvFile)); - dispatch(setRefetch(true)); - setCsvFile(null); - setIsModalActive(false); - }; - - const onModalCancel = () => { - setCsvFile(null); - setIsModalActive(false); - }; - - return ( - <> - - {getPageTitle('Assessment_results')} - - - - {''} - - - - {hasCreatePermission && } - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
    -
    -
    - -
    - - - - - -
    - - - - - ) -} - -Assessment_resultsTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ) -} - -export default Assessment_resultsTablesPage diff --git a/ref-frontend/src/pages/assessment_results/assessment_results-new.tsx b/ref-frontend/src/pages/assessment_results/assessment_results-new.tsx deleted file mode 100644 index e0b10c9..0000000 --- a/ref-frontend/src/pages/assessment_results/assessment_results-new.tsx +++ /dev/null @@ -1,393 +0,0 @@ -import { mdiAccount, mdiChartTimelineVariant, mdiMail, mdiUpload } from '@mdi/js' -import Head from 'next/head' -import React, { ReactElement } from 'react' -import CardBox from '../../components/CardBox' -import LayoutAuthenticated from '../../layouts/Authenticated' -import SectionMain from '../../components/SectionMain' -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton' -import { getPageTitle } from '../../config' - -import { Field, Form, Formik } from 'formik' -import FormField from '../../components/FormField' -import BaseDivider from '../../components/BaseDivider' -import BaseButtons from '../../components/BaseButtons' -import BaseButton from '../../components/BaseButton' -import FormCheckRadio from '../../components/FormCheckRadio' -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup' -import FormFilePicker from '../../components/FormFilePicker' -import FormImagePicker from '../../components/FormImagePicker' -import { SwitchField } from '../../components/SwitchField' - -import { SelectField } from '../../components/SelectField' -import { SelectFieldMany } from "../../components/SelectFieldMany"; -import {RichTextField} from "../../components/RichTextField"; - -import { create } from '../../stores/assessment_results/assessment_resultsSlice' -import { useAppDispatch } from '../../stores/hooks' -import { useRouter } from 'next/router' -import moment from 'moment'; - -const initialValues = { - - - - - - - - - - - - - - organization: '', - - - - - - - - - - - - - - - - assessment: '', - - - - - - - - - - - - - - - - student: '', - - - - - score: '', - - - - - - - - - - - - - - - - - - - - - - - - - grade_letter: 'A', - - - - - - - - - remarks: '', - - - - - - - - - - - - - -} - - -const Assessment_resultsNew = () => { - const router = useRouter() - const dispatch = useAppDispatch() - - - - - const handleSubmit = async (data) => { - await dispatch(create(data)) - await router.push('/assessment_results/assessment_results-list') - } - return ( - <> - - {getPageTitle('New Item')} - - - - {''} - - - handleSubmit(values)} - > -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/assessment_results/assessment_results-list')}/> - - -
    -
    -
    - - ) -} - -Assessment_resultsNew.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ) -} - -export default Assessment_resultsNew diff --git a/ref-frontend/src/pages/assessment_results/assessment_results-table.tsx b/ref-frontend/src/pages/assessment_results/assessment_results-table.tsx deleted file mode 100644 index 9d028b3..0000000 --- a/ref-frontend/src/pages/assessment_results/assessment_results-table.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import { mdiChartTimelineVariant } from '@mdi/js' -import Head from 'next/head' -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react' -import CardBox from '../../components/CardBox' -import LayoutAuthenticated from '../../layouts/Authenticated' -import SectionMain from '../../components/SectionMain' -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton' -import { getPageTitle } from '../../config' -import TableAssessment_results from '../../components/Assessment_results/TableAssessment_results' -import BaseButton from '../../components/BaseButton' -import axios from "axios"; -import Link from "next/link"; -import {useAppDispatch, useAppSelector} from "../../stores/hooks"; -import CardBoxModal from "../../components/CardBoxModal"; -import DragDropFilePicker from "../../components/DragDropFilePicker"; -import {setRefetch, uploadCsv} from '../../stores/assessment_results/assessment_resultsSlice'; - - -import {hasPermission} from "../../helpers/userPermissions"; - - - -const Assessment_resultsTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); - const [csvFile, setCsvFile] = useState(null); - const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); - - - const { currentUser } = useAppSelector((state) => state.auth); - - - const dispatch = useAppDispatch(); - - - const [filters] = useState([{label: 'Remarks', title: 'remarks'}, - - {label: 'Score', title: 'score', number: 'true'}, - - - - - - {label: 'Assessment', title: 'assessment'}, - - - - {label: 'Student', title: 'student'}, - - - - {label: 'GradeLetter', title: 'grade_letter', type: 'enum', options: ['A','B','C','D','E','F','P','N']}, - ]); - - const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_ASSESSMENT_RESULTS'); - - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getAssessment_resultsCSV = async () => { - const response = await axios({url: '/assessment_results?filetype=csv', method: 'GET',responseType: 'blob'}); - const type = response.headers['content-type'] - const blob = new Blob([response.data], { type: type }) - const link = document.createElement('a') - link.href = window.URL.createObjectURL(blob) - link.download = 'assessment_resultsCSV.csv' - link.click() - }; - - const onModalConfirm = async () => { - if (!csvFile) return; - await dispatch(uploadCsv(csvFile)); - dispatch(setRefetch(true)); - setCsvFile(null); - setIsModalActive(false); - }; - - const onModalCancel = () => { - setCsvFile(null); - setIsModalActive(false); - }; - - return ( - <> - - {getPageTitle('Assessment_results')} - - - - {''} - - - - {hasCreatePermission && } - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
    -
    - - - Back to table - - -
    -
    - - - -
    - - - - - ) -} - -Assessment_resultsTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ) -} - -export default Assessment_resultsTablesPage diff --git a/ref-frontend/src/pages/assessment_results/assessment_results-view.tsx b/ref-frontend/src/pages/assessment_results/assessment_results-view.tsx deleted file mode 100644 index 4d0f096..0000000 --- a/ref-frontend/src/pages/assessment_results/assessment_results-view.tsx +++ /dev/null @@ -1,474 +0,0 @@ -import React, { ReactElement, useEffect } from 'react'; -import Head from 'next/head' -import DatePicker from "react-datepicker"; -import "react-datepicker/dist/react-datepicker.css"; -import dayjs from "dayjs"; -import {useAppDispatch, useAppSelector} from "../../stores/hooks"; -import {useRouter} from "next/router"; -import { fetch } from '../../stores/assessment_results/assessment_resultsSlice' -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from "../../components/ImageField"; -import LayoutAuthenticated from "../../layouts/Authenticated"; -import {getPageTitle} from "../../config"; -import SectionTitleLineWithButton from "../../components/SectionTitleLineWithButton"; -import SectionMain from "../../components/SectionMain"; -import CardBox from "../../components/CardBox"; -import BaseButton from "../../components/BaseButton"; -import BaseDivider from "../../components/BaseDivider"; -import {mdiChartTimelineVariant} from "@mdi/js"; -import {SwitchField} from "../../components/SwitchField"; -import FormField from "../../components/FormField"; - -import {hasPermission} from "../../helpers/userPermissions"; - - -const Assessment_resultsView = () => { - const router = useRouter() - const dispatch = useAppDispatch() - const { assessment_results } = useAppSelector((state) => state.assessment_results) - - const { currentUser } = useAppSelector((state) => state.auth); - - - const { id } = router.query; - - function removeLastCharacter(str) { - console.log(str,`str`) - return str.slice(0, -1); - } - - useEffect(() => { - dispatch(fetch({ id })); - }, [dispatch, id]); - - - return ( - <> - - {getPageTitle('View assessment_results')} - - - - - - - - - - - - - - - - - - - - - - - - - - - - {hasPermission(currentUser, 'READ_ORGANIZATIONS') && -
    -

    Organization

    - - - - - - - - -

    {assessment_results?.organization?.name ?? 'No data'}

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -

    Assessment

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    {assessment_results?.assessment?.name ?? 'No data'}

    - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -

    Student

    - - - - - - - - - - - - - - - - - - -

    {assessment_results?.student?.student_number ?? 'No data'}

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - -
    -

    Score

    -

    {assessment_results?.score || 'No data'}

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -

    GradeLetter

    -

    {assessment_results?.grade_letter ?? 'No data'}

    -
    - - - - - - - - - - - - - - - - - -