first commi2t
This commit is contained in:
parent
994209af60
commit
2d13139bca
15
.env
Normal file
15
.env
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# MongoDB Configuration
|
||||||
|
MONGO_INITDB_ROOT_USERNAME=root
|
||||||
|
MONGO_INITDB_ROOT_PASSWORD=DaStrongPassword123!
|
||||||
|
MONGO_INITDB_DATABASE=Infinity
|
||||||
|
|
||||||
|
# Application Configuration
|
||||||
|
NODE_ENV=development
|
||||||
|
NEXT_PUBLIC_API_URL=http://localhost:3001
|
||||||
|
|
||||||
|
# Admin Account
|
||||||
|
ADMIN_USERNAME=admin
|
||||||
|
ADMIN_PASSWORD=ChangeThisPassword123!
|
||||||
|
|
||||||
|
# Database Connection
|
||||||
|
DB_URI=mongodb://root:DaStrongPassword123!@mongodb:27017/Infinity?authSource=admin
|
||||||
8
assets_sources.md
Normal file
8
assets_sources.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# InfinityGym
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
### Images
|
||||||
|
|
||||||
|
- Source 1: [Solar icons](https://www.figma.com/community/file/1166831539721848736/solar-icons-set)
|
||||||
|
- Srouce 2: [Icon scout](https://iconscout.com/)
|
||||||
76
docker-compose.yml
Normal file
76
docker-compose.yml
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
# MongoDB Service
|
||||||
|
mongodb:
|
||||||
|
image: mongo:6.0
|
||||||
|
container_name: infinity-mongodb
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME}
|
||||||
|
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD}
|
||||||
|
MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE}
|
||||||
|
volumes:
|
||||||
|
- mongodb_data:/data/db
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
|
networks:
|
||||||
|
- infinity-network
|
||||||
|
|
||||||
|
# Web Application (Next.js)
|
||||||
|
webapp:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: webapp/Dockerfile
|
||||||
|
container_name: infinity-webapp
|
||||||
|
restart: always
|
||||||
|
depends_on:
|
||||||
|
- mongodb
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=${NODE_ENV}
|
||||||
|
- DB_URI=${DB_URI}
|
||||||
|
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
networks:
|
||||||
|
- infinity-network
|
||||||
|
|
||||||
|
# Worker Service
|
||||||
|
worker:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: worker/Dockerfile
|
||||||
|
container_name: infinity-worker
|
||||||
|
restart: always
|
||||||
|
depends_on:
|
||||||
|
- mongodb
|
||||||
|
environment:
|
||||||
|
- DB_URI=${DB_URI}
|
||||||
|
networks:
|
||||||
|
- infinity-network
|
||||||
|
|
||||||
|
# Helper Service (runs once to create admin account)
|
||||||
|
create-admin:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: helpers/Dockerfile
|
||||||
|
container_name: infinity-create-admin
|
||||||
|
depends_on:
|
||||||
|
mongodb:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
- DB_URI=${DB_URI}
|
||||||
|
- ADMIN_USERNAME=${ADMIN_USERNAME}
|
||||||
|
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
||||||
|
networks:
|
||||||
|
- infinity-network
|
||||||
|
# This ensures the container exits after creating the admin account
|
||||||
|
command: sh -c "python create_account.py"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
infinity-network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mongodb_data:
|
||||||
|
driver: local
|
||||||
73867
document/طريقة الاستخدام.pdf
Normal file
73867
document/طريقة الاستخدام.pdf
Normal file
File diff suppressed because it is too large
Load Diff
BIN
document/طريقة التثبيت.pdf
Normal file
BIN
document/طريقة التثبيت.pdf
Normal file
Binary file not shown.
3
helpers/.env
Normal file
3
helpers/.env
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
DB_URI=mongodb+srv://xxx
|
||||||
|
ADMIN_USERNAME=xxx
|
||||||
|
ADMIN_PASSWORD=xxx
|
||||||
16
helpers/Dockerfile
Normal file
16
helpers/Dockerfile
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
FROM python:3.9-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install Python dependencies
|
||||||
|
COPY helpers/requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy helper scripts
|
||||||
|
COPY helpers/ .
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
# Command to run the create_account script
|
||||||
|
CMD ["python", "create_account.py"]
|
||||||
47
helpers/create_account.py
Normal file
47
helpers/create_account.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
from dotenv import load_dotenv # load env variables
|
||||||
|
import argparse # pars user args
|
||||||
|
import pymongo # for connecting to the db
|
||||||
|
import bcrypt # to hash the password
|
||||||
|
import os # deal with the os
|
||||||
|
|
||||||
|
# laod env variables
|
||||||
|
load_dotenv()
|
||||||
|
DB_URI = os.getenv("DB_URI")
|
||||||
|
|
||||||
|
def hash_password(password):
|
||||||
|
# Generate a salt and hash the password
|
||||||
|
salt = bcrypt.gensalt()
|
||||||
|
hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
|
||||||
|
return hashed.decode('utf-8')
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
parser = argparse.ArgumentParser(description="A script to parse command-line arguments")
|
||||||
|
parser.add_argument("-u", "--username", required=True, help="Username")
|
||||||
|
parser.add_argument("-p", "--password", required=True, help="Password")
|
||||||
|
|
||||||
|
# get args
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# connect to mongodb
|
||||||
|
client = pymongo.MongoClient(DB_URI)
|
||||||
|
|
||||||
|
# create the account
|
||||||
|
db = client['Infinity']
|
||||||
|
user_coll = db['users']
|
||||||
|
|
||||||
|
user_coll.insert_one({
|
||||||
|
# "username": args.username,
|
||||||
|
# "password": hash_password(args.password)
|
||||||
|
"username": os.getenv("ADMIN_USERNAME"),
|
||||||
|
"password": hash_password(os.getenv("ADMIN_PASSWORD"))
|
||||||
|
})
|
||||||
|
|
||||||
|
print('Account created successfully')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
print("\n \n please if this error wont be solved feel free to contact me")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
3
helpers/requirements.txt
Normal file
3
helpers/requirements.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
python-dotenv
|
||||||
|
pymongo
|
||||||
|
bcrypt
|
||||||
3
webapp/.eslintrc.json
Normal file
3
webapp/.eslintrc.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "next/core-web-vitals"
|
||||||
|
}
|
||||||
35
webapp/.gitignore
vendored
Normal file
35
webapp/.gitignore
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# 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 env files
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
43
webapp/Dockerfile
Normal file
43
webapp/Dockerfile
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Install dependencies only when needed
|
||||||
|
FROM node:18-alpine AS deps
|
||||||
|
WORKDIR /app
|
||||||
|
COPY webapp/package.json webapp/package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Rebuild the source code only when needed
|
||||||
|
FROM node:18-alpine AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY webapp .
|
||||||
|
|
||||||
|
# Next.js collects completely anonymous telemetry data about general usage.
|
||||||
|
# Learn more here: https://nextjs.org/telemetry
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Production image, copy all the files and run next
|
||||||
|
FROM node:18-alpine AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV production
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
|
|
||||||
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
|
RUN adduser --system --uid 1001 nextjs
|
||||||
|
|
||||||
|
COPY --from=builder /app/public ./public
|
||||||
|
|
||||||
|
# Automatically leverage output traces to reduce image size
|
||||||
|
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
|
|
||||||
|
USER nextjs
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
ENV PORT 3000
|
||||||
|
ENV HOSTNAME "0.0.0.0"
|
||||||
|
|
||||||
|
CMD ["node", "server.js"]
|
||||||
23
webapp/ecosystem.config.js
Normal file
23
webapp/ecosystem.config.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: 'Infinity',
|
||||||
|
exec_mode: 'cluster',
|
||||||
|
instances: 1, // Or a number of instances
|
||||||
|
script: 'node_modules/next/dist/bin/next',
|
||||||
|
args: 'start -p 5000',
|
||||||
|
env_local: {
|
||||||
|
DB_URI:"mongodb+srv://xxx",
|
||||||
|
NEXT_PUBLIC_API_BASE:"https://xxx"
|
||||||
|
},
|
||||||
|
env_development: {
|
||||||
|
DB_URI:"mongodb+srv://xxx",
|
||||||
|
NEXT_PUBLIC_API_BASE:"https://xxx"
|
||||||
|
},
|
||||||
|
env_production: {
|
||||||
|
DB_URI:"mongodb+srv://xxx",
|
||||||
|
NEXT_PUBLIC_API_BASE:"https://xxx"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
17
webapp/next.config.js
Normal file
17
webapp/next.config.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
typescript: {
|
||||||
|
ignoreBuildErrors: true,
|
||||||
|
},
|
||||||
|
async redirects() {
|
||||||
|
return [
|
||||||
|
{ // we redirect '/' to '/dashboard
|
||||||
|
source: '/',
|
||||||
|
destination: '/dashboard',
|
||||||
|
permanent: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = nextConfig
|
||||||
7296
webapp/package-lock.json
generated
Normal file
7296
webapp/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
58
webapp/package.json
Normal file
58
webapp/package.json
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"name": "gym_sync",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.11.1",
|
||||||
|
"@emotion/styled": "^11.11.0",
|
||||||
|
"@headlessui/react": "^1.7.16",
|
||||||
|
"@mui/base": "^5.0.0-beta.12",
|
||||||
|
"@mui/icons-material": "^5.14.1",
|
||||||
|
"@mui/material": "^5.14.2",
|
||||||
|
"@mui/styled-engine-sc": "^5.12.0",
|
||||||
|
"@popperjs/core": "^2.11.8",
|
||||||
|
"@reduxjs/toolkit": "^1.9.5",
|
||||||
|
"@tailwindcss/forms": "^0.5.4",
|
||||||
|
"@tailwindcss/line-clamp": "^0.4.4",
|
||||||
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
|
"@tippyjs/react": "^4.2.6",
|
||||||
|
"@types/bcrypt": "^5.0.0",
|
||||||
|
"@types/node": "20.4.4",
|
||||||
|
"@types/react": "^18.2.15",
|
||||||
|
"@types/react-dom": "^18.2.7",
|
||||||
|
"apexcharts": "^3.42.0",
|
||||||
|
"autoprefixer": "10.4.14",
|
||||||
|
"axios": "^1.4.0",
|
||||||
|
"bcrypt": "^5.1.0",
|
||||||
|
"eslint": "8.45.0",
|
||||||
|
"eslint-config-next": "^14.0.1",
|
||||||
|
"formik": "^2.4.2",
|
||||||
|
"formik-wizard-form": "^2.1.0",
|
||||||
|
"mongoose": "^7.4.1",
|
||||||
|
"next": "^13.4.12",
|
||||||
|
"next-intl": "^2.19.0",
|
||||||
|
"postcss": "8.4.27",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-animate-height": "^3.2.2",
|
||||||
|
"react-apexcharts": "^1.4.1",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-perfect-scrollbar": "^1.5.8",
|
||||||
|
"react-popper": "^2.3.0",
|
||||||
|
"react-quill": "^2.0.0",
|
||||||
|
"react-redux": "^8.1.1",
|
||||||
|
"react-select": "^5.8.0",
|
||||||
|
"redux-persist": "^6.0.0",
|
||||||
|
"styled-components": "^5.3.11",
|
||||||
|
"tailwindcss": "3.3.3",
|
||||||
|
"tippy.js": "^6.2.5",
|
||||||
|
"typescript": "5.1.6",
|
||||||
|
"universal-cookie": "^4.0.4",
|
||||||
|
"yup": "^1.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
webapp/postcss.config.js
Normal file
6
webapp/postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
BIN
webapp/public/assets/images/logo.png
Normal file
BIN
webapp/public/assets/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.2 KiB |
BIN
webapp/public/favicon.ico
Normal file
BIN
webapp/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.0 KiB |
1
webapp/public/next.svg
Normal file
1
webapp/public/next.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
1
webapp/public/vercel.svg
Normal file
1
webapp/public/vercel.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|
||||||
|
After Width: | Height: | Size: 629 B |
@ -0,0 +1,66 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import GeneralLook from '@/components/dashboard/home/generalLook'
|
||||||
|
import MembersOverviewChart from '@/components/dashboard/home/membersOverviewChart'
|
||||||
|
import ServicesSubscriptions from '@/components/dashboard/home/servicesSubscriptions'
|
||||||
|
import WorkersJobTypes from '@/components/dashboard/home/workersJobTypes'
|
||||||
|
import IncomeOutcome from '@/components/dashboard/home/incomeOutcome'
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { useAppSelector } from '@/redux/store';
|
||||||
|
import { AppDispatch } from '@/redux/store';
|
||||||
|
import { load } from '@/redux/features/statistics-slice'
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
|
||||||
|
const dispatch = useDispatch<AppDispatch>()
|
||||||
|
const t = useTranslations('statistics');
|
||||||
|
|
||||||
|
const report = useAppSelector((state) => state.statisticsReducer.value.report)
|
||||||
|
const loadedFirstTime = useAppSelector((state) => state.statisticsReducer.value.loadedFirstTime)
|
||||||
|
const isLoading = useAppSelector((state) => state.statisticsReducer.value.isLoading)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(loadedFirstTime) return;
|
||||||
|
if(isLoading) return;
|
||||||
|
async function a()
|
||||||
|
{
|
||||||
|
await dispatch(load({}))
|
||||||
|
}
|
||||||
|
a()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if(isLoading)
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="w-full h-[100vh] flex items-center justify-center gap-5">
|
||||||
|
<CircularProgress color="secondary" size={22} />
|
||||||
|
<h1 className="dark:text-text-light text-text">{t('loading')}...</h1>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<main className="min-h-[250px] max-h-[750px]">
|
||||||
|
<main className="w-full min-w-screen min-h-screen h-auto flex flex-col lg:gap-7 gap-3 pb-[50px]">
|
||||||
|
<section className="w-full flex lg:flex-row flex-col-reverse lg:gap-7 gap-3 lg:h-auto lg:min-h-[500px] items-stretch">
|
||||||
|
<GeneralLook/>
|
||||||
|
<MembersOverviewChart />
|
||||||
|
</section>
|
||||||
|
<section className="w-full flex lg:flex-row flex-col-reverse lg:gap-7 gap-3 lg:h-auto lg:min-h-[350px] items-stretch">
|
||||||
|
<ServicesSubscriptions />
|
||||||
|
<WorkersJobTypes />
|
||||||
|
</section>
|
||||||
|
<section className="w-full flex lg:flex-row flex-col-reverse lg:gap-7 gap-3 lg:h-auto lg:min-h-[350px] items-stretch">
|
||||||
|
<IncomeOutcome />
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import List from '@/components/dashboard/equipments/equipmentsList'
|
||||||
|
import PopUpWrapper from '@/components/dashboard/equipments/popUpsWrapper'
|
||||||
|
import AddNewButton from '@/components/dashboard/equipments/parts/addNewButton'
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<main className="min-h-[250px] max-h-[750px]">
|
||||||
|
<header className="relative w-full flex justify-center items-center">
|
||||||
|
<PopUpWrapper />
|
||||||
|
<AddNewButton />
|
||||||
|
</header>
|
||||||
|
<main className="w-full min-h-screen h-auto flex flex-col gap-7">
|
||||||
|
<List />
|
||||||
|
</main>
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import List from '@/components/dashboard/expenses/expensesList'
|
||||||
|
import PopUpWrapper from '@/components/dashboard/expenses/popUpsWrapper'
|
||||||
|
import AddNewButton from '@/components/dashboard/expenses/parts/addNewButton'
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<main className="min-h-[250px] max-h-[750px]">
|
||||||
|
<header className="relative w-full flex justify-center items-center">
|
||||||
|
<PopUpWrapper />
|
||||||
|
<AddNewButton />
|
||||||
|
</header>
|
||||||
|
<main className="w-full min-h-screen h-auto flex flex-col gap-7">
|
||||||
|
<List />
|
||||||
|
</main>
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import List from '@/components/dashboard/incomes/incomesList'
|
||||||
|
import PopUpWrapper from '@/components/dashboard/incomes/popUpsWrapper'
|
||||||
|
import AddNewButton from '@/components/dashboard/incomes/parts/addNewButton'
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<main className="min-h-[250px] max-h-[750px]">
|
||||||
|
<header className="relative w-full flex justify-center items-center">
|
||||||
|
<PopUpWrapper />
|
||||||
|
<AddNewButton />
|
||||||
|
</header>
|
||||||
|
<main className="w-full min-h-screen h-auto flex flex-col gap-7">
|
||||||
|
<List />
|
||||||
|
</main>
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { ReactNode } from 'react'
|
||||||
|
import Sidebar from '@/components/dashboard/sidebar';
|
||||||
|
import Header from '@/components/dashboard/header'
|
||||||
|
import { useAppSelector } from "@/redux/store";
|
||||||
|
|
||||||
|
// dashboard layout
|
||||||
|
export default function LocaleLayout({children} : { children : ReactNode }) {
|
||||||
|
|
||||||
|
const sidebarState = useAppSelector((state) => state.sidebarReducer.value.state);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section className="flex flex-row w-screen h-screen overflow-x-hidden no-scrollbar">
|
||||||
|
<Sidebar />
|
||||||
|
<div className={`transition-all duration-300 flex flex-col ${sidebarState ? 'w-full' : 'min-w-full'}`}>
|
||||||
|
<Header />
|
||||||
|
<div className="no-scrollbar relative w-full max-h-screen overflow-auto lg:p-7 p-5 bg-primary dark:bg-primary-dark">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import List from '@/components/dashboard/members/membersList'
|
||||||
|
import PopUpWrapper from '@/components/dashboard/members/popUpsWrapper'
|
||||||
|
import AddNewButton from '@/components/dashboard/members/parts/addNewButton'
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<main className="min-h-[250px] max-h-[750px]">
|
||||||
|
<header className="relative w-full flex justify-center items-center">
|
||||||
|
<PopUpWrapper />
|
||||||
|
<AddNewButton />
|
||||||
|
</header>
|
||||||
|
<main className="w-full min-h-screen h-auto flex flex-col gap-7">
|
||||||
|
<List />
|
||||||
|
</main>
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import List from '@/components/dashboard/products/productsList'
|
||||||
|
import PopUpWrapper from '@/components/dashboard/products/popUpsWrapper'
|
||||||
|
import AddNewButton from '@/components/dashboard/products/parts/addNewButton'
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<main className="min-h-[250px] max-h-[750px]">
|
||||||
|
<header className="relative w-full flex justify-center items-center">
|
||||||
|
<PopUpWrapper />
|
||||||
|
<AddNewButton />
|
||||||
|
</header>
|
||||||
|
<main className="w-full min-h-screen h-auto flex flex-col gap-7">
|
||||||
|
<List />
|
||||||
|
</main>
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import List from '@/components/dashboard/services/servicesList'
|
||||||
|
import ServicesPopUpWrapper from '@/components/dashboard/services/popUpsWrapper'
|
||||||
|
import AddNewButton from '@/components/dashboard/services/parts/addNewButton'
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<main className="min-h-[250px] max-h-[750px]">
|
||||||
|
<header className="relative w-full flex justify-center items-center">
|
||||||
|
<ServicesPopUpWrapper />
|
||||||
|
<AddNewButton />
|
||||||
|
</header>
|
||||||
|
<main className="w-full min-h-screen h-auto flex flex-col gap-7">
|
||||||
|
<List />
|
||||||
|
</main>
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import GeneralSettings from '@/components/dashboard/settings/generalSettings'
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<main className="min-h-[250px] max-h-[750px]">
|
||||||
|
<GeneralSettings />
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import List from '@/components/dashboard/workers/workersList'
|
||||||
|
import PopUpWrapper from '@/components/dashboard/workers/popUpsWrapper'
|
||||||
|
import AddNewButton from '@/components/dashboard/workers/parts/addNewButton'
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<main className="min-h-[250px] max-h-[750px]">
|
||||||
|
<header className="relative w-full flex justify-center items-center">
|
||||||
|
<PopUpWrapper />
|
||||||
|
<AddNewButton />
|
||||||
|
</header>
|
||||||
|
<main className="w-full min-h-screen h-auto flex flex-col gap-7">
|
||||||
|
<List />
|
||||||
|
</main>
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
62
webapp/src/app/[locale]/(GlobalWrapper)/(Auth)/layout.tsx
Normal file
62
webapp/src/app/[locale]/(GlobalWrapper)/(Auth)/layout.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useAppSelector } from "@/redux/store"
|
||||||
|
import { isLoggedIn , mountCheckIfValid } from "@/redux/features/auth-slice";
|
||||||
|
import { useDispatch } from "react-redux"
|
||||||
|
import { AppDispatch } from '@/redux/store';
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { ReactNode } from 'react'
|
||||||
|
import FullScreenLoader from "@/components/common/fullScreenLoader";
|
||||||
|
import { load } from '@/redux/features/settings-slice'
|
||||||
|
|
||||||
|
// HOC for auth pages ( only accept auth user )
|
||||||
|
export default function LocaleLayout({children} : { children : ReactNode }) {
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// load states
|
||||||
|
const isValid = useAppSelector((state) => state.authReducer.value.isValid)
|
||||||
|
const checkIfValidMounted = useAppSelector((state) => state.authReducer.value.checkIfValidMounted)
|
||||||
|
const notAuthRedirectPage = useAppSelector((state) => state.settingsReducer.value.notAuthRedirectPage)
|
||||||
|
const isLoadingSettings = useAppSelector((state) => state.settingsReducer.value.isLoadingSettings)
|
||||||
|
const loadedFirstTime = useAppSelector((state) => state.settingsReducer.value.loadedFirstTime)
|
||||||
|
// Get redux states
|
||||||
|
// init isLoggedIn
|
||||||
|
useEffect(() => {
|
||||||
|
if(checkIfValidMounted) return
|
||||||
|
dispatch(mountCheckIfValid())
|
||||||
|
async function a()
|
||||||
|
{
|
||||||
|
await dispatch(isLoggedIn({NotLoggedInCallback}))
|
||||||
|
}
|
||||||
|
a()
|
||||||
|
}, [])
|
||||||
|
// load settings
|
||||||
|
useEffect(() => {
|
||||||
|
if(loadedFirstTime) return
|
||||||
|
if(isLoadingSettings) return
|
||||||
|
async function a()
|
||||||
|
{
|
||||||
|
await dispatch(load({page : 1}))
|
||||||
|
}
|
||||||
|
a()
|
||||||
|
}, [])
|
||||||
|
// if wasnt logged in this will fire
|
||||||
|
function NotLoggedInCallback()
|
||||||
|
{
|
||||||
|
return router.push(notAuthRedirectPage)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
isValid ?
|
||||||
|
<>
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
:
|
||||||
|
<FullScreenLoader />
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
52
webapp/src/app/[locale]/(GlobalWrapper)/(NotAuth)/layout.tsx
Normal file
52
webapp/src/app/[locale]/(GlobalWrapper)/(NotAuth)/layout.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useAppSelector } from "@/redux/store"
|
||||||
|
import { isLoggedIn , mountCheckIfValid } from "@/redux/features/auth-slice";
|
||||||
|
import { useDispatch } from "react-redux"
|
||||||
|
import { AppDispatch } from '@/redux/store';
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { ReactNode } from 'react'
|
||||||
|
import FullScreenLoader from "@/components/common/fullScreenLoader";
|
||||||
|
|
||||||
|
//? HOC for no auth pages ( only accept no auth user )
|
||||||
|
export default function LocaleLayout({children} : { children : ReactNode }) {
|
||||||
|
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
const isValid = useAppSelector((state) => state.authReducer.value.isValid)
|
||||||
|
const checkIfValidMounted = useAppSelector((state) => state.authReducer.value.checkIfValidMounted)
|
||||||
|
|
||||||
|
const authRedirectPage = useAppSelector((state) => state.settingsReducer.value.authRedirectPage)
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// Get redux states
|
||||||
|
useEffect(() => { // init isLoggedIn
|
||||||
|
if(checkIfValidMounted) return
|
||||||
|
dispatch(mountCheckIfValid())
|
||||||
|
async function a()
|
||||||
|
{
|
||||||
|
await dispatch(isLoggedIn({LoggedInCallback}))
|
||||||
|
}
|
||||||
|
a()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// if logged in this will fire
|
||||||
|
function LoggedInCallback()
|
||||||
|
{
|
||||||
|
return router.push(authRedirectPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
!isValid ?
|
||||||
|
<>
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
:
|
||||||
|
<FullScreenLoader />
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
305
webapp/src/app/[locale]/(GlobalWrapper)/(NotAuth)/login/page.tsx
Normal file
305
webapp/src/app/[locale]/(GlobalWrapper)/(NotAuth)/login/page.tsx
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { AppDispatch } from '@/redux/store';
|
||||||
|
import { logIn } from '@/redux/features/auth-slice';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import * as Yup from "yup";
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { useAppSelector } from "@/redux/store"
|
||||||
|
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||||
|
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
|
||||||
|
|
||||||
|
export default function Index() {
|
||||||
|
// declare needed variables
|
||||||
|
const dispatch = useDispatch<AppDispatch>() //* to use redux reducers
|
||||||
|
const t = useTranslations('Auth');
|
||||||
|
const router = useRouter()
|
||||||
|
const authRedirectPage = useAppSelector((state) => state.settingsReducer.value.authRedirectPage)
|
||||||
|
// handle password show and hide state
|
||||||
|
const [ show, setShow ] = useState(false)
|
||||||
|
function handleShow(e : any) {
|
||||||
|
e.preventDefault()
|
||||||
|
setShow(true)
|
||||||
|
}
|
||||||
|
function handleHide(e : any) {
|
||||||
|
e.preventDefault()
|
||||||
|
setShow(false)
|
||||||
|
}
|
||||||
|
// Yup schema to validate the form
|
||||||
|
const validateUserSchema = Yup.object().shape({
|
||||||
|
username: Yup.string().required('thisFieldIsRequired'),
|
||||||
|
password: Yup.string().required('thisFieldIsRequired').min(8 , 'atList8Characters'),
|
||||||
|
});
|
||||||
|
// fire whene user logged successffully
|
||||||
|
function SuccessCallback()
|
||||||
|
{
|
||||||
|
return router.push(authRedirectPage)
|
||||||
|
}
|
||||||
|
// return the page ui
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Formik
|
||||||
|
// Initial values
|
||||||
|
initialValues={{ username: '', password: '' }}
|
||||||
|
// validate value
|
||||||
|
validationSchema={validateUserSchema}
|
||||||
|
// submit values
|
||||||
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
|
// call logIn which is a redux reducer that will ahndle the login proccess
|
||||||
|
await dispatch(logIn({
|
||||||
|
username : values.username,
|
||||||
|
password : values.password,
|
||||||
|
successCallback : SuccessCallback
|
||||||
|
}))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
values,
|
||||||
|
errors,
|
||||||
|
touched,
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
/* and other goodies */
|
||||||
|
}) => (
|
||||||
|
<main className="overflow-hidden w-full min-h-[100vh] max-h-[100vh] h-auto flex justify-start align-start items-center lg:items-start bg-primary dark:bg-primary-dark [&_*]:text-text-light [&_*]:dark:text-text-dark">
|
||||||
|
<form onSubmit={handleSubmit} className="w-full max-w-[771px] h-auto min-h-[100vh] lg:p-12 p-5 font-semibold bg-secondary-light dark:bg-secondary-dark">
|
||||||
|
<header className="h-[104px] flex flex-col justify-start items-start">
|
||||||
|
<h1 className="text-[40px]">{t('logIn')}</h1>
|
||||||
|
<p className="text-[20px]">{t('emailerLogoDesc1')}</p>
|
||||||
|
</header>
|
||||||
|
<div className="mt-[50px] gap-36 flex flex-col justify-between">
|
||||||
|
<div>
|
||||||
|
<div className="mb-4 flex flex-col gap-3">
|
||||||
|
<div>
|
||||||
|
<label className="block text-[16px] font-bold mb-1 ml-2" htmlFor="username">
|
||||||
|
{t('userName')}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
disabled={isSubmitting}
|
||||||
|
className="!text-text h-[50px] disabled:cursor-not-allowed disabled:opacity-60 bg-primary dark:bg-primary shadow appearance-none rounded-[5px] h-[45px] w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline"
|
||||||
|
id="username"
|
||||||
|
type="text"
|
||||||
|
placeholder={t('emailInputHolder')}
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.username}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="!text-error text-sm">{errors.username && touched.username && t(errors.username)}</p>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4 flex flex-col gap-3">
|
||||||
|
<div>
|
||||||
|
<label className="block text-[16px] font-bold mb-1 ml-2" htmlFor="password">
|
||||||
|
{t('password')}
|
||||||
|
</label>
|
||||||
|
<div className="relative w-full h-[50px]">
|
||||||
|
<input
|
||||||
|
disabled={isSubmitting}
|
||||||
|
className="!text-text h-[50px] disabled:cursor-not-allowed disabled:opacity-60 bg-primary dark:bg-primary shadow appearance-none rounded-[5px] w-full h-[45px] py-[5px] px-3 leading-tight focus:outline-none focus:shadow-outline"
|
||||||
|
id="password"
|
||||||
|
type={show ? "text" : "password"}
|
||||||
|
placeholder="*******"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.password}
|
||||||
|
/>
|
||||||
|
{
|
||||||
|
show ?
|
||||||
|
<button onClick={handleHide} className="hover:bg-primary-dark/5 p-1 rounded-full absolute ltr:right-[15px] rtl:left-[15px] top-[50%] translate-y-[-50%]">
|
||||||
|
<VisibilityOffIcon sx={{
|
||||||
|
fill: "#000"
|
||||||
|
}}/>
|
||||||
|
</button>
|
||||||
|
:
|
||||||
|
<button onClick={handleShow} className="hover:bg-primary-dark/5 p-1 rounded-full absolute ltr:right-[15px] rtl:left-[15px] top-[50%] translate-y-[-50%]">
|
||||||
|
<VisibilityIcon sx={{
|
||||||
|
fill: "#000"
|
||||||
|
}}/>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="!text-error text-sm">{errors.password && touched.password && t(errors.password)}</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-auto h-[70px] lg:h-[90px] flex justify-start align-start">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
className="
|
||||||
|
flex justify-center align-center items-center
|
||||||
|
rounded-[5px] w-full bg-secondary-dark dark:bg-secondary-light text-[20px]
|
||||||
|
disabled:animate-pulse h-[55px] mt-[20px]
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{
|
||||||
|
!isSubmitting ?
|
||||||
|
<>
|
||||||
|
{t('logIn')}
|
||||||
|
</>
|
||||||
|
:
|
||||||
|
<CircularProgress color="secondary" size={24} />
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer className="mt-auto w-full h-[50px]">
|
||||||
|
<h3>{t('madedWith')} ❤️ {t('by')} {t('developer')}</h3>
|
||||||
|
<h6 className="opacity-40">{t('allRightsReserved')}</h6>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div className="ltr:rotate-180 w-[216px] h-screen lg:flex hidden justify-start items-start bg-primary dark:bg-primary-dark">
|
||||||
|
<svg className="[&_*]:text-secondary-light [&_*]:fill-secondary-light [&_*]:dark:fill-secondary-dark" width="216" height="970" viewBox="0 0 216 970" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_3_5265)">
|
||||||
|
<path d="M108 970L89.9776 958.481C72.2251 946.895 35.7751 923.521 35.9776 900.618C35.7751 877.648 72.2251 854.812 100.778 831.236C129.6 808.333 151.2 785.431 151.2 761.854C151.2 739.019 129.6 716.049 122.378 693.146C115.425 669.772 122.175 646.667 136.823 623.764C151.2 600.457 172.8 577.285 176.378 554.382C180.225 531.21 165.375 507.903 147.623 485C129.6 461.895 108 438.521 104.423 415.618C100.575 392.648 115.425 369.813 129.6 346.236C143.775 323.333 158.625 300.431 158.423 276.854C158.625 254.019 143.775 231.049 126.023 208.146C108 184.772 86.4 161.667 82.8225 138.764C78.975 115.457 93.8251 92.2847 108 69.382C122.175 46.2097 137.025 22.9028 143.978 11.4514L151.2 4.50017e-06L216 1.66767e-06L216 11.5187C216 23.1048 216 46.4792 216 69.3819C216 92.3521 216 115.188 216 138.764C216 161.667 216 184.569 216 208.146C216 230.981 216 253.951 216 276.854C216 300.228 216 323.333 216 346.236C216 369.543 216 392.715 216 415.618C216 438.79 216 462.097 216 485C216 508.105 216 531.479 216 554.382C216 577.352 216 600.187 216 623.764C216 646.667 216 669.569 216 693.146C216 715.981 216 738.951 216 761.854C216 785.228 216 808.333 216 831.236C216 854.543 216 877.715 216 900.618C216 923.79 216 947.097 216 958.549L216 970L108 970Z" fill="currentColor"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_3_5265">
|
||||||
|
<rect width="970" height="216" fill="white" transform="translate(6.10352e-05 970) rotate(-90)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-screen justify-start items-center flex-col pt-[90px] lg:flex hidden bg-primary dark:bg-primary-dark">
|
||||||
|
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M499.875 382.4H0.125C0.0559644 382.4 0 382.456 0 382.525C0 382.594 0.0559553 382.65 0.124991 382.65H499.875C499.944 382.65 500 382.594 500 382.525C500 382.456 499.944 382.4 499.875 382.4Z" fill="#EBEBEB"/>
|
||||||
|
<path d="M433.665 393.04H400.795C400.726 393.04 400.67 393.096 400.67 393.165C400.67 393.234 400.726 393.29 400.795 393.29H433.665C433.734 393.29 433.79 393.234 433.79 393.165C433.79 393.096 433.734 393.04 433.665 393.04Z" fill="#EBEBEB"/>
|
||||||
|
<path d="M312.945 392.92H304.505C304.436 392.92 304.38 392.976 304.38 393.045C304.38 393.114 304.436 393.17 304.505 393.17H312.945C313.014 393.17 313.07 393.114 313.07 393.045C313.07 392.976 313.014 392.92 312.945 392.92Z" fill="#EBEBEB"/>
|
||||||
|
<path d="M449.775 398.49H430.835C430.766 398.49 430.71 398.546 430.71 398.615C430.71 398.684 430.766 398.74 430.835 398.74H449.775C449.844 398.74 449.9 398.684 449.9 398.615C449.9 398.546 449.844 398.49 449.775 398.49Z" fill="#EBEBEB"/>
|
||||||
|
<path d="M114.475 401.81H71.535C71.466 401.81 71.41 401.866 71.41 401.935C71.41 402.004 71.466 402.06 71.535 402.06H114.475C114.544 402.06 114.6 402.004 114.6 401.935C114.6 401.866 114.544 401.81 114.475 401.81Z" fill="#EBEBEB"/>
|
||||||
|
<path d="M110.765 390.89H104.685C104.616 390.89 104.56 390.946 104.56 391.015C104.56 391.084 104.616 391.14 104.685 391.14H110.765C110.834 391.14 110.89 391.084 110.89 391.015C110.89 390.946 110.834 390.89 110.765 390.89Z" fill="#EBEBEB"/>
|
||||||
|
<path d="M248.325 391.96H187.675C187.606 391.96 187.55 392.016 187.55 392.085C187.55 392.154 187.606 392.21 187.675 392.21H248.325C248.394 392.21 248.45 392.154 248.45 392.085C248.45 392.016 248.394 391.96 248.325 391.96Z" fill="#EBEBEB"/>
|
||||||
|
<path d="M237 337.8H43.91C42.3974 337.797 40.9476 337.195 39.8789 336.124C38.8102 335.054 38.21 333.603 38.21 332.09V60.66C38.2232 59.156 38.8292 57.718 39.8965 56.6582C40.9637 55.5985 42.406 55.0026 43.91 55H237C238.514 55 239.967 55.6016 241.038 56.6724C242.108 57.7433 242.71 59.1956 242.71 60.71V332.09C242.71 333.604 242.108 335.057 241.038 336.128C239.967 337.198 238.514 337.8 237 337.8ZM43.91 55.2C42.4637 55.2026 41.0775 55.7791 40.0557 56.8027C39.0339 57.8264 38.46 59.2136 38.46 60.66V332.09C38.46 333.536 39.0339 334.924 40.0557 335.947C41.0775 336.971 42.4637 337.547 43.91 337.55H237C238.447 337.547 239.835 336.971 240.858 335.948C241.881 334.924 242.457 333.537 242.46 332.09V60.66C242.457 59.2127 241.881 57.8255 240.858 56.8021C239.835 55.7787 238.447 55.2026 237 55.2H43.91Z" fill="#EBEBEB"/>
|
||||||
|
<path d="M453.31 337.8H260.21C258.696 337.797 257.246 337.195 256.175 336.125C255.105 335.054 254.503 333.604 254.5 332.09V60.66C254.516 59.1551 255.124 57.7171 256.193 56.6576C257.262 55.5981 258.705 55.0025 260.21 55H453.31C454.812 55.0052 456.252 55.6022 457.317 56.6617C458.382 57.7212 458.987 59.1578 459 60.66V332.09C459 333.601 458.401 335.05 457.335 336.121C456.268 337.191 454.821 337.795 453.31 337.8ZM260.21 55.2C258.763 55.2026 257.375 55.7787 256.352 56.8021C255.329 57.8255 254.753 59.2127 254.75 60.66V332.09C254.753 333.537 255.329 334.924 256.352 335.948C257.375 336.971 258.763 337.547 260.21 337.55H453.31C454.757 337.547 456.145 336.971 457.168 335.948C458.191 334.924 458.767 333.537 458.77 332.09V60.66C458.767 59.2127 458.191 57.8255 457.168 56.8021C456.145 55.7787 454.757 55.2026 453.31 55.2H260.21Z" fill="#EBEBEB"/>
|
||||||
|
<path d="M79.0578 349.828C76.8736 350.912 73.9874 350.028 73.93 347.59V347.59C73.93 340.81 79.37 339.42 87.54 339.42C95.71 339.42 101.15 340.81 101.15 347.59V347.59C101.116 349.67 98.7048 350.526 96.8225 349.641C93.9318 348.281 90.7429 347.61 87.5092 347.709C84.5521 347.799 81.6717 348.53 79.0578 349.828ZM104.365 355.666C104.35 355.657 104.344 355.638 104.349 355.622C105.168 353.021 105.607 350.316 105.65 347.59C105.65 333 93 333 87.54 333C82.08 333 69.43 333 69.43 347.61V347.61C69.5452 353.511 68.5918 359.646 67.8043 365.496C67.6047 366.979 67.5666 368.489 67.6968 369.999V369.999C68.4595 378.84 78.3297 382.42 87.2039 382.42H88.4027C97.7273 382.42 108.187 378.612 108.664 369.3V369.3C108.916 364.391 107.393 359.555 104.374 355.675C104.371 355.672 104.368 355.669 104.365 355.666V355.666Z" fill="#E6E6E6"/>
|
||||||
|
<path d="M129.11 355.001C129.11 355 129.11 355 129.11 354.999C128.648 353.47 128.395 351.886 128.36 350.29C128.36 344.05 133.36 342.77 140.91 342.77C148.46 342.77 153.45 344.05 153.45 350.29V350.29C153.426 352.224 151.185 353.014 149.433 352.195C146.773 350.952 143.842 350.338 140.87 350.429C136.545 350.562 132.398 352.179 129.124 355.008C129.119 355.013 129.11 355.009 129.11 355.001V355.001ZM156.379 357.73C156.386 357.73 156.391 357.726 156.393 357.72C157.152 355.315 157.559 352.812 157.6 350.29C157.6 336.83 145.9 336.83 140.91 336.83C135.92 336.83 124.21 336.83 124.21 350.29V350.29C124.32 355.735 123.434 361.394 122.705 366.792C122.521 368.154 122.485 369.54 122.602 370.926V370.926C123.292 379.07 132.377 382.38 140.549 382.38H141.64C150.231 382.38 159.867 378.869 160.306 370.289V370.289C160.537 365.774 159.139 361.325 156.368 357.753C156.361 357.744 156.367 357.73 156.379 357.73V357.73Z" fill="#E6E6E6"/>
|
||||||
|
<path d="M187.107 354.925C185.265 355.839 182.832 355.096 182.79 353.04V353.04C182.79 347.32 187.38 346.15 194.27 346.15C201.16 346.15 205.75 347.32 205.75 353.04V353.04C205.719 354.795 203.686 355.515 202.098 354.769C199.66 353.622 196.97 353.055 194.243 353.138C191.746 353.214 189.314 353.83 187.107 354.925ZM208.469 359.833C208.475 359.836 208.482 359.834 208.484 359.828C209.18 357.631 209.552 355.344 209.59 353.04C209.59 340.72 198.88 340.72 194.31 340.72C189.74 340.72 179 340.72 179 353V353C179.108 357.978 178.298 363.15 177.635 368.084C177.467 369.335 177.435 370.608 177.544 371.882V371.882C178.186 379.336 186.504 382.36 193.986 382.36H194.962C202.823 382.36 211.638 379.145 212.039 371.294V371.294C212.249 367.172 210.978 363.112 208.456 359.847C208.45 359.838 208.46 359.827 208.469 359.833V359.833Z" fill="#E6E6E6"/>
|
||||||
|
<path d="M241.137 357.476C239.468 358.304 237.263 357.623 237.22 355.76V355.76C237.22 350.57 241.38 349.51 247.63 349.51C253.88 349.51 258.05 350.57 258.05 355.76V355.76C258.022 357.354 256.176 358.011 254.733 357.333C252.521 356.294 250.082 355.781 247.608 355.856C245.344 355.925 243.139 356.484 241.137 357.476ZM260.49 361.902C260.49 361.907 260.497 361.908 260.498 361.904C261.125 359.915 261.459 357.845 261.49 355.76C261.49 344.58 251.78 344.58 247.63 344.58C243.48 344.58 233.77 344.58 233.77 355.76V355.76C233.873 360.256 233.142 364.927 232.534 369.382C232.377 370.527 232.347 371.692 232.447 372.857V372.857C233.03 379.621 240.58 382.36 247.369 382.36H248.272C255.405 382.36 263.404 379.444 263.77 372.32V372.32C263.962 368.567 262.801 364.869 260.498 361.9C260.495 361.897 260.49 361.898 260.49 361.902V361.902Z" fill="#E6E6E6"/>
|
||||||
|
<path d="M289.78 362.003C289.78 362.001 289.78 361.999 289.779 361.998C289.433 360.863 289.245 359.686 289.22 358.5C289.22 353.84 292.96 352.89 298.57 352.89C304.18 352.89 307.92 353.84 307.92 358.5V358.5C307.903 359.951 306.221 360.544 304.905 359.932C302.929 359.012 300.751 358.558 298.544 358.625C295.33 358.723 292.247 359.92 289.808 362.016C289.797 362.025 289.78 362.017 289.78 362.003V362.003ZM312.595 369.013C311.687 365.605 310.954 361.987 311.01 358.46V358.46C311.01 348.46 302.29 348.46 298.57 348.46C294.85 348.46 286.13 348.46 286.13 358.46V358.46C286.211 362.512 285.559 366.725 285.018 370.742C284.88 371.761 284.854 372.798 284.943 373.835V373.835C285.464 379.907 292.238 382.37 298.332 382.37H299.15C305.549 382.37 312.724 379.751 313.051 373.36V373.36C313.127 371.884 312.969 370.418 312.595 369.013Z" fill="#E6E6E6"/>
|
||||||
|
<path d="M198.23 100.33H76.25C48.6358 100.33 26.25 122.716 26.25 150.33V211.22C26.25 238.834 48.6358 261.22 76.25 261.22H198.23C225.844 261.22 248.23 238.834 248.23 211.22V150.33C248.23 122.716 225.844 100.33 198.23 100.33Z" fill="#F5F5F5"/>
|
||||||
|
<path d="M210.11 185H122.11C101.062 185 84 202.062 84 223.11C84 244.158 101.062 261.22 122.11 261.22H210.11C231.158 261.22 248.22 244.158 248.22 223.11C248.22 202.062 231.158 185 210.11 185Z" fill="#FAFAFA"/>
|
||||||
|
<path d="M132.759 127.604C136.623 113.918 126.34 100.33 112.119 100.33V100.33C102.519 100.33 94.0886 106.71 91.4799 115.949L58.1614 233.946C54.297 247.632 64.5799 261.22 78.8008 261.22V261.22C88.4008 261.22 96.8315 254.84 99.4402 245.601L132.759 127.604Z" fill="white"/>
|
||||||
|
<path d="M159.878 109.827C161.224 105.062 157.643 100.33 152.691 100.33V100.33C149.348 100.33 146.413 102.552 145.504 105.769L104.292 251.723C102.946 256.488 106.527 261.22 111.479 261.22V261.22C114.822 261.22 117.757 258.998 118.666 255.781L159.878 109.827Z" fill="white"/>
|
||||||
|
<path d="M424.56 100.33H302.58C274.966 100.33 252.58 122.716 252.58 150.33V211.22C252.58 238.834 274.966 261.22 302.58 261.22H424.56C452.174 261.22 474.56 238.834 474.56 211.22V150.33C474.56 122.716 452.174 100.33 424.56 100.33Z" fill="#F5F5F5"/>
|
||||||
|
<path d="M388 153.44H362.18C341.132 153.44 324.07 170.502 324.07 191.55C324.07 212.598 341.132 229.66 362.18 229.66H388C409.048 229.66 426.11 212.598 426.11 191.55C426.11 170.502 409.048 153.44 388 153.44Z" fill="#FAFAFA"/>
|
||||||
|
<path d="M449.23 166.115V217.005C449.23 224 454.9 229.67 461.895 229.67C468.89 229.67 474.56 224 474.56 217.005V166.115C474.56 159.12 468.89 153.45 461.895 153.45C454.9 153.45 449.23 159.12 449.23 166.115Z" fill="#FAFAFA"/>
|
||||||
|
<path d="M340.35 115.023C342.432 107.651 336.893 100.33 329.232 100.33V100.33C324.061 100.33 319.519 103.766 318.114 108.743L279.2 246.527C277.118 253.899 282.657 261.22 290.318 261.22V261.22C295.489 261.22 300.031 257.784 301.436 252.807L340.35 115.023Z" fill="white"/>
|
||||||
|
<path d="M370.821 115.023C372.903 107.65 367.364 100.33 359.703 100.33V100.33C354.531 100.33 349.99 103.767 348.584 108.744L309.679 246.527C307.597 253.9 313.136 261.22 320.797 261.22V261.22C325.969 261.22 330.51 257.783 331.916 252.806L370.821 115.023Z" fill="white"/>
|
||||||
|
<path d="M470.445 283.67H37.025C34.6308 283.67 32.69 285.611 32.69 288.005C32.69 290.399 34.6308 292.34 37.025 292.34H470.445C472.839 292.34 474.78 290.399 474.78 288.005C474.78 285.611 472.839 283.67 470.445 283.67Z" fill="#EBEBEB"/>
|
||||||
|
<path d="M32.69 288.005C32.69 290.399 34.6309 292.34 37.025 292.34C39.4192 292.34 41.36 290.399 41.36 288.005C41.36 285.611 39.4192 283.67 37.025 283.67C34.6309 283.67 32.69 285.611 32.69 288.005Z" fill="#E0E0E0"/>
|
||||||
|
<path d="M353.84 307.44H354.84C375.15 307.96 393.23 324.54 395.46 344.92C397.69 365.3 383.22 381.88 363 382.39H362C341.31 382.39 322.68 365.61 320.42 344.92C318.16 324.23 333.15 307.44 353.84 307.44ZM359.04 351.15C359.832 351.168 360.618 351.013 361.344 350.697C362.071 350.381 362.72 349.911 363.247 349.319C363.773 348.728 364.165 348.029 364.396 347.271C364.626 346.513 364.689 345.715 364.58 344.93C364.365 343.238 363.551 341.679 362.285 340.536C361.019 339.393 359.385 338.741 357.68 338.7C356.887 338.682 356.099 338.837 355.372 339.154C354.644 339.47 353.994 339.941 353.466 340.533C352.939 341.126 352.546 341.826 352.315 342.585C352.084 343.344 352.021 344.144 352.13 344.93C352.346 346.615 353.157 348.168 354.417 349.308C355.676 350.449 357.302 351.102 359 351.15H359.04Z" fill="#E0E0E0"/>
|
||||||
|
<path d="M354.65 307.45H355.65C376.35 307.45 394.96 324.24 397.23 344.94C399.5 365.64 384.56 382.41 363.87 382.41H362.87C342.56 381.9 324.48 365.32 322.24 344.95C320 324.58 334.45 308 354.65 307.45ZM360.41 351.15C361.202 351.168 361.988 351.013 362.714 350.697C363.441 350.381 364.09 349.911 364.617 349.319C365.143 348.728 365.535 348.029 365.766 347.271C365.996 346.513 366.059 345.715 365.95 344.93C365.734 343.23 364.912 341.664 363.636 340.52C362.36 339.376 360.714 338.73 359 338.7C358.208 338.684 357.421 338.84 356.695 339.157C355.969 339.474 355.32 339.945 354.793 340.537C354.267 341.13 353.875 341.829 353.644 342.587C353.414 343.346 353.351 344.145 353.46 344.93C353.678 346.629 354.501 348.192 355.777 349.334C357.053 350.476 358.698 351.121 360.41 351.15Z" fill="#F0F0F0"/>
|
||||||
|
<path d="M335.42 324.34C337.979 321.704 341.05 319.619 344.445 318.214C347.84 316.809 351.486 316.113 355.16 316.17C371.82 316.17 386.81 329.67 388.64 346.33C389.098 349.967 388.82 353.659 387.822 357.186C386.824 360.713 385.126 364.003 382.83 366.86C385.542 363.945 387.579 360.47 388.799 356.68C390.019 352.891 390.392 348.879 389.89 344.93C388.06 328.26 373.08 314.76 356.42 314.76C352.427 314.691 348.469 315.515 344.835 317.172C341.202 318.83 337.985 321.279 335.42 324.34Z" fill="#E6E6E6"/>
|
||||||
|
<path d="M410.28 318.71H411.1C428.36 319.15 443.72 333.24 445.62 350.55C447.52 367.86 435.24 381.95 418.08 382.38H417.26C399.68 382.38 383.86 368.12 381.93 350.54C380 332.96 392.7 318.71 410.28 318.71ZM414.7 355.85C415.373 355.863 416.041 355.731 416.658 355.462C417.275 355.192 417.826 354.792 418.273 354.29C418.721 353.787 419.054 353.193 419.25 352.549C419.447 351.906 419.501 351.227 419.41 350.56C419.224 349.123 418.53 347.799 417.454 346.829C416.377 345.859 414.989 345.306 413.54 345.27C412.867 345.255 412.198 345.386 411.58 345.655C410.962 345.924 410.411 346.324 409.963 346.827C409.515 347.33 409.181 347.924 408.986 348.569C408.79 349.213 408.737 349.893 408.83 350.56C409.002 352.003 409.692 353.335 410.771 354.308C411.851 355.281 413.247 355.829 414.7 355.85Z" fill="#E0E0E0"/>
|
||||||
|
<path d="M411 318.72H411.82C429.41 318.72 445.22 332.98 447.15 350.57C449.08 368.16 436.39 382.41 418.8 382.41H418C400.74 381.98 385.38 367.89 383.48 350.58C381.58 333.27 393.81 319.16 411 318.72ZM415.89 355.85C416.563 355.865 417.232 355.734 417.85 355.465C418.468 355.196 419.019 354.796 419.467 354.293C419.915 353.79 420.249 353.196 420.444 352.551C420.64 351.907 420.693 351.227 420.6 350.56C420.416 349.122 419.723 347.797 418.646 346.827C417.569 345.856 416.179 345.304 414.73 345.27C414.057 345.255 413.388 345.386 412.77 345.655C412.153 345.924 411.601 346.324 411.153 346.827C410.705 347.33 410.371 347.924 410.176 348.569C409.98 349.213 409.927 349.893 410.02 350.56C410.203 351.993 410.892 353.314 411.963 354.284C413.034 355.254 414.416 355.809 415.86 355.85H415.89Z" fill="#F5F5F5"/>
|
||||||
|
<path d="M394.63 333.06C396.805 330.823 399.415 329.054 402.299 327.862C405.183 326.671 408.28 326.081 411.4 326.13C425.56 326.13 438.29 337.6 439.85 351.75C440.237 354.841 439.999 357.979 439.151 360.976C438.302 363.974 436.86 366.77 434.91 369.2C437.215 366.723 438.948 363.769 439.985 360.548C441.022 357.327 441.338 353.917 440.91 350.56C439.35 336.4 426.63 324.93 412.47 324.93C409.079 324.872 405.717 325.572 402.631 326.979C399.545 328.385 396.811 330.463 394.63 333.06Z" fill="#E6E6E6"/>
|
||||||
|
<path d="M250 427.56C357.082 427.56 443.89 422.492 443.89 416.24C443.89 409.988 357.082 404.92 250 404.92C142.917 404.92 56.11 409.988 56.11 416.24C56.11 422.492 142.917 427.56 250 427.56Z" fill="#F5F5F5"/>
|
||||||
|
<path d="M235 199.68C233.394 198.651 231.445 198.302 229.581 198.709C227.717 199.116 226.091 200.245 225.06 201.85L224.84 202.2L224.42 202.86L223.92 203.6C223.59 204.11 223.25 204.6 222.92 205.12C222.22 206.12 221.51 207.12 220.77 208.12C219.337 210.054 217.798 211.907 216.16 213.67C214.642 215.324 212.967 216.828 211.16 218.16C210.73 218.48 210.3 218.77 209.87 219.05C209.53 218.41 209.19 217.64 208.87 216.83C208 214.515 207.278 212.147 206.71 209.74C205.49 204.74 204.55 199.4 203.66 194.19V194.07C203.521 193.206 203.071 192.423 202.395 191.867C201.719 191.312 200.864 191.022 199.99 191.053C199.115 191.084 198.282 191.433 197.647 192.034C197.012 192.636 196.618 193.449 196.54 194.32C195.963 199.989 195.896 205.699 196.34 211.38C196.553 214.378 197.007 217.355 197.7 220.28C198.095 221.945 198.648 223.569 199.35 225.13C199.779 226.076 200.291 226.983 200.88 227.84C201.656 228.976 202.623 229.967 203.74 230.77C205.261 231.871 207.065 232.513 208.94 232.62C209.859 232.675 210.78 232.604 211.68 232.41C212.084 232.328 212.481 232.217 212.87 232.08L213.41 231.89L213.72 231.78C215.347 231.17 216.919 230.424 218.42 229.55C221.2 227.905 223.786 225.952 226.13 223.73C228.355 221.632 230.414 219.365 232.29 216.95C233.21 215.76 234.1 214.55 234.94 213.31C235.36 212.68 235.77 212.05 236.17 211.4L236.76 210.4L237.39 209.3C238.297 207.695 238.555 205.803 238.11 204.014C237.666 202.225 236.553 200.674 235 199.68Z" fill="#CE7A63"/>
|
||||||
|
<path d="M202.79 195.75L208.37 189.92L197.37 187.08C197.37 187.08 192.7 192.36 197.03 196.99L202.79 195.75Z" fill="#CE7A63"/>
|
||||||
|
<path d="M203.65 201.47L196.49 201.93C196.234 201.952 195.975 201.921 195.732 201.837C195.488 201.753 195.265 201.62 195.077 201.444C194.888 201.269 194.739 201.056 194.638 200.819C194.536 200.583 194.486 200.327 194.49 200.07V196.82C194.495 196.289 194.698 195.779 195.058 195.389C195.419 194.999 195.911 194.757 196.44 194.71L203.61 194.25C203.863 194.233 204.116 194.269 204.354 194.356C204.593 194.442 204.81 194.577 204.993 194.752C205.177 194.927 205.322 195.138 205.419 195.371C205.517 195.605 205.565 195.857 205.56 196.11V199.36C205.558 199.885 205.362 200.391 205.009 200.781C204.657 201.17 204.173 201.416 203.65 201.47Z" fill="#263238"/>
|
||||||
|
<path d="M207.29 183.96L197.37 184.44V187.08L208.37 189.92L207.29 183.96Z" fill="#CE7A63"/>
|
||||||
|
<path d="M224.71 202.28C224.37 211.64 226.71 233.22 243.54 276.83L296.18 273C294.92 265.77 283.86 234.59 282.18 200.17C281.96 195.84 277.85 192.17 273.1 191.92C270.1 191.79 266.66 191.72 263.32 191.84C255.599 192.024 247.894 192.631 240.24 193.66C236.738 194.264 233.273 195.062 229.86 196.05C228.442 196.389 227.173 197.18 226.244 198.304C225.315 199.428 224.776 200.823 224.71 202.28Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.3" d="M254 219.9C253.881 219.901 253.766 219.859 253.676 219.782C253.586 219.705 253.527 219.597 253.51 219.48C253.487 219.35 253.516 219.216 253.59 219.108C253.665 218.999 253.78 218.924 253.91 218.9L262.91 217.3C263.043 217.276 263.179 217.306 263.29 217.383C263.401 217.46 263.476 217.577 263.5 217.71C263.524 217.843 263.494 217.979 263.417 218.09C263.34 218.201 263.223 218.276 263.09 218.3L254.09 219.9H254Z" fill="white"/>
|
||||||
|
<path opacity="0.3" d="M254.16 222C254.027 222.011 253.896 221.968 253.795 221.882C253.693 221.796 253.631 221.673 253.62 221.54C253.609 221.407 253.652 221.276 253.738 221.175C253.824 221.073 253.947 221.011 254.08 221L263.08 219.4C263.144 219.389 263.21 219.39 263.274 219.405C263.338 219.419 263.398 219.446 263.451 219.485C263.505 219.523 263.55 219.571 263.584 219.627C263.618 219.683 263.64 219.745 263.65 219.81C263.673 219.94 263.644 220.074 263.569 220.182C263.495 220.291 263.38 220.366 263.25 220.39L254.25 221.99L254.16 222Z" fill="white"/>
|
||||||
|
<path opacity="0.3" d="M254.3 224.18C254.181 224.181 254.066 224.139 253.976 224.062C253.886 223.985 253.827 223.877 253.81 223.76C253.787 223.63 253.816 223.496 253.89 223.388C253.965 223.279 254.08 223.204 254.21 223.18L263.21 221.59C263.34 221.567 263.474 221.596 263.582 221.67C263.691 221.745 263.766 221.86 263.79 221.99C263.81 222.121 263.779 222.255 263.703 222.363C263.626 222.471 263.51 222.546 263.38 222.57L254.38 224.17L254.3 224.18Z" fill="white"/>
|
||||||
|
<path opacity="0.3" d="M290.36 273.83C290.243 273.832 290.129 273.791 290.039 273.716C289.949 273.641 289.889 273.536 289.87 273.42C289.58 271.74 288.75 268.76 287.6 264.64C283.95 251.44 277.13 226.89 275.82 200.52C275.813 200.387 275.86 200.258 275.949 200.159C276.038 200.061 276.162 200.002 276.295 199.995C276.428 199.988 276.557 200.035 276.656 200.124C276.754 200.213 276.813 200.337 276.82 200.47C278.13 226.73 284.92 251.21 288.57 264.37C289.72 268.52 290.57 271.52 290.85 273.25C290.873 273.38 290.844 273.514 290.77 273.622C290.695 273.731 290.58 273.806 290.45 273.83H290.36Z" fill="white"/>
|
||||||
|
<path opacity="0.4" d="M282.75 208.49C277.82 206.25 273.1 204.86 273.75 208.49C275.395 216.693 279.596 224.163 285.75 229.83C284.48 223.09 283.43 215.91 282.75 208.49Z" fill="black"/>
|
||||||
|
<path d="M312.15 196L316.42 189.1L304.62 186.86C304.62 186.86 301.15 193.07 306.62 197.86L312.15 196Z" fill="#CE7A63"/>
|
||||||
|
<path d="M315.17 183.55L305.11 183.72L304.62 186.81L316.42 189.05L315.17 183.55Z" fill="#CE7A63"/>
|
||||||
|
<path d="M147.73 221.43C139.73 221.43 133.24 206.88 133.24 188.93C133.24 170.98 139.73 156.44 147.73 156.44H153.16V221.44L147.73 221.43Z" fill="#B90000"/>
|
||||||
|
<path d="M153.16 221.42C161.163 221.42 167.65 206.874 167.65 188.93C167.65 170.986 161.163 156.44 153.16 156.44C145.157 156.44 138.67 170.986 138.67 188.93C138.67 206.874 145.157 221.42 153.16 221.42Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.2" d="M153.16 221.42C161.163 221.42 167.65 206.874 167.65 188.93C167.65 170.986 161.163 156.44 153.16 156.44C145.157 156.44 138.67 170.986 138.67 188.93C138.67 206.874 145.157 221.42 153.16 221.42Z" fill="black"/>
|
||||||
|
<path d="M156.22 221.43C148.22 221.43 141.73 206.88 141.73 188.93C141.73 170.98 148.22 156.44 156.22 156.44H161.65V221.44L156.22 221.43Z" fill="#B90000"/>
|
||||||
|
<path d="M161.65 221.42C169.653 221.42 176.14 206.874 176.14 188.93C176.14 170.986 169.653 156.44 161.65 156.44C153.647 156.44 147.16 170.986 147.16 188.93C147.16 206.874 153.647 221.42 161.65 221.42Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.2" d="M161.65 221.42C169.653 221.42 176.14 206.874 176.14 188.93C176.14 170.986 169.653 156.44 161.65 156.44C153.647 156.44 147.16 170.986 147.16 188.93C147.16 206.874 153.647 221.42 161.65 221.42Z" fill="black"/>
|
||||||
|
<path opacity="0.2" d="M162.89 189.92L175.56 198C175.922 195.231 176.112 192.442 176.13 189.65L162.89 189.92Z" fill="black"/>
|
||||||
|
<path d="M350.93 190.71H163.71C163.238 190.71 162.785 190.522 162.451 190.189C162.118 189.855 161.93 189.402 161.93 188.93C161.93 188.458 162.118 188.005 162.451 187.671C162.785 187.338 163.238 187.15 163.71 187.15H350.93C351.401 187.153 351.853 187.341 352.186 187.674C352.519 188.008 352.707 188.459 352.71 188.93C352.707 189.401 352.519 189.852 352.186 190.186C351.853 190.519 351.401 190.707 350.93 190.71Z" fill="#263238"/>
|
||||||
|
<path opacity="0.9" d="M263.28 191.84C255.559 192.024 247.854 192.631 240.2 193.66C239.32 193.8 238.44 193.97 237.59 194.14C235.34 199.06 237.97 206.37 245.05 214.62L247.13 217.04L248.4 214.11C252.93 203.72 258.4 196.63 264.21 193.61L267.77 191.79C266.28 191.77 264.77 191.79 263.28 191.84Z" fill="white"/>
|
||||||
|
<path d="M255.61 167.9C255.69 174.9 257.06 187.65 263.28 191.84C256.79 195.2 251 203 246.56 213.31C239.93 205.59 236.75 197.93 240.2 193.66C248.07 191.17 246.59 185.49 243.95 180.25L255.61 167.9Z" fill="#CE7A63"/>
|
||||||
|
<path opacity="0.2" d="M250.86 172.94L243.95 180.24C244.58 181.438 245.102 182.69 245.51 183.98C248.58 183.31 252.21 179.75 251.86 176.65C251.759 175.362 251.42 174.104 250.86 172.94Z" fill="black"/>
|
||||||
|
<path d="M232.2 148.35C229.51 149.83 227.26 160.53 233.57 164.66C239.88 168.79 238.2 145.07 232.2 148.35Z" fill="#263238"/>
|
||||||
|
<path d="M258.28 156.84C257.73 166.62 257.91 172.37 253 177.49C245.61 185.2 233.53 181 231.14 171.3C229 162.57 230.26 148.04 239.8 143.93C241.872 143.002 244.149 142.626 246.409 142.84C248.67 143.054 250.836 143.85 252.697 145.15C254.558 146.45 256.051 148.21 257.029 150.259C258.008 152.308 258.439 154.575 258.28 156.84Z" fill="#CE7A63"/>
|
||||||
|
<path d="M233.57 150.29C224.72 146.74 217.57 136.01 222.29 130.22C228.53 122.5 235.48 135.33 255.58 139.92C271.84 143.62 265.83 157.78 260.43 163.46C256.97 164.38 253.74 165.12 253.74 165.12C253.74 165.12 250.17 156.66 253.68 151.4C249.89 152.6 239.63 152.72 233.57 150.29Z" fill="#263238"/>
|
||||||
|
<path d="M220.8 142.28C220.8 142.28 227.22 150.44 253.68 151.4C242 154.68 227 152.72 220.8 142.28Z" fill="#263238"/>
|
||||||
|
<path d="M240.78 171.51C240.723 171.502 240.672 171.473 240.636 171.428C240.601 171.384 240.584 171.327 240.59 171.27C240.595 171.208 240.624 171.151 240.671 171.11C240.717 171.069 240.778 171.047 240.84 171.05C241.833 171.12 242.828 170.959 243.748 170.579C244.667 170.199 245.486 169.61 246.14 168.86C246.158 168.836 246.181 168.815 246.208 168.8C246.234 168.784 246.263 168.774 246.293 168.77C246.323 168.766 246.354 168.768 246.384 168.776C246.413 168.783 246.441 168.797 246.465 168.815C246.489 168.833 246.51 168.856 246.525 168.883C246.541 168.909 246.551 168.938 246.555 168.968C246.559 168.998 246.557 169.029 246.549 169.059C246.542 169.088 246.528 169.116 246.51 169.14C245.812 169.957 244.931 170.599 243.939 171.012C242.947 171.426 241.872 171.6 240.8 171.52L240.78 171.51Z" fill="#263238"/>
|
||||||
|
<path d="M262.75 167.51C261.449 169.622 259.375 171.144 256.97 171.75C253.79 172.52 254.22 170.15 255.24 167.22C256.16 164.57 257.07 160.5 260.24 161.03C263.41 161.56 264.32 164.91 262.75 167.51Z" fill="#CE7A63"/>
|
||||||
|
<path d="M243.47 159.9C243.47 160.7 243.06 161.35 242.54 161.36C242.02 161.37 241.59 160.74 241.59 159.94C241.59 159.14 242 158.5 242.52 158.48C243.04 158.46 243.47 159.11 243.47 159.9Z" fill="#263238"/>
|
||||||
|
<path d="M234.23 160.1C234.23 160.9 233.82 161.55 233.3 161.57C232.78 161.59 232.3 160.94 232.3 160.15C232.3 159.36 232.71 158.7 233.23 158.68C233.75 158.66 234.23 159.31 234.23 160.1Z" fill="#263238"/>
|
||||||
|
<path d="M237.34 160.44C237.34 160.44 235.55 165.06 233.57 167.32C234.99 168.61 237.38 167.95 237.38 167.95L237.34 160.44Z" fill="#BA4D3C"/>
|
||||||
|
<path d="M247.46 155.76C247.398 155.765 247.336 155.756 247.277 155.736C247.219 155.715 247.165 155.682 247.12 155.64C246.715 155.219 246.215 154.902 245.661 154.716C245.107 154.529 244.517 154.479 243.94 154.57C243.881 154.585 243.819 154.587 243.759 154.577C243.699 154.568 243.641 154.546 243.59 154.513C243.539 154.48 243.494 154.437 243.46 154.387C243.426 154.337 243.402 154.28 243.39 154.22C243.366 154.098 243.39 153.972 243.457 153.867C243.524 153.763 243.629 153.689 243.75 153.66C244.472 153.54 245.213 153.595 245.909 153.821C246.605 154.048 247.237 154.438 247.75 154.96C247.837 155.048 247.885 155.166 247.885 155.29C247.885 155.414 247.837 155.532 247.75 155.62C247.674 155.701 247.571 155.751 247.46 155.76Z" fill="#263238"/>
|
||||||
|
<path d="M234 155.5C234.009 155.55 234.009 155.6 234 155.65C234.003 155.712 233.993 155.773 233.971 155.831C233.949 155.888 233.915 155.941 233.872 155.985C233.829 156.029 233.777 156.064 233.72 156.087C233.663 156.11 233.602 156.121 233.54 156.12C232.793 156.145 232.051 155.995 231.374 155.68C230.696 155.366 230.102 154.897 229.64 154.31C229.604 154.261 229.579 154.204 229.566 154.145C229.553 154.086 229.552 154.024 229.563 153.964C229.574 153.904 229.597 153.848 229.631 153.797C229.665 153.746 229.709 153.703 229.76 153.67C229.862 153.6 229.988 153.574 230.11 153.596C230.231 153.619 230.339 153.688 230.41 153.79C230.784 154.248 231.26 154.613 231.8 154.856C232.34 155.099 232.929 155.214 233.52 155.19C233.622 155.185 233.724 155.212 233.81 155.268C233.896 155.323 233.962 155.405 234 155.5Z" fill="#263238"/>
|
||||||
|
<path d="M313.7 204.36C313.345 201.456 312.794 198.579 312.05 195.75C311.827 194.924 311.317 194.204 310.611 193.72C309.906 193.236 309.051 193.019 308.2 193.108C307.349 193.197 306.557 193.587 305.967 194.206C305.377 194.826 305.027 195.636 304.98 196.49V196.64C304.837 201.831 304.295 207.002 303.36 212.11C303.129 213.225 302.825 214.325 302.45 215.4C302.226 216.023 301.948 216.625 301.62 217.2L301.46 217.12C299.636 216.289 297.937 215.209 296.41 213.91C294.573 212.351 292.833 210.681 291.2 208.91C289.51 207.07 287.84 205.14 286.2 203.13C285.4 202.13 284.62 201.13 283.83 200.13L282.68 198.59L281.61 197.13L281.44 196.89C280.336 195.366 278.678 194.334 276.823 194.017C274.967 193.7 273.061 194.122 271.513 195.193C269.965 196.263 268.898 197.898 268.54 199.746C268.183 201.594 268.563 203.509 269.6 205.08C270.09 205.83 270.47 206.39 270.9 207.02C271.33 207.65 271.76 208.23 272.2 208.83C273.07 210.02 273.99 211.17 274.92 212.32C276.8 214.633 278.803 216.842 280.92 218.94C282.01 220 283.13 221.04 284.31 222.05C285.49 223.06 286.76 224.05 288.08 224.96C290.938 227.04 294.131 228.616 297.52 229.62C300.22 230.468 303.128 230.376 305.77 229.36C307.194 228.765 308.481 227.883 309.55 226.77C310.41 225.876 311.141 224.867 311.72 223.77C312.606 222.052 313.22 220.207 313.54 218.3C313.836 216.692 314.017 215.064 314.08 213.43C314.202 210.401 314.075 207.368 313.7 204.36Z" fill="#CE7A63"/>
|
||||||
|
<path d="M260 275.63L243.55 276.83C243.55 276.83 185.89 290.21 188.11 320.59C189.6 340.99 198.27 373.99 204.22 394.82H217.72C216.91 373.99 218.37 342 212.72 321.15C221.46 319.54 251.48 308.81 262.08 305.27C264.633 300.656 265.795 295.402 265.426 290.142C265.056 284.882 263.172 279.842 260 275.63Z" fill="#263238"/>
|
||||||
|
<path opacity="0.4" d="M240.87 294.31C229.52 302.93 227.95 313.76 229.52 316.31C234.52 314.69 239.9 312.86 244.97 311.12C245 305.05 245.53 290.78 240.87 294.31Z" fill="black"/>
|
||||||
|
<path d="M281.74 274.05L260 275.63L281.74 274.06V274.05Z" fill="#263238"/>
|
||||||
|
<path d="M296.18 273L260 275.63C260 275.63 233 296.49 233.89 306.63C234.138 310.641 234.977 314.594 236.38 318.36C243.65 337.7 258.16 372.9 267.02 394.8H280.75C276.68 372.31 272.27 336.15 263.29 319.8C272.79 315.56 285.78 310.2 294.73 303.37C304.77 295.76 296.18 273 296.18 273Z" fill="#263238"/>
|
||||||
|
<path d="M209.93 414L218.37 414.15C218.37 414.15 218.16 406.31 217.72 394.8H204.22C207.48 406.25 209.93 414 209.93 414Z" fill="#CE7A63"/>
|
||||||
|
<path d="M207.82 407.7L218.82 407.78C219.018 407.78 219.209 407.85 219.359 407.979C219.509 408.107 219.609 408.285 219.64 408.48L221.01 417.48C221.04 417.704 221.022 417.933 220.956 418.149C220.89 418.366 220.778 418.566 220.629 418.736C220.479 418.905 220.294 419.041 220.087 419.133C219.881 419.225 219.656 419.272 219.43 419.27C215.43 419.17 209.7 418.89 204.7 418.85C198.84 418.85 198.99 419.09 192.12 419.04C187.97 419.04 187.12 414.79 188.87 414.43C196.87 412.76 198.15 412.62 205.29 408.43C206.049 407.955 206.925 407.702 207.82 407.7Z" fill="#263238"/>
|
||||||
|
<path d="M274.79 414L284.09 414.15C284.09 414.15 282.81 406.24 280.75 394.8H267C271.63 406.23 274.79 414 274.79 414Z" fill="#CE7A63"/>
|
||||||
|
<path d="M272.66 407.7L283.66 407.78C283.861 407.784 284.055 407.855 284.211 407.982C284.366 408.109 284.475 408.284 284.52 408.48L286.45 417.48C286.499 417.698 286.497 417.924 286.445 418.141C286.394 418.358 286.293 418.561 286.151 418.733C286.01 418.906 285.831 419.044 285.628 419.137C285.425 419.23 285.203 419.275 284.98 419.27C280.98 419.17 275.23 418.89 270.22 418.85C264.36 418.85 269.14 419.09 262.27 419.04C258.12 419.04 257.01 414.86 258.74 414.43C265.21 412.79 267.55 411.11 270.17 408.43C270.514 408.146 270.912 407.935 271.34 407.81C271.768 407.685 272.217 407.647 272.66 407.7Z" fill="#263238"/>
|
||||||
|
<path d="M353 221.43C345 221.43 338.51 206.88 338.51 188.93C338.51 170.98 345 156.44 353 156.44H358.43V221.44L353 221.43Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.2" d="M353 221.43C345 221.43 338.51 206.88 338.51 188.93C338.51 170.98 345 156.44 353 156.44H358.43V221.44L353 221.43Z" fill="black"/>
|
||||||
|
<path d="M358.43 221.42C366.433 221.42 372.92 206.874 372.92 188.93C372.92 170.986 366.433 156.44 358.43 156.44C350.427 156.44 343.94 170.986 343.94 188.93C343.94 206.874 350.427 221.42 358.43 221.42Z" fill="#B90000"/>
|
||||||
|
<path d="M361.49 221.43C353.49 221.43 347 206.88 347 188.93C347 170.98 353.49 156.44 361.49 156.44H366.92V221.44L361.49 221.43Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.2" d="M361.49 221.43C353.49 221.43 347 206.88 347 188.93C347 170.98 353.49 156.44 361.49 156.44H366.92V221.44L361.49 221.43Z" fill="black"/>
|
||||||
|
<path d="M382.63 175.19C381.31 168.48 379.04 162.94 376.07 159.19L376.85 158.57C379.93 162.47 382.26 168.16 383.62 175.03L382.63 175.19Z" fill="#B90000"/>
|
||||||
|
<path d="M384.91 188.93H383.91C383.912 186.278 383.775 183.628 383.5 180.99L384.5 180.88C384.78 183.554 384.917 186.241 384.91 188.93Z" fill="#B90000"/>
|
||||||
|
<path d="M366.92 221.42C374.923 221.42 381.41 206.874 381.41 188.93C381.41 170.986 374.923 156.44 366.92 156.44C358.917 156.44 352.43 170.986 352.43 188.93C352.43 206.874 358.917 221.42 366.92 221.42Z" fill="#B90000"/>
|
||||||
|
<path d="M368.61 206.68C364.24 206.68 360.69 198.68 360.69 188.93C360.69 179.18 364.24 171.19 368.61 171.19H371.61V206.68H368.61Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.2" d="M371.57 206.68H368.57C364.21 206.68 360.66 198.68 360.66 188.94C360.66 179.2 364.21 171.19 368.57 171.19H371.57C367.2 171.19 363.66 179.13 363.66 188.94C363.66 198.75 367.2 206.68 371.57 206.68Z" fill="black"/>
|
||||||
|
<path opacity="0.1" d="M371.57 206.67C375.939 206.67 379.48 198.728 379.48 188.93C379.48 179.132 375.939 171.19 371.57 171.19C367.201 171.19 363.66 179.132 363.66 188.93C363.66 198.728 367.201 206.67 371.57 206.67Z" fill="white"/>
|
||||||
|
<path d="M425.66 321L398.17 416.24H403.22L430.71 321H425.66Z" fill="#263238"/>
|
||||||
|
<path d="M341.84 416.24H346.89L374.38 321H369.32L341.84 416.24Z" fill="#263238"/>
|
||||||
|
<path d="M337.32 408.08C331.952 408.08 326.804 405.948 323.008 402.152C319.212 398.356 317.08 393.208 317.08 387.84C317.08 382.472 319.212 377.324 323.008 373.528C326.804 369.732 331.952 367.6 337.32 367.6H340.4V408.08H337.32Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.2" d="M337.32 408.08C331.952 408.08 326.804 405.948 323.008 402.152C319.212 398.356 317.08 393.208 317.08 387.84C317.08 382.472 319.212 377.324 323.008 373.528C326.804 369.732 331.952 367.6 337.32 367.6H340.4V408.08H337.32Z" fill="black"/>
|
||||||
|
<path d="M340.4 408.08C351.578 408.08 360.64 399.018 360.64 387.84C360.64 376.662 351.578 367.6 340.4 367.6C329.222 367.6 320.16 376.662 320.16 387.84C320.16 399.018 329.222 408.08 340.4 408.08Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.1" d="M337.32 367.6H340.4C345.768 367.6 350.916 369.732 354.712 373.528C358.508 377.324 360.64 382.472 360.64 387.84C360.64 393.208 358.508 398.356 354.712 402.152C350.916 405.948 345.768 408.08 340.4 408.08H337.32C331.952 408.08 326.804 405.948 323.008 402.152C319.212 398.356 317.08 393.208 317.08 387.84C317.08 382.472 319.212 377.324 323.008 373.528C326.804 369.732 331.952 367.6 337.32 367.6Z" fill="black"/>
|
||||||
|
<path d="M342.63 408.08C337.262 408.08 332.114 405.948 328.318 402.152C324.522 398.356 322.39 393.208 322.39 387.84C322.39 382.472 324.522 377.324 328.318 373.528C332.114 369.732 337.262 367.6 342.63 367.6H345.71V408.08H342.63Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.2" d="M342.63 408.08C337.262 408.08 332.114 405.948 328.318 402.152C324.522 398.356 322.39 393.208 322.39 387.84C322.39 382.472 324.522 377.324 328.318 373.528C332.114 369.732 337.262 367.6 342.63 367.6H345.71V408.08H342.63Z" fill="black"/>
|
||||||
|
<path d="M345.71 408.08C356.888 408.08 365.95 399.018 365.95 387.84C365.95 376.662 356.888 367.6 345.71 367.6C334.532 367.6 325.47 376.662 325.47 387.84C325.47 399.018 334.532 408.08 345.71 408.08Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.1" d="M342.63 367.6H345.71C351.078 367.6 356.226 369.732 360.022 373.528C363.818 377.324 365.95 382.472 365.95 387.84C365.95 393.208 363.818 398.356 360.022 402.152C356.226 405.948 351.078 408.08 345.71 408.08H342.63C337.262 408.08 332.114 405.948 328.318 402.152C324.522 398.356 322.39 393.208 322.39 387.84C322.39 382.472 324.522 377.324 328.318 373.528C332.114 369.732 337.262 367.6 342.63 367.6Z" fill="black"/>
|
||||||
|
<path d="M348.9 392.57H383.5C383.599 392.57 383.698 392.55 383.789 392.512C383.881 392.474 383.964 392.418 384.034 392.347C384.104 392.276 384.159 392.192 384.196 392.1C384.233 392.008 384.251 391.909 384.25 391.81V383.87C384.251 383.771 384.233 383.672 384.196 383.58C384.159 383.488 384.104 383.404 384.034 383.333C383.964 383.262 383.881 383.206 383.789 383.168C383.698 383.13 383.599 383.11 383.5 383.11H348.9C348.279 383.11 347.664 383.232 347.09 383.47C346.516 383.708 345.995 384.056 345.555 384.495C345.116 384.935 344.768 385.456 344.53 386.03C344.292 386.604 344.17 387.219 344.17 387.84C344.17 389.094 344.668 390.298 345.555 391.185C346.442 392.072 347.646 392.57 348.9 392.57Z" fill="#263238"/>
|
||||||
|
<path d="M389.42 408.08C384.052 408.08 378.904 405.948 375.108 402.152C371.312 398.356 369.18 393.208 369.18 387.84C369.18 382.472 371.312 377.324 375.108 373.528C378.904 369.732 384.052 367.6 389.42 367.6H392.5V408.08H389.42Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.2" d="M389.42 408.08C384.052 408.08 378.904 405.948 375.108 402.152C371.312 398.356 369.18 393.208 369.18 387.84C369.18 382.472 371.312 377.324 375.108 373.528C378.904 369.732 384.052 367.6 389.42 367.6H392.5V408.08H389.42Z" fill="black"/>
|
||||||
|
<path d="M392.5 408.08C403.678 408.08 412.74 399.018 412.74 387.84C412.74 376.662 403.678 367.6 392.5 367.6C381.322 367.6 372.26 376.662 372.26 387.84C372.26 399.018 381.322 408.08 392.5 408.08Z" fill="#B90000"/>
|
||||||
|
<path d="M394.72 408.08C389.352 408.08 384.204 405.948 380.408 402.152C376.612 398.356 374.48 393.208 374.48 387.84C374.48 382.472 376.612 377.324 380.408 373.528C384.204 369.732 389.352 367.6 394.72 367.6H397.8V408.08H394.72Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.2" d="M394.72 408.08C389.352 408.08 384.204 405.948 380.408 402.152C376.612 398.356 374.48 393.208 374.48 387.84C374.48 382.472 376.612 377.324 380.408 373.528C384.204 369.732 389.352 367.6 394.72 367.6H397.8V408.08H394.72Z" fill="black"/>
|
||||||
|
<path d="M397.8 408.08C408.978 408.08 418.04 399.018 418.04 387.84C418.04 376.662 408.978 367.6 397.8 367.6C386.622 367.6 377.56 376.662 377.56 387.84C377.56 399.018 386.622 408.08 397.8 408.08Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.2" d="M396.39 398.38C393.595 398.38 390.914 397.27 388.937 395.293C386.96 393.316 385.85 390.635 385.85 387.84C385.85 385.045 386.96 382.364 388.937 380.387C390.914 378.41 393.595 377.3 396.39 377.3H399.22V398.38H396.39Z" fill="black"/>
|
||||||
|
<path d="M399.22 398.38C405.041 398.38 409.76 393.661 409.76 387.84C409.76 382.019 405.041 377.3 399.22 377.3C393.399 377.3 388.68 382.019 388.68 387.84C388.68 393.661 393.399 398.38 399.22 398.38Z" fill="#B90000"/>
|
||||||
|
<path d="M356.14 362.09C353.749 362.146 351.371 361.724 349.146 360.848C346.92 359.972 344.893 358.66 343.182 356.989C341.471 355.318 340.111 353.322 339.183 351.118C338.255 348.914 337.776 346.547 337.776 344.155C337.776 341.763 338.255 339.396 339.183 337.192C340.111 334.988 341.471 332.992 343.182 331.321C344.893 329.65 346.92 328.338 349.146 327.462C351.371 326.586 353.749 326.164 356.14 326.22H358.87V362.09H356.14Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.2" d="M356.14 362.09C353.749 362.146 351.371 361.724 349.146 360.848C346.92 359.972 344.893 358.66 343.182 356.989C341.471 355.318 340.111 353.322 339.183 351.118C338.255 348.914 337.776 346.547 337.776 344.155C337.776 341.763 338.255 339.396 339.183 337.192C340.111 334.988 341.471 332.992 343.182 331.321C344.893 329.65 346.92 328.338 349.146 327.462C351.371 326.586 353.749 326.164 356.14 326.22H358.87V362.09H356.14Z" fill="black"/>
|
||||||
|
<path d="M376.776 344.395C376.924 334.494 369.018 326.347 359.117 326.198C349.215 326.049 341.068 333.956 340.92 343.857C340.771 353.758 348.677 361.905 358.578 362.054C368.48 362.203 376.627 354.296 376.776 344.395Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.1" d="M356.14 326.22H358.87C363.553 326.331 368.008 328.269 371.281 331.62C374.554 334.972 376.386 339.47 376.386 344.155C376.386 348.84 374.554 353.338 371.281 356.69C368.008 360.041 363.553 361.979 358.87 362.09H356.14C353.749 362.146 351.371 361.724 349.146 360.848C346.92 359.972 344.893 358.66 343.182 356.989C341.471 355.318 340.111 353.322 339.183 351.118C338.255 348.914 337.776 346.547 337.776 344.155C337.776 341.763 338.255 339.396 339.183 337.192C340.111 334.988 341.471 332.992 343.182 331.321C344.893 329.65 346.92 328.338 349.146 327.462C351.371 326.586 353.749 326.164 356.14 326.22Z" fill="black"/>
|
||||||
|
<path d="M360.84 362.09C356.157 361.979 351.702 360.041 348.429 356.69C345.156 353.338 343.324 348.84 343.324 344.155C343.324 339.47 345.156 334.972 348.429 331.62C351.702 328.269 356.157 326.331 360.84 326.22H363.57V362.09H360.84Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.2" d="M360.84 362.09C356.157 361.979 351.702 360.041 348.429 356.69C345.156 353.338 343.324 348.84 343.324 344.155C343.324 339.47 345.156 334.972 348.429 331.62C351.702 328.269 356.157 326.331 360.84 326.22H363.57V362.09H360.84Z" fill="black"/>
|
||||||
|
<path d="M381.476 344.406C381.625 334.504 373.719 326.357 363.817 326.209C353.916 326.06 345.769 333.966 345.62 343.867C345.472 353.769 353.378 361.916 363.279 362.064C373.18 362.213 381.328 354.307 381.476 344.406Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.1" d="M360.84 326.22H363.57C365.961 326.164 368.339 326.586 370.564 327.462C372.79 328.338 374.817 329.65 376.528 331.321C378.239 332.992 379.599 334.988 380.527 337.192C381.455 339.396 381.934 341.763 381.934 344.155C381.934 346.547 381.455 348.914 380.527 351.118C379.599 353.322 378.239 355.318 376.528 356.989C374.817 358.66 372.79 359.972 370.564 360.848C368.339 361.724 365.961 362.146 363.57 362.09H360.84C356.157 361.979 351.702 360.041 348.429 356.69C345.156 353.338 343.324 348.84 343.324 344.155C343.324 339.47 345.156 334.972 348.429 331.62C351.702 328.269 356.157 326.331 360.84 326.22Z" fill="black"/>
|
||||||
|
<path d="M397.06 348.31H366.37C365.267 348.31 364.209 347.872 363.428 347.092C362.648 346.311 362.21 345.253 362.21 344.15C362.21 343.047 362.648 341.989 363.428 341.208C364.209 340.428 365.267 339.99 366.37 339.99H397.06C397.235 339.99 397.403 340.06 397.527 340.183C397.65 340.307 397.72 340.475 397.72 340.65V347.65C397.72 347.825 397.65 347.993 397.527 348.117C397.403 348.24 397.235 348.31 397.06 348.31Z" fill="#263238"/>
|
||||||
|
<path d="M402.3 362.09C399.909 362.146 397.531 361.724 395.306 360.848C393.08 359.972 391.053 358.66 389.342 356.989C387.631 355.318 386.271 353.322 385.343 351.118C384.415 348.914 383.936 346.547 383.936 344.155C383.936 341.763 384.415 339.396 385.343 337.192C386.271 334.988 387.631 332.992 389.342 331.321C391.053 329.65 393.08 328.338 395.306 327.462C397.531 326.586 399.909 326.164 402.3 326.22H405V362.09H402.3Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.2" d="M402.3 362.09C399.909 362.146 397.531 361.724 395.306 360.848C393.08 359.972 391.053 358.66 389.342 356.989C387.631 355.318 386.271 353.322 385.343 351.118C384.415 348.914 383.936 346.547 383.936 344.155C383.936 341.763 384.415 339.396 385.343 337.192C386.271 334.988 387.631 332.992 389.342 331.321C391.053 329.65 393.08 328.338 395.306 327.462C397.531 326.586 399.909 326.164 402.3 326.22H405V362.09H402.3Z" fill="black"/>
|
||||||
|
<path d="M405.03 362.09C414.932 362.09 422.96 354.062 422.96 344.16C422.96 334.258 414.932 326.23 405.03 326.23C395.128 326.23 387.1 334.258 387.1 344.16C387.1 354.062 395.128 362.09 405.03 362.09Z" fill="#B90000"/>
|
||||||
|
<path d="M407 362.09C402.317 361.979 397.862 360.041 394.589 356.69C391.316 353.338 389.484 348.84 389.484 344.155C389.484 339.47 391.316 334.972 394.589 331.62C397.862 328.269 402.317 326.331 407 326.22H409.73V362.09H407Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.2" d="M407 362.09C402.317 361.979 397.862 360.041 394.589 356.69C391.316 353.338 389.484 348.84 389.484 344.155C389.484 339.47 391.316 334.972 394.589 331.62C397.862 328.269 402.317 326.331 407 326.22H409.73V362.09H407Z" fill="black"/>
|
||||||
|
<path d="M409.72 362.09C419.622 362.09 427.65 354.062 427.65 344.16C427.65 334.258 419.622 326.23 409.72 326.23C399.818 326.23 391.79 334.258 391.79 344.16C391.79 354.062 399.818 362.09 409.72 362.09Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.2" d="M408.47 353.49C407.218 353.531 405.971 353.32 404.803 352.869C403.635 352.418 402.569 351.737 401.669 350.867C400.769 349.996 400.054 348.953 399.565 347.8C399.076 346.647 398.824 345.407 398.824 344.155C398.824 342.903 399.076 341.663 399.565 340.51C400.054 339.357 400.769 338.314 401.669 337.443C402.569 336.573 403.635 335.892 404.803 335.441C405.971 334.99 407.218 334.779 408.47 334.82H411V353.49H408.47Z" fill="black"/>
|
||||||
|
<path d="M412.53 351.98C417.688 351.877 421.786 347.612 421.683 342.454C421.58 337.295 417.315 333.197 412.156 333.3C406.998 333.404 402.9 337.669 403.003 342.827C403.106 347.986 407.372 352.084 412.53 351.98Z" fill="#B90000"/>
|
||||||
|
<path d="M146.37 372.2C146.37 365.65 140.37 360.33 133.04 360.33C125.71 360.33 119.71 365.65 119.71 372.2C119.725 374.842 120.69 377.391 122.43 379.38C122.78 379.799 123.032 380.291 123.169 380.819C123.306 381.348 123.323 381.9 123.219 382.436C123.116 382.972 122.895 383.479 122.571 383.919C122.248 384.359 121.831 384.721 121.35 384.98C120.853 385.252 120.438 385.652 120.149 386.14C119.861 386.627 119.709 387.183 119.71 387.75V414.21C119.71 414.921 119.992 415.602 120.495 416.105C120.998 416.608 121.679 416.89 122.39 416.89H143.69C144.401 416.89 145.082 416.608 145.585 416.105C146.088 415.602 146.37 414.921 146.37 414.21V387.75C146.371 387.182 146.218 386.625 145.928 386.137C145.637 385.649 145.22 385.249 144.72 384.98C144.239 384.721 143.822 384.359 143.499 383.919C143.175 383.479 142.954 382.972 142.851 382.436C142.747 381.9 142.764 381.348 142.901 380.819C143.037 380.291 143.29 379.799 143.64 379.38C145.383 377.392 146.352 374.844 146.37 372.2Z" fill="#B90000"/>
|
||||||
|
<path opacity="0.2" d="M146.37 372.2C146.37 365.65 140.37 360.33 133.04 360.33C125.71 360.33 119.71 365.65 119.71 372.2C119.725 374.842 120.69 377.391 122.43 379.38C122.78 379.799 123.032 380.291 123.169 380.819C123.306 381.348 123.323 381.9 123.219 382.436C123.116 382.972 122.895 383.479 122.571 383.919C122.248 384.359 121.831 384.721 121.35 384.98C120.853 385.252 120.438 385.652 120.149 386.14C119.861 386.627 119.709 387.183 119.71 387.75V414.21C119.71 414.921 119.992 415.602 120.495 416.105C120.998 416.608 121.679 416.89 122.39 416.89H143.69C144.401 416.89 145.082 416.608 145.585 416.105C146.088 415.602 146.37 414.921 146.37 414.21V387.75C146.371 387.182 146.218 386.625 145.928 386.137C145.637 385.649 145.22 385.249 144.72 384.98C144.239 384.721 143.822 384.359 143.499 383.919C143.175 383.479 142.954 382.972 142.851 382.436C142.747 381.9 142.764 381.348 142.901 380.819C143.037 380.291 143.29 379.799 143.64 379.38C145.383 377.392 146.352 374.844 146.37 372.2Z" fill="white"/>
|
||||||
|
<path d="M138.61 362.62H127.46C126.854 362.617 126.274 362.375 125.847 361.946C125.42 361.517 125.18 360.936 125.18 360.33C125.183 359.726 125.424 359.148 125.851 358.721C126.278 358.294 126.856 358.053 127.46 358.05H138.61C139.216 358.05 139.797 358.29 140.226 358.717C140.655 359.144 140.897 359.724 140.9 360.33C140.897 360.937 140.655 361.517 140.226 361.946C139.797 362.375 139.217 362.617 138.61 362.62Z" fill="#263238"/>
|
||||||
|
<path d="M135.22 353.6H130.86C130.065 353.6 129.42 354.245 129.42 355.04V358.89C129.42 359.685 130.065 360.33 130.86 360.33H135.22C136.015 360.33 136.66 359.685 136.66 358.89V355.04C136.66 354.245 136.015 353.6 135.22 353.6Z" fill="#263238"/>
|
||||||
|
</svg>
|
||||||
|
<p className="!text-text dark:!text-text-dark font-semibold text-[18px]">{t('noHardWorkNoHappenes')}</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
100
webapp/src/app/[locale]/(GlobalWrapper)/layout.tsx
Normal file
100
webapp/src/app/[locale]/(GlobalWrapper)/layout.tsx
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useAppSelector } from "@/redux/store"
|
||||||
|
import { setThemeType } from '@/redux/features/theme-slice'
|
||||||
|
import { useDispatch } from "react-redux"
|
||||||
|
import { AppDispatch } from '@/redux/store';
|
||||||
|
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
||||||
|
import Alert from '@/components/common/alert'
|
||||||
|
import { ReactNode, useEffect } from 'react'
|
||||||
|
|
||||||
|
// this layout contain global state managment ( that should wrapp all pages )
|
||||||
|
export default function LocaleLayout({children} : { children : ReactNode }) {
|
||||||
|
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
|
||||||
|
// init redux states that need localstorage
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(setThemeType(
|
||||||
|
localStorage.getItem('theme') ? localStorage.getItem('theme') : 'light'
|
||||||
|
))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// get redux states
|
||||||
|
const themeType = useAppSelector((state) => state.themeTypeReducer.value.themeType)
|
||||||
|
|
||||||
|
// handle the mui theme provider
|
||||||
|
const theme = {
|
||||||
|
'primary': themeType == 'light' ? '#fff' : '#121212', // primary / primary-dark
|
||||||
|
'secondary': themeType == 'light' ? '#D9D9D9' : '#D9D9D9', // secondary / secondary-lite
|
||||||
|
'text': themeType == 'light' ? '#000000' : '#ffffff' , // text
|
||||||
|
'success': themeType == 'light' ? '#38C172' : '#38C172',
|
||||||
|
'error': themeType == 'light' ? '#E3342F' : '#E3342F', // danger
|
||||||
|
'warning': themeType == 'light' ? '#FFED4A' : '#FFED4A',
|
||||||
|
'info': themeType == 'light' ? '#026af2' : '#026af2',
|
||||||
|
'paper': themeType == 'light' ? '#fff' : '#121212',
|
||||||
|
}
|
||||||
|
|
||||||
|
// set mui theme
|
||||||
|
const MUITheme = createTheme({
|
||||||
|
typography: {
|
||||||
|
fontFamily: [
|
||||||
|
'Poppins'
|
||||||
|
].join(',')
|
||||||
|
},
|
||||||
|
palette: {
|
||||||
|
background : {
|
||||||
|
default: theme.primary,
|
||||||
|
paper: theme.primary,
|
||||||
|
},
|
||||||
|
primary : {
|
||||||
|
main: theme.primary,
|
||||||
|
dark: "#1D1D1D",
|
||||||
|
light: "#ffffff"
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
main: theme.secondary,
|
||||||
|
light: "#B90000",
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
primary : theme.text,
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
main: theme.success,
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
main: theme.warning
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
main: theme.error,
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
main: theme.info,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// wrap the layout with global components
|
||||||
|
function GlobalComponentsHOC({children} : { children : ReactNode }) {
|
||||||
|
|
||||||
|
// get redux states
|
||||||
|
const alertPayload = useAppSelector((state) => state.alertReducer.value)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Alert payload={alertPayload} />
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<body className={themeType}>
|
||||||
|
<ThemeProvider theme={MUITheme}>
|
||||||
|
<GlobalComponentsHOC>
|
||||||
|
{children}
|
||||||
|
</GlobalComponentsHOC>
|
||||||
|
</ThemeProvider>
|
||||||
|
</body>
|
||||||
|
);
|
||||||
|
}
|
||||||
38
webapp/src/app/[locale]/layout.tsx
Normal file
38
webapp/src/app/[locale]/layout.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import '@/styles/tailwind.css'
|
||||||
|
import { ReduxProvider } from '@/redux/provider';
|
||||||
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
|
import { notFound } from 'next/navigation';
|
||||||
|
import { ReactNode } from 'react'
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
|
||||||
|
// i18n
|
||||||
|
export function generateStaticParams() {
|
||||||
|
return [{locale: 'en'}, {locale: 'ar'}];
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is the root app layout
|
||||||
|
export default async function RootLayout({children, params: {locale}} : {children : ReactNode , params : {locale : string} }) {
|
||||||
|
|
||||||
|
// get the messages ( i18n ) for the current local
|
||||||
|
let messages;
|
||||||
|
try {
|
||||||
|
messages = (await import(`@/messages/${locale}.json`)).default;
|
||||||
|
} catch (error) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html lang={locale} dir={['ar' , 'fa'].includes(locale) ? 'rtl' : 'ltr'}>
|
||||||
|
<body>
|
||||||
|
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||||
|
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||||
|
<ReduxProvider>
|
||||||
|
{children}
|
||||||
|
</ReduxProvider>
|
||||||
|
</NextIntlClientProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
175
webapp/src/app/api/auth/route.ts
Normal file
175
webapp/src/app/api/auth/route.ts
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* @description This endpoint handle the auth
|
||||||
|
* * source : https://nextjs.org/docs/app/building-your-application/routing/route-handlers
|
||||||
|
* * https://nextjs.org/docs/app/api-reference/functions/cookies
|
||||||
|
*/
|
||||||
|
|
||||||
|
import dbConnect from '@/database/dbConnect'
|
||||||
|
import { IAuthReqBody , IAuthResData } from '@/types/IAPI'
|
||||||
|
import { IUserSchema } from '@/types/IDB'
|
||||||
|
import { NextResponse } from 'next/server'
|
||||||
|
import bcrypt from 'bcrypt'
|
||||||
|
import userModel from '@/database/models/userModel'
|
||||||
|
import { cookies } from 'next/headers'
|
||||||
|
import crypto from "crypto"
|
||||||
|
|
||||||
|
// handle the signin process by creating and sending the authToken
|
||||||
|
export async function POST(req : Request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the request body
|
||||||
|
const reqBody : IAuthReqBody = await req.json()
|
||||||
|
let responseData : IAuthResData;
|
||||||
|
// see if valid username
|
||||||
|
let userDoc : IUserSchema | null = await userModel.findOne({ username : reqBody.username })
|
||||||
|
|
||||||
|
// if invalid username
|
||||||
|
if(!userDoc)
|
||||||
|
{
|
||||||
|
responseData = {
|
||||||
|
success: false,
|
||||||
|
message: "invalidUsername",
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(responseData , {
|
||||||
|
status: 200,
|
||||||
|
headers: { "content-type": "application/json" }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// there is a user with that username
|
||||||
|
else
|
||||||
|
{
|
||||||
|
let passwordCheckResult = await bcrypt.compare(reqBody.password, userDoc.password)
|
||||||
|
// the password is wrong
|
||||||
|
if(!passwordCheckResult)
|
||||||
|
{
|
||||||
|
responseData = {
|
||||||
|
success: false,
|
||||||
|
message: "wrongPassword",
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(responseData , {
|
||||||
|
status: 200,
|
||||||
|
headers: { "content-type": "application/json" }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// password matched so logging successfully
|
||||||
|
{
|
||||||
|
let authToken = crypto.randomBytes(32).toString('hex');
|
||||||
|
// save the token to the db
|
||||||
|
await userModel.findOneAndUpdate({username: reqBody.username} , {
|
||||||
|
$set: {
|
||||||
|
"token" : {
|
||||||
|
value: authToken,
|
||||||
|
expiresAt: Math.floor(Date.now() / 1000) + 3 * 60 * 60,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// set the authToken cookie
|
||||||
|
cookies().set('authToken', authToken, { secure: true })
|
||||||
|
// prepare the response
|
||||||
|
responseData = {
|
||||||
|
success: true,
|
||||||
|
message: "loggedIn",
|
||||||
|
authToken: authToken,
|
||||||
|
}
|
||||||
|
// & sent the response with success set to true
|
||||||
|
return NextResponse.json(responseData , {
|
||||||
|
status: 200,
|
||||||
|
headers: { "content-type": "application/json" }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// there was an error while handling this request
|
||||||
|
}catch(e) {
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
} , {
|
||||||
|
status: 500,
|
||||||
|
headers: { "content-type": "application/json" }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// revalidate every 10 seconds
|
||||||
|
export const revalidate = 10;
|
||||||
|
|
||||||
|
// handle the check authToken validation
|
||||||
|
export async function GET(req: Request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// get the request params
|
||||||
|
const { searchParams } = new URL(req.url)
|
||||||
|
const authToken = searchParams.get('authToken')
|
||||||
|
|
||||||
|
// trow error if invalid auth token
|
||||||
|
if(!authToken)
|
||||||
|
{
|
||||||
|
throw {
|
||||||
|
specialError : 'invalidRequest'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(authToken == 'null')
|
||||||
|
{
|
||||||
|
throw {
|
||||||
|
specialError : 'invalidRequest'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if there was a valid authToken cookie
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// we try to get the user by the authToken
|
||||||
|
let userDoc = await userModel.findOne({'token.value': authToken})
|
||||||
|
// if the authToken is invalid we go return
|
||||||
|
if(!userDoc)
|
||||||
|
{
|
||||||
|
throw {
|
||||||
|
specialError : 'invalidToken'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if the authToken if valid we check if it expired or not yet
|
||||||
|
// we throw error if expired
|
||||||
|
if(userDoc.token.expiresAt < (Date.now() / 1000))
|
||||||
|
{
|
||||||
|
throw {
|
||||||
|
specialError : 'expiredToken'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// we return true
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "userIsLoggedIn",
|
||||||
|
} , {
|
||||||
|
status: 200,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// there was an error while handling this request
|
||||||
|
}catch(e : any) {
|
||||||
|
if(e.specialError) {
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: e.specialError,
|
||||||
|
} , {
|
||||||
|
status: 200,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
} , {
|
||||||
|
status: 500,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
227
webapp/src/app/api/user/actions/equipments/route.ts
Normal file
227
webapp/src/app/api/user/actions/equipments/route.ts
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
import dbConnect from "@/database/dbConnect";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import equipmentModel from "@/database/models/equipmentModel";
|
||||||
|
|
||||||
|
// POST METHOD
|
||||||
|
export async function POST(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the request payload
|
||||||
|
const { payload } = await req.json();
|
||||||
|
// get data from formData
|
||||||
|
const name : string | null = payload.name as string | null,
|
||||||
|
description : string | null = payload.description as string | null,
|
||||||
|
boughtAt_unix : number | null = payload.boughtAt_unix as number | null,
|
||||||
|
quantity: number | null = payload.quantity as number | null,
|
||||||
|
sallerName: string | null = payload.sallerName as string | null,
|
||||||
|
sallerAddress: string | null = payload.sallerAddress as string | null,
|
||||||
|
sallerPhone: string | null = payload.sallerPhone as string | null,
|
||||||
|
singlePrice: number | null = payload.singlePrice as number | null;
|
||||||
|
// save the doc
|
||||||
|
const doc = new equipmentModel({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
boughtAt_unix,
|
||||||
|
quantity,
|
||||||
|
sallerName,
|
||||||
|
sallerAddress,
|
||||||
|
sallerPhone,
|
||||||
|
singlePrice,
|
||||||
|
addedAt: Math.floor(new Date().getTime() / 1000),
|
||||||
|
})
|
||||||
|
await doc.save()
|
||||||
|
// return the success response with the new added doc
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "requestTerminatedWithSuccess",
|
||||||
|
data: doc,
|
||||||
|
}, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// set the revalidate variable
|
||||||
|
export const revalidate = 5;
|
||||||
|
// GET METHOD
|
||||||
|
export async function GET(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the parms
|
||||||
|
const { searchParams } = new URL(req.url)
|
||||||
|
const type = searchParams.get('type')
|
||||||
|
// if get by page
|
||||||
|
if(type=='page')
|
||||||
|
{
|
||||||
|
// get the page
|
||||||
|
const page : string | null = searchParams.get('page') ? searchParams.get('page') : '0',
|
||||||
|
range : number[] = [(parseInt(page?page : '0') - 1) * 4 , 4];
|
||||||
|
// get the docs
|
||||||
|
const docs = await equipmentModel.find({}).skip(range[0]).limit(range[1]),
|
||||||
|
// get the size of the docs
|
||||||
|
docs_count = await equipmentModel.countDocuments({});
|
||||||
|
// prepare the return data
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: {
|
||||||
|
docs,
|
||||||
|
docs_count,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// if type is search
|
||||||
|
else if(type == 'search')
|
||||||
|
{
|
||||||
|
// get the searchKeyword param
|
||||||
|
let searchKeyword = searchParams.get('searchKeyword')
|
||||||
|
// get the search results docs
|
||||||
|
let results = await equipmentModel.find({
|
||||||
|
$or: [
|
||||||
|
// search by ( case insensitive search )
|
||||||
|
{ name: { $regex: searchKeyword, $options: 'i' } },
|
||||||
|
{ description: { $regex: searchKeyword, $options: 'i' } },
|
||||||
|
{ sallerName: { $regex: searchKeyword, $options: 'i' } },
|
||||||
|
{ sallerAddress: { $regex: searchKeyword, $options: 'i' } }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
// get the docs
|
||||||
|
let responseData = {
|
||||||
|
docs: results,
|
||||||
|
docs_count : results.length,
|
||||||
|
}
|
||||||
|
// return the response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: responseData ? responseData : [],
|
||||||
|
} , {
|
||||||
|
status: 200,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// DELETE METHOD
|
||||||
|
export async function DELETE(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the parms
|
||||||
|
const { searchParams } = new URL(req.url)
|
||||||
|
const page = searchParams.get('page'),
|
||||||
|
_id : string | null = searchParams.get('_id'),
|
||||||
|
range : number[] = [(parseInt(page?page : '0') - 1) * 4 , 4];
|
||||||
|
// delete the doc
|
||||||
|
await equipmentModel.findByIdAndRemove(_id)
|
||||||
|
// get the docs by page
|
||||||
|
const docs = await equipmentModel.find({}).skip(range[0]).limit(range[1]),
|
||||||
|
// get the size of the docs
|
||||||
|
docs_count = await equipmentModel.countDocuments({});
|
||||||
|
// prepare the return data
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: {
|
||||||
|
docs,
|
||||||
|
docs_count,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// PUT METHOD
|
||||||
|
export async function PUT(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the request payload
|
||||||
|
const { payload } = await req.json();
|
||||||
|
// get data from formData
|
||||||
|
const name : string | null = payload.name as string | null,
|
||||||
|
description : string | null = payload.description as string | null,
|
||||||
|
boughtAt_unix : number | null = payload.boughtAt_unix as number | null,
|
||||||
|
quantity: number | null = payload.quantity as number | null,
|
||||||
|
sallerName: string | null = payload.sallerName as string | null,
|
||||||
|
sallerAddress: string | null = payload.sallerAddress as string | null,
|
||||||
|
sallerPhone: string | null = payload.sallerPhone as string | null,
|
||||||
|
singlePrice: number | null = payload.singlePrice as number | null,
|
||||||
|
_id : string | null = payload._id as string | null;
|
||||||
|
// update the doc
|
||||||
|
let updated_doc = await equipmentModel.findByIdAndUpdate(_id , {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
boughtAt_unix,
|
||||||
|
quantity,
|
||||||
|
sallerName,
|
||||||
|
sallerAddress,
|
||||||
|
sallerPhone,
|
||||||
|
singlePrice,
|
||||||
|
}, { new: true })
|
||||||
|
// return the success response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "requestTerminatedWithSuccess",
|
||||||
|
data: updated_doc,
|
||||||
|
}, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
237
webapp/src/app/api/user/actions/expenses/route.ts
Normal file
237
webapp/src/app/api/user/actions/expenses/route.ts
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
import dbConnect from "@/database/dbConnect";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import expenseModel from "@/database/models/expenseModel";
|
||||||
|
import statisticsModel from "@/database/models/statisticsModel";
|
||||||
|
|
||||||
|
// POST METHOD
|
||||||
|
export async function POST(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the request payload
|
||||||
|
const { payload } = await req.json();
|
||||||
|
// get data from formData
|
||||||
|
const name : string | null = payload.name as string | null,
|
||||||
|
description : string | null = payload.description as string | null,
|
||||||
|
amount : number = payload.amount ? payload.amount : 0 as number
|
||||||
|
// save the doc
|
||||||
|
const doc = new expenseModel({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
amount,
|
||||||
|
addedAt: Math.floor(new Date().getTime() / 1000),
|
||||||
|
})
|
||||||
|
await doc.save()
|
||||||
|
// add the outcome
|
||||||
|
await statisticsModel.findOneAndUpdate({} , {
|
||||||
|
$inc: {
|
||||||
|
totalOutcome: amount
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// return the success response with the new added doc
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "requestTerminatedWithSuccess",
|
||||||
|
data: doc,
|
||||||
|
}, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// set the revalidate variable
|
||||||
|
export const revalidate = 5;
|
||||||
|
// GET METHOD
|
||||||
|
export async function GET(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the parms
|
||||||
|
const { searchParams } = new URL(req.url)
|
||||||
|
const type = searchParams.get('type')
|
||||||
|
// if get by page
|
||||||
|
if(type=='page')
|
||||||
|
{
|
||||||
|
// get the page
|
||||||
|
const page : string | null = searchParams.get('page') ? searchParams.get('page') : '0',
|
||||||
|
range : number[] = [(parseInt(page?page : '0') - 1) * 4 , 4];
|
||||||
|
// get the docs
|
||||||
|
const docs = await expenseModel.find({}).skip(range[0]).limit(range[1]),
|
||||||
|
// get the size of the docs
|
||||||
|
docs_count = await expenseModel.countDocuments({});
|
||||||
|
// prepare the return data
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: {
|
||||||
|
docs,
|
||||||
|
docs_count,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// if type is search
|
||||||
|
else if(type == 'search')
|
||||||
|
{
|
||||||
|
// get the searchKeyword param
|
||||||
|
let searchKeyword = searchParams.get('searchKeyword')
|
||||||
|
// get the search results docs
|
||||||
|
let results = await expenseModel.find({
|
||||||
|
$or: [
|
||||||
|
// search by ( case insensitive search )
|
||||||
|
{ name: { $regex: searchKeyword, $options: 'i' } },
|
||||||
|
{ description: { $regex: searchKeyword, $options: 'i' } }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
// get the docs
|
||||||
|
let responseData = {
|
||||||
|
docs: results,
|
||||||
|
docs_count : results.length,
|
||||||
|
}
|
||||||
|
// return the response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: responseData ? responseData : [],
|
||||||
|
} , {
|
||||||
|
status: 200,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// DELETE METHOD
|
||||||
|
export async function DELETE(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the parms
|
||||||
|
const { searchParams } = new URL(req.url)
|
||||||
|
const page = searchParams.get('page'),
|
||||||
|
_id : string | null = searchParams.get('_id'),
|
||||||
|
range : number[] = [(parseInt(page?page : '0') - 1) * 4 , 4];
|
||||||
|
// update the outcome
|
||||||
|
// get the old amount
|
||||||
|
let doc = await expenseModel.findById(_id)
|
||||||
|
// remove the outcome
|
||||||
|
await statisticsModel.findOneAndUpdate({} , {
|
||||||
|
$inc: {
|
||||||
|
totalOutcome: doc?.amount ? -doc?.amount : 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// delete the doc
|
||||||
|
await expenseModel.findByIdAndRemove(_id)
|
||||||
|
// get the docs by page
|
||||||
|
const docs = await expenseModel.find({}).skip(range[0]).limit(range[1]),
|
||||||
|
// get the size of the docs
|
||||||
|
docs_count = await expenseModel.countDocuments({});
|
||||||
|
|
||||||
|
// prepare the return data
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: {
|
||||||
|
docs,
|
||||||
|
docs_count,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// PUT METHOD
|
||||||
|
export async function PUT(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the request payload
|
||||||
|
const { payload } = await req.json();
|
||||||
|
// get data from formData
|
||||||
|
const name : string | null = payload.name as string | null,
|
||||||
|
description : string | null = payload.description as string | null,
|
||||||
|
amount : number = payload.amount ? payload.amount : 0 as number,
|
||||||
|
_id : string | null = payload._id as string | null;
|
||||||
|
// update the outcome
|
||||||
|
// get the old amount
|
||||||
|
let doc = await expenseModel.findById(_id)
|
||||||
|
// deacrease the old outcome
|
||||||
|
await statisticsModel.findOneAndUpdate({} , {
|
||||||
|
$inc: {
|
||||||
|
totalOutcome: doc?.amount ? -doc?.amount : 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// add the new outcome
|
||||||
|
await statisticsModel.findOneAndUpdate({} , {
|
||||||
|
$inc: {
|
||||||
|
totalOutcome: amount
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// update the doc
|
||||||
|
let updated_doc = await expenseModel.findByIdAndUpdate(_id , {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
amount
|
||||||
|
}, { new: true })
|
||||||
|
// return the success response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "requestTerminatedWithSuccess",
|
||||||
|
data: updated_doc,
|
||||||
|
}, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
236
webapp/src/app/api/user/actions/incomes/route.ts
Normal file
236
webapp/src/app/api/user/actions/incomes/route.ts
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
import dbConnect from "@/database/dbConnect";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import incomesModel from "@/database/models/incomeModel";
|
||||||
|
import statisticsModel from "@/database/models/statisticsModel";
|
||||||
|
|
||||||
|
// POST METHOD
|
||||||
|
export async function POST(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the request payload
|
||||||
|
const { payload } = await req.json();
|
||||||
|
// get data from formData
|
||||||
|
const name : string | null = payload.name as string | null,
|
||||||
|
description : string | null = payload.description as string | null,
|
||||||
|
amount : number = payload.amount ? payload.amount : 0 as number
|
||||||
|
// save the doc
|
||||||
|
const doc = new incomesModel({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
amount,
|
||||||
|
addedAt: Math.floor(new Date().getTime() / 1000),
|
||||||
|
})
|
||||||
|
await doc.save()
|
||||||
|
// add the income
|
||||||
|
await statisticsModel.findOneAndUpdate({} , {
|
||||||
|
$inc: {
|
||||||
|
totalIncome: amount
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// return the success response with the new added doc
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "requestTerminatedWithSuccess",
|
||||||
|
data: doc,
|
||||||
|
}, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// set the revalidate variable
|
||||||
|
export const revalidate = 5;
|
||||||
|
// GET METHOD
|
||||||
|
export async function GET(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the parms
|
||||||
|
const { searchParams } = new URL(req.url)
|
||||||
|
const type = searchParams.get('type')
|
||||||
|
// if get by page
|
||||||
|
if(type == 'page')
|
||||||
|
{
|
||||||
|
// get the page
|
||||||
|
const page : string | null = searchParams.get('page') ? searchParams.get('page') : '0',
|
||||||
|
range : number[] = [(parseInt(page?page : '0') - 1) * 4 , 4];
|
||||||
|
// get the docs
|
||||||
|
const docs = await incomesModel.find({}).skip(range[0]).limit(range[1]),
|
||||||
|
// get the size of the docs
|
||||||
|
docs_count = await incomesModel.countDocuments({});
|
||||||
|
// prepare the return data
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: {
|
||||||
|
docs,
|
||||||
|
docs_count,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// if type is search
|
||||||
|
else if(type == 'search')
|
||||||
|
{
|
||||||
|
// get the searchKeyword param
|
||||||
|
let searchKeyword = searchParams.get('searchKeyword')
|
||||||
|
// get the search results docs
|
||||||
|
let results = await incomesModel.find({
|
||||||
|
$or: [
|
||||||
|
// search by ( case insensitive search )
|
||||||
|
{ name: { $regex: searchKeyword, $options: 'i' } },
|
||||||
|
{ description: { $regex: searchKeyword, $options: 'i' } }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
// get the docs
|
||||||
|
let responseData = {
|
||||||
|
docs: results,
|
||||||
|
docs_count : results.length,
|
||||||
|
}
|
||||||
|
// return the response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: responseData ? responseData : [],
|
||||||
|
} , {
|
||||||
|
status: 200,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// DELETE METHOD
|
||||||
|
export async function DELETE(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the parms
|
||||||
|
const { searchParams } = new URL(req.url)
|
||||||
|
const page = searchParams.get('page'),
|
||||||
|
_id : string | null = searchParams.get('_id'),
|
||||||
|
range : number[] = [(parseInt(page?page : '0') - 1) * 4 , 4];
|
||||||
|
// update the outcome
|
||||||
|
// get the old amount
|
||||||
|
let doc = await incomesModel.findById(_id)
|
||||||
|
// remove the outcome
|
||||||
|
await statisticsModel.findOneAndUpdate({} , {
|
||||||
|
$inc: {
|
||||||
|
totalIncome: doc?.amount ? -doc?.amount : 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// delete the doc
|
||||||
|
await incomesModel.findByIdAndRemove(_id)
|
||||||
|
// get the docs by page
|
||||||
|
const docs = await incomesModel.find({}).skip(range[0]).limit(range[1]),
|
||||||
|
// get the size of the docs
|
||||||
|
docs_count = await incomesModel.countDocuments({});
|
||||||
|
// prepare the return data
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: {
|
||||||
|
docs,
|
||||||
|
docs_count,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// PUT METHOD
|
||||||
|
export async function PUT(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the request payload
|
||||||
|
const { payload } = await req.json();
|
||||||
|
// get data from formData
|
||||||
|
const name : string | null = payload.name as string | null,
|
||||||
|
description : string | null = payload.description as string | null,
|
||||||
|
amount : number = payload.amount ? payload.amount : 0 as number,
|
||||||
|
_id : string | null = payload._id as string | null;
|
||||||
|
// update the income
|
||||||
|
// get the old amount
|
||||||
|
let doc = await incomesModel.findById(_id)
|
||||||
|
// re deacrease the old outcome
|
||||||
|
await statisticsModel.findOneAndUpdate({} , {
|
||||||
|
$inc: {
|
||||||
|
totalIncome: doc?.amount ? -doc?.amount : 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// add the new income
|
||||||
|
await statisticsModel.findOneAndUpdate({} , {
|
||||||
|
$inc: {
|
||||||
|
totalIncome: amount
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// update the doc
|
||||||
|
let updated_doc = await incomesModel.findByIdAndUpdate(_id , {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
amount
|
||||||
|
}, { new: true })
|
||||||
|
// return the success response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "requestTerminatedWithSuccess",
|
||||||
|
data: updated_doc,
|
||||||
|
}, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
354
webapp/src/app/api/user/actions/members/route.ts
Normal file
354
webapp/src/app/api/user/actions/members/route.ts
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
import dbConnect from "@/database/dbConnect";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import memberModel from "@/database/models/memberModel";
|
||||||
|
import serviceModel from "@/database/models/serviceModel";
|
||||||
|
import mongoose from 'mongoose'
|
||||||
|
import statisticsModel from "@/database/models/statisticsModel";
|
||||||
|
import incomeModel from "@/database/models/incomeModel";
|
||||||
|
|
||||||
|
// POST METHOD
|
||||||
|
export async function POST(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the request payload
|
||||||
|
const { payload } = await req.json();
|
||||||
|
// declare the needed variables
|
||||||
|
const firstName : string | null = payload.firstName as string | null,
|
||||||
|
lastName : string | null = payload.lastName as string | null,
|
||||||
|
email : string | null = payload.email as string | null,
|
||||||
|
phone : string | null = payload.phone as string | null,
|
||||||
|
address : string | null = payload.address as string | null,
|
||||||
|
services : [string] | [] = payload.services as [string] | [],
|
||||||
|
planDelay : number = payload.planDelay ? payload.planDelay : 1 as number,
|
||||||
|
gendre : string | null = payload.gendre as string | null,
|
||||||
|
bodyState : {
|
||||||
|
startBodyForm: String,
|
||||||
|
startWeight: Number,
|
||||||
|
} | null = payload.bodyState as {
|
||||||
|
startBodyForm: String,
|
||||||
|
startWeight: Number,
|
||||||
|
} | null;
|
||||||
|
// calculat month pay
|
||||||
|
const ids = services?.map((v , i) => {
|
||||||
|
return new mongoose.Types.ObjectId(v as any)
|
||||||
|
})
|
||||||
|
const servicesDocs = await serviceModel.aggregate([
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
_id: { $in : ids }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$group: {
|
||||||
|
_id: null,
|
||||||
|
totalPrice: {
|
||||||
|
$sum: "$price"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
// inc the value of totalSubscribers for all included services in the membership
|
||||||
|
await serviceModel.updateMany({_id : {$in : ids}} , {
|
||||||
|
$inc: {
|
||||||
|
totalSubscribers: 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const payMonth : number = servicesDocs[0] ? servicesDocs[0].totalPrice : 0
|
||||||
|
// declare needed variables
|
||||||
|
let current_date = new Date(),
|
||||||
|
current_date_unix = Math.floor(Date.now() / 1000),
|
||||||
|
planDelay_unix = planDelay ? planDelay * 2592000: 0 * 2592000,
|
||||||
|
planExpAt = new Date((current_date_unix + planDelay_unix) * 1000);
|
||||||
|
// save the doc
|
||||||
|
const doc = new memberModel({
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
email,
|
||||||
|
phone,
|
||||||
|
address,
|
||||||
|
payMonth,
|
||||||
|
services,
|
||||||
|
gendre,
|
||||||
|
bodyState,
|
||||||
|
registerAt: current_date.toUTCString(),
|
||||||
|
registerAt_unix: current_date_unix,
|
||||||
|
planUpdatedAt: current_date.toUTCString(),
|
||||||
|
planUpdatedAt_unix: current_date_unix,
|
||||||
|
planDelay,
|
||||||
|
planDelay_unix: planDelay_unix,
|
||||||
|
planExpAt: planExpAt.toUTCString(),
|
||||||
|
planExpAt_unix: current_date_unix + planDelay_unix,
|
||||||
|
active: true,
|
||||||
|
})
|
||||||
|
await doc.save()
|
||||||
|
|
||||||
|
// return the success response with the new added doc
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "requestTerminatedWithSuccess",
|
||||||
|
data: doc,
|
||||||
|
}, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// set the revalidate variable
|
||||||
|
export const revalidate = 5;
|
||||||
|
// GET METHOD
|
||||||
|
export async function GET(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the parms
|
||||||
|
const { searchParams } = new URL(req.url)
|
||||||
|
const type = searchParams.get('type')
|
||||||
|
// if get by page
|
||||||
|
if(type=='page')
|
||||||
|
{
|
||||||
|
// get the page
|
||||||
|
const page : string | null = searchParams.get('page') ? searchParams.get('page') : '0',
|
||||||
|
range : number[] = [(parseInt(page?page : '0') - 1) * 4 , 4];
|
||||||
|
// get the docs
|
||||||
|
const docs = await memberModel.find({}).skip(range[0]).limit(range[1]),
|
||||||
|
// get the size of the docs
|
||||||
|
docs_count = await memberModel.countDocuments({});
|
||||||
|
// prepare the return data
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: {
|
||||||
|
docs,
|
||||||
|
docs_count,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// if type is search
|
||||||
|
else if(type == 'search')
|
||||||
|
{
|
||||||
|
// get the searchKeyword param
|
||||||
|
let searchKeyword = searchParams.get('searchKeyword')
|
||||||
|
// get the search results docs
|
||||||
|
let results = await memberModel.find({
|
||||||
|
$or: [
|
||||||
|
// search by ( case insensitive search )
|
||||||
|
{ firstName: { $regex: searchKeyword, $options: 'i' } },
|
||||||
|
{ lastName: { $regex: searchKeyword, $options: 'i' } },
|
||||||
|
{ email: { $regex: searchKeyword, $options: 'i' } },
|
||||||
|
{ phone: { $regex: searchKeyword, $options: 'i' } },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
// get the docs
|
||||||
|
let responseData = {
|
||||||
|
docs: results,
|
||||||
|
docs_count : results.length,
|
||||||
|
}
|
||||||
|
// return the response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: responseData ? responseData : [],
|
||||||
|
} , {
|
||||||
|
status: 200,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// DELETE METHOD
|
||||||
|
export async function DELETE(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the parms
|
||||||
|
const { searchParams } = new URL(req.url)
|
||||||
|
const page = searchParams.get('page'),
|
||||||
|
_id : string | null = searchParams.get('_id'),
|
||||||
|
range : number[] = [(parseInt(page?page : '0') - 1) * 4 , 4];
|
||||||
|
// delete the doc
|
||||||
|
await memberModel.findByIdAndRemove(_id)
|
||||||
|
// get the docs by page
|
||||||
|
const docs = await memberModel.find({}).skip(range[0]).limit(range[1]),
|
||||||
|
// get the size of the docs
|
||||||
|
docs_count = await memberModel.countDocuments({});
|
||||||
|
// prepare the return data
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: {
|
||||||
|
docs,
|
||||||
|
docs_count,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// PUT METHOD
|
||||||
|
export async function PUT(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the request payload
|
||||||
|
const { args , payload } = await req.json();
|
||||||
|
// check the type
|
||||||
|
const type : "personal" | "subscription" | null = args.type as "personal" | "subscription" | null;
|
||||||
|
if(type == "personal")
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
const _id : string | null = payload._id as string | null,
|
||||||
|
firstName : string | null = payload.firstName as string | null,
|
||||||
|
lastName : string | null = payload.lastName as string | null,
|
||||||
|
email : string | null = payload.email as string | null,
|
||||||
|
phone : string | null = payload.phone as string | null,
|
||||||
|
address : string | null = payload.address as string | null,
|
||||||
|
active : boolean | null = payload.active as boolean | null;
|
||||||
|
// save the doc
|
||||||
|
const doc = await memberModel.findByIdAndUpdate( _id , {
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
email,
|
||||||
|
phone,
|
||||||
|
address,
|
||||||
|
active,
|
||||||
|
} , {
|
||||||
|
new: true
|
||||||
|
})
|
||||||
|
// return the success response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "requestTerminatedWithSuccess",
|
||||||
|
data: doc,
|
||||||
|
}, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if(type == "subscription")
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
const _id : string | null = payload._id as string | null,
|
||||||
|
services : [string] | [] = payload.services as [string] | [],
|
||||||
|
planDelay : number = payload.planDelay ? payload.planDelay : 1 as number,
|
||||||
|
bodyState : {
|
||||||
|
currentBodyForm: String,
|
||||||
|
currentWeight: Number,
|
||||||
|
} | null = payload.bodyState as {
|
||||||
|
currentBodyForm: String,
|
||||||
|
currentWeight: Number,
|
||||||
|
} | null;
|
||||||
|
// calculat month pay
|
||||||
|
const ids = services?.map((v , i) => {
|
||||||
|
return new mongoose.Types.ObjectId(v as string)
|
||||||
|
})
|
||||||
|
const servicesDocs = await serviceModel.aggregate([
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
_id: { $in : ids }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$group: {
|
||||||
|
_id: null,
|
||||||
|
totalPrice: {
|
||||||
|
$sum: "$price"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
const payMonth : number = servicesDocs[0] ? servicesDocs[0].totalPrice : 0
|
||||||
|
// declare needed variables
|
||||||
|
let current_date = new Date(),
|
||||||
|
current_date_unix = Math.floor(Date.now() / 1000),
|
||||||
|
planDelay_unix = planDelay ? planDelay * 2592000: 0 * 2592000,
|
||||||
|
planExpAt = new Date((current_date_unix + planDelay_unix) * 1000);
|
||||||
|
// save the doc
|
||||||
|
const doc = await memberModel.findByIdAndUpdate( _id , {
|
||||||
|
bodyState,
|
||||||
|
services,
|
||||||
|
payMonth,
|
||||||
|
planUpdatedAt: current_date.toUTCString(),
|
||||||
|
planUpdatedAt_unix: current_date_unix,
|
||||||
|
planDelay,
|
||||||
|
planDelay_unix: planDelay_unix,
|
||||||
|
planExpAt: planExpAt.toUTCString(),
|
||||||
|
planExpAt_unix: current_date_unix + planDelay_unix,
|
||||||
|
active: true,
|
||||||
|
} , {
|
||||||
|
new: true
|
||||||
|
})
|
||||||
|
// add the subscription income
|
||||||
|
let income = payMonth * planDelay
|
||||||
|
await statisticsModel.findOneAndUpdate({} , {
|
||||||
|
$inc: {
|
||||||
|
totalIncome: income
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// return the success response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "requestTerminatedWithSuccess",
|
||||||
|
data: doc,
|
||||||
|
}, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
209
webapp/src/app/api/user/actions/products/route.ts
Normal file
209
webapp/src/app/api/user/actions/products/route.ts
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
import dbConnect from "@/database/dbConnect";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import productsModel from "@/database/models/productsModel";
|
||||||
|
|
||||||
|
// POST METHOD
|
||||||
|
export async function POST(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the request payload
|
||||||
|
const { payload } = await req.json();
|
||||||
|
// get data from formData
|
||||||
|
const name : string | null = payload.name as string | null,
|
||||||
|
description : string | null = payload.description as string | null,
|
||||||
|
price : number | null = payload.price as number | null,
|
||||||
|
quantity : number | null = payload.quantity as number | null;
|
||||||
|
// save the doc
|
||||||
|
const doc = new productsModel({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
price,
|
||||||
|
quantity
|
||||||
|
})
|
||||||
|
await doc.save()
|
||||||
|
// return the success response with the new added doc
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "requestTerminatedWithSuccess",
|
||||||
|
data: doc,
|
||||||
|
}, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
console.log("e" , e)
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// set the revalidate variable
|
||||||
|
export const revalidate = 5;
|
||||||
|
// GET METHOD
|
||||||
|
export async function GET(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the parms
|
||||||
|
const { searchParams } = new URL(req.url)
|
||||||
|
const type = searchParams.get('type')
|
||||||
|
// if get by page
|
||||||
|
if(type=='page')
|
||||||
|
{
|
||||||
|
// get the page
|
||||||
|
const page : string | null = searchParams.get('page') ? searchParams.get('page') : '0',
|
||||||
|
range : number[] = [(parseInt(page?page : '0') - 1) * 4 , 4];
|
||||||
|
// get the docs
|
||||||
|
const docs = await productsModel.find({}).skip(range[0]).limit(range[1]),
|
||||||
|
// get the size of the docs
|
||||||
|
docs_count = await productsModel.countDocuments({});
|
||||||
|
// prepare the return data
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: {
|
||||||
|
docs,
|
||||||
|
docs_count,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// if type is search
|
||||||
|
else if(type == 'search')
|
||||||
|
{
|
||||||
|
// get the searchKeyword param
|
||||||
|
let searchKeyword = searchParams.get('searchKeyword')
|
||||||
|
// get the search results docs
|
||||||
|
let results = await productsModel.find({
|
||||||
|
$or: [
|
||||||
|
// search by ( case insensitive search )
|
||||||
|
{ name: { $regex: searchKeyword, $options: 'i' } },
|
||||||
|
{ description: { $regex: searchKeyword, $options: 'i' } }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
// get the docs
|
||||||
|
let responseData = {
|
||||||
|
docs: results,
|
||||||
|
docs_count : results.length,
|
||||||
|
}
|
||||||
|
// return the response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: responseData ? responseData : [],
|
||||||
|
} , {
|
||||||
|
status: 200,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// DELETE METHOD
|
||||||
|
export async function DELETE(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the parms
|
||||||
|
const { searchParams } = new URL(req.url)
|
||||||
|
const page = searchParams.get('page'),
|
||||||
|
_id : string | null = searchParams.get('_id'),
|
||||||
|
range : number[] = [(parseInt(page?page : '0') - 1) * 4 , 4];
|
||||||
|
// delete the doc
|
||||||
|
await productsModel.findByIdAndRemove(_id)
|
||||||
|
// get the docs by page
|
||||||
|
const docs = await productsModel.find({}).skip(range[0]).limit(range[1]),
|
||||||
|
// get the size of the docs
|
||||||
|
docs_count = await productsModel.countDocuments({});
|
||||||
|
// prepare the return data
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: {
|
||||||
|
docs,
|
||||||
|
docs_count,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// PUT METHOD
|
||||||
|
export async function PUT(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the request payload
|
||||||
|
const { payload } = await req.json();
|
||||||
|
// get data from formData
|
||||||
|
const name : string | null = payload.name as string | null,
|
||||||
|
description : string | null = payload.description as string | null,
|
||||||
|
price : number | null = payload.price as number | null,
|
||||||
|
quantity : number | null = payload.quantity as number,
|
||||||
|
_id : string | null = payload._id as string | null;
|
||||||
|
// update the doc
|
||||||
|
let updated_doc = await productsModel.findByIdAndUpdate(_id , {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
price,
|
||||||
|
quantity
|
||||||
|
}, { new: true })
|
||||||
|
// return the success response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "requestTerminatedWithSuccess",
|
||||||
|
data: updated_doc,
|
||||||
|
}, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
208
webapp/src/app/api/user/actions/services/route.ts
Normal file
208
webapp/src/app/api/user/actions/services/route.ts
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
import dbConnect from "@/database/dbConnect";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import serviceModel from "@/database/models/serviceModel";
|
||||||
|
|
||||||
|
// POST METHOD
|
||||||
|
export async function POST(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the request payload
|
||||||
|
const { payload } = await req.json();
|
||||||
|
// get data from formData
|
||||||
|
const name : string | null = payload.name as string | null,
|
||||||
|
description : string | null = payload.description as string | null,
|
||||||
|
status : boolean | null = payload.status as boolean | null,
|
||||||
|
price : number | null = payload.price as number | null;
|
||||||
|
// save the doc
|
||||||
|
const doc = new serviceModel({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
price,
|
||||||
|
status
|
||||||
|
})
|
||||||
|
await doc.save()
|
||||||
|
// return the success response with the new added doc
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "requestTerminatedWithSuccess",
|
||||||
|
data: doc,
|
||||||
|
}, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// set the revalidate variable
|
||||||
|
export const revalidate = 5;
|
||||||
|
// GET METHOD
|
||||||
|
export async function GET(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the parms
|
||||||
|
const { searchParams } = new URL(req.url)
|
||||||
|
const type = searchParams.get('type')
|
||||||
|
// if get by page
|
||||||
|
if(type=='page')
|
||||||
|
{
|
||||||
|
// get the page
|
||||||
|
const page : string | null = searchParams.get('page') ? searchParams.get('page') : '0',
|
||||||
|
range : number[] = [(parseInt(page?page : '0') - 1) * 4 , 4];
|
||||||
|
// get the docs
|
||||||
|
const docs = await serviceModel.find({}).skip(range[0]).limit(range[1]),
|
||||||
|
// get the size of the docs
|
||||||
|
docs_count = await serviceModel.countDocuments({});
|
||||||
|
// prepare the return data
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: {
|
||||||
|
docs,
|
||||||
|
docs_count,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// if type is search
|
||||||
|
else if(type == 'search')
|
||||||
|
{
|
||||||
|
// get the searchKeyword param
|
||||||
|
let searchKeyword = searchParams.get('searchKeyword')
|
||||||
|
// get the search results docs
|
||||||
|
let results = await serviceModel.find({
|
||||||
|
$or: [
|
||||||
|
// search by ( case insensitive search )
|
||||||
|
{ name: { $regex: searchKeyword, $options: 'i' } },
|
||||||
|
{ description: { $regex: searchKeyword, $options: 'i' } }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
// get the docs
|
||||||
|
let responseData = {
|
||||||
|
docs: results,
|
||||||
|
docs_count : results.length,
|
||||||
|
}
|
||||||
|
// return the response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: responseData ? responseData : [],
|
||||||
|
} , {
|
||||||
|
status: 200,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// DELETE METHOD
|
||||||
|
export async function DELETE(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the parms
|
||||||
|
const { searchParams } = new URL(req.url)
|
||||||
|
const page = searchParams.get('page'),
|
||||||
|
_id : string | null = searchParams.get('_id'),
|
||||||
|
range : number[] = [(parseInt(page?page : '0') - 1) * 4 , 4];
|
||||||
|
// delete the doc
|
||||||
|
await serviceModel.findByIdAndRemove(_id)
|
||||||
|
// get the docs by page
|
||||||
|
const docs = await serviceModel.find({}).skip(range[0]).limit(range[1]),
|
||||||
|
// get the size of the docs
|
||||||
|
docs_count = await serviceModel.countDocuments({});
|
||||||
|
// prepare the return data
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: {
|
||||||
|
docs,
|
||||||
|
docs_count,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// PUT METHOD
|
||||||
|
export async function PUT(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the request payload
|
||||||
|
const { payload } = await req.json();
|
||||||
|
// get data from formData
|
||||||
|
const name : string | null = payload.name as string | null,
|
||||||
|
description : string | null = payload.description as string | null,
|
||||||
|
price : number | null = payload.price as number | null,
|
||||||
|
status : string | null = payload.status == "active" ? payload.status as string | null : "deactivate",
|
||||||
|
_id : string | null = payload._id as string | null;
|
||||||
|
// update the doc
|
||||||
|
let updated_doc = await serviceModel.findByIdAndUpdate(_id , {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
price,
|
||||||
|
status
|
||||||
|
}, { new: true })
|
||||||
|
// return the success response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "requestTerminatedWithSuccess",
|
||||||
|
data: updated_doc,
|
||||||
|
}, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
98
webapp/src/app/api/user/actions/settings/route.ts
Normal file
98
webapp/src/app/api/user/actions/settings/route.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import dbConnect from "@/database/dbConnect";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import userModel from "@/database/models/userModel";
|
||||||
|
|
||||||
|
// set the revalidate variable
|
||||||
|
export const revalidate = 5;
|
||||||
|
// GET METHOD
|
||||||
|
export async function GET(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the settings from the db
|
||||||
|
let doc = await userModel.findOne({} , 'settings')
|
||||||
|
// return the success response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "requestTerminatedWithSuccess",
|
||||||
|
data: doc,
|
||||||
|
}, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// PUT METHOD
|
||||||
|
export async function PUT(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the request payload
|
||||||
|
const { payload } = await req.json();
|
||||||
|
// get data from formData
|
||||||
|
const appName : string | null = payload.appName as string | null,
|
||||||
|
appNameEN : string | null = payload.appNameEN as string | null,
|
||||||
|
gymName : string | null = payload.gymName as string | null,
|
||||||
|
email : string | null = payload.email as string | null,
|
||||||
|
phone : string | null = payload.phone as string | null,
|
||||||
|
address : string | null = payload.address as string | null,
|
||||||
|
showLogo : boolean | null = payload.showLogo as boolean | null,
|
||||||
|
logo : string | null = payload.logo as string | null,
|
||||||
|
currencySymbol : string | null = payload.currencySymbol as string | null;
|
||||||
|
// update the doc
|
||||||
|
let updated_doc = await userModel.updateMany({} , {
|
||||||
|
$set: {
|
||||||
|
settings: {
|
||||||
|
appName,
|
||||||
|
appNameEN,
|
||||||
|
gymName,
|
||||||
|
email,
|
||||||
|
phone,
|
||||||
|
address,
|
||||||
|
showLogo,
|
||||||
|
logo,
|
||||||
|
currencySymbol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { new: true })
|
||||||
|
// return the success response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "requestTerminatedWithSuccess",
|
||||||
|
data: updated_doc,
|
||||||
|
}, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
626
webapp/src/app/api/user/actions/statistics/route.ts
Normal file
626
webapp/src/app/api/user/actions/statistics/route.ts
Normal file
@ -0,0 +1,626 @@
|
|||||||
|
import dbConnect from "@/database/dbConnect";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import userModel from "@/database/models/userModel";
|
||||||
|
import memberModel from "@/database/models/memberModel";
|
||||||
|
import servicesModel from "@/database/models/serviceModel";
|
||||||
|
import statisticsModel from "@/database/models/statisticsModel";
|
||||||
|
import workerModel from "@/database/models/workerModel";
|
||||||
|
import { Types } from 'mongoose'
|
||||||
|
import { IStatisticsSchema } from '@/types/IDB'
|
||||||
|
|
||||||
|
// set the revalidate variable
|
||||||
|
export const revalidate = 5;
|
||||||
|
// GET METHOD
|
||||||
|
export async function GET(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get needed docs
|
||||||
|
let membersCount : number = await memberModel.count({})
|
||||||
|
let activeMembersCount : number = await memberModel.count({active: true})
|
||||||
|
let disActiveMembersCount : number = await memberModel.count({active: false})
|
||||||
|
let activeSubscriptionsCount : number = await memberModel.count({planExpAt_unix: {$gt : Math.floor(new Date().getTime() / 1000)}})
|
||||||
|
let expiredSoonSubscriptionsCount : number = await memberModel.count({planExpAt_unix: {$gt : Math.floor(new Date().getTime() / 1000) + 259200}})
|
||||||
|
let expiredSubscriptionsCount : number = await memberModel.count({planExpAt_unix: {$lte : Math.floor(new Date().getTime() / 1000)}})
|
||||||
|
let servicesCount : number = await servicesModel.count({})
|
||||||
|
let activeServicesCount : number = await servicesModel.count({status: "active"})
|
||||||
|
let disActiveServicesCount : number = await servicesModel.count({status: "active"})
|
||||||
|
let servicesNameAndSubscribers :
|
||||||
|
{
|
||||||
|
_id: Types.ObjectId,
|
||||||
|
name: string,
|
||||||
|
totalSubscribers: number
|
||||||
|
}[]
|
||||||
|
= await servicesModel.find({} , {name: 1 , totalSubscribers : 1})
|
||||||
|
let workersNameAndJobType : { count: number , jobType: string }[] = await workerModel.aggregate([
|
||||||
|
{
|
||||||
|
$group: {
|
||||||
|
_id: "$jobType",
|
||||||
|
count: { $sum: 1 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$project: {
|
||||||
|
jobType: "$_id",
|
||||||
|
count: 1,
|
||||||
|
_id: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
let membersGendre : { count: number , gendre: string }[] = await memberModel.aggregate([
|
||||||
|
{
|
||||||
|
$group: {
|
||||||
|
_id: "$gendre",
|
||||||
|
count: { $sum: 1 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$project: {
|
||||||
|
gendre: "$_id",
|
||||||
|
count: 1,
|
||||||
|
_id: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
// get members general overview
|
||||||
|
let membersGeneralOverview : {
|
||||||
|
thisWeek : {
|
||||||
|
'year': number,
|
||||||
|
'month': number,
|
||||||
|
'week': number,
|
||||||
|
'days': {
|
||||||
|
'sat': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'sun': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'mon': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'tue': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'wed': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'thu': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'fri': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
months : {
|
||||||
|
'year': number,
|
||||||
|
'month': number,
|
||||||
|
'weeks': {
|
||||||
|
'week': number,
|
||||||
|
'days': {
|
||||||
|
'sat': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'sun': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'mon': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'tue': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'wed': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'thu': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'fri': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
}
|
||||||
|
}[]
|
||||||
|
}[]
|
||||||
|
}[],
|
||||||
|
years : {
|
||||||
|
'year': number,
|
||||||
|
'months': {
|
||||||
|
'month': number,
|
||||||
|
'weeks': {
|
||||||
|
'week': number,
|
||||||
|
'days': {
|
||||||
|
'sat': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'sun': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'mon': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'tue': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'wed': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'thu': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'fri': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
}
|
||||||
|
}[]
|
||||||
|
}[]
|
||||||
|
}[]
|
||||||
|
}[],
|
||||||
|
thisMonth: {
|
||||||
|
'year': number,
|
||||||
|
'month': number,
|
||||||
|
'weeks': {
|
||||||
|
'week': number,
|
||||||
|
'days': {
|
||||||
|
'sat': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'sun': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'mon': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'tue': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'wed': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'thu': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'fri': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
}
|
||||||
|
}[]
|
||||||
|
}[]
|
||||||
|
},
|
||||||
|
thisYear: {
|
||||||
|
'year': number,
|
||||||
|
'months': {
|
||||||
|
'month': number,
|
||||||
|
'weeks': {
|
||||||
|
'week': number,
|
||||||
|
'days': {
|
||||||
|
'sat': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'sun': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'mon': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'tue': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'wed': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'thu': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'fri': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
}
|
||||||
|
}[]
|
||||||
|
}[]
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
} =
|
||||||
|
{
|
||||||
|
thisWeek : {
|
||||||
|
'year': 0,
|
||||||
|
'month': 0,
|
||||||
|
'week': 0,
|
||||||
|
'days': {
|
||||||
|
'sat': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'sun': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'mon': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'tue': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'wed': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'thu': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'fri': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
months : [{
|
||||||
|
'year': 0,
|
||||||
|
'month': 0,
|
||||||
|
'weeks': [{
|
||||||
|
'week': 0,
|
||||||
|
'days': [{
|
||||||
|
'sat': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'sun': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'mon': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'tue': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'wed': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'thu': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'fri': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}],
|
||||||
|
years : [{
|
||||||
|
'year': 0,
|
||||||
|
'months': [{
|
||||||
|
'month': 0,
|
||||||
|
'weeks': [{
|
||||||
|
'week': 0,
|
||||||
|
'days': {
|
||||||
|
'sat': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'sun': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'mon': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'tue': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'wed': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'thu': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'fri': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
let statistics = await statisticsModel.findOne({});
|
||||||
|
if(statistics?.listsTracking)
|
||||||
|
{
|
||||||
|
membersGeneralOverview.thisWeek = statistics?.listsTracking.thisWeek
|
||||||
|
membersGeneralOverview.thisMonth = statistics?.listsTracking.months.filter((month) => month.month == (new Date()).getMonth())[0]
|
||||||
|
membersGeneralOverview.thisYear = statistics?.listsTracking.years.filter((year) => year.year == (new Date()).getFullYear())[0]
|
||||||
|
}
|
||||||
|
// prepare the report
|
||||||
|
let report = {
|
||||||
|
membersCount: {
|
||||||
|
name: "membersCount",
|
||||||
|
value: membersCount,
|
||||||
|
},
|
||||||
|
activeSubscriptionsCount: {
|
||||||
|
name: "activeSubscriptionsCount",
|
||||||
|
value: activeSubscriptionsCount,
|
||||||
|
},
|
||||||
|
expiredSoonSubscriptionsCount: {
|
||||||
|
name: "expiredSoonSubscriptionsCount",
|
||||||
|
value: expiredSoonSubscriptionsCount,
|
||||||
|
},
|
||||||
|
expiredSubscriptionsCount: {
|
||||||
|
name: "expiredSubscriptionsCount",
|
||||||
|
value: expiredSubscriptionsCount,
|
||||||
|
},
|
||||||
|
activeMembersCount: {
|
||||||
|
name: "activeMembersCount",
|
||||||
|
value: activeMembersCount,
|
||||||
|
},
|
||||||
|
disActiveMembersCount: {
|
||||||
|
name: "disActiveMembersCount",
|
||||||
|
value: disActiveMembersCount,
|
||||||
|
},
|
||||||
|
servicesCount: {
|
||||||
|
name: "servicesCount",
|
||||||
|
value: servicesCount,
|
||||||
|
},
|
||||||
|
activeServicesCount: {
|
||||||
|
name: "activeServicesCount",
|
||||||
|
value: activeServicesCount,
|
||||||
|
},
|
||||||
|
disActiveServicesCount: {
|
||||||
|
name: "disActiveServicesCount",
|
||||||
|
value: disActiveServicesCount,
|
||||||
|
},
|
||||||
|
membersGeneralOverview: {
|
||||||
|
name: "membersGeneralOverview",
|
||||||
|
value: membersGeneralOverview,
|
||||||
|
},
|
||||||
|
servicesNameAndSubscribers: {
|
||||||
|
name: "servicesNameAndSubscribers",
|
||||||
|
value: servicesNameAndSubscribers,
|
||||||
|
},
|
||||||
|
workersNameAndJobType: {
|
||||||
|
name: "workersNameAndJobType",
|
||||||
|
value: workersNameAndJobType,
|
||||||
|
},
|
||||||
|
membersGendre: {
|
||||||
|
name: "membersGendre",
|
||||||
|
value: membersGendre,
|
||||||
|
},
|
||||||
|
totalIncome: statistics?.totalIncome,
|
||||||
|
totalOutcome: statistics?.totalOutcome
|
||||||
|
}
|
||||||
|
// return the success response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "requestTerminatedWithSuccess",
|
||||||
|
data: report,
|
||||||
|
}, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
console.log(e)
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
227
webapp/src/app/api/user/actions/workers/route.ts
Normal file
227
webapp/src/app/api/user/actions/workers/route.ts
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
import dbConnect from "@/database/dbConnect";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import workerModel from "@/database/models/workerModel";
|
||||||
|
|
||||||
|
// POST METHOD
|
||||||
|
export async function POST(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the request payload
|
||||||
|
const { payload } = await req.json();
|
||||||
|
// get data from formData
|
||||||
|
const firstName : string | null = payload.firstName as string | null,
|
||||||
|
lastName : string | null = payload.lastName as string | null,
|
||||||
|
jobType : boolean | null = payload.jobType as boolean | null,
|
||||||
|
monthlySalary : number | null = payload.monthlySalary as number | null,
|
||||||
|
gendre : string | null = payload.gendre as string | null,
|
||||||
|
phone : string | null = payload.phone as string | null,
|
||||||
|
email : string | null = payload.email as string | null,
|
||||||
|
address : string | null = payload.address as string | null;
|
||||||
|
// save the doc
|
||||||
|
const doc = new workerModel({
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
jobType,
|
||||||
|
monthlySalary,
|
||||||
|
gendre,
|
||||||
|
phone,
|
||||||
|
email,
|
||||||
|
address
|
||||||
|
})
|
||||||
|
await doc.save()
|
||||||
|
// return the success response with the new added doc
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "requestTerminatedWithSuccess",
|
||||||
|
data: doc,
|
||||||
|
}, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
console.log("e" , e)
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// set the revalidate variable
|
||||||
|
export const revalidate = 5;
|
||||||
|
// GET METHOD
|
||||||
|
export async function GET(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the parms
|
||||||
|
const { searchParams } = new URL(req.url)
|
||||||
|
const type = searchParams.get('type')
|
||||||
|
// if get by page
|
||||||
|
if(type=='page')
|
||||||
|
{
|
||||||
|
// get the page
|
||||||
|
const page : string | null = searchParams.get('page') ? searchParams.get('page') : '0',
|
||||||
|
range : number[] = [(parseInt(page?page : '0') - 1) * 4 , 4];
|
||||||
|
// get the docs
|
||||||
|
const docs = await workerModel.find({}).skip(range[0]).limit(range[1]),
|
||||||
|
// get the size of the docs
|
||||||
|
docs_count = await workerModel.countDocuments({});
|
||||||
|
// prepare the return data
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: {
|
||||||
|
docs,
|
||||||
|
docs_count,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// if type is search
|
||||||
|
else if(type == 'search')
|
||||||
|
{
|
||||||
|
// get the searchKeyword param
|
||||||
|
let searchKeyword = searchParams.get('searchKeyword')
|
||||||
|
// get the search results docs
|
||||||
|
let results = await workerModel.find({
|
||||||
|
$or: [
|
||||||
|
// search by ( case insensitive search )
|
||||||
|
{ firstName: { $regex: searchKeyword, $options: 'i' } },
|
||||||
|
{ lastName: { $regex: searchKeyword, $options: 'i' } },
|
||||||
|
{ email: { $regex: searchKeyword, $options: 'i' } },
|
||||||
|
{ phone: { $regex: searchKeyword, $options: 'i' } }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
// get the docs
|
||||||
|
let responseData = {
|
||||||
|
docs: results,
|
||||||
|
docs_count : results.length,
|
||||||
|
}
|
||||||
|
// return the response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: responseData ? responseData : [],
|
||||||
|
} , {
|
||||||
|
status: 200,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// DELETE METHOD
|
||||||
|
export async function DELETE(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the parms
|
||||||
|
const { searchParams } = new URL(req.url)
|
||||||
|
const page = searchParams.get('page'),
|
||||||
|
_id : string | null = searchParams.get('_id'),
|
||||||
|
range : number[] = [(parseInt(page?page : '0') - 1) * 4 , 4];
|
||||||
|
// delete the doc
|
||||||
|
await workerModel.findByIdAndRemove(_id)
|
||||||
|
// get the docs by page
|
||||||
|
const docs = await workerModel.find({}).skip(range[0]).limit(range[1]),
|
||||||
|
// get the size of the docs
|
||||||
|
docs_count = await workerModel.countDocuments({});
|
||||||
|
// prepare the return data
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "docsGetedSuccessfully",
|
||||||
|
data: {
|
||||||
|
docs,
|
||||||
|
docs_count,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// PUT METHOD
|
||||||
|
export async function PUT(req:Request)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
// connect to the db
|
||||||
|
dbConnect();
|
||||||
|
// get the request payload
|
||||||
|
const { payload } = await req.json();
|
||||||
|
// get data from formData
|
||||||
|
const firstName : string | null = payload.firstName as string | null,
|
||||||
|
lastName : string | null = payload.lastName as string | null,
|
||||||
|
jobType : boolean | null = payload.jobType as boolean | null,
|
||||||
|
monthlySalary : number | null = payload.monthlySalary as number | null,
|
||||||
|
gendre : string | null = payload.gendre as string | null,
|
||||||
|
phone : string | null = payload.phone as string | null,
|
||||||
|
email : string | null = payload.email as string | null,
|
||||||
|
address : string | null = payload.address as string | null,
|
||||||
|
_id : string | null = payload._id as string | null;
|
||||||
|
// update the doc
|
||||||
|
let updated_doc = await workerModel.findByIdAndUpdate(_id , {
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
jobType,
|
||||||
|
monthlySalary,
|
||||||
|
gendre,
|
||||||
|
phone,
|
||||||
|
email,
|
||||||
|
address
|
||||||
|
}, { new: true })
|
||||||
|
// return the success response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: "requestTerminatedWithSuccess",
|
||||||
|
data: updated_doc,
|
||||||
|
}, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
// catch any error and return an error response
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
message: "serverError",
|
||||||
|
}, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
16
webapp/src/app/layout.tsx
Normal file
16
webapp/src/app/layout.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
|
//? This is the root app layout
|
||||||
|
export default async function RootLayout({children, params: {locale}} : {children : ReactNode , params : {locale : string} }) {
|
||||||
|
|
||||||
|
|
||||||
|
//? auto redirect to the local
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
38
webapp/src/components/common/alert.tsx
Normal file
38
webapp/src/components/common/alert.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { Snackbar, SnackbarContent } from '@mui/material';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { AppDispatch } from '@/redux/store';
|
||||||
|
import { clearAlert } from '@/redux/features/alert-slice'
|
||||||
|
|
||||||
|
export default function Alert({ payload } : any ) {
|
||||||
|
|
||||||
|
const dispatch = useDispatch<AppDispatch>()
|
||||||
|
const t = useTranslations('Common');
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
dispatch(clearAlert());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If success is null so dont mount the snackbar ( there is no need for an alert )
|
||||||
|
if(payload.success == null) return
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Snackbar
|
||||||
|
open={payload.message ? true : false}
|
||||||
|
onClose={handleClose}
|
||||||
|
autoHideDuration={3000} //Todo make a global settings for this things
|
||||||
|
>
|
||||||
|
<SnackbarContent
|
||||||
|
sx={{
|
||||||
|
backgroundColor: payload.success == true ? theme.palette.success.main : theme.palette.error.main,
|
||||||
|
color: 'white',
|
||||||
|
}}
|
||||||
|
message={<div className="font-tajawal">{t(`${payload.message}`)}</div>}
|
||||||
|
/>
|
||||||
|
</Snackbar>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
12
webapp/src/components/common/fullScreenLoader.tsx
Normal file
12
webapp/src/components/common/fullScreenLoader.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
|
||||||
|
export default function FullScreenLoader()
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="animate__animated screen_loader fixed inset-0 z-[60] grid place-content-center bg-secondary-light dark:bg-secondary-dark">
|
||||||
|
<CircularProgress color="secondary" size={54} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
170
webapp/src/components/dashboard/equipments/equipmentsList.tsx
Normal file
170
webapp/src/components/dashboard/equipments/equipmentsList.tsx
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { useAppSelector } from '@/redux/store';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import { AppDispatch } from '@/redux/store';
|
||||||
|
import { setSearchKeyword ,load , search , delete_ } from '@/redux/features/equipments-slice'
|
||||||
|
import ListPagination from './parts/equipmentsListPagination'
|
||||||
|
import { setDetailsPopUp , setUpdatePopUp , setCurrentPage } from '@/redux/features/equipments-slice';
|
||||||
|
|
||||||
|
export default function List()
|
||||||
|
{
|
||||||
|
const t = useTranslations('equipments');
|
||||||
|
const dispatch = useDispatch<AppDispatch>()
|
||||||
|
|
||||||
|
const listContent = useAppSelector((state) => state.equipmentsReducer.value.listContent)
|
||||||
|
const isLoading = useAppSelector((state) => state.equipmentsReducer.value.isLoading)
|
||||||
|
const loadedFirstTime = useAppSelector((state) => state.equipmentsReducer.value.loadedFirstTime)
|
||||||
|
const searchKeyword = useAppSelector((state) => state.equipmentsReducer.value.searchKeyword)
|
||||||
|
const currentPage = useAppSelector((state) => state.equipmentsReducer.value.currentPage)
|
||||||
|
const appGeneralSettings = useAppSelector((state) => state.settingsReducer.value.appGeneralSettings)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(loadedFirstTime) return
|
||||||
|
if(isLoading) return
|
||||||
|
async function a()
|
||||||
|
{
|
||||||
|
await dispatch(load({page : 1}))
|
||||||
|
}
|
||||||
|
a()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-3 w-full w-auto">
|
||||||
|
<header className="w-full flex lg:flex-row flex-col lg:justify-between lg:items-center gap-3">
|
||||||
|
<h1 className="font-semibold text-[20px] text-text dark:text-text-dark">{t('equipments')}</h1>
|
||||||
|
<label htmlFor="email" className="relative text-gray-400 focus-within:text-gray-600 block">
|
||||||
|
<svg className="text-secondary dark:text-primary/50 cursor-pointer w-[15px] h-[15px] absolute top-1/2 transform -translate-y-1/2 rtl:left-3 ltr:right-3" width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M18.4479 16.9688L14.9063 13.4375C16.0489 11.9817 16.669 10.184 16.6667 8.33334C16.6667 6.68516 16.1779 5.074 15.2623 3.70359C14.3466 2.33318 13.0451 1.26507 11.5224 0.634341C9.99965 0.00361068 8.32409 -0.161417 6.70759 0.160126C5.09108 0.48167 3.60622 1.27534 2.44078 2.44078C1.27534 3.60622 0.48167 5.09108 0.160126 6.70759C-0.161417 8.32409 0.00361068 9.99965 0.634341 11.5224C1.26507 13.0451 2.33318 14.3466 3.70359 15.2623C5.074 16.1779 6.68516 16.6667 8.33334 16.6667C10.184 16.669 11.9817 16.0489 13.4375 14.9063L16.9688 18.4479C17.0656 18.5456 17.1808 18.6231 17.3077 18.6759C17.4347 18.7288 17.5708 18.756 17.7083 18.756C17.8459 18.756 17.982 18.7288 18.1089 18.6759C18.2359 18.6231 18.3511 18.5456 18.4479 18.4479C18.5456 18.3511 18.6231 18.2359 18.6759 18.1089C18.7288 17.982 18.756 17.8459 18.756 17.7083C18.756 17.5708 18.7288 17.4347 18.6759 17.3077C18.6231 17.1808 18.5456 17.0656 18.4479 16.9688ZM2.08334 8.33334C2.08334 7.0972 2.44989 5.88883 3.13665 4.86102C3.82341 3.83322 4.79953 3.03214 5.94157 2.55909C7.0836 2.08604 8.34027 1.96227 9.55265 2.20343C10.765 2.44459 11.8787 3.03984 12.7528 3.91392C13.6268 4.788 14.2221 5.90164 14.4632 7.11402C14.7044 8.3264 14.5806 9.58307 14.1076 10.7251C13.6345 11.8671 12.8335 12.8433 11.8057 13.53C10.7778 14.2168 9.56947 14.5833 8.33334 14.5833C6.67573 14.5833 5.08602 13.9249 3.91392 12.7528C2.74182 11.5807 2.08334 9.99094 2.08334 8.33334Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<input onChange={(async(e) => {
|
||||||
|
if(e.target.value.length == 0)
|
||||||
|
{
|
||||||
|
dispatch(setCurrentPage(1));
|
||||||
|
}
|
||||||
|
dispatch(setSearchKeyword(e.target.value))
|
||||||
|
await dispatch(search({
|
||||||
|
searchKeyword: e.target.value
|
||||||
|
}))
|
||||||
|
})}
|
||||||
|
value={searchKeyword}
|
||||||
|
className="lg:w-[300px] w-full text-text dark:text-text-dark rounded-[5px] px-3 h-[50px] border-[1px] !border-secondary-light bg-primary-dark/5 placeholder:font-semibold" type="text" placeholder={t('searchForEquipments')}></input>
|
||||||
|
</label>
|
||||||
|
</header>
|
||||||
|
<div className="panel !p-0 mb-7 dark:bg-primary-dark">
|
||||||
|
<header className="w-full lg:flex gap-3 hidden justify-between items-center p-5 opacity-50 font-semibold border-b-[1px] border-secondary-light">
|
||||||
|
<span className="w-full text-start">#</span>
|
||||||
|
<span className="w-full text-start">{t('name')}</span>
|
||||||
|
<span className="w-full text-start">{t('quantity')}</span>
|
||||||
|
<span className="w-full text-start">{t('sallerName')}</span>
|
||||||
|
<span className="w-full text-start">{t('sallerPhone')}</span>
|
||||||
|
<span className="w-full text-start">{t('singlePrice')}</span>
|
||||||
|
<span className="w-full text-start">{t('actions')}</span>
|
||||||
|
</header>
|
||||||
|
<main className="flex flex-col justify-center items-center">
|
||||||
|
{
|
||||||
|
!isLoading && listContent.flat().length == 0 && currentPage >= 2 && searchKeyword.length == 0 ?
|
||||||
|
<div className="p-5 py-10 flex justify-start items-center gap-3 [&_*]:fill-warning">
|
||||||
|
<svg width="25" height="25" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path opacity="0.5" d="M15.2069 2.85079C15.6886 1.98507 16.3858 1.26524 17.2277 0.76451C18.0696 0.263778 19.0261 0 20 0C20.9739 0 21.9304 0.263778 22.7723 0.76451C23.6142 1.26524 24.3114 1.98507 24.7931 2.85079L39.309 29.3012C39.77 30.1312 40.0083 31.0711 39.9998 32.0257C39.9913 32.9802 39.7363 33.9155 39.2607 34.7368C38.7781 35.5842 38.085 36.286 37.2518 36.7713C36.4186 37.2565 35.4747 37.5078 34.5159 37.4998H5.48407C4.52527 37.5078 3.58143 37.2565 2.74819 36.7713C1.91496 36.286 1.22192 35.5842 0.739307 34.7368C0.263677 33.9155 0.00870576 32.9802 0.000218482 32.0257C-0.0082688 31.0711 0.230027 30.1312 0.690974 29.3012L15.2069 2.85079ZM20 30.6818C20.3296 30.6818 20.6518 30.5819 20.9259 30.3946C21.2 30.2073 21.4136 29.9411 21.5397 29.6296C21.6659 29.3182 21.6989 28.9754 21.6346 28.6448C21.5703 28.3142 21.4115 28.0104 21.1785 27.7721C20.9454 27.5337 20.6484 27.3713 20.3251 27.3056C20.0018 27.2398 19.6668 27.2736 19.3622 27.4026C19.0577 27.5316 18.7974 27.7501 18.6143 28.0304C18.4312 28.3107 18.3334 28.6402 18.3334 28.9773C18.3334 29.4294 18.509 29.8629 18.8215 30.1826C19.1341 30.5022 19.558 30.6818 20 30.6818ZM18.3334 22.1593C18.3334 22.6114 18.509 23.0449 18.8215 23.3646C19.1341 23.6843 19.558 23.8638 20 23.8638C20.442 23.8638 20.8659 23.6843 21.1785 23.3646C21.491 23.0449 21.6666 22.6114 21.6666 22.1593V11.9324C21.6666 11.4803 21.491 11.0467 21.1785 10.7271C20.8659 10.4074 20.442 10.2279 20 10.2279C19.558 10.2279 19.1341 10.4074 18.8215 10.7271C18.509 11.0467 18.3334 11.4803 18.3334 11.9324V22.1593Z" fill="currentColor"/>
|
||||||
|
<circle cx="20" cy="29" r="2" fill="currentColor"/>
|
||||||
|
<rect x="18" y="10" width="4" height="14" rx="2" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="text-text dark:text-text-dark font-semibold">{t('thisPageIsEmpty')}</h2>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<></>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!isLoading && listContent.flat().length == 0 && currentPage < 2 && searchKeyword.length == 0 ?
|
||||||
|
<div className="p-5 py-10 flex justify-start items-center gap-3 [&_*]:fill-warning">
|
||||||
|
<svg width="25" height="25" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path opacity="0.5" d="M15.2069 2.85079C15.6886 1.98507 16.3858 1.26524 17.2277 0.76451C18.0696 0.263778 19.0261 0 20 0C20.9739 0 21.9304 0.263778 22.7723 0.76451C23.6142 1.26524 24.3114 1.98507 24.7931 2.85079L39.309 29.3012C39.77 30.1312 40.0083 31.0711 39.9998 32.0257C39.9913 32.9802 39.7363 33.9155 39.2607 34.7368C38.7781 35.5842 38.085 36.286 37.2518 36.7713C36.4186 37.2565 35.4747 37.5078 34.5159 37.4998H5.48407C4.52527 37.5078 3.58143 37.2565 2.74819 36.7713C1.91496 36.286 1.22192 35.5842 0.739307 34.7368C0.263677 33.9155 0.00870576 32.9802 0.000218482 32.0257C-0.0082688 31.0711 0.230027 30.1312 0.690974 29.3012L15.2069 2.85079ZM20 30.6818C20.3296 30.6818 20.6518 30.5819 20.9259 30.3946C21.2 30.2073 21.4136 29.9411 21.5397 29.6296C21.6659 29.3182 21.6989 28.9754 21.6346 28.6448C21.5703 28.3142 21.4115 28.0104 21.1785 27.7721C20.9454 27.5337 20.6484 27.3713 20.3251 27.3056C20.0018 27.2398 19.6668 27.2736 19.3622 27.4026C19.0577 27.5316 18.7974 27.7501 18.6143 28.0304C18.4312 28.3107 18.3334 28.6402 18.3334 28.9773C18.3334 29.4294 18.509 29.8629 18.8215 30.1826C19.1341 30.5022 19.558 30.6818 20 30.6818ZM18.3334 22.1593C18.3334 22.6114 18.509 23.0449 18.8215 23.3646C19.1341 23.6843 19.558 23.8638 20 23.8638C20.442 23.8638 20.8659 23.6843 21.1785 23.3646C21.491 23.0449 21.6666 22.6114 21.6666 22.1593V11.9324C21.6666 11.4803 21.491 11.0467 21.1785 10.7271C20.8659 10.4074 20.442 10.2279 20 10.2279C19.558 10.2279 19.1341 10.4074 18.8215 10.7271C18.509 11.0467 18.3334 11.4803 18.3334 11.9324V22.1593Z" fill="currentColor"/>
|
||||||
|
<circle cx="20" cy="29" r="2" fill="currentColor"/>
|
||||||
|
<rect x="18" y="10" width="4" height="14" rx="2" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="text-text dark:text-text-dark font-semibold">{t('noData')}</h2>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<></>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!isLoading && listContent.flat().length == 0 && searchKeyword.length != 0 ?
|
||||||
|
<div className="p-5 py-10 flex justify-start items-center gap-3 [&_*]:fill-warning">
|
||||||
|
<svg width="25" height="25" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M27.3438 20.3125C27.7656 20.3125 28.125 20.6719 28.125 21.0938C28.125 21.5156 27.7656 21.875 27.3438 21.875C26.9219 21.875 26.5625 21.5156 26.5625 21.0938C26.5625 20.6719 26.9219 20.3125 27.3438 20.3125ZM13.2812 20.3125C13.7031 20.3125 14.0625 20.6719 14.0625 21.0938C14.0625 21.5156 13.7031 21.875 13.2812 21.875C12.8594 21.875 12.5 21.5156 12.5 21.0938C12.5 20.6719 12.8594 20.3125 13.2812 20.3125Z" fill="currentColor"/>
|
||||||
|
<path d="M36.6094 37.3451C36.4094 37.3451 36.2094 37.2686 36.0562 37.117L32.5938 33.6545C32.2891 33.3498 32.2891 32.8545 32.5938 32.5498C32.8984 32.2451 33.3937 32.2451 33.6984 32.5498L37.1609 36.0123C37.4656 36.317 37.4656 36.8123 37.1609 37.117C37.0094 37.2701 36.8094 37.3451 36.6094 37.3451Z" fill="currentColor"/>
|
||||||
|
<path d="M44.5312 48.4375C43.4891 48.4375 42.5078 48.0312 41.7688 47.2937L36.3 41.825C35.5625 41.0859 35.1562 40.1062 35.1562 39.0625C35.1562 38.0188 35.5625 37.0391 36.3 36.3C37.7156 34.8844 40.4078 34.8844 41.8234 36.3L47.2922 41.7688C48.0313 42.5078 48.4375 43.4875 48.4375 44.5312C48.4375 45.575 48.0312 46.5547 47.2937 47.2937C46.5562 48.0328 45.5734 48.4375 44.5312 48.4375ZM39.0625 36.75C38.4266 36.75 37.8234 36.9875 37.4047 37.4063C36.9625 37.8484 36.7188 38.4375 36.7188 39.0625C36.7188 39.6875 36.9625 40.2766 37.4047 40.7203L42.8734 46.1891C43.7594 47.0734 45.3016 47.0734 46.1875 46.1891C46.6312 45.7453 46.875 45.1563 46.875 44.5312C46.875 43.9062 46.6313 43.3172 46.1891 42.8734L40.7203 37.4047C40.3016 36.9875 39.6984 36.75 39.0625 36.75ZM20.3125 39.0625C9.97344 39.0625 1.5625 30.6516 1.5625 20.3125C1.5625 9.97344 9.97344 1.5625 20.3125 1.5625C30.6516 1.5625 39.0625 9.97344 39.0625 20.3125C39.0625 30.6516 30.6516 39.0625 20.3125 39.0625ZM20.3125 3.125C10.8359 3.125 3.125 10.8359 3.125 20.3125C3.125 29.7891 10.8359 37.5 20.3125 37.5C29.7891 37.5 37.5 29.7891 37.5 20.3125C37.5 10.8359 29.7891 3.125 20.3125 3.125Z" fill="currentColor"/>
|
||||||
|
<path d="M21.875 25.0328C21.4438 25.0328 21.0938 24.6828 21.0938 24.2516C21.0938 23.8219 20.7437 23.4703 20.3125 23.4703C19.8813 23.4703 19.5312 23.8219 19.5312 24.2516C19.5312 24.6828 19.1812 25.0328 18.75 25.0328C18.3188 25.0328 17.9688 24.6828 17.9688 24.2516C17.9688 22.9594 19.0203 21.9078 20.3125 21.9078C21.6047 21.9078 22.6562 22.9594 22.6562 24.2516C22.6562 24.6828 22.3062 25.0328 21.875 25.0328ZM27.3438 22.6562C26.4969 22.6562 25.7812 21.9406 25.7812 21.0937C25.7812 20.2469 26.4969 19.5312 27.3438 19.5312C28.1906 19.5312 28.9062 20.2469 28.9062 21.0937C28.9062 21.9406 28.1906 22.6562 27.3438 22.6562ZM13.2812 22.6562C12.4344 22.6562 11.7188 21.9406 11.7188 21.0937C11.7188 20.2469 12.4344 19.5312 13.2812 19.5312C14.1281 19.5312 14.8438 20.2469 14.8438 21.0937C14.8438 21.9406 14.1281 22.6562 13.2812 22.6562Z" fill="currentColor"/>
|
||||||
|
<path d="M36.6094 37.3451C36.4094 37.3451 36.2094 37.2686 36.0562 37.117L32.5938 33.6545C32.2891 33.3498 32.2891 32.8545 32.5938 32.5498C32.8984 32.2451 33.3937 32.2451 33.6984 32.5498L37.1609 36.0123C37.4656 36.317 37.4656 36.8123 37.1609 37.117C37.0094 37.2701 36.8094 37.3451 36.6094 37.3451Z" fill="currentColor"/>
|
||||||
|
<path d="M44.5312 48.4375C43.4891 48.4375 42.5078 48.0312 41.7688 47.2937L36.3 41.825C35.5625 41.0859 35.1562 40.1062 35.1562 39.0625C35.1562 38.0188 35.5625 37.0391 36.3 36.3C37.7156 34.8844 40.4078 34.8844 41.8234 36.3L47.2922 41.7688C48.0313 42.5078 48.4375 43.4875 48.4375 44.5312C48.4375 45.575 48.0312 46.5547 47.2937 47.2937C46.5562 48.0328 45.5734 48.4375 44.5312 48.4375ZM39.0625 36.75C38.4266 36.75 37.8234 36.9875 37.4047 37.4063C36.9625 37.8484 36.7188 38.4375 36.7188 39.0625C36.7188 39.6875 36.9625 40.2766 37.4047 40.7203L42.8734 46.1891C43.7594 47.0734 45.3016 47.0734 46.1875 46.1891C46.6312 45.7453 46.875 45.1563 46.875 44.5312C46.875 43.9062 46.6313 43.3172 46.1891 42.8734L40.7203 37.4047C40.3016 36.9875 39.6984 36.75 39.0625 36.75ZM20.3125 39.0625C9.97344 39.0625 1.5625 30.6516 1.5625 20.3125C1.5625 9.97344 9.97344 1.5625 20.3125 1.5625C30.6516 1.5625 39.0625 9.97344 39.0625 20.3125C39.0625 30.6516 30.6516 39.0625 20.3125 39.0625ZM20.3125 3.125C10.8359 3.125 3.125 10.8359 3.125 20.3125C3.125 29.7891 10.8359 37.5 20.3125 37.5C29.7891 37.5 37.5 29.7891 37.5 20.3125C37.5 10.8359 29.7891 3.125 20.3125 3.125Z" fill="currentColor"/>
|
||||||
|
<path d="M21.875 25.0328C21.4438 25.0328 21.0938 24.6828 21.0938 24.2516C21.0938 23.8219 20.7437 23.4703 20.3125 23.4703C19.8813 23.4703 19.5312 23.8219 19.5312 24.2516C19.5312 24.6828 19.1812 25.0328 18.75 25.0328C18.3188 25.0328 17.9688 24.6828 17.9688 24.2516C17.9688 22.9594 19.0203 21.9078 20.3125 21.9078C21.6047 21.9078 22.6562 22.9594 22.6562 24.2516C22.6562 24.6828 22.3062 25.0328 21.875 25.0328ZM27.3438 22.6562C26.4969 22.6562 25.7812 21.9406 25.7812 21.0937C25.7812 20.2469 26.4969 19.5312 27.3438 19.5312C28.1906 19.5312 28.9062 20.2469 28.9062 21.0937C28.9062 21.9406 28.1906 22.6562 27.3438 22.6562ZM13.2812 22.6562C12.4344 22.6562 11.7188 21.9406 11.7188 21.0937C11.7188 20.2469 12.4344 19.5312 13.2812 19.5312C14.1281 19.5312 14.8438 20.2469 14.8438 21.0937C14.8438 21.9406 14.1281 22.6562 13.2812 22.6562Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="text-text dark:text-text-dark font-semibold">{t('noSearchResults')}</h2>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<></>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// single sender account
|
||||||
|
isLoading ?
|
||||||
|
<div className="p-5 flex justify-start items-center gap-3">
|
||||||
|
<CircularProgress color="secondary" size={24} />
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
listContent.flat().map((v , i) => {
|
||||||
|
return (
|
||||||
|
<div key={i} className="w-full flex gap-3 justify-between items-center p-5 font-semibold h-[70px] border-b-[1px] border-secondary-light/30">
|
||||||
|
<span className="w-full text-start whitespace-nowrap text-ellipsis overflow-hidden lg:block hidden">{i + 1}</span>
|
||||||
|
<span className="w-full text-start whitespace-nowrap text-ellipsis overflow-hidden">{v.name}</span>
|
||||||
|
<span className="w-full text-start flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden">{v.quantity}</span>
|
||||||
|
<span className="w-full text-start flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden">{v.sallerName}</span>
|
||||||
|
<span className="w-full text-start flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden">{v.sallerPhone}</span>
|
||||||
|
<span className="w-full text-start flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden">{appGeneralSettings.currencySymbol} {v.singlePrice}</span>
|
||||||
|
|
||||||
|
<span className="lg:w-full w-[80px] h-full text-start flex justify-start items-center gap-3">
|
||||||
|
<button onClick={async() => {
|
||||||
|
dispatch(setUpdatePopUp({
|
||||||
|
status: true,
|
||||||
|
data: v
|
||||||
|
}))
|
||||||
|
}} className="[&_*]:fill-success [&_*]:dark:fill-success">
|
||||||
|
<svg width="15" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6.36069 6.61884L5.39925 9.91891C5.3176 10.1996 5.39219 10.5041 5.59355 10.7111C5.74073 10.8626 5.93856 10.9446 6.14093 10.9446C6.21527 10.9446 6.29012 10.9337 6.36321 10.9109L9.56961 9.92151C9.69234 9.88363 9.80423 9.81515 9.89471 9.72177L15.7732 3.67103C15.9184 3.52161 16.0001 3.31876 16.0001 3.10761C16.0001 2.89646 15.9184 2.6936 15.7732 2.54419L13.528 0.23346C13.2256 -0.0778202 12.7357 -0.0778202 12.4333 0.23346L6.55499 6.28421C6.46426 6.37759 6.39774 6.49225 6.36069 6.61884ZM7.78786 7.26889L12.9806 1.92371L14.1308 3.10761L8.93806 8.45279L7.29541 8.95966L7.78786 7.26889Z" fill="#4DB33D"/>
|
||||||
|
<path d="M15.2258 7.4375C14.7981 7.4375 14.4516 7.79444 14.4516 8.23438V13.5469C14.4516 14.2794 13.8727 14.875 13.1613 14.875H2.83871C2.12727 14.875 1.54839 14.2794 1.54839 13.5469V2.92188C1.54839 2.18933 2.12727 1.59375 2.83871 1.59375H8C8.42767 1.59375 8.77419 1.23681 8.77419 0.796875C8.77419 0.356936 8.42767 0 8 0H2.83871C1.27344 0 0 1.311 0 2.92188V13.5469C0 15.1577 1.27344 16.4688 2.83871 16.4688H13.1613C14.7266 16.4688 16 15.1577 16 13.5469V8.23438C16 7.79444 15.6535 7.4375 15.2258 7.4375Z" fill="#4DB33D"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button onClick={async() => {
|
||||||
|
await dispatch(delete_({
|
||||||
|
id: v._id
|
||||||
|
}))
|
||||||
|
}} className="[&_*]:fill-error [&_*]:dark:fill-error">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M2.0625 4.625C2.0625 4.27983 2.32153 4.00001 2.64107 4.00001L4.63925 3.99968C5.03626 3.98881 5.38652 3.71612 5.52161 3.31268C5.52516 3.30208 5.52924 3.28899 5.54389 3.24152L5.62999 2.96245C5.68267 2.79134 5.72857 2.64227 5.79281 2.50902C6.04657 1.98262 6.51606 1.61708 7.0586 1.52349C7.19593 1.4998 7.34136 1.4999 7.50832 1.50002H10.1168C10.2838 1.4999 10.4292 1.4998 10.5665 1.52349C11.1091 1.61708 11.5786 1.98262 11.8323 2.50902C11.8966 2.64227 11.9425 2.79134 11.9951 2.96245L12.0812 3.24152C12.0959 3.28899 12.1 3.30208 12.1035 3.31268C12.2387 3.71612 12.6584 3.98915 13.0553 4.00001H14.9839C15.3034 4.00001 15.5625 4.27983 15.5625 4.625C15.5625 4.97018 15.3034 5.25 14.9839 5.25H2.64107C2.32153 5.25 2.0625 4.97018 2.0625 4.625Z" fill="currentColor"/>
|
||||||
|
<path opacity="0.5" d="M8.7051 16.4997H9.29528C11.3259 16.4997 12.3412 16.4997 13.0013 15.8523C13.6615 15.2049 13.7291 14.143 13.8641 12.0191L14.0588 8.95863C14.132 7.80626 14.1687 7.23003 13.8375 6.86489C13.5063 6.49976 12.9471 6.49976 11.8286 6.49976H6.17179C5.05329 6.49976 4.49403 6.49976 4.16286 6.86489C3.83169 7.23003 3.86833 7.80626 3.94162 8.95863L4.13625 12.0191C4.27133 14.143 4.33887 15.2049 4.99901 15.8523C5.65915 16.4997 6.67446 16.4997 8.7051 16.4997Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button onClick={async() => {
|
||||||
|
dispatch(setDetailsPopUp({
|
||||||
|
status: true,
|
||||||
|
data: v ? v : null
|
||||||
|
}))
|
||||||
|
}} className="[&_*]:fill-info [&_*]:dark:fill-info">
|
||||||
|
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M13.3336 13.3333H1.66641V1.66672H6.875V0H1.66641C0.749649 0 0 0.749883 0 1.66672V13.3333C0 14.2501 0.749649 15 1.66641 15H13.3336C14.2504 15 15 14.2501 15 13.3333V8.125H13.3336V13.3333ZM8.75 0V1.66672H12.167L3.74996 10.0833L4.9166 11.25L13.3336 2.83316V6.25H15V0H8.75Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
<ListPagination />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { useDispatch } from "react-redux"
|
||||||
|
import { setAddPopUp } from "@/redux/features/equipments-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
export default function AddNewButton()
|
||||||
|
{
|
||||||
|
// setup needed varibles
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const t = useTranslations('equipments');
|
||||||
|
// handle the open of the pop up
|
||||||
|
function handleClickOpen()
|
||||||
|
{
|
||||||
|
dispatch(setAddPopUp(true))
|
||||||
|
}
|
||||||
|
// return the component ui
|
||||||
|
return (
|
||||||
|
<button onClick={handleClickOpen}
|
||||||
|
className="
|
||||||
|
bg-secondary-light dark:bg-primary text-primary-light dark:text-text
|
||||||
|
fixed z-50 bottom-7 ltr:right-7 rtl:left-7
|
||||||
|
w-auto h-18 p-3 rounded-md drop-shadow-lg
|
||||||
|
flex justify-between items-center gap-3
|
||||||
|
">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 49 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M25 0.5C26.3807 0.5 27.5 1.61929 27.5 3L27.5 46C27.5 47.3807 26.3807 48.5 25 48.5C23.6193 48.5 22.5 47.3807 22.5 46L22.5 3C22.5 1.61929 23.6193 0.5 25 0.5Z" fill="currentColor"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.5 25C48.5 26.3807 47.3807 27.5 46 27.5L3 27.5C1.61929 27.5 0.5 26.3807 0.5 25C0.5 23.6193 1.61929 22.5 3 22.5L46 22.5C47.3807 22.5 48.5 23.6193 48.5 25Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h3>{t('addNewEquipment')}</h3>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
270
webapp/src/components/dashboard/equipments/parts/addPopUp.tsx
Normal file
270
webapp/src/components/dashboard/equipments/parts/addPopUp.tsx
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
import { forwardRef , ReactElement , Ref } from "react"
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { AppDispatch, useAppSelector } from "@/redux/store";
|
||||||
|
import { setAddPopUp , add } from "@/redux/features/equipments-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import Slide from '@mui/material/Slide';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import * as Yup from "yup";
|
||||||
|
|
||||||
|
export default function AddPopUp()
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
// redux
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// intl-18
|
||||||
|
const t = useTranslations('equipments');
|
||||||
|
// mui
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
// get state from redux
|
||||||
|
const addPopUp = useAppSelector((state) => state.equipmentsReducer.value.addPopUp)
|
||||||
|
// setup the popup animation
|
||||||
|
const Transition = forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: ReactElement<any, any>;
|
||||||
|
},
|
||||||
|
ref: Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
// setup the function that close the popup
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch(setAddPopUp(false));
|
||||||
|
};
|
||||||
|
// we dont mount the pop up if we dont need it
|
||||||
|
if(!addPopUp) return <></>
|
||||||
|
// we mount the popup if we needed it
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
open={addPopUp}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
keepMounted
|
||||||
|
onClose={handleClose}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth={'md'}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// This is the title of the pop up
|
||||||
|
}
|
||||||
|
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
|
||||||
|
<h1 className="text-[25px] font-tajawal">{t('addNewEquipment')}</h1>
|
||||||
|
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</DialogTitle>
|
||||||
|
{
|
||||||
|
// Formik form start from here
|
||||||
|
}
|
||||||
|
<Formik
|
||||||
|
// Initial values
|
||||||
|
initialValues={{
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
boughtAt_unix: Math.floor(new Date().getTime() / 1000),
|
||||||
|
quantity: 0,
|
||||||
|
sallerName: '',
|
||||||
|
sallerAddress: '',
|
||||||
|
sallerPhone: '',
|
||||||
|
singlePrice: 0,
|
||||||
|
}}
|
||||||
|
// validat the inputs
|
||||||
|
validationSchema={Yup.object().shape({
|
||||||
|
name: Yup.string().required('thisFieldIsRequired'),
|
||||||
|
description: Yup.string().required('thisFieldIsRequired'),
|
||||||
|
quantity: Yup.number().typeError('mustBeANumber').required('thisFieldIsRequired'),
|
||||||
|
sallerName: Yup.string().required('thisFieldIsRequired'),
|
||||||
|
sallerAddress: Yup.string().required('thisFieldIsRequired'),
|
||||||
|
sallerPhone: Yup.string().required('thisFieldIsRequired'),
|
||||||
|
singlePrice: Yup.number().typeError('mustBeANumber').required('thisFieldIsRequired'),
|
||||||
|
})}
|
||||||
|
// this function handle the submition of the formik form
|
||||||
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
|
await dispatch(add(values))
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
values,
|
||||||
|
setFieldValue,
|
||||||
|
errors,
|
||||||
|
touched
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{
|
||||||
|
// dialog content contain the formik form
|
||||||
|
}
|
||||||
|
<DialogContent>
|
||||||
|
{
|
||||||
|
// dialog text content contain a small description to the current form part
|
||||||
|
}
|
||||||
|
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
|
||||||
|
<h1 className="text-[18px] font-tajawal">{t('equipmentAddText1')}</h1>
|
||||||
|
</DialogContentText>
|
||||||
|
<div className="
|
||||||
|
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80
|
||||||
|
[&_*]:text-text/80 [&_*]:dark:text-text-dark/80
|
||||||
|
[&_*]:placeholder-text/80 [&_*]:dark:placeholder-text-dark/80
|
||||||
|
w-full h-auto mt-[36px] flex flex-col gap-3
|
||||||
|
">
|
||||||
|
<div className="w-full flex lg:flex-row flex-col lg:gap-12 gap-7">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-3">
|
||||||
|
<h3 className="font-bold">{t('equipmentInformations')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.name}
|
||||||
|
placeholder={t('name')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.name && touched?.name && t(errors?.name)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="singlePrice"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.singlePrice == 0 ? '' : values.singlePrice}
|
||||||
|
placeholder={t('singlePrice')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.singlePrice && touched?.singlePrice && t(errors?.singlePrice)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="quantity"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.quantity == 0 ? '' : values.quantity}
|
||||||
|
placeholder={t('quantity')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.quantity && touched?.quantity && t(errors?.quantity)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.description}
|
||||||
|
placeholder={t('description')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.description && touched?.description && t(errors?.description)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-3">
|
||||||
|
<h3 className="font-bold">{t('sallerDetails')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="sallerName"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.sallerName}
|
||||||
|
placeholder={t('sallerName')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.sallerName && touched?.sallerName && t(errors?.sallerName)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="sallerPhone"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.sallerPhone}
|
||||||
|
placeholder={t('sallerPhone')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.sallerPhone && touched?.sallerPhone && t(errors?.sallerPhone)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
id="sallerAddress"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.sallerAddress}
|
||||||
|
placeholder={t('sallerAddress')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.sallerAddress && touched?.sallerAddress && t(errors?.sallerAddress)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
{
|
||||||
|
// dialog actions contain buttons that effect the popup status
|
||||||
|
}
|
||||||
|
<DialogActions sx={{ padding: '16px 24px' }}>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button type="submit" className="btn font-semibold">
|
||||||
|
{
|
||||||
|
isSubmitting ?
|
||||||
|
<CircularProgress color="secondary" size={24} />
|
||||||
|
:
|
||||||
|
t('done')
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,282 @@
|
|||||||
|
import { forwardRef , ReactElement , Ref } from "react"
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { AppDispatch, useAppSelector } from "@/redux/store";
|
||||||
|
import { setDetailsPopUp } from "@/redux/features/equipments-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import Slide from '@mui/material/Slide';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
|
||||||
|
export default function ServiceDetailsPopUp()
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
// redux
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// intl-18
|
||||||
|
const t = useTranslations('equipments');
|
||||||
|
// mui
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
// get state from redux
|
||||||
|
const detailsPopUp = useAppSelector((state) => state.equipmentsReducer.value.detailsPopUp)
|
||||||
|
const detailsPopUpData = useAppSelector((state) => state.equipmentsReducer.value.detailsPopUpData)
|
||||||
|
// setup the popup animation
|
||||||
|
const Transition = forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: ReactElement<any, any>;
|
||||||
|
},
|
||||||
|
ref: Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
// setup the function that close the popup
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch(setDetailsPopUp({
|
||||||
|
status: false,
|
||||||
|
data: null
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
// we dont mount the pop up if we dont need it
|
||||||
|
if(!detailsPopUp) return <></>
|
||||||
|
// convert unix into read able date
|
||||||
|
function UnixToReadAbleDate(unix_time : number)
|
||||||
|
{
|
||||||
|
let dateObj = new Date(unix_time * 1000)
|
||||||
|
// we got the year
|
||||||
|
let year = dateObj.getUTCFullYear(),
|
||||||
|
// get month
|
||||||
|
month = dateObj.getUTCMonth(),
|
||||||
|
// get day
|
||||||
|
day = dateObj.getUTCDay(),
|
||||||
|
// get the hours
|
||||||
|
hours = dateObj.getUTCHours(),
|
||||||
|
// minutes
|
||||||
|
minutes = dateObj.getUTCMinutes(),
|
||||||
|
// seconds
|
||||||
|
seconds = dateObj.getUTCSeconds();
|
||||||
|
// now we format the string
|
||||||
|
let formattedTime = year.toString()+ '-' + month.toString() + '-' + day.toString();
|
||||||
|
return formattedTime;
|
||||||
|
}
|
||||||
|
// we mount the popup if we needed it
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
open={detailsPopUp}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
keepMounted
|
||||||
|
onClose={handleClose}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth={'md'}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// This is the title of the pop up
|
||||||
|
}
|
||||||
|
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
|
||||||
|
<h1 className="text-[25px] font-tajawal">{t('equipmentDetails')}</h1>
|
||||||
|
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</DialogTitle>
|
||||||
|
{
|
||||||
|
// Formik form start from here
|
||||||
|
}
|
||||||
|
<Formik
|
||||||
|
// Initial values
|
||||||
|
initialValues={{
|
||||||
|
name: detailsPopUpData?.name ? detailsPopUpData?.name : '',
|
||||||
|
description: detailsPopUpData?.description ? detailsPopUpData?.description : '',
|
||||||
|
addedAt: detailsPopUpData?.addedAt ? detailsPopUpData?.addedAt : Math.floor(new Date().getTime() / 1000),
|
||||||
|
quantity: detailsPopUpData?.quantity ? detailsPopUpData?.quantity : '',
|
||||||
|
sallerName: detailsPopUpData?.sallerName ? detailsPopUpData?.sallerName : '',
|
||||||
|
sallerAddress: detailsPopUpData?.sallerAddress ? detailsPopUpData?.sallerAddress : '',
|
||||||
|
sallerPhone: detailsPopUpData?.sallerPhone ? detailsPopUpData?.sallerPhone : '',
|
||||||
|
singlePrice: detailsPopUpData?.singlePrice ? detailsPopUpData?.singlePrice : '',
|
||||||
|
}}
|
||||||
|
// this function handle the submition of the formik form
|
||||||
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
values,
|
||||||
|
touched,
|
||||||
|
errors
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{
|
||||||
|
// dialog content contain the formik form
|
||||||
|
}
|
||||||
|
<DialogContent>
|
||||||
|
{
|
||||||
|
// dialog text content contain a small description to the current form part
|
||||||
|
}
|
||||||
|
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
|
||||||
|
<h1 className="text-[18px] font-tajawal">{t('equipmentDetailsText1')}</h1>
|
||||||
|
</DialogContentText>
|
||||||
|
<div className="
|
||||||
|
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80
|
||||||
|
[&_*]:text-text/80 [&_*]:dark:text-text-dark/80
|
||||||
|
[&_*]:placeholder-text/80 [&_*]:dark:placeholder-text-dark/80
|
||||||
|
w-full h-auto mt-[36px] flex flex-col gap-3
|
||||||
|
">
|
||||||
|
<div className="w-full flex lg:flex-row flex-col lg:gap-12 gap-7">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-3">
|
||||||
|
<h3 className="font-bold">{t('equipmentInformations')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
disabled
|
||||||
|
id="name"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.name}
|
||||||
|
placeholder={t('name')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.name && touched?.name && t(errors?.name)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
disabled
|
||||||
|
type="number"
|
||||||
|
id="singlePrice"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.singlePrice == 0 ? '' : values.singlePrice}
|
||||||
|
placeholder={t('singlePrice')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.singlePrice && touched?.singlePrice && t(errors?.singlePrice)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
disabled
|
||||||
|
type="number"
|
||||||
|
id="quantity"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.quantity == 0 ? '' : values.quantity}
|
||||||
|
placeholder={t('quantity')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.quantity && touched?.quantity && t(errors?.quantity)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
disabled
|
||||||
|
id="description"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.description}
|
||||||
|
placeholder={t('description')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.description && touched?.description && t(errors?.description)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-3">
|
||||||
|
<h3 className="font-bold">{t('sallerDetails')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
disabled
|
||||||
|
id="sallerName"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.sallerName}
|
||||||
|
placeholder={t('sallerName')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.sallerName && touched?.sallerName && t(errors?.sallerName)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
disabled
|
||||||
|
id="sallerPhone"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.sallerPhone}
|
||||||
|
placeholder={t('sallerPhone')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.sallerPhone && touched?.sallerPhone && t(errors?.sallerPhone)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
disabled
|
||||||
|
id="sallerAddress"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.sallerAddress}
|
||||||
|
placeholder={t('sallerAddress')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.sallerAddress && touched?.sallerAddress && t(errors?.sallerAddress)}</p>
|
||||||
|
</div>
|
||||||
|
<p className="font-bold">{t('addedAt') + ' : ' + UnixToReadAbleDate(values.addedAt)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
{
|
||||||
|
// dialog actions contain buttons that effect the popup status
|
||||||
|
}
|
||||||
|
<DialogActions sx={{ padding: '16px 24px' }}>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button type="submit" className="btn font-semibold">
|
||||||
|
{t('close')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
import Pagination from '@mui/material/Pagination';
|
||||||
|
import { setCurrentPage } from '@/redux/features/equipments-slice';
|
||||||
|
import { load } from '@/redux/features/equipments-slice'
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { AppDispatch , useAppSelector } from '@/redux/store';
|
||||||
|
|
||||||
|
export default function ListPagination() {
|
||||||
|
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// load needed states
|
||||||
|
const currentPage = useAppSelector((state) => state.equipmentsReducer.value.currentPage)
|
||||||
|
const total = useAppSelector((state) => state.equipmentsReducer.value.total)
|
||||||
|
// handle the pagination
|
||||||
|
const handlePagination = (event: React.ChangeEvent<unknown>, value: number) => {
|
||||||
|
dispatch(load({page:value}));
|
||||||
|
dispatch(setCurrentPage(value));
|
||||||
|
};
|
||||||
|
// show the pagination ui
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{Math.ceil(total / 4) > 1 ? (
|
||||||
|
<div className="flex justify-center items-center py-12">
|
||||||
|
<Pagination
|
||||||
|
sx={{ direction: 'ltr' }}
|
||||||
|
count={Math.ceil(total / 4)}
|
||||||
|
page={currentPage}
|
||||||
|
onChange={handlePagination}
|
||||||
|
variant="outlined"
|
||||||
|
shape="rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
267
webapp/src/components/dashboard/equipments/parts/updatePopUp.tsx
Normal file
267
webapp/src/components/dashboard/equipments/parts/updatePopUp.tsx
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
import { forwardRef , ReactElement , Ref, useEffect } from "react"
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { AppDispatch, useAppSelector } from "@/redux/store";
|
||||||
|
import { setUpdatePopUp , update } from "@/redux/features/equipments-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import Slide from '@mui/material/Slide';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
|
||||||
|
export default function UpdatePopUp()
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
// redux
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// intl-18
|
||||||
|
const t = useTranslations('equipments');
|
||||||
|
// mui
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
// get state from redux
|
||||||
|
const updatePopUp = useAppSelector((state) => state.equipmentsReducer.value.updatePopUp)
|
||||||
|
const updatePopUpData = useAppSelector((state) => state.equipmentsReducer.value.updatePopUpData)
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(updatePopUp , "updatePopUp")
|
||||||
|
}, [updatePopUp])
|
||||||
|
// setup the popup animation
|
||||||
|
const Transition = forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: ReactElement<any, any>;
|
||||||
|
},
|
||||||
|
ref: Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
// setup the function that close the popup
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch(setUpdatePopUp({
|
||||||
|
status: false,
|
||||||
|
data: null
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
// we dont mount the pop up if we dont need it
|
||||||
|
if(!updatePopUp) return <></>
|
||||||
|
// we mount the popup if we needed it
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
open={updatePopUp}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
keepMounted
|
||||||
|
onClose={handleClose}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth={'md'}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// This is the title of the pop up
|
||||||
|
}
|
||||||
|
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
|
||||||
|
<h1 className="text-[25px] font-tajawal">{t('updateEquipment')}</h1>
|
||||||
|
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</DialogTitle>
|
||||||
|
{
|
||||||
|
// Formik form start from here
|
||||||
|
}
|
||||||
|
<Formik
|
||||||
|
// Initial values
|
||||||
|
initialValues={{
|
||||||
|
_id: updatePopUpData?._id ? updatePopUpData?._id : '' ,
|
||||||
|
name: updatePopUpData?.name ? updatePopUpData?.name : '',
|
||||||
|
description: updatePopUpData?.description ? updatePopUpData?.description : '',
|
||||||
|
quantity: updatePopUpData?.quantity ? updatePopUpData?.quantity : '',
|
||||||
|
sallerName: updatePopUpData?.sallerName ? updatePopUpData?.sallerName : '',
|
||||||
|
sallerAddress: updatePopUpData?.sallerAddress ? updatePopUpData?.sallerAddress : '',
|
||||||
|
sallerPhone: updatePopUpData?.sallerPhone ? updatePopUpData?.sallerPhone : '',
|
||||||
|
singlePrice: updatePopUpData?.singlePrice ? updatePopUpData?.singlePrice : '',
|
||||||
|
}}
|
||||||
|
// this function handle the submition of the formik form
|
||||||
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
|
// dispatch the update function
|
||||||
|
await dispatch(update(values))
|
||||||
|
// close the popup
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
values,
|
||||||
|
setFieldValue,
|
||||||
|
errors
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{
|
||||||
|
// dialog content contain the formik form
|
||||||
|
}
|
||||||
|
<DialogContent>
|
||||||
|
{
|
||||||
|
// dialog text content contain a small description to the current form part
|
||||||
|
}
|
||||||
|
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
|
||||||
|
<h1 className="text-[18px] font-tajawal">{t('equipmentUpdateText1')}</h1>
|
||||||
|
</DialogContentText>
|
||||||
|
<div className="
|
||||||
|
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80
|
||||||
|
[&_*]:text-text/80 [&_*]:dark:text-text-dark/80
|
||||||
|
[&_*]:placeholder-text/80 [&_*]:dark:placeholder-text-dark/80
|
||||||
|
w-full h-auto mt-[36px] flex flex-col gap-3
|
||||||
|
">
|
||||||
|
<div className="w-full flex lg:flex-row flex-col lg:gap-12 gap-7">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-3">
|
||||||
|
<h3 className="font-bold">{t('equipmentInformations')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.name}
|
||||||
|
placeholder={t('name')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.name && touched?.name && t(errors?.name)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="singlePrice"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.singlePrice == 0 ? '' : values.singlePrice}
|
||||||
|
placeholder={t('singlePrice')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.singlePrice && touched?.singlePrice && t(errors?.singlePrice)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="quantity"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.quantity == 0 ? '' : values.quantity}
|
||||||
|
placeholder={t('quantity')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.quantity && touched?.quantity && t(errors?.quantity)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.description}
|
||||||
|
placeholder={t('description')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.description && touched?.description && t(errors?.description)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-3">
|
||||||
|
<h3 className="font-bold">{t('sallerDetails')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="sallerName"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.sallerName}
|
||||||
|
placeholder={t('sallerName')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.sallerName && touched?.sallerName && t(errors?.sallerName)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="sallerPhone"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.sallerPhone}
|
||||||
|
placeholder={t('sallerPhone')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.sallerPhone && touched?.sallerPhone && t(errors?.sallerPhone)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
id="sallerAddress"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.sallerAddress}
|
||||||
|
placeholder={t('sallerAddress')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.sallerAddress && touched?.sallerAddress && t(errors?.sallerAddress)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
{
|
||||||
|
// dialog actions contain buttons that effect the popup status
|
||||||
|
}
|
||||||
|
<DialogActions sx={{ padding: '16px 24px' }}>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button type="submit" className="btn font-semibold">
|
||||||
|
{
|
||||||
|
isSubmitting ?
|
||||||
|
<CircularProgress color="secondary" size={24} />
|
||||||
|
:
|
||||||
|
t('save')
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
14
webapp/src/components/dashboard/equipments/popUpsWrapper.tsx
Normal file
14
webapp/src/components/dashboard/equipments/popUpsWrapper.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import AddPopUp from './parts/addPopUp'
|
||||||
|
import DetailsPopUp from './parts/detailsPopUp'
|
||||||
|
import UpdatePopUp from './parts/updatePopUp'
|
||||||
|
|
||||||
|
export default function PopUpWrapper()
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<UpdatePopUp />
|
||||||
|
<AddPopUp />
|
||||||
|
<DetailsPopUp />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
180
webapp/src/components/dashboard/expenses/expensesList.tsx
Normal file
180
webapp/src/components/dashboard/expenses/expensesList.tsx
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { useAppSelector } from '@/redux/store';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import { AppDispatch } from '@/redux/store';
|
||||||
|
import { setSearchKeyword ,load , search , delete_ } from '@/redux/features/expenses-slice'
|
||||||
|
import ListPagination from './parts/expensesListPagination'
|
||||||
|
import { setDetailsPopUp , setUpdatePopUp , setCurrentPage } from '@/redux/features/expenses-slice';
|
||||||
|
|
||||||
|
export default function List()
|
||||||
|
{
|
||||||
|
const t = useTranslations('expenses');
|
||||||
|
const dispatch = useDispatch<AppDispatch>()
|
||||||
|
|
||||||
|
const listContent = useAppSelector((state) => state.expensesReducer.value.listContent)
|
||||||
|
const isLoading = useAppSelector((state) => state.expensesReducer.value.isLoading)
|
||||||
|
const loadedFirstTime = useAppSelector((state) => state.expensesReducer.value.loadedFirstTime)
|
||||||
|
const searchKeyword = useAppSelector((state) => state.expensesReducer.value.searchKeyword)
|
||||||
|
const currentPage = useAppSelector((state) => state.expensesReducer.value.currentPage)
|
||||||
|
const appGeneralSettings = useAppSelector((state) => state.settingsReducer.value.appGeneralSettings)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(loadedFirstTime) return
|
||||||
|
if(isLoading) return
|
||||||
|
async function a()
|
||||||
|
{
|
||||||
|
await dispatch(load({page : 1}))
|
||||||
|
}
|
||||||
|
a()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// convert unix date into human read able format year-month-day
|
||||||
|
function UnixToReadAbleDate(unix_time : number)
|
||||||
|
{
|
||||||
|
let dateObj = new Date(unix_time * 1000)
|
||||||
|
// we got the year
|
||||||
|
let year = dateObj.getUTCFullYear(),
|
||||||
|
// get month
|
||||||
|
month = dateObj.getUTCMonth(),
|
||||||
|
// get day
|
||||||
|
day = dateObj.getUTCDay();
|
||||||
|
// now we format the string
|
||||||
|
let formattedTime = year.toString()+ '-' + month.toString() + '-' + day.toString();
|
||||||
|
return formattedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-3 w-full w-auto">
|
||||||
|
<header className="w-full flex lg:flex-row flex-col lg:justify-between lg:items-center gap-3">
|
||||||
|
<h1 className="font-semibold text-[20px] text-text dark:text-text-dark">{t('expenses')}</h1>
|
||||||
|
<label htmlFor="email" className="relative text-gray-400 focus-within:text-gray-600 block">
|
||||||
|
<svg className="text-secondary dark:text-primary/50 cursor-pointer w-[15px] h-[15px] absolute top-1/2 transform -translate-y-1/2 rtl:left-3 ltr:right-3" width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M18.4479 16.9688L14.9063 13.4375C16.0489 11.9817 16.669 10.184 16.6667 8.33334C16.6667 6.68516 16.1779 5.074 15.2623 3.70359C14.3466 2.33318 13.0451 1.26507 11.5224 0.634341C9.99965 0.00361068 8.32409 -0.161417 6.70759 0.160126C5.09108 0.48167 3.60622 1.27534 2.44078 2.44078C1.27534 3.60622 0.48167 5.09108 0.160126 6.70759C-0.161417 8.32409 0.00361068 9.99965 0.634341 11.5224C1.26507 13.0451 2.33318 14.3466 3.70359 15.2623C5.074 16.1779 6.68516 16.6667 8.33334 16.6667C10.184 16.669 11.9817 16.0489 13.4375 14.9063L16.9688 18.4479C17.0656 18.5456 17.1808 18.6231 17.3077 18.6759C17.4347 18.7288 17.5708 18.756 17.7083 18.756C17.8459 18.756 17.982 18.7288 18.1089 18.6759C18.2359 18.6231 18.3511 18.5456 18.4479 18.4479C18.5456 18.3511 18.6231 18.2359 18.6759 18.1089C18.7288 17.982 18.756 17.8459 18.756 17.7083C18.756 17.5708 18.7288 17.4347 18.6759 17.3077C18.6231 17.1808 18.5456 17.0656 18.4479 16.9688ZM2.08334 8.33334C2.08334 7.0972 2.44989 5.88883 3.13665 4.86102C3.82341 3.83322 4.79953 3.03214 5.94157 2.55909C7.0836 2.08604 8.34027 1.96227 9.55265 2.20343C10.765 2.44459 11.8787 3.03984 12.7528 3.91392C13.6268 4.788 14.2221 5.90164 14.4632 7.11402C14.7044 8.3264 14.5806 9.58307 14.1076 10.7251C13.6345 11.8671 12.8335 12.8433 11.8057 13.53C10.7778 14.2168 9.56947 14.5833 8.33334 14.5833C6.67573 14.5833 5.08602 13.9249 3.91392 12.7528C2.74182 11.5807 2.08334 9.99094 2.08334 8.33334Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<input onChange={(async(e) => {
|
||||||
|
if(e.target.value.length == 0)
|
||||||
|
{
|
||||||
|
dispatch(setCurrentPage(1));
|
||||||
|
}
|
||||||
|
dispatch(setSearchKeyword(e.target.value))
|
||||||
|
await dispatch(search({
|
||||||
|
searchKeyword: e.target.value
|
||||||
|
}))
|
||||||
|
})}
|
||||||
|
value={searchKeyword}
|
||||||
|
className="lg:w-[300px] w-full text-text dark:text-text-dark rounded-[5px] px-3 h-[50px] border-[1px] !border-secondary-light bg-primary-dark/5 placeholder:font-semibold" type="text" placeholder={t('searchForExpenses')}></input>
|
||||||
|
</label>
|
||||||
|
</header>
|
||||||
|
<div className="panel !p-0 mb-7 dark:bg-primary-dark">
|
||||||
|
<header className="w-full lg:flex gap-3 hidden justify-between items-center p-5 opacity-50 font-semibold border-b-[1px] border-secondary-light">
|
||||||
|
<span className="w-full text-start">#</span>
|
||||||
|
<span className="w-full text-start">{t('name')}</span>
|
||||||
|
<span className="w-full text-start">{t('amount')}</span>
|
||||||
|
<span className="w-full text-start">{t('addedAt')}</span>
|
||||||
|
<span className="w-full text-start">{t('actions')}</span>
|
||||||
|
</header>
|
||||||
|
<main className="flex flex-col justify-center items-center">
|
||||||
|
{
|
||||||
|
!isLoading && listContent.flat().length == 0 && currentPage >= 2 && searchKeyword.length == 0 ?
|
||||||
|
<div className="p-5 py-10 flex justify-start items-center gap-3 [&_*]:fill-warning">
|
||||||
|
<svg width="25" height="25" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path opacity="0.5" d="M15.2069 2.85079C15.6886 1.98507 16.3858 1.26524 17.2277 0.76451C18.0696 0.263778 19.0261 0 20 0C20.9739 0 21.9304 0.263778 22.7723 0.76451C23.6142 1.26524 24.3114 1.98507 24.7931 2.85079L39.309 29.3012C39.77 30.1312 40.0083 31.0711 39.9998 32.0257C39.9913 32.9802 39.7363 33.9155 39.2607 34.7368C38.7781 35.5842 38.085 36.286 37.2518 36.7713C36.4186 37.2565 35.4747 37.5078 34.5159 37.4998H5.48407C4.52527 37.5078 3.58143 37.2565 2.74819 36.7713C1.91496 36.286 1.22192 35.5842 0.739307 34.7368C0.263677 33.9155 0.00870576 32.9802 0.000218482 32.0257C-0.0082688 31.0711 0.230027 30.1312 0.690974 29.3012L15.2069 2.85079ZM20 30.6818C20.3296 30.6818 20.6518 30.5819 20.9259 30.3946C21.2 30.2073 21.4136 29.9411 21.5397 29.6296C21.6659 29.3182 21.6989 28.9754 21.6346 28.6448C21.5703 28.3142 21.4115 28.0104 21.1785 27.7721C20.9454 27.5337 20.6484 27.3713 20.3251 27.3056C20.0018 27.2398 19.6668 27.2736 19.3622 27.4026C19.0577 27.5316 18.7974 27.7501 18.6143 28.0304C18.4312 28.3107 18.3334 28.6402 18.3334 28.9773C18.3334 29.4294 18.509 29.8629 18.8215 30.1826C19.1341 30.5022 19.558 30.6818 20 30.6818ZM18.3334 22.1593C18.3334 22.6114 18.509 23.0449 18.8215 23.3646C19.1341 23.6843 19.558 23.8638 20 23.8638C20.442 23.8638 20.8659 23.6843 21.1785 23.3646C21.491 23.0449 21.6666 22.6114 21.6666 22.1593V11.9324C21.6666 11.4803 21.491 11.0467 21.1785 10.7271C20.8659 10.4074 20.442 10.2279 20 10.2279C19.558 10.2279 19.1341 10.4074 18.8215 10.7271C18.509 11.0467 18.3334 11.4803 18.3334 11.9324V22.1593Z" fill="currentColor"/>
|
||||||
|
<circle cx="20" cy="29" r="2" fill="currentColor"/>
|
||||||
|
<rect x="18" y="10" width="4" height="14" rx="2" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="text-text dark:text-text-dark font-semibold">{t('thisPageIsEmpty')}</h2>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<></>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!isLoading && listContent.flat().length == 0 && currentPage < 2 && searchKeyword.length == 0 ?
|
||||||
|
<div className="p-5 py-10 flex justify-start items-center gap-3 [&_*]:fill-warning">
|
||||||
|
<svg width="25" height="25" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path opacity="0.5" d="M15.2069 2.85079C15.6886 1.98507 16.3858 1.26524 17.2277 0.76451C18.0696 0.263778 19.0261 0 20 0C20.9739 0 21.9304 0.263778 22.7723 0.76451C23.6142 1.26524 24.3114 1.98507 24.7931 2.85079L39.309 29.3012C39.77 30.1312 40.0083 31.0711 39.9998 32.0257C39.9913 32.9802 39.7363 33.9155 39.2607 34.7368C38.7781 35.5842 38.085 36.286 37.2518 36.7713C36.4186 37.2565 35.4747 37.5078 34.5159 37.4998H5.48407C4.52527 37.5078 3.58143 37.2565 2.74819 36.7713C1.91496 36.286 1.22192 35.5842 0.739307 34.7368C0.263677 33.9155 0.00870576 32.9802 0.000218482 32.0257C-0.0082688 31.0711 0.230027 30.1312 0.690974 29.3012L15.2069 2.85079ZM20 30.6818C20.3296 30.6818 20.6518 30.5819 20.9259 30.3946C21.2 30.2073 21.4136 29.9411 21.5397 29.6296C21.6659 29.3182 21.6989 28.9754 21.6346 28.6448C21.5703 28.3142 21.4115 28.0104 21.1785 27.7721C20.9454 27.5337 20.6484 27.3713 20.3251 27.3056C20.0018 27.2398 19.6668 27.2736 19.3622 27.4026C19.0577 27.5316 18.7974 27.7501 18.6143 28.0304C18.4312 28.3107 18.3334 28.6402 18.3334 28.9773C18.3334 29.4294 18.509 29.8629 18.8215 30.1826C19.1341 30.5022 19.558 30.6818 20 30.6818ZM18.3334 22.1593C18.3334 22.6114 18.509 23.0449 18.8215 23.3646C19.1341 23.6843 19.558 23.8638 20 23.8638C20.442 23.8638 20.8659 23.6843 21.1785 23.3646C21.491 23.0449 21.6666 22.6114 21.6666 22.1593V11.9324C21.6666 11.4803 21.491 11.0467 21.1785 10.7271C20.8659 10.4074 20.442 10.2279 20 10.2279C19.558 10.2279 19.1341 10.4074 18.8215 10.7271C18.509 11.0467 18.3334 11.4803 18.3334 11.9324V22.1593Z" fill="currentColor"/>
|
||||||
|
<circle cx="20" cy="29" r="2" fill="currentColor"/>
|
||||||
|
<rect x="18" y="10" width="4" height="14" rx="2" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="text-text dark:text-text-dark font-semibold">{t('noData')}</h2>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<></>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!isLoading && listContent.flat().length == 0 && searchKeyword.length != 0 ?
|
||||||
|
<div className="p-5 py-10 flex justify-start items-center gap-3 [&_*]:fill-warning">
|
||||||
|
<svg width="25" height="25" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M27.3438 20.3125C27.7656 20.3125 28.125 20.6719 28.125 21.0938C28.125 21.5156 27.7656 21.875 27.3438 21.875C26.9219 21.875 26.5625 21.5156 26.5625 21.0938C26.5625 20.6719 26.9219 20.3125 27.3438 20.3125ZM13.2812 20.3125C13.7031 20.3125 14.0625 20.6719 14.0625 21.0938C14.0625 21.5156 13.7031 21.875 13.2812 21.875C12.8594 21.875 12.5 21.5156 12.5 21.0938C12.5 20.6719 12.8594 20.3125 13.2812 20.3125Z" fill="currentColor"/>
|
||||||
|
<path d="M36.6094 37.3451C36.4094 37.3451 36.2094 37.2686 36.0562 37.117L32.5938 33.6545C32.2891 33.3498 32.2891 32.8545 32.5938 32.5498C32.8984 32.2451 33.3937 32.2451 33.6984 32.5498L37.1609 36.0123C37.4656 36.317 37.4656 36.8123 37.1609 37.117C37.0094 37.2701 36.8094 37.3451 36.6094 37.3451Z" fill="currentColor"/>
|
||||||
|
<path d="M44.5312 48.4375C43.4891 48.4375 42.5078 48.0312 41.7688 47.2937L36.3 41.825C35.5625 41.0859 35.1562 40.1062 35.1562 39.0625C35.1562 38.0188 35.5625 37.0391 36.3 36.3C37.7156 34.8844 40.4078 34.8844 41.8234 36.3L47.2922 41.7688C48.0313 42.5078 48.4375 43.4875 48.4375 44.5312C48.4375 45.575 48.0312 46.5547 47.2937 47.2937C46.5562 48.0328 45.5734 48.4375 44.5312 48.4375ZM39.0625 36.75C38.4266 36.75 37.8234 36.9875 37.4047 37.4063C36.9625 37.8484 36.7188 38.4375 36.7188 39.0625C36.7188 39.6875 36.9625 40.2766 37.4047 40.7203L42.8734 46.1891C43.7594 47.0734 45.3016 47.0734 46.1875 46.1891C46.6312 45.7453 46.875 45.1563 46.875 44.5312C46.875 43.9062 46.6313 43.3172 46.1891 42.8734L40.7203 37.4047C40.3016 36.9875 39.6984 36.75 39.0625 36.75ZM20.3125 39.0625C9.97344 39.0625 1.5625 30.6516 1.5625 20.3125C1.5625 9.97344 9.97344 1.5625 20.3125 1.5625C30.6516 1.5625 39.0625 9.97344 39.0625 20.3125C39.0625 30.6516 30.6516 39.0625 20.3125 39.0625ZM20.3125 3.125C10.8359 3.125 3.125 10.8359 3.125 20.3125C3.125 29.7891 10.8359 37.5 20.3125 37.5C29.7891 37.5 37.5 29.7891 37.5 20.3125C37.5 10.8359 29.7891 3.125 20.3125 3.125Z" fill="currentColor"/>
|
||||||
|
<path d="M21.875 25.0328C21.4438 25.0328 21.0938 24.6828 21.0938 24.2516C21.0938 23.8219 20.7437 23.4703 20.3125 23.4703C19.8813 23.4703 19.5312 23.8219 19.5312 24.2516C19.5312 24.6828 19.1812 25.0328 18.75 25.0328C18.3188 25.0328 17.9688 24.6828 17.9688 24.2516C17.9688 22.9594 19.0203 21.9078 20.3125 21.9078C21.6047 21.9078 22.6562 22.9594 22.6562 24.2516C22.6562 24.6828 22.3062 25.0328 21.875 25.0328ZM27.3438 22.6562C26.4969 22.6562 25.7812 21.9406 25.7812 21.0937C25.7812 20.2469 26.4969 19.5312 27.3438 19.5312C28.1906 19.5312 28.9062 20.2469 28.9062 21.0937C28.9062 21.9406 28.1906 22.6562 27.3438 22.6562ZM13.2812 22.6562C12.4344 22.6562 11.7188 21.9406 11.7188 21.0937C11.7188 20.2469 12.4344 19.5312 13.2812 19.5312C14.1281 19.5312 14.8438 20.2469 14.8438 21.0937C14.8438 21.9406 14.1281 22.6562 13.2812 22.6562Z" fill="currentColor"/>
|
||||||
|
<path d="M36.6094 37.3451C36.4094 37.3451 36.2094 37.2686 36.0562 37.117L32.5938 33.6545C32.2891 33.3498 32.2891 32.8545 32.5938 32.5498C32.8984 32.2451 33.3937 32.2451 33.6984 32.5498L37.1609 36.0123C37.4656 36.317 37.4656 36.8123 37.1609 37.117C37.0094 37.2701 36.8094 37.3451 36.6094 37.3451Z" fill="currentColor"/>
|
||||||
|
<path d="M44.5312 48.4375C43.4891 48.4375 42.5078 48.0312 41.7688 47.2937L36.3 41.825C35.5625 41.0859 35.1562 40.1062 35.1562 39.0625C35.1562 38.0188 35.5625 37.0391 36.3 36.3C37.7156 34.8844 40.4078 34.8844 41.8234 36.3L47.2922 41.7688C48.0313 42.5078 48.4375 43.4875 48.4375 44.5312C48.4375 45.575 48.0312 46.5547 47.2937 47.2937C46.5562 48.0328 45.5734 48.4375 44.5312 48.4375ZM39.0625 36.75C38.4266 36.75 37.8234 36.9875 37.4047 37.4063C36.9625 37.8484 36.7188 38.4375 36.7188 39.0625C36.7188 39.6875 36.9625 40.2766 37.4047 40.7203L42.8734 46.1891C43.7594 47.0734 45.3016 47.0734 46.1875 46.1891C46.6312 45.7453 46.875 45.1563 46.875 44.5312C46.875 43.9062 46.6313 43.3172 46.1891 42.8734L40.7203 37.4047C40.3016 36.9875 39.6984 36.75 39.0625 36.75ZM20.3125 39.0625C9.97344 39.0625 1.5625 30.6516 1.5625 20.3125C1.5625 9.97344 9.97344 1.5625 20.3125 1.5625C30.6516 1.5625 39.0625 9.97344 39.0625 20.3125C39.0625 30.6516 30.6516 39.0625 20.3125 39.0625ZM20.3125 3.125C10.8359 3.125 3.125 10.8359 3.125 20.3125C3.125 29.7891 10.8359 37.5 20.3125 37.5C29.7891 37.5 37.5 29.7891 37.5 20.3125C37.5 10.8359 29.7891 3.125 20.3125 3.125Z" fill="currentColor"/>
|
||||||
|
<path d="M21.875 25.0328C21.4438 25.0328 21.0938 24.6828 21.0938 24.2516C21.0938 23.8219 20.7437 23.4703 20.3125 23.4703C19.8813 23.4703 19.5312 23.8219 19.5312 24.2516C19.5312 24.6828 19.1812 25.0328 18.75 25.0328C18.3188 25.0328 17.9688 24.6828 17.9688 24.2516C17.9688 22.9594 19.0203 21.9078 20.3125 21.9078C21.6047 21.9078 22.6562 22.9594 22.6562 24.2516C22.6562 24.6828 22.3062 25.0328 21.875 25.0328ZM27.3438 22.6562C26.4969 22.6562 25.7812 21.9406 25.7812 21.0937C25.7812 20.2469 26.4969 19.5312 27.3438 19.5312C28.1906 19.5312 28.9062 20.2469 28.9062 21.0937C28.9062 21.9406 28.1906 22.6562 27.3438 22.6562ZM13.2812 22.6562C12.4344 22.6562 11.7188 21.9406 11.7188 21.0937C11.7188 20.2469 12.4344 19.5312 13.2812 19.5312C14.1281 19.5312 14.8438 20.2469 14.8438 21.0937C14.8438 21.9406 14.1281 22.6562 13.2812 22.6562Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="text-text dark:text-text-dark font-semibold">{t('noSearchResults')}</h2>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<></>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// single sender account
|
||||||
|
isLoading ?
|
||||||
|
<div className="p-5 flex justify-start items-center gap-3">
|
||||||
|
<CircularProgress color="secondary" size={24} />
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
listContent.flat().map((v , i) => {
|
||||||
|
return (
|
||||||
|
<div key={i} className="w-full flex gap-3 justify-between items-center p-5 font-semibold h-[70px] border-b-[1px] border-secondary-light/30">
|
||||||
|
<span className="w-full text-start whitespace-nowrap text-ellipsis overflow-hidden lg:block hidden">{i + 1}</span>
|
||||||
|
<span className="w-full text-start whitespace-nowrap text-ellipsis overflow-hidden">{v.name}</span>
|
||||||
|
<span className="w-full text-start flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden">{appGeneralSettings.currencySymbol} {v.amount}</span>
|
||||||
|
<span className="w-full text-start flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden">{UnixToReadAbleDate(v?.addedAt ? v?.addedAt : 0)}</span>
|
||||||
|
<span className="lg:w-full w-[80px] h-full text-start flex justify-start items-center gap-3">
|
||||||
|
<button onClick={async() => {
|
||||||
|
dispatch(setUpdatePopUp({
|
||||||
|
status: true,
|
||||||
|
data: v
|
||||||
|
}))
|
||||||
|
}} className="[&_*]:fill-success [&_*]:dark:fill-success">
|
||||||
|
<svg width="15" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6.36069 6.61884L5.39925 9.91891C5.3176 10.1996 5.39219 10.5041 5.59355 10.7111C5.74073 10.8626 5.93856 10.9446 6.14093 10.9446C6.21527 10.9446 6.29012 10.9337 6.36321 10.9109L9.56961 9.92151C9.69234 9.88363 9.80423 9.81515 9.89471 9.72177L15.7732 3.67103C15.9184 3.52161 16.0001 3.31876 16.0001 3.10761C16.0001 2.89646 15.9184 2.6936 15.7732 2.54419L13.528 0.23346C13.2256 -0.0778202 12.7357 -0.0778202 12.4333 0.23346L6.55499 6.28421C6.46426 6.37759 6.39774 6.49225 6.36069 6.61884ZM7.78786 7.26889L12.9806 1.92371L14.1308 3.10761L8.93806 8.45279L7.29541 8.95966L7.78786 7.26889Z" fill="#4DB33D"/>
|
||||||
|
<path d="M15.2258 7.4375C14.7981 7.4375 14.4516 7.79444 14.4516 8.23438V13.5469C14.4516 14.2794 13.8727 14.875 13.1613 14.875H2.83871C2.12727 14.875 1.54839 14.2794 1.54839 13.5469V2.92188C1.54839 2.18933 2.12727 1.59375 2.83871 1.59375H8C8.42767 1.59375 8.77419 1.23681 8.77419 0.796875C8.77419 0.356936 8.42767 0 8 0H2.83871C1.27344 0 0 1.311 0 2.92188V13.5469C0 15.1577 1.27344 16.4688 2.83871 16.4688H13.1613C14.7266 16.4688 16 15.1577 16 13.5469V8.23438C16 7.79444 15.6535 7.4375 15.2258 7.4375Z" fill="#4DB33D"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button onClick={async() => {
|
||||||
|
await dispatch(delete_({
|
||||||
|
id: v._id
|
||||||
|
}))
|
||||||
|
}} className="[&_*]:fill-error [&_*]:dark:fill-error">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M2.0625 4.625C2.0625 4.27983 2.32153 4.00001 2.64107 4.00001L4.63925 3.99968C5.03626 3.98881 5.38652 3.71612 5.52161 3.31268C5.52516 3.30208 5.52924 3.28899 5.54389 3.24152L5.62999 2.96245C5.68267 2.79134 5.72857 2.64227 5.79281 2.50902C6.04657 1.98262 6.51606 1.61708 7.0586 1.52349C7.19593 1.4998 7.34136 1.4999 7.50832 1.50002H10.1168C10.2838 1.4999 10.4292 1.4998 10.5665 1.52349C11.1091 1.61708 11.5786 1.98262 11.8323 2.50902C11.8966 2.64227 11.9425 2.79134 11.9951 2.96245L12.0812 3.24152C12.0959 3.28899 12.1 3.30208 12.1035 3.31268C12.2387 3.71612 12.6584 3.98915 13.0553 4.00001H14.9839C15.3034 4.00001 15.5625 4.27983 15.5625 4.625C15.5625 4.97018 15.3034 5.25 14.9839 5.25H2.64107C2.32153 5.25 2.0625 4.97018 2.0625 4.625Z" fill="currentColor"/>
|
||||||
|
<path opacity="0.5" d="M8.7051 16.4997H9.29528C11.3259 16.4997 12.3412 16.4997 13.0013 15.8523C13.6615 15.2049 13.7291 14.143 13.8641 12.0191L14.0588 8.95863C14.132 7.80626 14.1687 7.23003 13.8375 6.86489C13.5063 6.49976 12.9471 6.49976 11.8286 6.49976H6.17179C5.05329 6.49976 4.49403 6.49976 4.16286 6.86489C3.83169 7.23003 3.86833 7.80626 3.94162 8.95863L4.13625 12.0191C4.27133 14.143 4.33887 15.2049 4.99901 15.8523C5.65915 16.4997 6.67446 16.4997 8.7051 16.4997Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button onClick={async() => {
|
||||||
|
dispatch(setDetailsPopUp({
|
||||||
|
status: true,
|
||||||
|
data: v ? v : null
|
||||||
|
}))
|
||||||
|
}} className="[&_*]:fill-info [&_*]:dark:fill-info">
|
||||||
|
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M13.3336 13.3333H1.66641V1.66672H6.875V0H1.66641C0.749649 0 0 0.749883 0 1.66672V13.3333C0 14.2501 0.749649 15 1.66641 15H13.3336C14.2504 15 15 14.2501 15 13.3333V8.125H13.3336V13.3333ZM8.75 0V1.66672H12.167L3.74996 10.0833L4.9166 11.25L13.3336 2.83316V6.25H15V0H8.75Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
<ListPagination />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { useDispatch } from "react-redux"
|
||||||
|
import { setAddPopUp } from "@/redux/features/expenses-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
export default function AddNewButton()
|
||||||
|
{
|
||||||
|
// setup needed varibles
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const t = useTranslations('expenses');
|
||||||
|
// handle the open of the pop up
|
||||||
|
function handleClickOpen()
|
||||||
|
{
|
||||||
|
dispatch(setAddPopUp(true))
|
||||||
|
}
|
||||||
|
// return the component ui
|
||||||
|
return (
|
||||||
|
<button onClick={handleClickOpen}
|
||||||
|
className="
|
||||||
|
bg-secondary-light dark:bg-primary text-primary-light dark:text-text
|
||||||
|
fixed z-50 bottom-7 ltr:right-7 rtl:left-7
|
||||||
|
w-auto h-18 p-3 rounded-md drop-shadow-lg
|
||||||
|
flex justify-between items-center gap-3
|
||||||
|
">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 49 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M25 0.5C26.3807 0.5 27.5 1.61929 27.5 3L27.5 46C27.5 47.3807 26.3807 48.5 25 48.5C23.6193 48.5 22.5 47.3807 22.5 46L22.5 3C22.5 1.61929 23.6193 0.5 25 0.5Z" fill="currentColor"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.5 25C48.5 26.3807 47.3807 27.5 46 27.5L3 27.5C1.61929 27.5 0.5 26.3807 0.5 25C0.5 23.6193 1.61929 22.5 3 22.5L46 22.5C47.3807 22.5 48.5 23.6193 48.5 25Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h3>{t('addNewExpense')}</h3>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
196
webapp/src/components/dashboard/expenses/parts/addPopUp.tsx
Normal file
196
webapp/src/components/dashboard/expenses/parts/addPopUp.tsx
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
import { forwardRef , ReactElement , Ref } from "react"
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { AppDispatch, useAppSelector } from "@/redux/store";
|
||||||
|
import { setAddPopUp , add } from "@/redux/features/expenses-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import Slide from '@mui/material/Slide';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import * as Yup from "yup";
|
||||||
|
|
||||||
|
export default function AddPopUp()
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
// redux
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// intl-18
|
||||||
|
const t = useTranslations('expenses');
|
||||||
|
// mui
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
// get state from redux
|
||||||
|
const addPopUp = useAppSelector((state) => state.expensesReducer.value.addPopUp)
|
||||||
|
// setup the popup animation
|
||||||
|
const Transition = forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: ReactElement<any, any>;
|
||||||
|
},
|
||||||
|
ref: Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
// setup the function that close the popup
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch(setAddPopUp(false));
|
||||||
|
};
|
||||||
|
// we dont mount the pop up if we dont need it
|
||||||
|
if(!addPopUp) return <></>
|
||||||
|
// we mount the popup if we needed it
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
open={addPopUp}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
keepMounted
|
||||||
|
onClose={handleClose}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth={'sm'}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// This is the title of the pop up
|
||||||
|
}
|
||||||
|
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
|
||||||
|
<h1 className="text-[25px] font-tajawal">{t('addNewExpense')}</h1>
|
||||||
|
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</DialogTitle>
|
||||||
|
{
|
||||||
|
// Formik form start from here
|
||||||
|
}
|
||||||
|
<Formik
|
||||||
|
// Initial values
|
||||||
|
initialValues={{
|
||||||
|
name : '',
|
||||||
|
description : '',
|
||||||
|
amount: 0,
|
||||||
|
}}
|
||||||
|
// validat the inputs
|
||||||
|
validationSchema={Yup.object().shape({
|
||||||
|
name: Yup.string().required('thisFieldIsRequired'),
|
||||||
|
description: Yup.string().required('thisFieldIsRequired'),
|
||||||
|
amount: Yup.number().typeError('mustBeANumber').required('thisFieldIsRequired'),
|
||||||
|
})}
|
||||||
|
// this function handle the submition of the formik form
|
||||||
|
onSubmit={async (values) => {
|
||||||
|
await dispatch(add(values))
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
values,
|
||||||
|
errors,
|
||||||
|
touched
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{
|
||||||
|
// dialog content contain the formik form
|
||||||
|
}
|
||||||
|
<DialogContent>
|
||||||
|
{
|
||||||
|
// dialog text content contain a small description to the current form part
|
||||||
|
}
|
||||||
|
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
|
||||||
|
<h1 className="text-[18px] font-tajawal">{t('expenseAddText1')}</h1>
|
||||||
|
</DialogContentText>
|
||||||
|
<div className="
|
||||||
|
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80
|
||||||
|
[&_*]:text-text/80 [&_*]:dark:text-text-dark/80
|
||||||
|
[&_*]:placeholder-text/80 [&_*]:dark:placeholder-text-dark/80
|
||||||
|
w-full h-auto mt-[36px] flex flex-col gap-3
|
||||||
|
">
|
||||||
|
<div className="w-full flex lg:flex-row flex-col lg:gap-12 gap-7">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('expenseInformations')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.name}
|
||||||
|
placeholder={t('name')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.name && touched?.name && t(errors?.name)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.description}
|
||||||
|
placeholder={t('description')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.description && touched?.description && t(errors?.description)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="amount"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.amount == 0 ? '' : values.amount}
|
||||||
|
placeholder={t('amount')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.amount && touched?.amount && t(errors?.amount)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
{
|
||||||
|
// dialog actions contain buttons that effect the popup status
|
||||||
|
}
|
||||||
|
<DialogActions sx={{ padding: '16px 24px' }}>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button type="submit" className="btn font-semibold">
|
||||||
|
{
|
||||||
|
isSubmitting ?
|
||||||
|
<CircularProgress color="secondary" size={24} />
|
||||||
|
:
|
||||||
|
t('done')
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
184
webapp/src/components/dashboard/expenses/parts/detailsPopUp.tsx
Normal file
184
webapp/src/components/dashboard/expenses/parts/detailsPopUp.tsx
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
import { forwardRef , ReactElement , Ref } from "react"
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { AppDispatch, useAppSelector } from "@/redux/store";
|
||||||
|
import { setDetailsPopUp } from "@/redux/features/expenses-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import Slide from '@mui/material/Slide';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
|
||||||
|
export default function ServiceDetailsPopUp()
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
// redux
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// intl-18
|
||||||
|
const t = useTranslations('expenses');
|
||||||
|
// mui
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
// get state from redux
|
||||||
|
const detailsPopUp = useAppSelector((state) => state.expensesReducer.value.detailsPopUp)
|
||||||
|
const detailsPopUpData = useAppSelector((state) => state.expensesReducer.value.detailsPopUpData)
|
||||||
|
// setup the popup animation
|
||||||
|
const Transition = forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: ReactElement<any, any>;
|
||||||
|
},
|
||||||
|
ref: Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
// setup the function that close the popup
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch(setDetailsPopUp({
|
||||||
|
status: false,
|
||||||
|
data: null
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
// we dont mount the pop up if we dont need it
|
||||||
|
if(!detailsPopUp) return <></>
|
||||||
|
// we mount the popup if we needed it
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
open={detailsPopUp}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
keepMounted
|
||||||
|
onClose={handleClose}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth={'sm'}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// This is the title of the pop up
|
||||||
|
}
|
||||||
|
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
|
||||||
|
<h1 className="text-[25px] font-tajawal">{t('expenseDetails')}</h1>
|
||||||
|
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</DialogTitle>
|
||||||
|
{
|
||||||
|
// Formik form start from here
|
||||||
|
}
|
||||||
|
<Formik
|
||||||
|
// Initial values
|
||||||
|
initialValues={{
|
||||||
|
name: detailsPopUpData?.name,
|
||||||
|
description: detailsPopUpData?.description,
|
||||||
|
amount: detailsPopUpData?.amount,
|
||||||
|
addedAt: detailsPopUpData?.addedAt,
|
||||||
|
}}
|
||||||
|
// this function handle the submition of the formik form
|
||||||
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
values,
|
||||||
|
errors
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{
|
||||||
|
// dialog content contain the formik form
|
||||||
|
}
|
||||||
|
<DialogContent>
|
||||||
|
{
|
||||||
|
// dialog text content contain a small description to the current form part
|
||||||
|
}
|
||||||
|
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
|
||||||
|
<h1 className="text-[18px] font-tajawal">{t('expenseDetailsText1')}</h1>
|
||||||
|
</DialogContentText>
|
||||||
|
<div className="
|
||||||
|
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80
|
||||||
|
[&_*]:text-text/80 [&_*]:dark:text-text-dark/80
|
||||||
|
[&_*]:placeholder-text/80 [&_*]:dark:placeholder-text-dark/80
|
||||||
|
w-full h-auto mt-[36px] flex flex-col gap-3
|
||||||
|
">
|
||||||
|
<div className="w-full flex lg:flex-row flex-col lg:gap-12 gap-7">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('expenseInformations')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.name}
|
||||||
|
placeholder={t('name')}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.description}
|
||||||
|
placeholder={t('description')}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
disabled
|
||||||
|
id="price"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.amount}
|
||||||
|
placeholder={t('amount')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
{
|
||||||
|
// dialog actions contain buttons that effect the popup status
|
||||||
|
}
|
||||||
|
<DialogActions sx={{ padding: '16px 24px' }}>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button type="submit" className="btn font-semibold">
|
||||||
|
{t('close')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
import Pagination from '@mui/material/Pagination';
|
||||||
|
import { setCurrentPage } from '@/redux/features/expenses-slice';
|
||||||
|
import { load } from '@/redux/features/expenses-slice'
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { AppDispatch , useAppSelector } from '@/redux/store';
|
||||||
|
|
||||||
|
export default function ListPagination() {
|
||||||
|
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// load needed states
|
||||||
|
const currentPage = useAppSelector((state) => state.expensesReducer.value.currentPage)
|
||||||
|
const total = useAppSelector((state) => state.expensesReducer.value.total)
|
||||||
|
// handle the pagination
|
||||||
|
const handlePagination = (event: React.ChangeEvent<unknown>, value: number) => {
|
||||||
|
dispatch(load({page:value}));
|
||||||
|
dispatch(setCurrentPage(value));
|
||||||
|
};
|
||||||
|
// show the pagination ui
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{Math.ceil(total / 4) > 1 ? (
|
||||||
|
<div className="flex justify-center items-center py-12">
|
||||||
|
<Pagination
|
||||||
|
sx={{ direction: 'ltr' }}
|
||||||
|
count={Math.ceil(total / 4)}
|
||||||
|
page={currentPage}
|
||||||
|
onChange={handlePagination}
|
||||||
|
variant="outlined"
|
||||||
|
shape="rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
195
webapp/src/components/dashboard/expenses/parts/updatePopUp.tsx
Normal file
195
webapp/src/components/dashboard/expenses/parts/updatePopUp.tsx
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
import { forwardRef , ReactElement , Ref, useEffect } from "react"
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { AppDispatch, useAppSelector } from "@/redux/store";
|
||||||
|
import { setUpdatePopUp , update } from "@/redux/features/expenses-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import Slide from '@mui/material/Slide';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
|
||||||
|
export default function UpdatePopUp()
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
// redux
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// intl-18
|
||||||
|
const t = useTranslations('expenses');
|
||||||
|
// mui
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
// get state from redux
|
||||||
|
const updatePopUp = useAppSelector((state) => state.expensesReducer.value.updatePopUp)
|
||||||
|
const updatePopUpData = useAppSelector((state) => state.expensesReducer.value.updatePopUpData)
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(updatePopUp , "updatePopUp")
|
||||||
|
}, [updatePopUp])
|
||||||
|
// setup the popup animation
|
||||||
|
const Transition = forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: ReactElement<any, any>;
|
||||||
|
},
|
||||||
|
ref: Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
// setup the function that close the popup
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch(setUpdatePopUp({
|
||||||
|
status: false,
|
||||||
|
data: null
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
// we dont mount the pop up if we dont need it
|
||||||
|
if(!updatePopUp) return <></>
|
||||||
|
// we mount the popup if we needed it
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
open={updatePopUp}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
keepMounted
|
||||||
|
onClose={handleClose}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth={'sm'}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// This is the title of the pop up
|
||||||
|
}
|
||||||
|
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
|
||||||
|
<h1 className="text-[25px] font-tajawal">{t('updateExpense')}</h1>
|
||||||
|
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</DialogTitle>
|
||||||
|
{
|
||||||
|
// Formik form start from here
|
||||||
|
}
|
||||||
|
<Formik
|
||||||
|
// Initial values
|
||||||
|
initialValues={{
|
||||||
|
_id: updatePopUpData?._id ? updatePopUpData?._id : '',
|
||||||
|
name : updatePopUpData?.name ? updatePopUpData?.name : '',
|
||||||
|
description : updatePopUpData?.description ? updatePopUpData?.description : '',
|
||||||
|
amount: updatePopUpData?.amount ? updatePopUpData?.amount : 0
|
||||||
|
}}
|
||||||
|
// this function handle the submition of the formik form
|
||||||
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
|
// dispatch the update function
|
||||||
|
await dispatch(update(values))
|
||||||
|
// close the popup
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
values,
|
||||||
|
setFieldValue
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{
|
||||||
|
// dialog content contain the formik form
|
||||||
|
}
|
||||||
|
<DialogContent>
|
||||||
|
{
|
||||||
|
// dialog text content contain a small description to the current form part
|
||||||
|
}
|
||||||
|
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
|
||||||
|
<h1 className="text-[18px] font-tajawal">{t('expenseUpdateText1')}</h1>
|
||||||
|
</DialogContentText>
|
||||||
|
<div className="
|
||||||
|
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80
|
||||||
|
[&_*]:text-text/80 [&_*]:dark:text-text-dark/80
|
||||||
|
[&_*]:placeholder-text/80 [&_*]:dark:placeholder-text-dark/80
|
||||||
|
w-full h-auto mt-[36px] flex flex-col gap-3
|
||||||
|
">
|
||||||
|
<div className="w-full flex lg:flex-row flex-col lg:gap-12 gap-7">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('expenseInformations')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.name}
|
||||||
|
placeholder={t('name')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.description}
|
||||||
|
placeholder={t('description')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="amount"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.amount}
|
||||||
|
placeholder={t('amount')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
{
|
||||||
|
// dialog actions contain buttons that effect the popup status
|
||||||
|
}
|
||||||
|
<DialogActions sx={{ padding: '16px 24px' }}>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button type="submit" className="btn font-semibold">
|
||||||
|
{
|
||||||
|
isSubmitting ?
|
||||||
|
<CircularProgress color="secondary" size={24} />
|
||||||
|
:
|
||||||
|
t('save')
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
14
webapp/src/components/dashboard/expenses/popUpsWrapper.tsx
Normal file
14
webapp/src/components/dashboard/expenses/popUpsWrapper.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import AddPopUp from './parts/addPopUp'
|
||||||
|
import DetailsPopUp from './parts/detailsPopUp'
|
||||||
|
import UpdatePopUp from './parts/updatePopUp'
|
||||||
|
|
||||||
|
export default function PopUpWrapper()
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<UpdatePopUp />
|
||||||
|
<AddPopUp />
|
||||||
|
<DetailsPopUp />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
100
webapp/src/components/dashboard/header.tsx
Normal file
100
webapp/src/components/dashboard/header.tsx
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { AppDispatch } from '@/redux/store';
|
||||||
|
|
||||||
|
import { useAppSelector } from '@/redux/store';
|
||||||
|
import { setThemeType } from '@/redux/features/theme-slice';
|
||||||
|
import { toggleSidebar } from '@/redux/features/sidebar-slice';
|
||||||
|
import Cookies from 'universal-cookie';
|
||||||
|
import Link from 'next-intl/link';
|
||||||
|
import {usePathname} from 'next-intl/client';
|
||||||
|
import { logOut } from '@/redux/features/auth-slice';
|
||||||
|
|
||||||
|
export default function Header () {
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
const t = useTranslations('header');
|
||||||
|
const cookies = new Cookies({ path: '/' });
|
||||||
|
|
||||||
|
const themeType = useAppSelector((state) => state.themeTypeReducer.value.themeType);
|
||||||
|
|
||||||
|
function handleLogout()
|
||||||
|
{
|
||||||
|
dispatch(logOut())
|
||||||
|
router.push('/login')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header className="border-b dark:border-primary-light/10 w-full flex justify-between items-center bg-primary shadow lg:px-[18px] px-[7.5px] py-[14px] [&_*]:text-text duration-300 dark:bg-secondary-dark [&_*]:dark:text-text-light">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="collapse-icon flex flex-none rounded-full p-2 ltr:ml-2 rtl:mr-2"
|
||||||
|
onClick={() => dispatch(toggleSidebar())}
|
||||||
|
>
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M20 7L4 7" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
||||||
|
<path opacity="0.5" d="M20 12L4 12" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
||||||
|
<path d="M20 17L4 17" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button className="ltr:border-r rtl:border-l ltr:pr-3 rtl:pl-3">
|
||||||
|
<Link href={pathname} locale={cookies.get('NEXT_LOCALE') == 'ar'?'en' : 'ar'}>
|
||||||
|
{
|
||||||
|
cookies.get('NEXT_LOCALE') == 'ar'?
|
||||||
|
'en' : 'ar'
|
||||||
|
}
|
||||||
|
</Link>
|
||||||
|
</button>
|
||||||
|
{themeType === 'light' ? (
|
||||||
|
<button
|
||||||
|
className={`${
|
||||||
|
themeType === 'light' &&
|
||||||
|
'flex items-center rounded-full bg-white-light/40 p-2 hover:bg-white-light/90 hover:text-primary dark:bg-dark/40 dark:hover:bg-dark/60'
|
||||||
|
}`}
|
||||||
|
onClick={() => dispatch(setThemeType('dark'))}
|
||||||
|
>
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="12" cy="12" r="5" stroke="currentColor" strokeWidth="1.5" />
|
||||||
|
<path d="M12 2V4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
||||||
|
<path d="M12 20V22" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
||||||
|
<path d="M4 12L2 12" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
||||||
|
<path d="M22 12L20 12" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
||||||
|
<path opacity="0.5" d="M19.7778 4.22266L17.5558 6.25424" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
||||||
|
<path opacity="0.5" d="M4.22217 4.22266L6.44418 6.25424" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
||||||
|
<path opacity="0.5" d="M6.44434 17.5557L4.22211 19.7779" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
||||||
|
<path opacity="0.5" d="M19.7778 19.7773L17.5558 17.5551" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
{themeType === 'dark' && (
|
||||||
|
<button
|
||||||
|
className={`${
|
||||||
|
themeType === 'dark' &&
|
||||||
|
'flex items-center rounded-full bg-white-light/40 p-2 hover:bg-white-light/90 hover:text-primary dark:bg-dark/40 dark:hover:bg-dark/60'
|
||||||
|
}`}
|
||||||
|
onClick={() => dispatch(setThemeType('light'))}
|
||||||
|
>
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M21.0672 11.8568L20.4253 11.469L21.0672 11.8568ZM12.1432 2.93276L11.7553 2.29085V2.29085L12.1432 2.93276ZM21.25 12C21.25 17.1086 17.1086 21.25 12 21.25V22.75C17.9371 22.75 22.75 17.9371 22.75 12H21.25ZM12 21.25C6.89137 21.25 2.75 17.1086 2.75 12H1.25C1.25 17.9371 6.06294 22.75 12 22.75V21.25ZM2.75 12C2.75 6.89137 6.89137 2.75 12 2.75V1.25C6.06294 1.25 1.25 6.06294 1.25 12H2.75ZM15.5 14.25C12.3244 14.25 9.75 11.6756 9.75 8.5H8.25C8.25 12.5041 11.4959 15.75 15.5 15.75V14.25ZM20.4253 11.469C19.4172 13.1373 17.5882 14.25 15.5 14.25V15.75C18.1349 15.75 20.4407 14.3439 21.7092 12.2447L20.4253 11.469ZM9.75 8.5C9.75 6.41182 10.8627 4.5828 12.531 3.57467L11.7553 2.29085C9.65609 3.5593 8.25 5.86509 8.25 8.5H9.75ZM12 2.75C11.9115 2.75 11.8077 2.71008 11.7324 2.63168C11.6686 2.56527 11.6538 2.50244 11.6503 2.47703C11.6461 2.44587 11.6482 2.35557 11.7553 2.29085L12.531 3.57467C13.0342 3.27065 13.196 2.71398 13.1368 2.27627C13.0754 1.82126 12.7166 1.25 12 1.25V2.75ZM21.7092 12.2447C21.6444 12.3518 21.5541 12.3539 21.523 12.3497C21.4976 12.3462 21.4347 12.3314 21.3683 12.2676C21.2899 12.1923 21.25 12.0885 21.25 12H22.75C22.75 11.2834 22.1787 10.9246 21.7237 10.8632C21.286 10.804 20.7293 10.9658 20.4253 11.469L21.7092 12.2447Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button onClick={handleLogout} className="ltr:rotate-180 rtl:ml-2 ltr:mr-2">
|
||||||
|
<svg className="stroke-text dark:stroke-text-dark" width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M9.00195 7C9.01406 4.82497 9.11051 3.64706 9.87889 2.87868C10.7576 2 12.1718 2 15.0002 2H16.0002C18.8286 2 20.2429 2 21.1215 2.87868C22.0002 3.75736 22.0002 5.17157 22.0002 8V16C22.0002 18.8284 22.0002 20.2426 21.1215 21.1213C20.2429 22 18.8286 22 16.0002 22H15.0002C12.1718 22 10.7576 22 9.87889 21.1213C9.11051 20.3529 9.01406 19.175 9.00195 17" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<path d="M15 12H2M2 12L5.5 9M2 12L5.5 15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
};
|
||||||
122
webapp/src/components/dashboard/home/generalLook.tsx
Normal file
122
webapp/src/components/dashboard/home/generalLook.tsx
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useAppSelector } from '@/redux/store';
|
||||||
|
|
||||||
|
export default function GeneralLook()
|
||||||
|
{
|
||||||
|
|
||||||
|
const t = useTranslations('statistics');
|
||||||
|
const report = useAppSelector((state) => state.statisticsReducer.value.report)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="lg:w-1/3 w-full flex flex-col lg:gap-7 gap-3">
|
||||||
|
<div className="w-full h-full flex lg:flex-row flex-col justify-between items-center gap-3 lg:px-5 p-5 border border-secondary-light bg-primary dark:bg-primary-dark rounded-md shadow">
|
||||||
|
<div className="w-full h-full text-start flex items-center gap-2">
|
||||||
|
<span className="w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md bg-info flex items-center justify-center">
|
||||||
|
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 28 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8.69141 3.22229C8.69141 1.44589 7.24365 0 5.46436 0C3.68506 0 2.23779 1.44589 2.23779 3.22229C2.23779 4.9987 3.68506 6.44361 5.46436 6.44361C7.24365 6.44361 8.69141 4.9987 8.69141 3.22229ZM5.46436 1.99675C6.14111 1.99675 6.69141 2.54664 6.69141 3.22229C6.69141 3.89795 6.14111 4.44686 5.46436 4.44686C4.78809 4.44686 4.23779 3.89795 4.23779 3.22229C4.23779 2.54664 4.78809 1.99675 5.46436 1.99675ZM1 14.4238C1.55225 14.4238 2 13.9773 2 13.4254C2 13.1859 2.0271 12.9508 2.07495 12.7212C2.21057 12.0707 2.53113 11.4708 3.01514 10.988C3.67041 10.3338 4.5415 9.974 5.46826 9.974C5.70428 9.974 5.93713 9.99837 6.16113 10.0446C6.38989 10.0917 6.60944 10.1617 6.81299 10.2548C7.3125 10.4781 7.90723 10.2626 8.1377 9.76048C8.36719 9.25934 8.14551 8.66656 7.64307 8.43744C6.98193 8.13617 6.22998 7.97725 5.46826 7.97725C4.00732 7.97725 2.63379 8.54469 1.60107 9.57621C0.568359 10.6068 0 11.9737 0 13.4254C0 13.9773 0.447754 14.4238 1 14.4238ZM19.3086 3.22229C19.3086 4.9987 20.7563 6.44361 22.5356 6.44361C24.3149 6.44361 25.7622 4.9987 25.7622 3.22229C25.7622 1.44589 24.3149 0 22.5356 0C20.7563 0 19.3086 1.44589 19.3086 3.22229ZM22.5356 4.44686C21.8589 4.44686 21.3086 3.89795 21.3086 3.22229C21.3086 2.54664 21.8589 1.99675 22.5356 1.99675C23.2119 1.99675 23.7622 2.54664 23.7622 3.22229C23.7622 3.89795 23.2119 4.44686 22.5356 4.44686ZM22.5317 7.97725C21.77 7.97725 21.0181 8.13617 20.3569 8.43744C19.8545 8.66656 19.6328 9.25934 19.8623 9.76048C20.0918 10.2626 20.6865 10.479 21.187 10.2548C21.3906 10.1617 21.6101 10.0917 21.8389 10.0446C22.0629 9.99837 22.2957 9.974 22.5317 9.974C23.4585 9.974 24.3296 10.3338 24.9849 10.988C25.4689 11.4708 25.7894 12.0707 25.925 12.7212C25.9729 12.9508 26 13.1859 26 13.4254C26 13.9773 26.4478 14.4238 27 14.4238C27.5522 14.4238 28 13.9773 28 13.4254C28 11.9737 27.4316 10.6068 26.3989 9.57621C25.3662 8.54469 23.9927 7.97725 22.5317 7.97725ZM17.7988 4.47221C17.7988 2.38089 16.0947 0.679558 14 0.679558C11.9053 0.679558 10.2012 2.38089 10.2012 4.47221C10.2012 6.56354 11.9053 8.26487 14 8.26487C16.0947 8.26487 17.7988 6.56354 17.7988 4.47221ZM14 2.67631C14.9917 2.67631 15.7988 3.48164 15.7988 4.47221C15.7988 5.46279 14.9917 6.26812 14 6.26812C13.0083 6.26812 12.2012 5.46279 12.2012 4.47221C12.2012 3.48164 13.0083 2.67631 14 2.67631ZM8.38965 18C8.94171 18 9.38928 17.5527 9.38953 17.0011C9.38953 17.001 9.38953 17.0013 9.38953 17.0011C9.38953 14.4633 11.458 12.3978 14 12.3978C16.542 12.3978 18.6104 14.4628 18.6104 17.0007C18.6104 17.0005 18.6104 17.0008 18.6104 17.0007C18.6106 17.5522 19.0583 17.999 19.6104 17.999C20.1626 17.999 20.6104 17.5525 20.6104 17.0007C20.6104 13.362 17.645 10.401 14 10.401C10.355 10.401 7.38965 13.362 7.38965 17.0016C7.38965 17.5535 7.8374 18 8.38965 18Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span className="max-h-[40px] flex flex-col [&_p]:text-text [&_p]:dark:text-text-dark">
|
||||||
|
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{report?.membersCount.value}</p>
|
||||||
|
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{t('totalMembers')}</p>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-full text-start flex items-center gap-2">
|
||||||
|
<span className="w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md bg-success flex items-center justify-center">
|
||||||
|
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 28 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8.69141 3.22229C8.69141 1.44589 7.24365 0 5.46436 0C3.68506 0 2.23779 1.44589 2.23779 3.22229C2.23779 4.9987 3.68506 6.44361 5.46436 6.44361C7.24365 6.44361 8.69141 4.9987 8.69141 3.22229ZM5.46436 1.99675C6.14111 1.99675 6.69141 2.54664 6.69141 3.22229C6.69141 3.89795 6.14111 4.44686 5.46436 4.44686C4.78809 4.44686 4.23779 3.89795 4.23779 3.22229C4.23779 2.54664 4.78809 1.99675 5.46436 1.99675ZM1 14.4238C1.55225 14.4238 2 13.9773 2 13.4254C2 13.1859 2.0271 12.9508 2.07495 12.7212C2.21057 12.0707 2.53113 11.4708 3.01514 10.988C3.67041 10.3338 4.5415 9.974 5.46826 9.974C5.70428 9.974 5.93713 9.99837 6.16113 10.0446C6.38989 10.0917 6.60944 10.1617 6.81299 10.2548C7.3125 10.4781 7.90723 10.2626 8.1377 9.76048C8.36719 9.25934 8.14551 8.66656 7.64307 8.43744C6.98193 8.13617 6.22998 7.97725 5.46826 7.97725C4.00732 7.97725 2.63379 8.54469 1.60107 9.57621C0.568359 10.6068 0 11.9737 0 13.4254C0 13.9773 0.447754 14.4238 1 14.4238ZM19.3086 3.22229C19.3086 4.9987 20.7563 6.44361 22.5356 6.44361C24.3149 6.44361 25.7622 4.9987 25.7622 3.22229C25.7622 1.44589 24.3149 0 22.5356 0C20.7563 0 19.3086 1.44589 19.3086 3.22229ZM22.5356 4.44686C21.8589 4.44686 21.3086 3.89795 21.3086 3.22229C21.3086 2.54664 21.8589 1.99675 22.5356 1.99675C23.2119 1.99675 23.7622 2.54664 23.7622 3.22229C23.7622 3.89795 23.2119 4.44686 22.5356 4.44686ZM22.5317 7.97725C21.77 7.97725 21.0181 8.13617 20.3569 8.43744C19.8545 8.66656 19.6328 9.25934 19.8623 9.76048C20.0918 10.2626 20.6865 10.479 21.187 10.2548C21.3906 10.1617 21.6101 10.0917 21.8389 10.0446C22.0629 9.99837 22.2957 9.974 22.5317 9.974C23.4585 9.974 24.3296 10.3338 24.9849 10.988C25.4689 11.4708 25.7894 12.0707 25.925 12.7212C25.9729 12.9508 26 13.1859 26 13.4254C26 13.9773 26.4478 14.4238 27 14.4238C27.5522 14.4238 28 13.9773 28 13.4254C28 11.9737 27.4316 10.6068 26.3989 9.57621C25.3662 8.54469 23.9927 7.97725 22.5317 7.97725ZM17.7988 4.47221C17.7988 2.38089 16.0947 0.679558 14 0.679558C11.9053 0.679558 10.2012 2.38089 10.2012 4.47221C10.2012 6.56354 11.9053 8.26487 14 8.26487C16.0947 8.26487 17.7988 6.56354 17.7988 4.47221ZM14 2.67631C14.9917 2.67631 15.7988 3.48164 15.7988 4.47221C15.7988 5.46279 14.9917 6.26812 14 6.26812C13.0083 6.26812 12.2012 5.46279 12.2012 4.47221C12.2012 3.48164 13.0083 2.67631 14 2.67631ZM8.38965 18C8.94171 18 9.38928 17.5527 9.38953 17.0011C9.38953 17.001 9.38953 17.0013 9.38953 17.0011C9.38953 14.4633 11.458 12.3978 14 12.3978C16.542 12.3978 18.6104 14.4628 18.6104 17.0007C18.6104 17.0005 18.6104 17.0008 18.6104 17.0007C18.6106 17.5522 19.0583 17.999 19.6104 17.999C20.1626 17.999 20.6104 17.5525 20.6104 17.0007C20.6104 13.362 17.645 10.401 14 10.401C10.355 10.401 7.38965 13.362 7.38965 17.0016C7.38965 17.5535 7.8374 18 8.38965 18Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span className="max-h-[40px] flex flex-col [&_p]:text-text [&_p]:dark:text-text-dark">
|
||||||
|
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{report?.activeMembersCount.value}</p>
|
||||||
|
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{t('totalActiveMembers')}</p>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-full text-start flex items-center gap-2">
|
||||||
|
<span className="w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md bg-error flex items-center justify-center">
|
||||||
|
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 28 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8.69141 3.22229C8.69141 1.44589 7.24365 0 5.46436 0C3.68506 0 2.23779 1.44589 2.23779 3.22229C2.23779 4.9987 3.68506 6.44361 5.46436 6.44361C7.24365 6.44361 8.69141 4.9987 8.69141 3.22229ZM5.46436 1.99675C6.14111 1.99675 6.69141 2.54664 6.69141 3.22229C6.69141 3.89795 6.14111 4.44686 5.46436 4.44686C4.78809 4.44686 4.23779 3.89795 4.23779 3.22229C4.23779 2.54664 4.78809 1.99675 5.46436 1.99675ZM1 14.4238C1.55225 14.4238 2 13.9773 2 13.4254C2 13.1859 2.0271 12.9508 2.07495 12.7212C2.21057 12.0707 2.53113 11.4708 3.01514 10.988C3.67041 10.3338 4.5415 9.974 5.46826 9.974C5.70428 9.974 5.93713 9.99837 6.16113 10.0446C6.38989 10.0917 6.60944 10.1617 6.81299 10.2548C7.3125 10.4781 7.90723 10.2626 8.1377 9.76048C8.36719 9.25934 8.14551 8.66656 7.64307 8.43744C6.98193 8.13617 6.22998 7.97725 5.46826 7.97725C4.00732 7.97725 2.63379 8.54469 1.60107 9.57621C0.568359 10.6068 0 11.9737 0 13.4254C0 13.9773 0.447754 14.4238 1 14.4238ZM19.3086 3.22229C19.3086 4.9987 20.7563 6.44361 22.5356 6.44361C24.3149 6.44361 25.7622 4.9987 25.7622 3.22229C25.7622 1.44589 24.3149 0 22.5356 0C20.7563 0 19.3086 1.44589 19.3086 3.22229ZM22.5356 4.44686C21.8589 4.44686 21.3086 3.89795 21.3086 3.22229C21.3086 2.54664 21.8589 1.99675 22.5356 1.99675C23.2119 1.99675 23.7622 2.54664 23.7622 3.22229C23.7622 3.89795 23.2119 4.44686 22.5356 4.44686ZM22.5317 7.97725C21.77 7.97725 21.0181 8.13617 20.3569 8.43744C19.8545 8.66656 19.6328 9.25934 19.8623 9.76048C20.0918 10.2626 20.6865 10.479 21.187 10.2548C21.3906 10.1617 21.6101 10.0917 21.8389 10.0446C22.0629 9.99837 22.2957 9.974 22.5317 9.974C23.4585 9.974 24.3296 10.3338 24.9849 10.988C25.4689 11.4708 25.7894 12.0707 25.925 12.7212C25.9729 12.9508 26 13.1859 26 13.4254C26 13.9773 26.4478 14.4238 27 14.4238C27.5522 14.4238 28 13.9773 28 13.4254C28 11.9737 27.4316 10.6068 26.3989 9.57621C25.3662 8.54469 23.9927 7.97725 22.5317 7.97725ZM17.7988 4.47221C17.7988 2.38089 16.0947 0.679558 14 0.679558C11.9053 0.679558 10.2012 2.38089 10.2012 4.47221C10.2012 6.56354 11.9053 8.26487 14 8.26487C16.0947 8.26487 17.7988 6.56354 17.7988 4.47221ZM14 2.67631C14.9917 2.67631 15.7988 3.48164 15.7988 4.47221C15.7988 5.46279 14.9917 6.26812 14 6.26812C13.0083 6.26812 12.2012 5.46279 12.2012 4.47221C12.2012 3.48164 13.0083 2.67631 14 2.67631ZM8.38965 18C8.94171 18 9.38928 17.5527 9.38953 17.0011C9.38953 17.001 9.38953 17.0013 9.38953 17.0011C9.38953 14.4633 11.458 12.3978 14 12.3978C16.542 12.3978 18.6104 14.4628 18.6104 17.0007C18.6104 17.0005 18.6104 17.0008 18.6104 17.0007C18.6106 17.5522 19.0583 17.999 19.6104 17.999C20.1626 17.999 20.6104 17.5525 20.6104 17.0007C20.6104 13.362 17.645 10.401 14 10.401C10.355 10.401 7.38965 13.362 7.38965 17.0016C7.38965 17.5535 7.8374 18 8.38965 18Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span className="max-h-[40px] flex flex-col [&_p]:text-text [&_p]:dark:text-text-dark">
|
||||||
|
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{report?.disActiveMembersCount.value}</p>
|
||||||
|
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{t('totalUnActiveMembers')}</p>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-full flex lg:flex-row flex-col justify-between items-center gap-3 lg:px-5 p-5 border border-secondary-light bg-primary dark:bg-primary-dark rounded-md shadow">
|
||||||
|
<div className="w-full h-full text-start flex items-center gap-2">
|
||||||
|
<span className="w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md bg-info flex items-center justify-center">
|
||||||
|
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 28 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0.466812 0C0.343006 0 0.22427 0.0482399 0.136726 0.134107C0.0491819 0.219973 0 0.336433 0 0.457867V9.78279C0 9.90422 0.0491819 10.0207 0.136726 10.1065C0.22427 10.1924 0.343006 10.2407 0.466812 10.2407H5.57467C5.69847 10.2407 5.81721 10.1924 5.90475 10.1065C5.9923 10.0207 6.04148 9.90422 6.04148 9.78279V9.1088L14.7615 12.0062C14.7735 12.0097 14.7857 12.0128 14.7979 12.0153C15.8128 12.2681 16.9845 12.1179 17.8696 11.5346L26.8996 6.72149C26.9021 6.72151 26.9045 6.72151 26.907 6.72149L27.1283 6.59786C27.4933 6.39598 27.7702 6.06987 27.9067 5.68125C28.0431 5.29263 28.0296 4.86844 27.8687 4.48893C27.582 3.82502 27.0191 3.52008 26.5793 3.4569V3.45323C26.3111 3.41598 26.0378 3.43629 25.7783 3.51275C25.7627 3.51836 25.7474 3.52478 25.7325 3.53199L20.7545 5.7966C20.761 5.73433 20.775 5.67572 20.775 5.61253C20.775 4.58325 19.9067 3.74718 18.8527 3.74718H14.7074C14.6906 3.71605 14.67 3.67209 14.6532 3.64279C14.4318 3.24967 14.1528 2.89054 13.8251 2.57688C12.9156 1.70753 11.6947 1.22287 10.4248 1.22708H6.04148V0.457867C6.04148 0.336433 5.9923 0.219973 5.90475 0.134107C5.81721 0.0482399 5.69847 0 5.57467 0H0.466812ZM0.933624 0.915734H5.10785V9.32492H0.933624V0.915734ZM6.04054 2.14282H10.4248C11.0564 2.14018 11.6791 2.28787 12.2396 2.57321C12.8002 2.85855 13.2817 3.27302 13.643 3.78107C13.7121 3.87997 13.7775 3.98161 13.8363 4.08692C13.9101 4.21604 13.9595 4.29571 13.9988 4.38545L14.1192 4.66292H18.8527C19.4128 4.66292 19.8414 5.08232 19.8414 5.61253C19.8414 6.00996 19.5996 6.34237 19.2438 6.48523L19.2373 6.48706C19.1197 6.53285 18.9908 6.56032 18.8527 6.56032H12.3593V7.47605H18.8527C19.0487 7.47605 19.2326 7.43759 19.411 7.38448C19.4665 7.38084 19.5208 7.36751 19.5716 7.3451L26.0864 4.38179C26.0948 4.38179 26.2488 4.33234 26.444 4.36072C26.6447 4.3882 26.8361 4.44314 27.0097 4.84332C27.0851 5.01556 27.0918 5.20929 27.0286 5.38616C26.9653 5.56303 26.8366 5.71015 26.668 5.79843L26.4496 5.91747L17.3869 10.7507C17.3737 10.7577 17.361 10.7654 17.3486 10.7736C16.7324 11.1802 15.7764 11.3139 15.0295 11.128H15.0276L6.04148 8.14271L6.04054 2.14282Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span className="max-h-[40px] flex flex-col [&_p]:text-text [&_p]:dark:text-text-dark">
|
||||||
|
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{report?.servicesCount.value}</p>
|
||||||
|
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{t('totalServices')}</p>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-full text-start flex items-center gap-2">
|
||||||
|
<span className="w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md bg-success flex items-center justify-center">
|
||||||
|
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 28 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0.466812 0C0.343006 0 0.22427 0.0482399 0.136726 0.134107C0.0491819 0.219973 0 0.336433 0 0.457867V9.78279C0 9.90422 0.0491819 10.0207 0.136726 10.1065C0.22427 10.1924 0.343006 10.2407 0.466812 10.2407H5.57467C5.69847 10.2407 5.81721 10.1924 5.90475 10.1065C5.9923 10.0207 6.04148 9.90422 6.04148 9.78279V9.1088L14.7615 12.0062C14.7735 12.0097 14.7857 12.0128 14.7979 12.0153C15.8128 12.2681 16.9845 12.1179 17.8696 11.5346L26.8996 6.72149C26.9021 6.72151 26.9045 6.72151 26.907 6.72149L27.1283 6.59786C27.4933 6.39598 27.7702 6.06987 27.9067 5.68125C28.0431 5.29263 28.0296 4.86844 27.8687 4.48893C27.582 3.82502 27.0191 3.52008 26.5793 3.4569V3.45323C26.3111 3.41598 26.0378 3.43629 25.7783 3.51275C25.7627 3.51836 25.7474 3.52478 25.7325 3.53199L20.7545 5.7966C20.761 5.73433 20.775 5.67572 20.775 5.61253C20.775 4.58325 19.9067 3.74718 18.8527 3.74718H14.7074C14.6906 3.71605 14.67 3.67209 14.6532 3.64279C14.4318 3.24967 14.1528 2.89054 13.8251 2.57688C12.9156 1.70753 11.6947 1.22287 10.4248 1.22708H6.04148V0.457867C6.04148 0.336433 5.9923 0.219973 5.90475 0.134107C5.81721 0.0482399 5.69847 0 5.57467 0H0.466812ZM0.933624 0.915734H5.10785V9.32492H0.933624V0.915734ZM6.04054 2.14282H10.4248C11.0564 2.14018 11.6791 2.28787 12.2396 2.57321C12.8002 2.85855 13.2817 3.27302 13.643 3.78107C13.7121 3.87997 13.7775 3.98161 13.8363 4.08692C13.9101 4.21604 13.9595 4.29571 13.9988 4.38545L14.1192 4.66292H18.8527C19.4128 4.66292 19.8414 5.08232 19.8414 5.61253C19.8414 6.00996 19.5996 6.34237 19.2438 6.48523L19.2373 6.48706C19.1197 6.53285 18.9908 6.56032 18.8527 6.56032H12.3593V7.47605H18.8527C19.0487 7.47605 19.2326 7.43759 19.411 7.38448C19.4665 7.38084 19.5208 7.36751 19.5716 7.3451L26.0864 4.38179C26.0948 4.38179 26.2488 4.33234 26.444 4.36072C26.6447 4.3882 26.8361 4.44314 27.0097 4.84332C27.0851 5.01556 27.0918 5.20929 27.0286 5.38616C26.9653 5.56303 26.8366 5.71015 26.668 5.79843L26.4496 5.91747L17.3869 10.7507C17.3737 10.7577 17.361 10.7654 17.3486 10.7736C16.7324 11.1802 15.7764 11.3139 15.0295 11.128H15.0276L6.04148 8.14271L6.04054 2.14282Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span className="max-h-[40px] flex flex-col [&_p]:text-text [&_p]:dark:text-text-dark">
|
||||||
|
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{report?.activeServicesCount.value}</p>
|
||||||
|
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{t('totalActiveServices')}</p>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-full text-start flex items-center gap-2">
|
||||||
|
<span className="w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md bg-error flex items-center justify-center">
|
||||||
|
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 28 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0.466812 0C0.343006 0 0.22427 0.0482399 0.136726 0.134107C0.0491819 0.219973 0 0.336433 0 0.457867V9.78279C0 9.90422 0.0491819 10.0207 0.136726 10.1065C0.22427 10.1924 0.343006 10.2407 0.466812 10.2407H5.57467C5.69847 10.2407 5.81721 10.1924 5.90475 10.1065C5.9923 10.0207 6.04148 9.90422 6.04148 9.78279V9.1088L14.7615 12.0062C14.7735 12.0097 14.7857 12.0128 14.7979 12.0153C15.8128 12.2681 16.9845 12.1179 17.8696 11.5346L26.8996 6.72149C26.9021 6.72151 26.9045 6.72151 26.907 6.72149L27.1283 6.59786C27.4933 6.39598 27.7702 6.06987 27.9067 5.68125C28.0431 5.29263 28.0296 4.86844 27.8687 4.48893C27.582 3.82502 27.0191 3.52008 26.5793 3.4569V3.45323C26.3111 3.41598 26.0378 3.43629 25.7783 3.51275C25.7627 3.51836 25.7474 3.52478 25.7325 3.53199L20.7545 5.7966C20.761 5.73433 20.775 5.67572 20.775 5.61253C20.775 4.58325 19.9067 3.74718 18.8527 3.74718H14.7074C14.6906 3.71605 14.67 3.67209 14.6532 3.64279C14.4318 3.24967 14.1528 2.89054 13.8251 2.57688C12.9156 1.70753 11.6947 1.22287 10.4248 1.22708H6.04148V0.457867C6.04148 0.336433 5.9923 0.219973 5.90475 0.134107C5.81721 0.0482399 5.69847 0 5.57467 0H0.466812ZM0.933624 0.915734H5.10785V9.32492H0.933624V0.915734ZM6.04054 2.14282H10.4248C11.0564 2.14018 11.6791 2.28787 12.2396 2.57321C12.8002 2.85855 13.2817 3.27302 13.643 3.78107C13.7121 3.87997 13.7775 3.98161 13.8363 4.08692C13.9101 4.21604 13.9595 4.29571 13.9988 4.38545L14.1192 4.66292H18.8527C19.4128 4.66292 19.8414 5.08232 19.8414 5.61253C19.8414 6.00996 19.5996 6.34237 19.2438 6.48523L19.2373 6.48706C19.1197 6.53285 18.9908 6.56032 18.8527 6.56032H12.3593V7.47605H18.8527C19.0487 7.47605 19.2326 7.43759 19.411 7.38448C19.4665 7.38084 19.5208 7.36751 19.5716 7.3451L26.0864 4.38179C26.0948 4.38179 26.2488 4.33234 26.444 4.36072C26.6447 4.3882 26.8361 4.44314 27.0097 4.84332C27.0851 5.01556 27.0918 5.20929 27.0286 5.38616C26.9653 5.56303 26.8366 5.71015 26.668 5.79843L26.4496 5.91747L17.3869 10.7507C17.3737 10.7577 17.361 10.7654 17.3486 10.7736C16.7324 11.1802 15.7764 11.3139 15.0295 11.128H15.0276L6.04148 8.14271L6.04054 2.14282Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span className="max-h-[40px] flex flex-col [&_p]:text-text [&_p]:dark:text-text-dark">
|
||||||
|
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{report?.disActiveServicesCount.value}</p>
|
||||||
|
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{t('totalUnActiveServices')}</p>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-full flex lg:flex-row flex-col justify-between items-center gap-3 lg:px-5 p-5 border border-secondary-light bg-primary dark:bg-primary-dark rounded-md shadow">
|
||||||
|
<div className="w-full h-full text-start flex items-center gap-2">
|
||||||
|
<span className="w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md bg-info flex items-center justify-center">
|
||||||
|
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5045 31.7874C10.5348 31.8115 10.5693 31.8292 10.5998 31.8532C10.7647 31.9805 10.943 32.0872 11.1128 32.2075C11.4932 32.4797 11.874 32.7502 12.2768 32.9832C12.2895 32.9904 12.3032 32.9955 12.3158 33.0029C12.8205 33.2929 13.336 33.5632 13.8722 33.7894L14.048 33.8625C14.297 33.9667 14.5395 34.0675 14.8147 34.1637C15.5243 34.4014 16.1282 34.5657 16.706 34.6829C17.0753 34.7592 17.4262 34.8167 17.76 34.8605C17.811 34.8675 17.8632 34.8692 17.9143 34.8755C18.148 34.9032 18.3815 34.9304 18.5908 34.9415C18.9795 34.9807 19.388 35.0002 19.777 35.0002C28.0485 35.0002 34.777 28.2717 34.777 20.0002C34.777 19.079 34.0315 18.3335 33.1103 18.3335C32.1892 18.3335 31.4437 19.079 31.4437 20.0002C31.4437 26.4325 26.2093 31.6669 19.777 31.6669C19.396 31.6669 19.0208 31.6282 18.6458 31.5914C18.3745 31.5695 18.0815 31.5234 17.7855 31.4732C17.5233 31.4274 17.2617 31.3789 17.0043 31.3155C16.8143 31.2695 16.6328 31.2175 16.45 31.1634C15.5797 30.9029 14.7393 30.5564 13.956 30.1054C13.952 30.103 13.9478 30.1015 13.9438 30.0992C13.6425 29.9254 13.3605 29.7207 13.0757 29.5207C12.9278 29.4154 12.7732 29.3247 12.6293 29.2117C12.469 29.0875 12.3242 28.9439 12.1702 28.8114C11.9348 28.6065 11.6948 28.4059 11.4727 28.1852C11.3623 28.0737 11.2665 27.9475 11.1605 27.8314L14.1457 27.2935C15.0507 27.1292 15.6528 26.2632 15.49 25.3567C15.3257 24.4517 14.4712 23.8495 13.5532 24.0124L6.5935 25.2672C6.159 25.3454 5.77317 25.5944 5.52083 25.9574C5.2685 26.3204 5.17083 26.7679 5.249 27.204L6.50383 34.162C6.65033 34.9677 7.35183 35.5325 8.14283 35.5325C8.2405 35.5325 8.33983 35.5244 8.44067 35.5065C9.34567 35.3422 9.94783 34.4762 9.785 33.5697L9.265 30.6892C9.27767 30.7015 9.29233 30.7117 9.305 30.724C9.72217 31.1282 10.115 31.4822 10.5045 31.7874ZM29.2725 8.21304C29.2432 8.18971 29.21 8.17271 29.1805 8.14954C29.0075 8.01588 28.8207 7.90354 28.6422 7.77771C28.27 7.51238 27.8982 7.24738 27.5045 7.01938C27.4757 7.00271 27.4442 6.99088 27.4152 6.97438C26.9245 6.69471 26.4253 6.43071 25.905 6.21121L25.7292 6.13804C25.4802 6.03388 25.2377 5.93304 24.9625 5.83688C24.2512 5.59921 23.6473 5.43321 23.0697 5.31771C22.702 5.24138 22.3515 5.18388 22.0177 5.13988C21.9668 5.13288 21.9152 5.13138 21.8643 5.12488C21.6305 5.09721 21.3968 5.07004 21.1867 5.05888C20.7975 5.01971 20.389 5.00021 20 5.00021C11.7285 5.00021 5 11.7287 5 20.0002C5 20.9214 5.7455 21.6669 6.66667 21.6669C7.58783 21.6669 8.33333 20.9214 8.33333 20.0002C8.33333 13.5679 13.5677 8.33354 20 8.33354C20.381 8.33354 20.7562 8.37221 21.1312 8.40904C21.4025 8.43088 21.6955 8.47704 21.9915 8.52721C22.2538 8.57304 22.5153 8.62154 22.7727 8.68488C22.9627 8.73104 23.1442 8.78304 23.327 8.83704C24.5443 9.20154 25.6875 9.76371 26.7213 10.493C26.8608 10.5929 27.0077 10.6785 27.1437 10.7854C27.303 10.9087 27.4468 11.0519 27.6 11.1834C27.839 11.3914 28.0817 11.595 28.3095 11.8215C28.4177 11.9312 28.5122 12.055 28.6162 12.1692L25.6315 12.7069C24.7265 12.8712 24.1243 13.7372 24.2872 14.6437C24.4337 15.4494 25.1352 16.0142 25.9262 16.0142C26.0238 16.0142 26.1232 16.006 26.224 15.9882L33.1837 14.7334C33.6182 14.6552 34.004 14.4062 34.2563 14.0432C34.5087 13.6802 34.6063 13.2327 34.5282 12.7965L33.2733 5.83854C33.109 4.93354 32.2512 4.32821 31.3365 4.49421C30.4315 4.65854 29.8293 5.52454 29.9922 6.43104L30.5123 9.31104C30.4922 9.29104 30.4687 9.27504 30.4483 9.25521C30.0412 8.86204 29.656 8.51521 29.2725 8.21304Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span className="max-h-[40px] flex flex-col [&_p]:text-text [&_p]:dark:text-text-dark">
|
||||||
|
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{report?.activeSubscriptionsCount.value}</p>
|
||||||
|
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{t('totalActiveSubscriptions')}</p>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-full text-start flex items-center gap-2">
|
||||||
|
<span className="w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md bg-warning flex items-center justify-center">
|
||||||
|
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5045 31.7874C10.5348 31.8115 10.5693 31.8292 10.5998 31.8532C10.7647 31.9805 10.943 32.0872 11.1128 32.2075C11.4932 32.4797 11.874 32.7502 12.2768 32.9832C12.2895 32.9904 12.3032 32.9955 12.3158 33.0029C12.8205 33.2929 13.336 33.5632 13.8722 33.7894L14.048 33.8625C14.297 33.9667 14.5395 34.0675 14.8147 34.1637C15.5243 34.4014 16.1282 34.5657 16.706 34.6829C17.0753 34.7592 17.4262 34.8167 17.76 34.8605C17.811 34.8675 17.8632 34.8692 17.9143 34.8755C18.148 34.9032 18.3815 34.9304 18.5908 34.9415C18.9795 34.9807 19.388 35.0002 19.777 35.0002C28.0485 35.0002 34.777 28.2717 34.777 20.0002C34.777 19.079 34.0315 18.3335 33.1103 18.3335C32.1892 18.3335 31.4437 19.079 31.4437 20.0002C31.4437 26.4325 26.2093 31.6669 19.777 31.6669C19.396 31.6669 19.0208 31.6282 18.6458 31.5914C18.3745 31.5695 18.0815 31.5234 17.7855 31.4732C17.5233 31.4274 17.2617 31.3789 17.0043 31.3155C16.8143 31.2695 16.6328 31.2175 16.45 31.1634C15.5797 30.9029 14.7393 30.5564 13.956 30.1054C13.952 30.103 13.9478 30.1015 13.9438 30.0992C13.6425 29.9254 13.3605 29.7207 13.0757 29.5207C12.9278 29.4154 12.7732 29.3247 12.6293 29.2117C12.469 29.0875 12.3242 28.9439 12.1702 28.8114C11.9348 28.6065 11.6948 28.4059 11.4727 28.1852C11.3623 28.0737 11.2665 27.9475 11.1605 27.8314L14.1457 27.2935C15.0507 27.1292 15.6528 26.2632 15.49 25.3567C15.3257 24.4517 14.4712 23.8495 13.5532 24.0124L6.5935 25.2672C6.159 25.3454 5.77317 25.5944 5.52083 25.9574C5.2685 26.3204 5.17083 26.7679 5.249 27.204L6.50383 34.162C6.65033 34.9677 7.35183 35.5325 8.14283 35.5325C8.2405 35.5325 8.33983 35.5244 8.44067 35.5065C9.34567 35.3422 9.94783 34.4762 9.785 33.5697L9.265 30.6892C9.27767 30.7015 9.29233 30.7117 9.305 30.724C9.72217 31.1282 10.115 31.4822 10.5045 31.7874ZM29.2725 8.21304C29.2432 8.18971 29.21 8.17271 29.1805 8.14954C29.0075 8.01588 28.8207 7.90354 28.6422 7.77771C28.27 7.51238 27.8982 7.24738 27.5045 7.01938C27.4757 7.00271 27.4442 6.99088 27.4152 6.97438C26.9245 6.69471 26.4253 6.43071 25.905 6.21121L25.7292 6.13804C25.4802 6.03388 25.2377 5.93304 24.9625 5.83688C24.2512 5.59921 23.6473 5.43321 23.0697 5.31771C22.702 5.24138 22.3515 5.18388 22.0177 5.13988C21.9668 5.13288 21.9152 5.13138 21.8643 5.12488C21.6305 5.09721 21.3968 5.07004 21.1867 5.05888C20.7975 5.01971 20.389 5.00021 20 5.00021C11.7285 5.00021 5 11.7287 5 20.0002C5 20.9214 5.7455 21.6669 6.66667 21.6669C7.58783 21.6669 8.33333 20.9214 8.33333 20.0002C8.33333 13.5679 13.5677 8.33354 20 8.33354C20.381 8.33354 20.7562 8.37221 21.1312 8.40904C21.4025 8.43088 21.6955 8.47704 21.9915 8.52721C22.2538 8.57304 22.5153 8.62154 22.7727 8.68488C22.9627 8.73104 23.1442 8.78304 23.327 8.83704C24.5443 9.20154 25.6875 9.76371 26.7213 10.493C26.8608 10.5929 27.0077 10.6785 27.1437 10.7854C27.303 10.9087 27.4468 11.0519 27.6 11.1834C27.839 11.3914 28.0817 11.595 28.3095 11.8215C28.4177 11.9312 28.5122 12.055 28.6162 12.1692L25.6315 12.7069C24.7265 12.8712 24.1243 13.7372 24.2872 14.6437C24.4337 15.4494 25.1352 16.0142 25.9262 16.0142C26.0238 16.0142 26.1232 16.006 26.224 15.9882L33.1837 14.7334C33.6182 14.6552 34.004 14.4062 34.2563 14.0432C34.5087 13.6802 34.6063 13.2327 34.5282 12.7965L33.2733 5.83854C33.109 4.93354 32.2512 4.32821 31.3365 4.49421C30.4315 4.65854 29.8293 5.52454 29.9922 6.43104L30.5123 9.31104C30.4922 9.29104 30.4687 9.27504 30.4483 9.25521C30.0412 8.86204 29.656 8.51521 29.2725 8.21304Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="max-h-[40px] flex flex-col [&_p]:text-text [&_p]:dark:text-text-dark">
|
||||||
|
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{report?.expiredSoonSubscriptionsCount.value}</p>
|
||||||
|
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{t('expiredSoonSubscriptionsCount')}</p>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="w-full h-full text-start flex items-center gap-2">
|
||||||
|
<span className="w-[50px] h-[50px] min-w-[50px] min-h-[50px] rounded-md bg-error flex items-center justify-center">
|
||||||
|
<svg className="text-text-dark" width="26" height="26" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5045 31.7874C10.5348 31.8115 10.5693 31.8292 10.5998 31.8532C10.7647 31.9805 10.943 32.0872 11.1128 32.2075C11.4932 32.4797 11.874 32.7502 12.2768 32.9832C12.2895 32.9904 12.3032 32.9955 12.3158 33.0029C12.8205 33.2929 13.336 33.5632 13.8722 33.7894L14.048 33.8625C14.297 33.9667 14.5395 34.0675 14.8147 34.1637C15.5243 34.4014 16.1282 34.5657 16.706 34.6829C17.0753 34.7592 17.4262 34.8167 17.76 34.8605C17.811 34.8675 17.8632 34.8692 17.9143 34.8755C18.148 34.9032 18.3815 34.9304 18.5908 34.9415C18.9795 34.9807 19.388 35.0002 19.777 35.0002C28.0485 35.0002 34.777 28.2717 34.777 20.0002C34.777 19.079 34.0315 18.3335 33.1103 18.3335C32.1892 18.3335 31.4437 19.079 31.4437 20.0002C31.4437 26.4325 26.2093 31.6669 19.777 31.6669C19.396 31.6669 19.0208 31.6282 18.6458 31.5914C18.3745 31.5695 18.0815 31.5234 17.7855 31.4732C17.5233 31.4274 17.2617 31.3789 17.0043 31.3155C16.8143 31.2695 16.6328 31.2175 16.45 31.1634C15.5797 30.9029 14.7393 30.5564 13.956 30.1054C13.952 30.103 13.9478 30.1015 13.9438 30.0992C13.6425 29.9254 13.3605 29.7207 13.0757 29.5207C12.9278 29.4154 12.7732 29.3247 12.6293 29.2117C12.469 29.0875 12.3242 28.9439 12.1702 28.8114C11.9348 28.6065 11.6948 28.4059 11.4727 28.1852C11.3623 28.0737 11.2665 27.9475 11.1605 27.8314L14.1457 27.2935C15.0507 27.1292 15.6528 26.2632 15.49 25.3567C15.3257 24.4517 14.4712 23.8495 13.5532 24.0124L6.5935 25.2672C6.159 25.3454 5.77317 25.5944 5.52083 25.9574C5.2685 26.3204 5.17083 26.7679 5.249 27.204L6.50383 34.162C6.65033 34.9677 7.35183 35.5325 8.14283 35.5325C8.2405 35.5325 8.33983 35.5244 8.44067 35.5065C9.34567 35.3422 9.94783 34.4762 9.785 33.5697L9.265 30.6892C9.27767 30.7015 9.29233 30.7117 9.305 30.724C9.72217 31.1282 10.115 31.4822 10.5045 31.7874ZM29.2725 8.21304C29.2432 8.18971 29.21 8.17271 29.1805 8.14954C29.0075 8.01588 28.8207 7.90354 28.6422 7.77771C28.27 7.51238 27.8982 7.24738 27.5045 7.01938C27.4757 7.00271 27.4442 6.99088 27.4152 6.97438C26.9245 6.69471 26.4253 6.43071 25.905 6.21121L25.7292 6.13804C25.4802 6.03388 25.2377 5.93304 24.9625 5.83688C24.2512 5.59921 23.6473 5.43321 23.0697 5.31771C22.702 5.24138 22.3515 5.18388 22.0177 5.13988C21.9668 5.13288 21.9152 5.13138 21.8643 5.12488C21.6305 5.09721 21.3968 5.07004 21.1867 5.05888C20.7975 5.01971 20.389 5.00021 20 5.00021C11.7285 5.00021 5 11.7287 5 20.0002C5 20.9214 5.7455 21.6669 6.66667 21.6669C7.58783 21.6669 8.33333 20.9214 8.33333 20.0002C8.33333 13.5679 13.5677 8.33354 20 8.33354C20.381 8.33354 20.7562 8.37221 21.1312 8.40904C21.4025 8.43088 21.6955 8.47704 21.9915 8.52721C22.2538 8.57304 22.5153 8.62154 22.7727 8.68488C22.9627 8.73104 23.1442 8.78304 23.327 8.83704C24.5443 9.20154 25.6875 9.76371 26.7213 10.493C26.8608 10.5929 27.0077 10.6785 27.1437 10.7854C27.303 10.9087 27.4468 11.0519 27.6 11.1834C27.839 11.3914 28.0817 11.595 28.3095 11.8215C28.4177 11.9312 28.5122 12.055 28.6162 12.1692L25.6315 12.7069C24.7265 12.8712 24.1243 13.7372 24.2872 14.6437C24.4337 15.4494 25.1352 16.0142 25.9262 16.0142C26.0238 16.0142 26.1232 16.006 26.224 15.9882L33.1837 14.7334C33.6182 14.6552 34.004 14.4062 34.2563 14.0432C34.5087 13.6802 34.6063 13.2327 34.5282 12.7965L33.2733 5.83854C33.109 4.93354 32.2512 4.32821 31.3365 4.49421C30.4315 4.65854 29.8293 5.52454 29.9922 6.43104L30.5123 9.31104C30.4922 9.29104 30.4687 9.27504 30.4483 9.25521C30.0412 8.86204 29.656 8.51521 29.2725 8.21304Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span className="max-h-[40px] flex flex-col [&_p]:text-text [&_p]:dark:text-text-dark">
|
||||||
|
<p className="h-[28px] font-semibold text-[20px] p-0 m-0">{report?.expiredSubscriptionsCount.value}</p>
|
||||||
|
<p className="font-semibold opacity-50 p-0 m-0 text-[12px]">{t('totalExpiredSubscriptions')}</p>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
138
webapp/src/components/dashboard/home/incomeOutcome.tsx
Normal file
138
webapp/src/components/dashboard/home/incomeOutcome.tsx
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import { useAppSelector } from '@/redux/store';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import Cookies from 'universal-cookie';
|
||||||
|
const ReactApexChart = dynamic(() => import('react-apexcharts'), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function IncomeOutcome()
|
||||||
|
{
|
||||||
|
// get needed redux state
|
||||||
|
const report = useAppSelector((state) => state.statisticsReducer.value.report)
|
||||||
|
const currencySymbol = useAppSelector((state) => state.settingsReducer.value.appGeneralSettings.currencySymbol)
|
||||||
|
const themeType = useAppSelector((state) => state.themeTypeReducer.value.themeType)
|
||||||
|
// declare global variables
|
||||||
|
const cookies = new Cookies();
|
||||||
|
const t = useTranslations('statistics');
|
||||||
|
const locale = cookies.get("NEXT_LOCALE")
|
||||||
|
const isRtl = locale == 'ar' ? true : false
|
||||||
|
const isDark = themeType == 'dark' ? true : false
|
||||||
|
// if loading show load screen
|
||||||
|
if(!report) return
|
||||||
|
// prepare chart data
|
||||||
|
let data = [report?.totalIncome , report?.totalOutcome]
|
||||||
|
// prepare chart labels
|
||||||
|
let labels = [t('income') , t('outcome')]
|
||||||
|
// prepare chart options
|
||||||
|
var options = {
|
||||||
|
series: [{
|
||||||
|
name: currencySymbol,
|
||||||
|
data: data
|
||||||
|
}],
|
||||||
|
annotations: {
|
||||||
|
points: [{
|
||||||
|
x: t('incomes'),
|
||||||
|
seriesIndex: 0,
|
||||||
|
label: {
|
||||||
|
borderColor: '#775DD0',
|
||||||
|
offsetY: 0,
|
||||||
|
style: {
|
||||||
|
color: '#fff',
|
||||||
|
background: '#775DD0',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
chart: {
|
||||||
|
height: 350,
|
||||||
|
type: 'bar',
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
borderRadius: 10,
|
||||||
|
columnWidth: '50%',
|
||||||
|
horizontal: true,
|
||||||
|
distributed: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: 'top',
|
||||||
|
horizontalAlign: 'right',
|
||||||
|
fontSize: '16px',
|
||||||
|
markers: {
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
offsetX: isRtl ? 5 : -5,
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
type: isDark ? '' : 'gradient',
|
||||||
|
gradient: isDark ? {} : {
|
||||||
|
shadeIntensity: 1,
|
||||||
|
inverseColors: !1,
|
||||||
|
opacityFrom: isDark ? 0.19 : 0.28,
|
||||||
|
opacityTo: 0.05,
|
||||||
|
stops: isDark ? [100, 100] : [45, 100],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
colors: ['#0263FF' , '#E3342F'],
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
width: 2,
|
||||||
|
curve: 'straight',
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
row: {
|
||||||
|
colors: [isDark ? '#333' : '#fff', '#f2f2f2'] // Adjust the grid row color for dark mode
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
labels: {
|
||||||
|
rotate: -45,
|
||||||
|
offsetX: isRtl ? 2 : 0,
|
||||||
|
offsetY: 5,
|
||||||
|
style: {
|
||||||
|
colors: isDark ? '#fff' : '#000',
|
||||||
|
fontSize: '12px',
|
||||||
|
cssClass: 'apexcharts-xaxis-title',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
categories: labels,
|
||||||
|
tickPlacement: 'on'
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
tickAmount: 7,
|
||||||
|
labels: {
|
||||||
|
offsetX: isRtl ? -30 : -10,
|
||||||
|
offsetY: 0,
|
||||||
|
style: {
|
||||||
|
colors: isDark ? '#fff' : '#000',
|
||||||
|
fontSize: '12px',
|
||||||
|
cssClass: 'apexcharts-yaxis-title',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
theme: isDark ? 'dark' : 'light',
|
||||||
|
x: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// return the ui
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-5 [&_*]:dark:!text-text-dark p-5 w-full shadow border border-secondary-light bg-primary dark:bg-primary-dark rounded-[5px]">
|
||||||
|
<h3 className="text-xl font-bold">{t('incomeOutcomeGeneralOverview')}</h3>
|
||||||
|
<ReactApexChart series={options.series} options={options} type="bar" height={350} width={'100%'} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
687
webapp/src/components/dashboard/home/membersOverviewChart.tsx
Normal file
687
webapp/src/components/dashboard/home/membersOverviewChart.tsx
Normal file
@ -0,0 +1,687 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useAppSelector } from '@/redux/store';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import Cookies from 'universal-cookie';
|
||||||
|
const ReactApexChart = dynamic(() => import('react-apexcharts'), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { AppDispatch } from '@/redux/store';
|
||||||
|
import { setCurrentMembersGeneralOverviewDuration } from '@/redux/features/statistics-slice'
|
||||||
|
|
||||||
|
export default function MembersOverviewChart()
|
||||||
|
{
|
||||||
|
// get needed redux state
|
||||||
|
const report = useAppSelector((state) => state.statisticsReducer.value.report)
|
||||||
|
const currentMembersGeneralOverviewDuration = useAppSelector((state) => state.statisticsReducer.value.currentMembersGeneralOverviewDuration)
|
||||||
|
const themeType = useAppSelector((state) => state.themeTypeReducer.value.themeType)
|
||||||
|
// declare global variables
|
||||||
|
const dispatch = useDispatch<AppDispatch>()
|
||||||
|
const cookies = new Cookies();
|
||||||
|
const t = useTranslations('statistics');
|
||||||
|
const locale = cookies.get("NEXT_LOCALE")
|
||||||
|
const isRtl = locale == 'ar' ? true : false
|
||||||
|
const isDark = themeType == 'dark' ? true : false
|
||||||
|
const [chartSeries , setChartSeries] = useState<{}[]>([]);
|
||||||
|
const [labels , setLabels] = useState<string[]>([])
|
||||||
|
// revenue Chart
|
||||||
|
// prepare chart series and handle the transaction between currentMembersGeneralOverviewDuration
|
||||||
|
useEffect(() => {
|
||||||
|
// if chart duration is set to this week
|
||||||
|
if(currentMembersGeneralOverviewDuration == 'thisWeek')
|
||||||
|
{
|
||||||
|
// set the labels
|
||||||
|
setLabels([t('sat') , t('sun') , t('mon') , t('tue') , t('wed') , t('thu') , t('fri')])
|
||||||
|
// init this weekdata
|
||||||
|
let data_thisWeek = {
|
||||||
|
'sat': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'sun': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'mon': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'tue': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'wed': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'thu': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
},
|
||||||
|
'fri': {
|
||||||
|
'totalMembers': 0,
|
||||||
|
'totalActiveSubs': 0,
|
||||||
|
'totalUnActiveSubs': 0,
|
||||||
|
'totalMansMembers': 0,
|
||||||
|
'totalGirlsMembers': 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if there is a data from the backend we overwrite the currentData
|
||||||
|
if(report?.membersGeneralOverview?.value.thisWeek.days) {
|
||||||
|
data_thisWeek = report?.membersGeneralOverview?.value.thisWeek.days;
|
||||||
|
}
|
||||||
|
// prepare the chart series
|
||||||
|
setChartSeries([
|
||||||
|
{
|
||||||
|
name: t('totalMembers'),
|
||||||
|
data: [data_thisWeek['sat']['totalMembers'], data_thisWeek['sun']['totalMembers'], data_thisWeek['mon']['totalMembers'], data_thisWeek['tue']['totalMembers'], data_thisWeek['wed']['totalMembers'], data_thisWeek['thu']['totalMembers'], data_thisWeek['fri']['totalMembers']],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('totalActiveSubs'),
|
||||||
|
data: [data_thisWeek['sat']['totalActiveSubs'], data_thisWeek['sun']['totalActiveSubs'], data_thisWeek['mon']['totalActiveSubs'], data_thisWeek['tue']['totalActiveSubs'], data_thisWeek['wed']['totalActiveSubs'], data_thisWeek['thu']['totalActiveSubs'], data_thisWeek['fri']['totalActiveSubs']],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('totalUnActiveSubs'),
|
||||||
|
data: [data_thisWeek['sat']['totalUnActiveSubs'], data_thisWeek['sun']['totalUnActiveSubs'], data_thisWeek['mon']['totalUnActiveSubs'], data_thisWeek['tue']['totalUnActiveSubs'], data_thisWeek['wed']['totalUnActiveSubs'], data_thisWeek['thu']['totalUnActiveSubs'], data_thisWeek['fri']['totalUnActiveSubs']],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('totalMansMembers'),
|
||||||
|
data: [data_thisWeek['sat']['totalMansMembers'], data_thisWeek['sun']['totalMansMembers'], data_thisWeek['mon']['totalMansMembers'], data_thisWeek['tue']['totalMansMembers'], data_thisWeek['wed']['totalMansMembers'], data_thisWeek['thu']['totalMansMembers'], data_thisWeek['fri']['totalMansMembers']],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('totalGirlsMembers'),
|
||||||
|
data: [data_thisWeek['sat']['totalGirlsMembers'], data_thisWeek['sun']['totalGirlsMembers'], data_thisWeek['mon']['totalGirlsMembers'], data_thisWeek['tue']['totalGirlsMembers'], data_thisWeek['wed']['totalGirlsMembers'], data_thisWeek['thu']['totalGirlsMembers'], data_thisWeek['fri']['totalGirlsMembers']],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}
|
||||||
|
// if chart duration is set to this month
|
||||||
|
else if(currentMembersGeneralOverviewDuration == 'thisMonth')
|
||||||
|
{
|
||||||
|
let thisMonthTotalSum : {
|
||||||
|
week : number,
|
||||||
|
thisWeekSum : {
|
||||||
|
"totalMembers": number,
|
||||||
|
"totalActiveSubs": number,
|
||||||
|
"totalUnActiveSubs": number,
|
||||||
|
"totalMansMembers": number,
|
||||||
|
"totalGirlsMembers": number
|
||||||
|
}
|
||||||
|
}[];
|
||||||
|
if(report?.membersGeneralOverview?.value.thisMonth)
|
||||||
|
{
|
||||||
|
// if we founded a data for the current month
|
||||||
|
thisMonthTotalSum = report?.membersGeneralOverview?.value.thisMonth.weeks.map((v : any , i : number) : {
|
||||||
|
week : number,
|
||||||
|
thisWeekSum : {
|
||||||
|
"totalMembers": number,
|
||||||
|
"totalActiveSubs": number,
|
||||||
|
"totalUnActiveSubs": number,
|
||||||
|
"totalMansMembers": number,
|
||||||
|
"totalGirlsMembers": number
|
||||||
|
}
|
||||||
|
} => {
|
||||||
|
// we init the this week sum
|
||||||
|
let thisWeekSum = {
|
||||||
|
"totalMembers": 0,
|
||||||
|
"totalActiveSubs": 0,
|
||||||
|
"totalUnActiveSubs": 0,
|
||||||
|
"totalMansMembers": 0,
|
||||||
|
"totalGirlsMembers": 0
|
||||||
|
}
|
||||||
|
// we map through this month weeks
|
||||||
|
v.days.map((w : {
|
||||||
|
'sat': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'sun': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'mon': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'tue': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'wed': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'thu': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'fri': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
}
|
||||||
|
} , i : number) => {
|
||||||
|
// we loop through week days and we sum there data
|
||||||
|
Object.keys(w).forEach(function(key : "sat" | "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "_id" , index : number) {
|
||||||
|
if(key == '_id') return
|
||||||
|
thisWeekSum.totalMembers = thisWeekSum.totalMembers + w[key].totalMembers
|
||||||
|
thisWeekSum.totalActiveSubs = thisWeekSum.totalActiveSubs + w[key].totalActiveSubs
|
||||||
|
thisWeekSum.totalUnActiveSubs = thisWeekSum.totalUnActiveSubs + w[key].totalUnActiveSubs
|
||||||
|
thisWeekSum.totalMansMembers = thisWeekSum.totalMansMembers + w[key].totalMansMembers
|
||||||
|
thisWeekSum.totalGirlsMembers = thisWeekSum.totalGirlsMembers + w[key].totalGirlsMembers
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// each week we return there sum data
|
||||||
|
return {
|
||||||
|
week: v.week,
|
||||||
|
thisWeekSum: thisWeekSum
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let labels : [] = [];
|
||||||
|
thisMonthTotalSum.forEach(function(v , i) {
|
||||||
|
labels.push(t('week')+ ' ' +thisMonthTotalSum[i]["week"])
|
||||||
|
})
|
||||||
|
// set the labels
|
||||||
|
setLabels(labels)
|
||||||
|
// we setup chart_series ( chart data )
|
||||||
|
let a : {
|
||||||
|
name: string,
|
||||||
|
data: {
|
||||||
|
"totalMembers": number,
|
||||||
|
"totalActiveSubs": number,
|
||||||
|
"totalUnActiveSubs": number,
|
||||||
|
"totalMansMembers": number,
|
||||||
|
"totalGirlsMembers": number
|
||||||
|
}
|
||||||
|
}[] = ['totalMembers' , 'totalActiveSubs' , 'totalUnActiveSubs' , 'totalMansMembers' , 'totalGirlsMembers'].map((v : string , i : number) : {
|
||||||
|
name: string,
|
||||||
|
data: {
|
||||||
|
"totalMembers": number,
|
||||||
|
"totalActiveSubs": number,
|
||||||
|
"totalUnActiveSubs": number,
|
||||||
|
"totalMansMembers": number,
|
||||||
|
"totalGirlsMembers": number
|
||||||
|
}
|
||||||
|
} => {
|
||||||
|
let data = thisMonthTotalSum.map((v2 : {
|
||||||
|
week: number,
|
||||||
|
thisWeekSum: {
|
||||||
|
"totalMembers": number,
|
||||||
|
"totalActiveSubs": number,
|
||||||
|
"totalUnActiveSubs": number,
|
||||||
|
"totalMansMembers": number,
|
||||||
|
"totalGirlsMembers": number
|
||||||
|
}
|
||||||
|
},i2 : number) => {
|
||||||
|
let index : 'totalMembers' | 'totalActiveSubs' | 'totalUnActiveSubs' | 'totalMansMembers' | 'totalGirlsMembers' = v;
|
||||||
|
return v2.thisWeekSum[index];
|
||||||
|
})
|
||||||
|
// return the line data
|
||||||
|
return {
|
||||||
|
name: t(v),
|
||||||
|
data: data
|
||||||
|
};
|
||||||
|
})
|
||||||
|
setChartSeries(a)
|
||||||
|
}
|
||||||
|
// if there was no data we use default return
|
||||||
|
else {
|
||||||
|
setChartSeries([
|
||||||
|
{
|
||||||
|
name: '1'+t('week'),
|
||||||
|
data: [0,0,0,0,0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '2'+t('week'),
|
||||||
|
data: [0,0,0,0,0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '3'+t('week'),
|
||||||
|
data: [0,0,0,0,0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '4'+t('week'),
|
||||||
|
data: [0,0,0,0,0],
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if chart duration is set to this month
|
||||||
|
else if(currentMembersGeneralOverviewDuration == 'thisYear')
|
||||||
|
{
|
||||||
|
// set the labels
|
||||||
|
setLabels([t('1month'), t('2month'), t('3month'), t('4month'), t('5month'), t('6month'), t('7month'), t('8month'), t('9month'), t('10month'), t('11month')])
|
||||||
|
// check if there is current month report
|
||||||
|
let thisYearTotalSum : {
|
||||||
|
month: number,
|
||||||
|
thisMonthWeeksSum: {
|
||||||
|
"totalMembers": number,
|
||||||
|
"totalActiveSubs": number,
|
||||||
|
"totalUnActiveSubs": number,
|
||||||
|
"totalMansMembers": number,
|
||||||
|
"totalGirlsMembers": number
|
||||||
|
}
|
||||||
|
}[]
|
||||||
|
if(report?.membersGeneralOverview?.value.thisYear)
|
||||||
|
{
|
||||||
|
// if we founded a data for the current month
|
||||||
|
thisYearTotalSum = report?.membersGeneralOverview?.value.thisYear.months.map((v : any , i : number) : {
|
||||||
|
month : number,
|
||||||
|
thisMonthWeeksSum : {
|
||||||
|
"totalMembers": number,
|
||||||
|
"totalActiveSubs": number,
|
||||||
|
"totalUnActiveSubs": number,
|
||||||
|
"totalMansMembers": number,
|
||||||
|
"totalGirlsMembers": number
|
||||||
|
}
|
||||||
|
} => {
|
||||||
|
let summedWeeks = v.weeks.map((w : any , i : number) => {
|
||||||
|
// we map through this month weeks
|
||||||
|
let weekSum = w.days.map((d : {
|
||||||
|
'sat': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'sun': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'mon': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'tue': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'wed': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'thu': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
},
|
||||||
|
'fri': {
|
||||||
|
'totalMembers': number,
|
||||||
|
'totalActiveSubs': number,
|
||||||
|
'totalUnActiveSubs': number,
|
||||||
|
'totalMansMembers': number,
|
||||||
|
'totalGirlsMembers': number,
|
||||||
|
}
|
||||||
|
} , i : number) => {
|
||||||
|
// we init the this week sum
|
||||||
|
let thisWeekSum = {
|
||||||
|
"totalMembers": 0,
|
||||||
|
"totalActiveSubs": 0,
|
||||||
|
"totalUnActiveSubs": 0,
|
||||||
|
"totalMansMembers": 0,
|
||||||
|
"totalGirlsMembers": 0
|
||||||
|
}
|
||||||
|
// we loop through week days and we sum there data
|
||||||
|
Object.keys(d).forEach(function(key : "sat" | "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "_id" , index : number) {
|
||||||
|
if(key == '_id') return
|
||||||
|
thisWeekSum.totalMembers = thisWeekSum.totalMembers + d[key].totalMembers
|
||||||
|
thisWeekSum.totalActiveSubs = thisWeekSum.totalActiveSubs + d[key].totalActiveSubs
|
||||||
|
thisWeekSum.totalUnActiveSubs = thisWeekSum.totalUnActiveSubs + d[key].totalUnActiveSubs
|
||||||
|
thisWeekSum.totalMansMembers = thisWeekSum.totalMansMembers + d[key].totalMansMembers
|
||||||
|
thisWeekSum.totalGirlsMembers = thisWeekSum.totalGirlsMembers + d[key].totalGirlsMembers
|
||||||
|
})
|
||||||
|
// each week we return there sum data
|
||||||
|
return thisWeekSum
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
week: w.week,
|
||||||
|
weekSum: weekSum
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let thisMonthWeeksSum = {
|
||||||
|
"totalMembers": 0,
|
||||||
|
"totalActiveSubs": 0,
|
||||||
|
"totalUnActiveSubs": 0,
|
||||||
|
"totalMansMembers": 0,
|
||||||
|
"totalGirlsMembers": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
summedWeeks.map((v : {week: number, weekSum: {
|
||||||
|
"totalMembers": number,
|
||||||
|
"totalActiveSubs": number,
|
||||||
|
"totalUnActiveSubs": number,
|
||||||
|
"totalMansMembers": number,
|
||||||
|
"totalGirlsMembers": number
|
||||||
|
}[]},i : number) => {
|
||||||
|
thisMonthWeeksSum.totalMembers += v.weekSum[0].totalMembers
|
||||||
|
thisMonthWeeksSum.totalActiveSubs += v.weekSum[0].totalActiveSubs
|
||||||
|
thisMonthWeeksSum.totalUnActiveSubs += v.weekSum[0].totalUnActiveSubs
|
||||||
|
thisMonthWeeksSum.totalMansMembers += v.weekSum[0].totalMansMembers
|
||||||
|
thisMonthWeeksSum.totalGirlsMembers += v.weekSum[0].totalGirlsMembers
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
month: v.month,
|
||||||
|
thisMonthWeeksSum: thisMonthWeeksSum
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// prepare the labels
|
||||||
|
let labels : [] = [];
|
||||||
|
thisYearTotalSum.forEach(function(v , i) {
|
||||||
|
labels.push(t(thisYearTotalSum[i]["month"]+"month"))
|
||||||
|
})
|
||||||
|
setLabels(labels)
|
||||||
|
// we setup chart_series ( chart data )
|
||||||
|
let a : {
|
||||||
|
name: string,
|
||||||
|
data: {
|
||||||
|
"totalMembers": number,
|
||||||
|
"totalActiveSubs": number,
|
||||||
|
"totalUnActiveSubs": number,
|
||||||
|
"totalMansMembers": number,
|
||||||
|
"totalGirlsMembers": number
|
||||||
|
}
|
||||||
|
}[] = ['totalMembers' , 'totalActiveSubs' , 'totalUnActiveSubs' , 'totalMansMembers' , 'totalGirlsMembers'].map((v : string , i : number) : {
|
||||||
|
name: string,
|
||||||
|
data: {
|
||||||
|
"totalMembers": number,
|
||||||
|
"totalActiveSubs": number,
|
||||||
|
"totalUnActiveSubs": number,
|
||||||
|
"totalMansMembers": number,
|
||||||
|
"totalGirlsMembers": number
|
||||||
|
}
|
||||||
|
} => {
|
||||||
|
let data = thisYearTotalSum.map((v2 : {
|
||||||
|
month: number,
|
||||||
|
thisMonthWeeksSum: {
|
||||||
|
"totalMembers": number,
|
||||||
|
"totalActiveSubs": number,
|
||||||
|
"totalUnActiveSubs": number,
|
||||||
|
"totalMansMembers": number,
|
||||||
|
"totalGirlsMembers": number
|
||||||
|
}
|
||||||
|
},i2 : number) => {
|
||||||
|
let index : 'totalMembers' | 'totalActiveSubs' | 'totalUnActiveSubs' | 'totalMansMembers' | 'totalGirlsMembers' = v;
|
||||||
|
return v2.thisMonthWeeksSum[index];
|
||||||
|
})
|
||||||
|
// return the line data
|
||||||
|
return {
|
||||||
|
name: t(v),
|
||||||
|
data: data
|
||||||
|
};
|
||||||
|
})
|
||||||
|
setChartSeries(a)
|
||||||
|
}
|
||||||
|
// if there was no data we use default return
|
||||||
|
else {
|
||||||
|
setChartSeries([
|
||||||
|
{
|
||||||
|
name: '1'+t('week'),
|
||||||
|
data: [0,0,0,0,0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '2'+t('week'),
|
||||||
|
data: [0,0,0,0,0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '3'+t('week'),
|
||||||
|
data: [0,0,0,0,0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '4'+t('week'),
|
||||||
|
data: [0,0,0,0,0],
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [currentMembersGeneralOverviewDuration])
|
||||||
|
// prepare the chart options
|
||||||
|
const options : any = {
|
||||||
|
series: chartSeries,
|
||||||
|
options: {
|
||||||
|
chart: {
|
||||||
|
height: 325,
|
||||||
|
type: 'area',
|
||||||
|
fontFamily: 'Nunito, sans-serif',
|
||||||
|
zoom: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
toolbar: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data_thisWeekLabels: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
show: true,
|
||||||
|
curve: 'smooth',
|
||||||
|
width: 2,
|
||||||
|
lineCap: 'square',
|
||||||
|
},
|
||||||
|
dropShadow: {
|
||||||
|
enabled: true,
|
||||||
|
opacity: 0.2,
|
||||||
|
blur: 10,
|
||||||
|
left: -7,
|
||||||
|
top: 22,
|
||||||
|
},
|
||||||
|
colors: ['#38C172', '#38C172' , '#E3342F' , '#0263FF', '#FF30F7'],
|
||||||
|
markers: {
|
||||||
|
//discrete: [
|
||||||
|
// {
|
||||||
|
// seriesIndex: 0,
|
||||||
|
// data_thisWeekPointIndex: 1,
|
||||||
|
// fillColor: '#38C172',
|
||||||
|
// strokeColor: 'transparent',
|
||||||
|
// size: 7,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// seriesIndex: 1,
|
||||||
|
// data_thisWeekPointIndex: 1,
|
||||||
|
// fillColor: '#E3342F',
|
||||||
|
// strokeColor: 'transparent',
|
||||||
|
// size: 7,
|
||||||
|
// },
|
||||||
|
//],
|
||||||
|
},
|
||||||
|
labels: labels,
|
||||||
|
xaxis: {
|
||||||
|
axisBorder: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisTicks: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
crosshairs: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
offsetX: isRtl ? 2 : 0,
|
||||||
|
offsetY: 5,
|
||||||
|
style: {
|
||||||
|
colors: isDark ? '#fff' : '#000',
|
||||||
|
fontSize: '12px',
|
||||||
|
cssClass: 'apexcharts-xaxis-title',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
tickAmount: 7,
|
||||||
|
labels: {
|
||||||
|
formatter: (value: number) => {
|
||||||
|
if(value > 1000)
|
||||||
|
{
|
||||||
|
return parseInt(value.toFixed(0)) + t('k')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return parseInt(value.toFixed(0))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
offsetX: isRtl ? -30 : -10,
|
||||||
|
offsetY: 0,
|
||||||
|
style: {
|
||||||
|
colors: isDark ? '#fff' : '#000',
|
||||||
|
fontSize: '12px',
|
||||||
|
cssClass: 'apexcharts-yaxis-title',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
borderColor: isDark ? '#191E3A' : '#E0E6ED',
|
||||||
|
strokeDashArray: 5,
|
||||||
|
xaxis: {
|
||||||
|
lines: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
lines: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: 'top',
|
||||||
|
horizontalAlign: 'right',
|
||||||
|
fontSize: '16px',
|
||||||
|
markers: {
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
offsetX: isRtl ? 5 : -5,
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
theme: isDark ? 'dark' : 'light',
|
||||||
|
x: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
type: 'gradient',
|
||||||
|
gradient: {
|
||||||
|
shadeIntensity: 1,
|
||||||
|
inverseColors: !1,
|
||||||
|
opacityFrom: isDark ? 0.19 : 0.28,
|
||||||
|
opacityTo: 0.05,
|
||||||
|
stops: isDark ? [100, 100] : [45, 100],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
responsive: [{
|
||||||
|
breakpoint: 420,
|
||||||
|
options: {
|
||||||
|
yaxis: {
|
||||||
|
show: false,
|
||||||
|
showAlways: false,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
offsetY: 10,
|
||||||
|
height: 60,
|
||||||
|
position: 'bottom',
|
||||||
|
horizontalAlign: 'center',
|
||||||
|
fontSize: '12px',
|
||||||
|
markers: {
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
offsetX: isRtl ? 5 : -5,
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// return the ui
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-5 [&_*]:dark:!text-text-dark p-5 lg:w-2/3 w-full shadow border border-secondary-light bg-primary dark:bg-primary-dark rounded-[5px]">
|
||||||
|
<h3 className="text-xl font-bold">{t('membersGeneralOverview')}</h3>
|
||||||
|
<div className="w-full flex gap-3 justify-end">
|
||||||
|
<button disabled={currentMembersGeneralOverviewDuration == 'thisWeek'} onClick={() => {
|
||||||
|
dispatch(setCurrentMembersGeneralOverviewDuration('thisWeek'))
|
||||||
|
}} className="disabled:dark:opacity-[0.5] disabled:opacity-[0.5]">
|
||||||
|
{t('thisWeek')}
|
||||||
|
</button>
|
||||||
|
<button disabled={currentMembersGeneralOverviewDuration == 'thisMonth'} onClick={() => {
|
||||||
|
dispatch(setCurrentMembersGeneralOverviewDuration('thisMonth'))
|
||||||
|
}} className="disabled:dark:opacity-[0.5] disabled:opacity-[0.5]">
|
||||||
|
{t('thisMonth')}
|
||||||
|
</button>
|
||||||
|
<button disabled={currentMembersGeneralOverviewDuration == 'thisYear'} onClick={() => {
|
||||||
|
dispatch(setCurrentMembersGeneralOverviewDuration('thisYear'))
|
||||||
|
}} className="disabled:dark:opacity-[0.5] disabled:opacity-[0.5]">
|
||||||
|
{t('thisYear')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<ReactApexChart series={options.series} options={options.options} type="area" height={350} width={'100%'} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
147
webapp/src/components/dashboard/home/servicesSubscriptions.tsx
Normal file
147
webapp/src/components/dashboard/home/servicesSubscriptions.tsx
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import { useAppSelector } from '@/redux/store';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import Cookies from 'universal-cookie';
|
||||||
|
const ReactApexChart = dynamic(() => import('react-apexcharts'), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function ServicesSubscriptions()
|
||||||
|
{
|
||||||
|
// get redux needed state
|
||||||
|
const report = useAppSelector((state) => state.statisticsReducer.value.report)
|
||||||
|
const themeType = useAppSelector((state) => state.themeTypeReducer.value.themeType)
|
||||||
|
// declare global variables
|
||||||
|
const cookies = new Cookies();
|
||||||
|
const t = useTranslations('statistics');
|
||||||
|
const locale = cookies.get("NEXT_LOCALE")
|
||||||
|
const isRtl = locale == 'ar' ? true : false
|
||||||
|
const isDark = themeType == 'dark' ? true : false
|
||||||
|
// if loading show load screen
|
||||||
|
if(!report) return
|
||||||
|
// setup chart data
|
||||||
|
let data = report?.servicesNameAndSubscribers.value.map((v : {
|
||||||
|
_id: string,
|
||||||
|
name: string,
|
||||||
|
totalSubscribers: number,
|
||||||
|
} , i : number) => {
|
||||||
|
return v?.totalSubscribers;
|
||||||
|
})
|
||||||
|
// setup chart labels
|
||||||
|
let labels = report?.servicesNameAndSubscribers.value.map((v : {
|
||||||
|
_id: string,
|
||||||
|
name: string,
|
||||||
|
totalSubscribers: number,
|
||||||
|
} , i : number) => {
|
||||||
|
return v?.name;
|
||||||
|
})
|
||||||
|
// setup chart options
|
||||||
|
var options = {
|
||||||
|
series: [{
|
||||||
|
name: t('subscription'),
|
||||||
|
data: data
|
||||||
|
}],
|
||||||
|
annotations: {
|
||||||
|
points: [{
|
||||||
|
x: t('services'),
|
||||||
|
seriesIndex: 0,
|
||||||
|
label: {
|
||||||
|
borderColor: '#775DD0',
|
||||||
|
offsetY: 0,
|
||||||
|
style: {
|
||||||
|
color: '#fff',
|
||||||
|
background: '#775DD0',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
chart: {
|
||||||
|
height: 350,
|
||||||
|
type: 'bar',
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
borderRadius: 10,
|
||||||
|
columnWidth: '50%',
|
||||||
|
distributed: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
type: isDark ? '' : 'gradient',
|
||||||
|
gradient: isDark ? {} : {
|
||||||
|
shadeIntensity: 1,
|
||||||
|
inverseColors: !1,
|
||||||
|
opacityFrom: isDark ? 0.19 : 0.28,
|
||||||
|
opacityTo: 0.05,
|
||||||
|
stops: isDark ? [100, 100] : [45, 100],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: 'top',
|
||||||
|
horizontalAlign: 'right',
|
||||||
|
fontSize: '16px',
|
||||||
|
markers: {
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
offsetX: isRtl ? 5 : -5,
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
colors: ['#38C172', '#38C172' , '#E3342F' , '#0263FF', '#FF30F7'],
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
width: 2,
|
||||||
|
curve: 'straight',
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
row: {
|
||||||
|
colors: [isDark ? '#333' : '#fff', '#f2f2f2'] // Adjust the grid row color for dark mode
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
labels: {
|
||||||
|
rotate: -45,
|
||||||
|
offsetX: isRtl ? 2 : 0,
|
||||||
|
offsetY: 5,
|
||||||
|
style: {
|
||||||
|
colors: isDark ? '#fff' : '#000',
|
||||||
|
fontSize: '12px',
|
||||||
|
cssClass: 'apexcharts-xaxis-title',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
categories: labels,
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
tickAmount: 7,
|
||||||
|
labels: {
|
||||||
|
offsetX: isRtl ? -30 : -10,
|
||||||
|
offsetY: 0,
|
||||||
|
style: {
|
||||||
|
colors: isDark ? '#fff' : '#000',
|
||||||
|
fontSize: '12px',
|
||||||
|
cssClass: 'apexcharts-yaxis-title',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
theme: isDark ? 'dark' : 'light',
|
||||||
|
x: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// return the ui
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-5 [&_*]:dark:!text-text-dark p-5 lg:w-1/2 w-full shadow border border-secondary-light bg-primary dark:bg-primary-dark rounded-[5px]">
|
||||||
|
<h3 className="text-xl font-bold">{t('sevicesGeneralOverview')}</h3>
|
||||||
|
<ReactApexChart series={options.series} options={options} type="bar" height={350} width={'100%'} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
148
webapp/src/components/dashboard/home/workersJobTypes.tsx
Normal file
148
webapp/src/components/dashboard/home/workersJobTypes.tsx
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import { useAppSelector } from '@/redux/store';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import Cookies from 'universal-cookie';
|
||||||
|
const ReactApexChart = dynamic(() => import('react-apexcharts'), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function WorkersJobTypes()
|
||||||
|
{
|
||||||
|
// get needed redux state
|
||||||
|
const report = useAppSelector((state) => state.statisticsReducer.value.report)
|
||||||
|
const themeType = useAppSelector((state) => state.themeTypeReducer.value.themeType)
|
||||||
|
// declare global variables
|
||||||
|
const cookies = new Cookies();
|
||||||
|
const t = useTranslations('statistics');
|
||||||
|
const locale = cookies.get("NEXT_LOCALE")
|
||||||
|
const isRtl = locale == 'ar' ? true : false
|
||||||
|
const isDark = themeType == 'dark' ? true : false
|
||||||
|
// if loading show load screen
|
||||||
|
if(!report) return
|
||||||
|
// prepare chart data
|
||||||
|
let data = report?.workersNameAndJobType.value.map((v : {
|
||||||
|
_id: string,
|
||||||
|
jobType: string,
|
||||||
|
count: number,
|
||||||
|
} , i : number) => {
|
||||||
|
return v?.count;
|
||||||
|
})
|
||||||
|
// prepare chart labels
|
||||||
|
let labels = report?.workersNameAndJobType.value.map((v : {
|
||||||
|
_id: string,
|
||||||
|
jobType: string,
|
||||||
|
count: number,
|
||||||
|
} , i : number) => {
|
||||||
|
return v?.jobType;
|
||||||
|
})
|
||||||
|
// prepare chart options
|
||||||
|
var options = {
|
||||||
|
series: [{
|
||||||
|
name: t('worker'),
|
||||||
|
data: data
|
||||||
|
}],
|
||||||
|
annotations: {
|
||||||
|
points: [{
|
||||||
|
x: t('workers'),
|
||||||
|
seriesIndex: 0,
|
||||||
|
label: {
|
||||||
|
borderColor: '#775DD0',
|
||||||
|
offsetY: 0,
|
||||||
|
style: {
|
||||||
|
color: '#fff',
|
||||||
|
background: '#775DD0',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
chart: {
|
||||||
|
height: 350,
|
||||||
|
type: 'bar',
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
borderRadius: 10,
|
||||||
|
columnWidth: '50%',
|
||||||
|
distributed: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
width: 2,
|
||||||
|
curve: 'straight',
|
||||||
|
},
|
||||||
|
colors: ['#38C172', '#38C172' , '#E3342F' , '#0263FF', '#FF30F7'],
|
||||||
|
grid: {
|
||||||
|
row: {
|
||||||
|
colors: [isDark ? '#333' : '#fff', '#f2f2f2'] // Adjust the grid row color for dark mode
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
type: isDark ? '' : 'gradient',
|
||||||
|
gradient: isDark ? {} : {
|
||||||
|
shadeIntensity: 1,
|
||||||
|
inverseColors: !1,
|
||||||
|
opacityFrom: isDark ? 0.19 : 0.28,
|
||||||
|
opacityTo: 0.05,
|
||||||
|
stops: isDark ? [100, 100] : [45, 100],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: 'top',
|
||||||
|
horizontalAlign: 'right',
|
||||||
|
fontSize: '16px',
|
||||||
|
markers: {
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
offsetX: isRtl ? 5 : -5,
|
||||||
|
},
|
||||||
|
itemMargin: {
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
labels: {
|
||||||
|
rotate: -45,
|
||||||
|
offsetX: isRtl ? 2 : 0,
|
||||||
|
offsetY: 5,
|
||||||
|
style: {
|
||||||
|
colors: isDark ? '#fff' : '#000',
|
||||||
|
fontSize: '12px',
|
||||||
|
cssClass: 'apexcharts-xaxis-title',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
categories: labels,
|
||||||
|
tickPlacement: 'on'
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
tickAmount: 7,
|
||||||
|
labels: {
|
||||||
|
offsetX: isRtl ? -30 : -10,
|
||||||
|
offsetY: 0,
|
||||||
|
style: {
|
||||||
|
colors: isDark ? '#fff' : '#000',
|
||||||
|
fontSize: '12px',
|
||||||
|
cssClass: 'apexcharts-yaxis-title',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
theme: isDark ? 'dark' : 'light',
|
||||||
|
x: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// return the ui
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-5 [&_*]:dark:!text-text-dark p-5 lg:w-1/2 w-full shadow border border-secondary-light bg-primary dark:bg-primary-dark rounded-[5px]">
|
||||||
|
<h3 className="text-xl font-bold">{t('workersGeneralOverview')}</h3>
|
||||||
|
<ReactApexChart series={options.series} options={options} type="bar" height={350} width={'100%'} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
180
webapp/src/components/dashboard/incomes/incomesList.tsx
Normal file
180
webapp/src/components/dashboard/incomes/incomesList.tsx
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { useAppSelector } from '@/redux/store';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import { AppDispatch } from '@/redux/store';
|
||||||
|
import { setSearchKeyword ,load , search , delete_ } from '@/redux/features/incomes-slice'
|
||||||
|
import ListPagination from './parts/incomesListPagination'
|
||||||
|
import { setDetailsPopUp , setUpdatePopUp , setCurrentPage } from '@/redux/features/incomes-slice';
|
||||||
|
|
||||||
|
export default function List()
|
||||||
|
{
|
||||||
|
const t = useTranslations('incomes');
|
||||||
|
const dispatch = useDispatch<AppDispatch>()
|
||||||
|
|
||||||
|
const listContent = useAppSelector((state) => state.incomesReducer.value.listContent)
|
||||||
|
const isLoading = useAppSelector((state) => state.incomesReducer.value.isLoading)
|
||||||
|
const loadedFirstTime = useAppSelector((state) => state.incomesReducer.value.loadedFirstTime)
|
||||||
|
const searchKeyword = useAppSelector((state) => state.incomesReducer.value.searchKeyword)
|
||||||
|
const currentPage = useAppSelector((state) => state.incomesReducer.value.currentPage)
|
||||||
|
const appGeneralSettings = useAppSelector((state) => state.settingsReducer.value.appGeneralSettings)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(loadedFirstTime) return
|
||||||
|
if(isLoading) return
|
||||||
|
async function a()
|
||||||
|
{
|
||||||
|
await dispatch(load({page : 1}))
|
||||||
|
}
|
||||||
|
a()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// convert unix date into human read able format year-month-day
|
||||||
|
function UnixToReadAbleDate(unix_time : number)
|
||||||
|
{
|
||||||
|
let dateObj = new Date(unix_time * 1000)
|
||||||
|
// we got the year
|
||||||
|
let year = dateObj.getUTCFullYear(),
|
||||||
|
// get month
|
||||||
|
month = dateObj.getUTCMonth(),
|
||||||
|
// get day
|
||||||
|
day = dateObj.getUTCDay();
|
||||||
|
// now we format the string
|
||||||
|
let formattedTime = year.toString()+ '-' + month.toString() + '-' + day.toString();
|
||||||
|
return formattedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-3 w-full w-auto">
|
||||||
|
<header className="w-full flex lg:flex-row flex-col lg:justify-between lg:items-center gap-3">
|
||||||
|
<h1 className="font-semibold text-[20px] text-text dark:text-text-dark">{t('incomes')}</h1>
|
||||||
|
<label htmlFor="email" className="relative text-gray-400 focus-within:text-gray-600 block">
|
||||||
|
<svg className="text-secondary dark:text-primary/50 cursor-pointer w-[15px] h-[15px] absolute top-1/2 transform -translate-y-1/2 rtl:left-3 ltr:right-3" width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M18.4479 16.9688L14.9063 13.4375C16.0489 11.9817 16.669 10.184 16.6667 8.33334C16.6667 6.68516 16.1779 5.074 15.2623 3.70359C14.3466 2.33318 13.0451 1.26507 11.5224 0.634341C9.99965 0.00361068 8.32409 -0.161417 6.70759 0.160126C5.09108 0.48167 3.60622 1.27534 2.44078 2.44078C1.27534 3.60622 0.48167 5.09108 0.160126 6.70759C-0.161417 8.32409 0.00361068 9.99965 0.634341 11.5224C1.26507 13.0451 2.33318 14.3466 3.70359 15.2623C5.074 16.1779 6.68516 16.6667 8.33334 16.6667C10.184 16.669 11.9817 16.0489 13.4375 14.9063L16.9688 18.4479C17.0656 18.5456 17.1808 18.6231 17.3077 18.6759C17.4347 18.7288 17.5708 18.756 17.7083 18.756C17.8459 18.756 17.982 18.7288 18.1089 18.6759C18.2359 18.6231 18.3511 18.5456 18.4479 18.4479C18.5456 18.3511 18.6231 18.2359 18.6759 18.1089C18.7288 17.982 18.756 17.8459 18.756 17.7083C18.756 17.5708 18.7288 17.4347 18.6759 17.3077C18.6231 17.1808 18.5456 17.0656 18.4479 16.9688ZM2.08334 8.33334C2.08334 7.0972 2.44989 5.88883 3.13665 4.86102C3.82341 3.83322 4.79953 3.03214 5.94157 2.55909C7.0836 2.08604 8.34027 1.96227 9.55265 2.20343C10.765 2.44459 11.8787 3.03984 12.7528 3.91392C13.6268 4.788 14.2221 5.90164 14.4632 7.11402C14.7044 8.3264 14.5806 9.58307 14.1076 10.7251C13.6345 11.8671 12.8335 12.8433 11.8057 13.53C10.7778 14.2168 9.56947 14.5833 8.33334 14.5833C6.67573 14.5833 5.08602 13.9249 3.91392 12.7528C2.74182 11.5807 2.08334 9.99094 2.08334 8.33334Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<input onChange={(async(e) => {
|
||||||
|
if(e.target.value.length == 0)
|
||||||
|
{
|
||||||
|
dispatch(setCurrentPage(1));
|
||||||
|
}
|
||||||
|
dispatch(setSearchKeyword(e.target.value))
|
||||||
|
await dispatch(search({
|
||||||
|
searchKeyword: e.target.value
|
||||||
|
}))
|
||||||
|
})}
|
||||||
|
value={searchKeyword}
|
||||||
|
className="lg:w-[300px] w-full text-text dark:text-text-dark rounded-[5px] px-3 h-[50px] border-[1px] !border-secondary-light bg-primary-dark/5 placeholder:font-semibold" type="text" placeholder={t('searchForIncomes')}></input>
|
||||||
|
</label>
|
||||||
|
</header>
|
||||||
|
<div className="panel !p-0 mb-7 dark:bg-primary-dark">
|
||||||
|
<header className="w-full lg:flex gap-3 hidden justify-between items-center p-5 opacity-50 font-semibold border-b-[1px] border-secondary-light">
|
||||||
|
<span className="w-full text-start">#</span>
|
||||||
|
<span className="w-full text-start">{t('name')}</span>
|
||||||
|
<span className="w-full text-start">{t('amount')}</span>
|
||||||
|
<span className="w-full text-start">{t('addedAt')}</span>
|
||||||
|
<span className="w-full text-start">{t('actions')}</span>
|
||||||
|
</header>
|
||||||
|
<main className="flex flex-col justify-center items-center">
|
||||||
|
{
|
||||||
|
!isLoading && listContent.flat().length == 0 && currentPage >= 2 && searchKeyword.length == 0 ?
|
||||||
|
<div className="p-5 py-10 flex justify-start items-center gap-3 [&_*]:fill-warning">
|
||||||
|
<svg width="25" height="25" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path opacity="0.5" d="M15.2069 2.85079C15.6886 1.98507 16.3858 1.26524 17.2277 0.76451C18.0696 0.263778 19.0261 0 20 0C20.9739 0 21.9304 0.263778 22.7723 0.76451C23.6142 1.26524 24.3114 1.98507 24.7931 2.85079L39.309 29.3012C39.77 30.1312 40.0083 31.0711 39.9998 32.0257C39.9913 32.9802 39.7363 33.9155 39.2607 34.7368C38.7781 35.5842 38.085 36.286 37.2518 36.7713C36.4186 37.2565 35.4747 37.5078 34.5159 37.4998H5.48407C4.52527 37.5078 3.58143 37.2565 2.74819 36.7713C1.91496 36.286 1.22192 35.5842 0.739307 34.7368C0.263677 33.9155 0.00870576 32.9802 0.000218482 32.0257C-0.0082688 31.0711 0.230027 30.1312 0.690974 29.3012L15.2069 2.85079ZM20 30.6818C20.3296 30.6818 20.6518 30.5819 20.9259 30.3946C21.2 30.2073 21.4136 29.9411 21.5397 29.6296C21.6659 29.3182 21.6989 28.9754 21.6346 28.6448C21.5703 28.3142 21.4115 28.0104 21.1785 27.7721C20.9454 27.5337 20.6484 27.3713 20.3251 27.3056C20.0018 27.2398 19.6668 27.2736 19.3622 27.4026C19.0577 27.5316 18.7974 27.7501 18.6143 28.0304C18.4312 28.3107 18.3334 28.6402 18.3334 28.9773C18.3334 29.4294 18.509 29.8629 18.8215 30.1826C19.1341 30.5022 19.558 30.6818 20 30.6818ZM18.3334 22.1593C18.3334 22.6114 18.509 23.0449 18.8215 23.3646C19.1341 23.6843 19.558 23.8638 20 23.8638C20.442 23.8638 20.8659 23.6843 21.1785 23.3646C21.491 23.0449 21.6666 22.6114 21.6666 22.1593V11.9324C21.6666 11.4803 21.491 11.0467 21.1785 10.7271C20.8659 10.4074 20.442 10.2279 20 10.2279C19.558 10.2279 19.1341 10.4074 18.8215 10.7271C18.509 11.0467 18.3334 11.4803 18.3334 11.9324V22.1593Z" fill="currentColor"/>
|
||||||
|
<circle cx="20" cy="29" r="2" fill="currentColor"/>
|
||||||
|
<rect x="18" y="10" width="4" height="14" rx="2" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="text-text dark:text-text-dark font-semibold">{t('thisPageIsEmpty')}</h2>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<></>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!isLoading && listContent.flat().length == 0 && currentPage < 2 && searchKeyword.length == 0 ?
|
||||||
|
<div className="p-5 py-10 flex justify-start items-center gap-3 [&_*]:fill-warning">
|
||||||
|
<svg width="25" height="25" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path opacity="0.5" d="M15.2069 2.85079C15.6886 1.98507 16.3858 1.26524 17.2277 0.76451C18.0696 0.263778 19.0261 0 20 0C20.9739 0 21.9304 0.263778 22.7723 0.76451C23.6142 1.26524 24.3114 1.98507 24.7931 2.85079L39.309 29.3012C39.77 30.1312 40.0083 31.0711 39.9998 32.0257C39.9913 32.9802 39.7363 33.9155 39.2607 34.7368C38.7781 35.5842 38.085 36.286 37.2518 36.7713C36.4186 37.2565 35.4747 37.5078 34.5159 37.4998H5.48407C4.52527 37.5078 3.58143 37.2565 2.74819 36.7713C1.91496 36.286 1.22192 35.5842 0.739307 34.7368C0.263677 33.9155 0.00870576 32.9802 0.000218482 32.0257C-0.0082688 31.0711 0.230027 30.1312 0.690974 29.3012L15.2069 2.85079ZM20 30.6818C20.3296 30.6818 20.6518 30.5819 20.9259 30.3946C21.2 30.2073 21.4136 29.9411 21.5397 29.6296C21.6659 29.3182 21.6989 28.9754 21.6346 28.6448C21.5703 28.3142 21.4115 28.0104 21.1785 27.7721C20.9454 27.5337 20.6484 27.3713 20.3251 27.3056C20.0018 27.2398 19.6668 27.2736 19.3622 27.4026C19.0577 27.5316 18.7974 27.7501 18.6143 28.0304C18.4312 28.3107 18.3334 28.6402 18.3334 28.9773C18.3334 29.4294 18.509 29.8629 18.8215 30.1826C19.1341 30.5022 19.558 30.6818 20 30.6818ZM18.3334 22.1593C18.3334 22.6114 18.509 23.0449 18.8215 23.3646C19.1341 23.6843 19.558 23.8638 20 23.8638C20.442 23.8638 20.8659 23.6843 21.1785 23.3646C21.491 23.0449 21.6666 22.6114 21.6666 22.1593V11.9324C21.6666 11.4803 21.491 11.0467 21.1785 10.7271C20.8659 10.4074 20.442 10.2279 20 10.2279C19.558 10.2279 19.1341 10.4074 18.8215 10.7271C18.509 11.0467 18.3334 11.4803 18.3334 11.9324V22.1593Z" fill="currentColor"/>
|
||||||
|
<circle cx="20" cy="29" r="2" fill="currentColor"/>
|
||||||
|
<rect x="18" y="10" width="4" height="14" rx="2" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="text-text dark:text-text-dark font-semibold">{t('noData')}</h2>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<></>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!isLoading && listContent.flat().length == 0 && searchKeyword.length != 0 ?
|
||||||
|
<div className="p-5 py-10 flex justify-start items-center gap-3 [&_*]:fill-warning">
|
||||||
|
<svg width="25" height="25" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M27.3438 20.3125C27.7656 20.3125 28.125 20.6719 28.125 21.0938C28.125 21.5156 27.7656 21.875 27.3438 21.875C26.9219 21.875 26.5625 21.5156 26.5625 21.0938C26.5625 20.6719 26.9219 20.3125 27.3438 20.3125ZM13.2812 20.3125C13.7031 20.3125 14.0625 20.6719 14.0625 21.0938C14.0625 21.5156 13.7031 21.875 13.2812 21.875C12.8594 21.875 12.5 21.5156 12.5 21.0938C12.5 20.6719 12.8594 20.3125 13.2812 20.3125Z" fill="currentColor"/>
|
||||||
|
<path d="M36.6094 37.3451C36.4094 37.3451 36.2094 37.2686 36.0562 37.117L32.5938 33.6545C32.2891 33.3498 32.2891 32.8545 32.5938 32.5498C32.8984 32.2451 33.3937 32.2451 33.6984 32.5498L37.1609 36.0123C37.4656 36.317 37.4656 36.8123 37.1609 37.117C37.0094 37.2701 36.8094 37.3451 36.6094 37.3451Z" fill="currentColor"/>
|
||||||
|
<path d="M44.5312 48.4375C43.4891 48.4375 42.5078 48.0312 41.7688 47.2937L36.3 41.825C35.5625 41.0859 35.1562 40.1062 35.1562 39.0625C35.1562 38.0188 35.5625 37.0391 36.3 36.3C37.7156 34.8844 40.4078 34.8844 41.8234 36.3L47.2922 41.7688C48.0313 42.5078 48.4375 43.4875 48.4375 44.5312C48.4375 45.575 48.0312 46.5547 47.2937 47.2937C46.5562 48.0328 45.5734 48.4375 44.5312 48.4375ZM39.0625 36.75C38.4266 36.75 37.8234 36.9875 37.4047 37.4063C36.9625 37.8484 36.7188 38.4375 36.7188 39.0625C36.7188 39.6875 36.9625 40.2766 37.4047 40.7203L42.8734 46.1891C43.7594 47.0734 45.3016 47.0734 46.1875 46.1891C46.6312 45.7453 46.875 45.1563 46.875 44.5312C46.875 43.9062 46.6313 43.3172 46.1891 42.8734L40.7203 37.4047C40.3016 36.9875 39.6984 36.75 39.0625 36.75ZM20.3125 39.0625C9.97344 39.0625 1.5625 30.6516 1.5625 20.3125C1.5625 9.97344 9.97344 1.5625 20.3125 1.5625C30.6516 1.5625 39.0625 9.97344 39.0625 20.3125C39.0625 30.6516 30.6516 39.0625 20.3125 39.0625ZM20.3125 3.125C10.8359 3.125 3.125 10.8359 3.125 20.3125C3.125 29.7891 10.8359 37.5 20.3125 37.5C29.7891 37.5 37.5 29.7891 37.5 20.3125C37.5 10.8359 29.7891 3.125 20.3125 3.125Z" fill="currentColor"/>
|
||||||
|
<path d="M21.875 25.0328C21.4438 25.0328 21.0938 24.6828 21.0938 24.2516C21.0938 23.8219 20.7437 23.4703 20.3125 23.4703C19.8813 23.4703 19.5312 23.8219 19.5312 24.2516C19.5312 24.6828 19.1812 25.0328 18.75 25.0328C18.3188 25.0328 17.9688 24.6828 17.9688 24.2516C17.9688 22.9594 19.0203 21.9078 20.3125 21.9078C21.6047 21.9078 22.6562 22.9594 22.6562 24.2516C22.6562 24.6828 22.3062 25.0328 21.875 25.0328ZM27.3438 22.6562C26.4969 22.6562 25.7812 21.9406 25.7812 21.0937C25.7812 20.2469 26.4969 19.5312 27.3438 19.5312C28.1906 19.5312 28.9062 20.2469 28.9062 21.0937C28.9062 21.9406 28.1906 22.6562 27.3438 22.6562ZM13.2812 22.6562C12.4344 22.6562 11.7188 21.9406 11.7188 21.0937C11.7188 20.2469 12.4344 19.5312 13.2812 19.5312C14.1281 19.5312 14.8438 20.2469 14.8438 21.0937C14.8438 21.9406 14.1281 22.6562 13.2812 22.6562Z" fill="currentColor"/>
|
||||||
|
<path d="M36.6094 37.3451C36.4094 37.3451 36.2094 37.2686 36.0562 37.117L32.5938 33.6545C32.2891 33.3498 32.2891 32.8545 32.5938 32.5498C32.8984 32.2451 33.3937 32.2451 33.6984 32.5498L37.1609 36.0123C37.4656 36.317 37.4656 36.8123 37.1609 37.117C37.0094 37.2701 36.8094 37.3451 36.6094 37.3451Z" fill="currentColor"/>
|
||||||
|
<path d="M44.5312 48.4375C43.4891 48.4375 42.5078 48.0312 41.7688 47.2937L36.3 41.825C35.5625 41.0859 35.1562 40.1062 35.1562 39.0625C35.1562 38.0188 35.5625 37.0391 36.3 36.3C37.7156 34.8844 40.4078 34.8844 41.8234 36.3L47.2922 41.7688C48.0313 42.5078 48.4375 43.4875 48.4375 44.5312C48.4375 45.575 48.0312 46.5547 47.2937 47.2937C46.5562 48.0328 45.5734 48.4375 44.5312 48.4375ZM39.0625 36.75C38.4266 36.75 37.8234 36.9875 37.4047 37.4063C36.9625 37.8484 36.7188 38.4375 36.7188 39.0625C36.7188 39.6875 36.9625 40.2766 37.4047 40.7203L42.8734 46.1891C43.7594 47.0734 45.3016 47.0734 46.1875 46.1891C46.6312 45.7453 46.875 45.1563 46.875 44.5312C46.875 43.9062 46.6313 43.3172 46.1891 42.8734L40.7203 37.4047C40.3016 36.9875 39.6984 36.75 39.0625 36.75ZM20.3125 39.0625C9.97344 39.0625 1.5625 30.6516 1.5625 20.3125C1.5625 9.97344 9.97344 1.5625 20.3125 1.5625C30.6516 1.5625 39.0625 9.97344 39.0625 20.3125C39.0625 30.6516 30.6516 39.0625 20.3125 39.0625ZM20.3125 3.125C10.8359 3.125 3.125 10.8359 3.125 20.3125C3.125 29.7891 10.8359 37.5 20.3125 37.5C29.7891 37.5 37.5 29.7891 37.5 20.3125C37.5 10.8359 29.7891 3.125 20.3125 3.125Z" fill="currentColor"/>
|
||||||
|
<path d="M21.875 25.0328C21.4438 25.0328 21.0938 24.6828 21.0938 24.2516C21.0938 23.8219 20.7437 23.4703 20.3125 23.4703C19.8813 23.4703 19.5312 23.8219 19.5312 24.2516C19.5312 24.6828 19.1812 25.0328 18.75 25.0328C18.3188 25.0328 17.9688 24.6828 17.9688 24.2516C17.9688 22.9594 19.0203 21.9078 20.3125 21.9078C21.6047 21.9078 22.6562 22.9594 22.6562 24.2516C22.6562 24.6828 22.3062 25.0328 21.875 25.0328ZM27.3438 22.6562C26.4969 22.6562 25.7812 21.9406 25.7812 21.0937C25.7812 20.2469 26.4969 19.5312 27.3438 19.5312C28.1906 19.5312 28.9062 20.2469 28.9062 21.0937C28.9062 21.9406 28.1906 22.6562 27.3438 22.6562ZM13.2812 22.6562C12.4344 22.6562 11.7188 21.9406 11.7188 21.0937C11.7188 20.2469 12.4344 19.5312 13.2812 19.5312C14.1281 19.5312 14.8438 20.2469 14.8438 21.0937C14.8438 21.9406 14.1281 22.6562 13.2812 22.6562Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="text-text dark:text-text-dark font-semibold">{t('noSearchResults')}</h2>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<></>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// single sender account
|
||||||
|
isLoading ?
|
||||||
|
<div className="p-5 flex justify-start items-center gap-3">
|
||||||
|
<CircularProgress color="secondary" size={24} />
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
listContent.flat().map((v , i) => {
|
||||||
|
return (
|
||||||
|
<div key={i} className="w-full flex gap-3 justify-between items-center p-5 font-semibold h-[70px] border-b-[1px] border-secondary-light/30">
|
||||||
|
<span className="w-full text-start whitespace-nowrap text-ellipsis overflow-hidden lg:block hidden">{i + 1}</span>
|
||||||
|
<span className="w-full text-start whitespace-nowrap text-ellipsis overflow-hidden">{v.name}</span>
|
||||||
|
<span className="w-full text-start flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden">{appGeneralSettings.currencySymbol} {v.amount}</span>
|
||||||
|
<span className="w-full text-start flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden">{UnixToReadAbleDate(v?.addedAt ? v?.addedAt : 0)}</span>
|
||||||
|
<span className="lg:w-full w-[80px] h-full text-start flex justify-start items-center gap-3">
|
||||||
|
<button onClick={async() => {
|
||||||
|
dispatch(setUpdatePopUp({
|
||||||
|
status: true,
|
||||||
|
data: v
|
||||||
|
}))
|
||||||
|
}} className="[&_*]:fill-success [&_*]:dark:fill-success">
|
||||||
|
<svg width="15" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6.36069 6.61884L5.39925 9.91891C5.3176 10.1996 5.39219 10.5041 5.59355 10.7111C5.74073 10.8626 5.93856 10.9446 6.14093 10.9446C6.21527 10.9446 6.29012 10.9337 6.36321 10.9109L9.56961 9.92151C9.69234 9.88363 9.80423 9.81515 9.89471 9.72177L15.7732 3.67103C15.9184 3.52161 16.0001 3.31876 16.0001 3.10761C16.0001 2.89646 15.9184 2.6936 15.7732 2.54419L13.528 0.23346C13.2256 -0.0778202 12.7357 -0.0778202 12.4333 0.23346L6.55499 6.28421C6.46426 6.37759 6.39774 6.49225 6.36069 6.61884ZM7.78786 7.26889L12.9806 1.92371L14.1308 3.10761L8.93806 8.45279L7.29541 8.95966L7.78786 7.26889Z" fill="#4DB33D"/>
|
||||||
|
<path d="M15.2258 7.4375C14.7981 7.4375 14.4516 7.79444 14.4516 8.23438V13.5469C14.4516 14.2794 13.8727 14.875 13.1613 14.875H2.83871C2.12727 14.875 1.54839 14.2794 1.54839 13.5469V2.92188C1.54839 2.18933 2.12727 1.59375 2.83871 1.59375H8C8.42767 1.59375 8.77419 1.23681 8.77419 0.796875C8.77419 0.356936 8.42767 0 8 0H2.83871C1.27344 0 0 1.311 0 2.92188V13.5469C0 15.1577 1.27344 16.4688 2.83871 16.4688H13.1613C14.7266 16.4688 16 15.1577 16 13.5469V8.23438C16 7.79444 15.6535 7.4375 15.2258 7.4375Z" fill="#4DB33D"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button onClick={async() => {
|
||||||
|
await dispatch(delete_({
|
||||||
|
id: v._id
|
||||||
|
}))
|
||||||
|
}} className="[&_*]:fill-error [&_*]:dark:fill-error">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M2.0625 4.625C2.0625 4.27983 2.32153 4.00001 2.64107 4.00001L4.63925 3.99968C5.03626 3.98881 5.38652 3.71612 5.52161 3.31268C5.52516 3.30208 5.52924 3.28899 5.54389 3.24152L5.62999 2.96245C5.68267 2.79134 5.72857 2.64227 5.79281 2.50902C6.04657 1.98262 6.51606 1.61708 7.0586 1.52349C7.19593 1.4998 7.34136 1.4999 7.50832 1.50002H10.1168C10.2838 1.4999 10.4292 1.4998 10.5665 1.52349C11.1091 1.61708 11.5786 1.98262 11.8323 2.50902C11.8966 2.64227 11.9425 2.79134 11.9951 2.96245L12.0812 3.24152C12.0959 3.28899 12.1 3.30208 12.1035 3.31268C12.2387 3.71612 12.6584 3.98915 13.0553 4.00001H14.9839C15.3034 4.00001 15.5625 4.27983 15.5625 4.625C15.5625 4.97018 15.3034 5.25 14.9839 5.25H2.64107C2.32153 5.25 2.0625 4.97018 2.0625 4.625Z" fill="currentColor"/>
|
||||||
|
<path opacity="0.5" d="M8.7051 16.4997H9.29528C11.3259 16.4997 12.3412 16.4997 13.0013 15.8523C13.6615 15.2049 13.7291 14.143 13.8641 12.0191L14.0588 8.95863C14.132 7.80626 14.1687 7.23003 13.8375 6.86489C13.5063 6.49976 12.9471 6.49976 11.8286 6.49976H6.17179C5.05329 6.49976 4.49403 6.49976 4.16286 6.86489C3.83169 7.23003 3.86833 7.80626 3.94162 8.95863L4.13625 12.0191C4.27133 14.143 4.33887 15.2049 4.99901 15.8523C5.65915 16.4997 6.67446 16.4997 8.7051 16.4997Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button onClick={async() => {
|
||||||
|
dispatch(setDetailsPopUp({
|
||||||
|
status: true,
|
||||||
|
data: v ? v : null
|
||||||
|
}))
|
||||||
|
}} className="[&_*]:fill-info [&_*]:dark:fill-info">
|
||||||
|
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M13.3336 13.3333H1.66641V1.66672H6.875V0H1.66641C0.749649 0 0 0.749883 0 1.66672V13.3333C0 14.2501 0.749649 15 1.66641 15H13.3336C14.2504 15 15 14.2501 15 13.3333V8.125H13.3336V13.3333ZM8.75 0V1.66672H12.167L3.74996 10.0833L4.9166 11.25L13.3336 2.83316V6.25H15V0H8.75Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
<ListPagination />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { useDispatch } from "react-redux"
|
||||||
|
import { setAddPopUp } from "@/redux/features/incomes-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
export default function AddNewButton()
|
||||||
|
{
|
||||||
|
// setup needed varibles
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const t = useTranslations('incomes');
|
||||||
|
// handle the open of the pop up
|
||||||
|
function handleClickOpen()
|
||||||
|
{
|
||||||
|
dispatch(setAddPopUp(true))
|
||||||
|
}
|
||||||
|
// return the component ui
|
||||||
|
return (
|
||||||
|
<button onClick={handleClickOpen}
|
||||||
|
className="
|
||||||
|
bg-secondary-light dark:bg-primary text-primary-light dark:text-text
|
||||||
|
fixed z-50 bottom-7 ltr:right-7 rtl:left-7
|
||||||
|
w-auto h-18 p-3 rounded-md drop-shadow-lg
|
||||||
|
flex justify-between items-center gap-3
|
||||||
|
">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 49 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M25 0.5C26.3807 0.5 27.5 1.61929 27.5 3L27.5 46C27.5 47.3807 26.3807 48.5 25 48.5C23.6193 48.5 22.5 47.3807 22.5 46L22.5 3C22.5 1.61929 23.6193 0.5 25 0.5Z" fill="currentColor"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.5 25C48.5 26.3807 47.3807 27.5 46 27.5L3 27.5C1.61929 27.5 0.5 26.3807 0.5 25C0.5 23.6193 1.61929 22.5 3 22.5L46 22.5C47.3807 22.5 48.5 23.6193 48.5 25Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h3>{t('addNewIncome')}</h3>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
196
webapp/src/components/dashboard/incomes/parts/addPopUp.tsx
Normal file
196
webapp/src/components/dashboard/incomes/parts/addPopUp.tsx
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
import { forwardRef , ReactElement , Ref } from "react"
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { AppDispatch, useAppSelector } from "@/redux/store";
|
||||||
|
import { setAddPopUp , add } from "@/redux/features/incomes-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import Slide from '@mui/material/Slide';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import * as Yup from "yup";
|
||||||
|
|
||||||
|
export default function AddPopUp()
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
// redux
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// intl-18
|
||||||
|
const t = useTranslations('incomes');
|
||||||
|
// mui
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
// get state from redux
|
||||||
|
const addPopUp = useAppSelector((state) => state.incomesReducer.value.addPopUp)
|
||||||
|
// setup the popup animation
|
||||||
|
const Transition = forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: ReactElement<any, any>;
|
||||||
|
},
|
||||||
|
ref: Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
// setup the function that close the popup
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch(setAddPopUp(false));
|
||||||
|
};
|
||||||
|
// we dont mount the pop up if we dont need it
|
||||||
|
if(!addPopUp) return <></>
|
||||||
|
// we mount the popup if we needed it
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
open={addPopUp}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
keepMounted
|
||||||
|
onClose={handleClose}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth={'sm'}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// This is the title of the pop up
|
||||||
|
}
|
||||||
|
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
|
||||||
|
<h1 className="text-[25px] font-tajawal">{t('addNewIncome')}</h1>
|
||||||
|
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</DialogTitle>
|
||||||
|
{
|
||||||
|
// Formik form start from here
|
||||||
|
}
|
||||||
|
<Formik
|
||||||
|
// Initial values
|
||||||
|
initialValues={{
|
||||||
|
name : '',
|
||||||
|
description : '',
|
||||||
|
amount: 0,
|
||||||
|
}}
|
||||||
|
// validat the inputs
|
||||||
|
validationSchema={Yup.object().shape({
|
||||||
|
name: Yup.string().required('thisFieldIsRequired'),
|
||||||
|
description: Yup.string().required('thisFieldIsRequired'),
|
||||||
|
amount: Yup.number().typeError('mustBeANumber').required('thisFieldIsRequired'),
|
||||||
|
})}
|
||||||
|
// this function handle the submition of the formik form
|
||||||
|
onSubmit={async (values) => {
|
||||||
|
await dispatch(add(values))
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
values,
|
||||||
|
errors,
|
||||||
|
touched
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{
|
||||||
|
// dialog content contain the formik form
|
||||||
|
}
|
||||||
|
<DialogContent>
|
||||||
|
{
|
||||||
|
// dialog text content contain a small description to the current form part
|
||||||
|
}
|
||||||
|
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
|
||||||
|
<h1 className="text-[18px] font-tajawal">{t('incomeAddText1')}</h1>
|
||||||
|
</DialogContentText>
|
||||||
|
<div className="
|
||||||
|
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80
|
||||||
|
[&_*]:text-text/80 [&_*]:dark:text-text-dark/80
|
||||||
|
[&_*]:placeholder-text/80 [&_*]:dark:placeholder-text-dark/80
|
||||||
|
w-full h-auto mt-[36px] flex flex-col gap-3
|
||||||
|
">
|
||||||
|
<div className="w-full flex lg:flex-row flex-col lg:gap-12 gap-7">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('incomeInformations')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.name}
|
||||||
|
placeholder={t('name')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.name && touched?.name && t(errors?.name)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.description}
|
||||||
|
placeholder={t('description')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.description && touched?.description && t(errors?.description)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="amount"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.amount == 0 ? '' : values.amount}
|
||||||
|
placeholder={t('amount')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.amount && touched?.amount && t(errors?.amount)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
{
|
||||||
|
// dialog actions contain buttons that effect the popup status
|
||||||
|
}
|
||||||
|
<DialogActions sx={{ padding: '16px 24px' }}>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button type="submit" className="btn font-semibold">
|
||||||
|
{
|
||||||
|
isSubmitting ?
|
||||||
|
<CircularProgress color="secondary" size={24} />
|
||||||
|
:
|
||||||
|
t('done')
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
184
webapp/src/components/dashboard/incomes/parts/detailsPopUp.tsx
Normal file
184
webapp/src/components/dashboard/incomes/parts/detailsPopUp.tsx
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
import { forwardRef , ReactElement , Ref } from "react"
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { AppDispatch, useAppSelector } from "@/redux/store";
|
||||||
|
import { setDetailsPopUp } from "@/redux/features/incomes-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import Slide from '@mui/material/Slide';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
|
||||||
|
export default function ServiceDetailsPopUp()
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
// redux
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// intl-18
|
||||||
|
const t = useTranslations('incomes');
|
||||||
|
// mui
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
// get state from redux
|
||||||
|
const detailsPopUp = useAppSelector((state) => state.incomesReducer.value.detailsPopUp)
|
||||||
|
const detailsPopUpData = useAppSelector((state) => state.incomesReducer.value.detailsPopUpData)
|
||||||
|
// setup the popup animation
|
||||||
|
const Transition = forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: ReactElement<any, any>;
|
||||||
|
},
|
||||||
|
ref: Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
// setup the function that close the popup
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch(setDetailsPopUp({
|
||||||
|
status: false,
|
||||||
|
data: null
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
// we dont mount the pop up if we dont need it
|
||||||
|
if(!detailsPopUp) return <></>
|
||||||
|
// we mount the popup if we needed it
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
open={detailsPopUp}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
keepMounted
|
||||||
|
onClose={handleClose}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth={'sm'}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// This is the title of the pop up
|
||||||
|
}
|
||||||
|
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
|
||||||
|
<h1 className="text-[25px] font-tajawal">{t('incomeDetails')}</h1>
|
||||||
|
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</DialogTitle>
|
||||||
|
{
|
||||||
|
// Formik form start from here
|
||||||
|
}
|
||||||
|
<Formik
|
||||||
|
// Initial values
|
||||||
|
initialValues={{
|
||||||
|
name: detailsPopUpData?.name,
|
||||||
|
description: detailsPopUpData?.description,
|
||||||
|
amount: detailsPopUpData?.amount,
|
||||||
|
addedAt: detailsPopUpData?.addedAt,
|
||||||
|
}}
|
||||||
|
// this function handle the submition of the formik form
|
||||||
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
values,
|
||||||
|
errors
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{
|
||||||
|
// dialog content contain the formik form
|
||||||
|
}
|
||||||
|
<DialogContent>
|
||||||
|
{
|
||||||
|
// dialog text content contain a small description to the current form part
|
||||||
|
}
|
||||||
|
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
|
||||||
|
<h1 className="text-[18px] font-tajawal">{t('incomeDetailsText1')}</h1>
|
||||||
|
</DialogContentText>
|
||||||
|
<div className="
|
||||||
|
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80
|
||||||
|
[&_*]:text-text/80 [&_*]:dark:text-text-dark/80
|
||||||
|
[&_*]:placeholder-text/80 [&_*]:dark:placeholder-text-dark/80
|
||||||
|
w-full h-auto mt-[36px] flex flex-col gap-3
|
||||||
|
">
|
||||||
|
<div className="w-full flex lg:flex-row flex-col lg:gap-12 gap-7">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('incomeInformations')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.name}
|
||||||
|
placeholder={t('name')}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.description}
|
||||||
|
placeholder={t('description')}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
disabled
|
||||||
|
id="price"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.amount}
|
||||||
|
placeholder={t('amount')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
{
|
||||||
|
// dialog actions contain buttons that effect the popup status
|
||||||
|
}
|
||||||
|
<DialogActions sx={{ padding: '16px 24px' }}>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button type="submit" className="btn font-semibold">
|
||||||
|
{t('close')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
import Pagination from '@mui/material/Pagination';
|
||||||
|
import { setCurrentPage } from '@/redux/features/incomes-slice';
|
||||||
|
import { load } from '@/redux/features/incomes-slice'
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { AppDispatch , useAppSelector } from '@/redux/store';
|
||||||
|
|
||||||
|
export default function ListPagination() {
|
||||||
|
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// load needed states
|
||||||
|
const currentPage = useAppSelector((state) => state.incomesReducer.value.currentPage)
|
||||||
|
const total = useAppSelector((state) => state.incomesReducer.value.total)
|
||||||
|
// handle the pagination
|
||||||
|
const handlePagination = (event: React.ChangeEvent<unknown>, value: number) => {
|
||||||
|
dispatch(load({page:value}));
|
||||||
|
dispatch(setCurrentPage(value));
|
||||||
|
};
|
||||||
|
// show the pagination ui
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{Math.ceil(total / 4) > 1 ? (
|
||||||
|
<div className="flex justify-center items-center py-12">
|
||||||
|
<Pagination
|
||||||
|
sx={{ direction: 'ltr' }}
|
||||||
|
count={Math.ceil(total / 4)}
|
||||||
|
page={currentPage}
|
||||||
|
onChange={handlePagination}
|
||||||
|
variant="outlined"
|
||||||
|
shape="rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
195
webapp/src/components/dashboard/incomes/parts/updatePopUp.tsx
Normal file
195
webapp/src/components/dashboard/incomes/parts/updatePopUp.tsx
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
import { forwardRef , ReactElement , Ref, useEffect } from "react"
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { AppDispatch, useAppSelector } from "@/redux/store";
|
||||||
|
import { setUpdatePopUp , update } from "@/redux/features/incomes-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import Slide from '@mui/material/Slide';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
|
||||||
|
export default function UpdatePopUp()
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
// redux
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// intl-18
|
||||||
|
const t = useTranslations('incomes');
|
||||||
|
// mui
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
// get state from redux
|
||||||
|
const updatePopUp = useAppSelector((state) => state.incomesReducer.value.updatePopUp)
|
||||||
|
const updatePopUpData = useAppSelector((state) => state.incomesReducer.value.updatePopUpData)
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(updatePopUp , "updatePopUp")
|
||||||
|
}, [updatePopUp])
|
||||||
|
// setup the popup animation
|
||||||
|
const Transition = forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: ReactElement<any, any>;
|
||||||
|
},
|
||||||
|
ref: Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
// setup the function that close the popup
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch(setUpdatePopUp({
|
||||||
|
status: false,
|
||||||
|
data: null
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
// we dont mount the pop up if we dont need it
|
||||||
|
if(!updatePopUp) return <></>
|
||||||
|
// we mount the popup if we needed it
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
open={updatePopUp}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
keepMounted
|
||||||
|
onClose={handleClose}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth={'sm'}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// This is the title of the pop up
|
||||||
|
}
|
||||||
|
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
|
||||||
|
<h1 className="text-[25px] font-tajawal">{t('updateIncome')}</h1>
|
||||||
|
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</DialogTitle>
|
||||||
|
{
|
||||||
|
// Formik form start from here
|
||||||
|
}
|
||||||
|
<Formik
|
||||||
|
// Initial values
|
||||||
|
initialValues={{
|
||||||
|
_id: updatePopUpData?._id ? updatePopUpData?._id : '',
|
||||||
|
name : updatePopUpData?.name ? updatePopUpData?.name : '',
|
||||||
|
description : updatePopUpData?.description ? updatePopUpData?.description : '',
|
||||||
|
amount: updatePopUpData?.amount ? updatePopUpData?.amount : 0
|
||||||
|
}}
|
||||||
|
// this function handle the submition of the formik form
|
||||||
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
|
// dispatch the update function
|
||||||
|
await dispatch(update(values))
|
||||||
|
// close the popup
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
values,
|
||||||
|
setFieldValue
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{
|
||||||
|
// dialog content contain the formik form
|
||||||
|
}
|
||||||
|
<DialogContent>
|
||||||
|
{
|
||||||
|
// dialog text content contain a small description to the current form part
|
||||||
|
}
|
||||||
|
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
|
||||||
|
<h1 className="text-[18px] font-tajawal">{t('incomeUpdateText1')}</h1>
|
||||||
|
</DialogContentText>
|
||||||
|
<div className="
|
||||||
|
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80
|
||||||
|
[&_*]:text-text/80 [&_*]:dark:text-text-dark/80
|
||||||
|
[&_*]:placeholder-text/80 [&_*]:dark:placeholder-text-dark/80
|
||||||
|
w-full h-auto mt-[36px] flex flex-col gap-3
|
||||||
|
">
|
||||||
|
<div className="w-full flex lg:flex-row flex-col lg:gap-12 gap-7">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('incomeInformations')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.name}
|
||||||
|
placeholder={t('name')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.description}
|
||||||
|
placeholder={t('description')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="amount"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.amount}
|
||||||
|
placeholder={t('amount')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
{
|
||||||
|
// dialog actions contain buttons that effect the popup status
|
||||||
|
}
|
||||||
|
<DialogActions sx={{ padding: '16px 24px' }}>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button type="submit" className="btn font-semibold">
|
||||||
|
{
|
||||||
|
isSubmitting ?
|
||||||
|
<CircularProgress color="secondary" size={24} />
|
||||||
|
:
|
||||||
|
t('save')
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
14
webapp/src/components/dashboard/incomes/popUpsWrapper.tsx
Normal file
14
webapp/src/components/dashboard/incomes/popUpsWrapper.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import AddPopUp from './parts/addPopUp'
|
||||||
|
import DetailsPopUp from './parts/detailsPopUp'
|
||||||
|
import UpdatePopUp from './parts/updatePopUp'
|
||||||
|
|
||||||
|
export default function PopUpWrapper()
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<UpdatePopUp />
|
||||||
|
<AddPopUp />
|
||||||
|
<DetailsPopUp />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
207
webapp/src/components/dashboard/members/membersList.tsx
Normal file
207
webapp/src/components/dashboard/members/membersList.tsx
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { useAppSelector } from '@/redux/store';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import { AppDispatch } from '@/redux/store';
|
||||||
|
import { setSearchKeyword ,load , search , delete_ } from '@/redux/features/members-slice'
|
||||||
|
import ListPagination from './parts/membersListPagination'
|
||||||
|
import { setDetailsPopUp , setUpdatePopUp , setUpdateSubPopUp , setCurrentPage } from '@/redux/features/members-slice';
|
||||||
|
|
||||||
|
export default function List()
|
||||||
|
{
|
||||||
|
const t = useTranslations('members');
|
||||||
|
const dispatch = useDispatch<AppDispatch>()
|
||||||
|
|
||||||
|
const listContent = useAppSelector((state) => state.membersReducer.value.listContent)
|
||||||
|
const isLoading = useAppSelector((state) => state.membersReducer.value.isLoading)
|
||||||
|
const loadedFirstTime = useAppSelector((state) => state.membersReducer.value.loadedFirstTime)
|
||||||
|
const searchKeyword = useAppSelector((state) => state.membersReducer.value.searchKeyword)
|
||||||
|
const currentPage = useAppSelector((state) => state.membersReducer.value.currentPage)
|
||||||
|
const appGeneralSettings = useAppSelector((state) => state.settingsReducer.value.appGeneralSettings)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(loadedFirstTime) return;
|
||||||
|
if(isLoading) return;
|
||||||
|
async function a()
|
||||||
|
{
|
||||||
|
await dispatch(load({page : 1}))
|
||||||
|
}
|
||||||
|
a()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-3 w-full w-auto">
|
||||||
|
<header className="w-full flex lg:flex-row flex-col lg:justify-between lg:items-center gap-3">
|
||||||
|
<h1 className="font-semibold text-[20px] text-text dark:text-text-dark">{t('members')}</h1>
|
||||||
|
<label htmlFor="email" className="relative text-gray-400 focus-within:text-gray-600 block">
|
||||||
|
<svg className="text-secondary dark:text-primary/50 cursor-pointer w-[15px] h-[15px] absolute top-1/2 transform -translate-y-1/2 rtl:left-3 ltr:right-3" width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M18.4479 16.9688L14.9063 13.4375C16.0489 11.9817 16.669 10.184 16.6667 8.33334C16.6667 6.68516 16.1779 5.074 15.2623 3.70359C14.3466 2.33318 13.0451 1.26507 11.5224 0.634341C9.99965 0.00361068 8.32409 -0.161417 6.70759 0.160126C5.09108 0.48167 3.60622 1.27534 2.44078 2.44078C1.27534 3.60622 0.48167 5.09108 0.160126 6.70759C-0.161417 8.32409 0.00361068 9.99965 0.634341 11.5224C1.26507 13.0451 2.33318 14.3466 3.70359 15.2623C5.074 16.1779 6.68516 16.6667 8.33334 16.6667C10.184 16.669 11.9817 16.0489 13.4375 14.9063L16.9688 18.4479C17.0656 18.5456 17.1808 18.6231 17.3077 18.6759C17.4347 18.7288 17.5708 18.756 17.7083 18.756C17.8459 18.756 17.982 18.7288 18.1089 18.6759C18.2359 18.6231 18.3511 18.5456 18.4479 18.4479C18.5456 18.3511 18.6231 18.2359 18.6759 18.1089C18.7288 17.982 18.756 17.8459 18.756 17.7083C18.756 17.5708 18.7288 17.4347 18.6759 17.3077C18.6231 17.1808 18.5456 17.0656 18.4479 16.9688ZM2.08334 8.33334C2.08334 7.0972 2.44989 5.88883 3.13665 4.86102C3.82341 3.83322 4.79953 3.03214 5.94157 2.55909C7.0836 2.08604 8.34027 1.96227 9.55265 2.20343C10.765 2.44459 11.8787 3.03984 12.7528 3.91392C13.6268 4.788 14.2221 5.90164 14.4632 7.11402C14.7044 8.3264 14.5806 9.58307 14.1076 10.7251C13.6345 11.8671 12.8335 12.8433 11.8057 13.53C10.7778 14.2168 9.56947 14.5833 8.33334 14.5833C6.67573 14.5833 5.08602 13.9249 3.91392 12.7528C2.74182 11.5807 2.08334 9.99094 2.08334 8.33334Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<input onChange={(async(e) => {
|
||||||
|
if(e.target.value.length == 0)
|
||||||
|
{
|
||||||
|
dispatch(setCurrentPage(1));
|
||||||
|
}
|
||||||
|
dispatch(setSearchKeyword(e.target.value))
|
||||||
|
await dispatch(search({
|
||||||
|
searchKeyword: e.target.value
|
||||||
|
}))
|
||||||
|
})}
|
||||||
|
value={searchKeyword}
|
||||||
|
className="lg:w-[300px] w-full text-text dark:text-text-dark rounded-[5px] px-3 h-[50px] border-[1px] !border-secondary-light bg-primary-dark/5 placeholder:font-semibold" type="text" placeholder={t('searchForServices')}></input>
|
||||||
|
</label>
|
||||||
|
</header>
|
||||||
|
<div className="panel !p-0 mb-7 dark:bg-primary-dark">
|
||||||
|
<header className="w-full lg:flex gap-3 hidden justify-between items-center p-5 opacity-50 font-semibold border-b-[1px] border-secondary-light">
|
||||||
|
<span className="w-full text-start">#</span>
|
||||||
|
<span className="w-full text-start">{t('firstName')}</span>
|
||||||
|
<span className="w-full text-start">{t('lastName')}</span>
|
||||||
|
<span className="w-full text-start">{t('gendre')}</span>
|
||||||
|
<span className="w-full text-start">{t('payMonth')}</span>
|
||||||
|
<span className="w-full text-start">{t('planDelay')}</span>
|
||||||
|
<span className="w-full text-start">{t('planStatus')}</span>
|
||||||
|
<span className="w-full text-start">{t('actions')}</span>
|
||||||
|
</header>
|
||||||
|
<main className="flex flex-col justify-center items-center">
|
||||||
|
{
|
||||||
|
!isLoading && listContent.flat().length == 0 && currentPage >= 2 && searchKeyword.length == 0 ?
|
||||||
|
<div className="p-5 py-10 flex justify-start items-center gap-3 [&_*]:fill-warning">
|
||||||
|
<svg width="25" height="25" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path opacity="0.5" d="M15.2069 2.85079C15.6886 1.98507 16.3858 1.26524 17.2277 0.76451C18.0696 0.263778 19.0261 0 20 0C20.9739 0 21.9304 0.263778 22.7723 0.76451C23.6142 1.26524 24.3114 1.98507 24.7931 2.85079L39.309 29.3012C39.77 30.1312 40.0083 31.0711 39.9998 32.0257C39.9913 32.9802 39.7363 33.9155 39.2607 34.7368C38.7781 35.5842 38.085 36.286 37.2518 36.7713C36.4186 37.2565 35.4747 37.5078 34.5159 37.4998H5.48407C4.52527 37.5078 3.58143 37.2565 2.74819 36.7713C1.91496 36.286 1.22192 35.5842 0.739307 34.7368C0.263677 33.9155 0.00870576 32.9802 0.000218482 32.0257C-0.0082688 31.0711 0.230027 30.1312 0.690974 29.3012L15.2069 2.85079ZM20 30.6818C20.3296 30.6818 20.6518 30.5819 20.9259 30.3946C21.2 30.2073 21.4136 29.9411 21.5397 29.6296C21.6659 29.3182 21.6989 28.9754 21.6346 28.6448C21.5703 28.3142 21.4115 28.0104 21.1785 27.7721C20.9454 27.5337 20.6484 27.3713 20.3251 27.3056C20.0018 27.2398 19.6668 27.2736 19.3622 27.4026C19.0577 27.5316 18.7974 27.7501 18.6143 28.0304C18.4312 28.3107 18.3334 28.6402 18.3334 28.9773C18.3334 29.4294 18.509 29.8629 18.8215 30.1826C19.1341 30.5022 19.558 30.6818 20 30.6818ZM18.3334 22.1593C18.3334 22.6114 18.509 23.0449 18.8215 23.3646C19.1341 23.6843 19.558 23.8638 20 23.8638C20.442 23.8638 20.8659 23.6843 21.1785 23.3646C21.491 23.0449 21.6666 22.6114 21.6666 22.1593V11.9324C21.6666 11.4803 21.491 11.0467 21.1785 10.7271C20.8659 10.4074 20.442 10.2279 20 10.2279C19.558 10.2279 19.1341 10.4074 18.8215 10.7271C18.509 11.0467 18.3334 11.4803 18.3334 11.9324V22.1593Z" fill="currentColor"/>
|
||||||
|
<circle cx="20" cy="29" r="2" fill="currentColor"/>
|
||||||
|
<rect x="18" y="10" width="4" height="14" rx="2" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="text-text dark:text-text-dark font-semibold">{t('thisPageIsEmpty')}</h2>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<></>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!isLoading && listContent.flat().length == 0 && currentPage < 2 && searchKeyword.length == 0 ?
|
||||||
|
<div className="p-5 py-10 flex justify-start items-center gap-3 [&_*]:fill-warning">
|
||||||
|
<svg width="25" height="25" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path opacity="0.5" d="M15.2069 2.85079C15.6886 1.98507 16.3858 1.26524 17.2277 0.76451C18.0696 0.263778 19.0261 0 20 0C20.9739 0 21.9304 0.263778 22.7723 0.76451C23.6142 1.26524 24.3114 1.98507 24.7931 2.85079L39.309 29.3012C39.77 30.1312 40.0083 31.0711 39.9998 32.0257C39.9913 32.9802 39.7363 33.9155 39.2607 34.7368C38.7781 35.5842 38.085 36.286 37.2518 36.7713C36.4186 37.2565 35.4747 37.5078 34.5159 37.4998H5.48407C4.52527 37.5078 3.58143 37.2565 2.74819 36.7713C1.91496 36.286 1.22192 35.5842 0.739307 34.7368C0.263677 33.9155 0.00870576 32.9802 0.000218482 32.0257C-0.0082688 31.0711 0.230027 30.1312 0.690974 29.3012L15.2069 2.85079ZM20 30.6818C20.3296 30.6818 20.6518 30.5819 20.9259 30.3946C21.2 30.2073 21.4136 29.9411 21.5397 29.6296C21.6659 29.3182 21.6989 28.9754 21.6346 28.6448C21.5703 28.3142 21.4115 28.0104 21.1785 27.7721C20.9454 27.5337 20.6484 27.3713 20.3251 27.3056C20.0018 27.2398 19.6668 27.2736 19.3622 27.4026C19.0577 27.5316 18.7974 27.7501 18.6143 28.0304C18.4312 28.3107 18.3334 28.6402 18.3334 28.9773C18.3334 29.4294 18.509 29.8629 18.8215 30.1826C19.1341 30.5022 19.558 30.6818 20 30.6818ZM18.3334 22.1593C18.3334 22.6114 18.509 23.0449 18.8215 23.3646C19.1341 23.6843 19.558 23.8638 20 23.8638C20.442 23.8638 20.8659 23.6843 21.1785 23.3646C21.491 23.0449 21.6666 22.6114 21.6666 22.1593V11.9324C21.6666 11.4803 21.491 11.0467 21.1785 10.7271C20.8659 10.4074 20.442 10.2279 20 10.2279C19.558 10.2279 19.1341 10.4074 18.8215 10.7271C18.509 11.0467 18.3334 11.4803 18.3334 11.9324V22.1593Z" fill="currentColor"/>
|
||||||
|
<circle cx="20" cy="29" r="2" fill="currentColor"/>
|
||||||
|
<rect x="18" y="10" width="4" height="14" rx="2" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="text-text dark:text-text-dark font-semibold">{t('noData')}</h2>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<></>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!isLoading && listContent.flat().length == 0 && searchKeyword.length != 0 ?
|
||||||
|
<div className="p-5 py-10 flex justify-start items-center gap-3 [&_*]:fill-warning">
|
||||||
|
<svg width="25" height="25" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M27.3438 20.3125C27.7656 20.3125 28.125 20.6719 28.125 21.0938C28.125 21.5156 27.7656 21.875 27.3438 21.875C26.9219 21.875 26.5625 21.5156 26.5625 21.0938C26.5625 20.6719 26.9219 20.3125 27.3438 20.3125ZM13.2812 20.3125C13.7031 20.3125 14.0625 20.6719 14.0625 21.0938C14.0625 21.5156 13.7031 21.875 13.2812 21.875C12.8594 21.875 12.5 21.5156 12.5 21.0938C12.5 20.6719 12.8594 20.3125 13.2812 20.3125Z" fill="currentColor"/>
|
||||||
|
<path d="M36.6094 37.3451C36.4094 37.3451 36.2094 37.2686 36.0562 37.117L32.5938 33.6545C32.2891 33.3498 32.2891 32.8545 32.5938 32.5498C32.8984 32.2451 33.3937 32.2451 33.6984 32.5498L37.1609 36.0123C37.4656 36.317 37.4656 36.8123 37.1609 37.117C37.0094 37.2701 36.8094 37.3451 36.6094 37.3451Z" fill="currentColor"/>
|
||||||
|
<path d="M44.5312 48.4375C43.4891 48.4375 42.5078 48.0312 41.7688 47.2937L36.3 41.825C35.5625 41.0859 35.1562 40.1062 35.1562 39.0625C35.1562 38.0188 35.5625 37.0391 36.3 36.3C37.7156 34.8844 40.4078 34.8844 41.8234 36.3L47.2922 41.7688C48.0313 42.5078 48.4375 43.4875 48.4375 44.5312C48.4375 45.575 48.0312 46.5547 47.2937 47.2937C46.5562 48.0328 45.5734 48.4375 44.5312 48.4375ZM39.0625 36.75C38.4266 36.75 37.8234 36.9875 37.4047 37.4063C36.9625 37.8484 36.7188 38.4375 36.7188 39.0625C36.7188 39.6875 36.9625 40.2766 37.4047 40.7203L42.8734 46.1891C43.7594 47.0734 45.3016 47.0734 46.1875 46.1891C46.6312 45.7453 46.875 45.1563 46.875 44.5312C46.875 43.9062 46.6313 43.3172 46.1891 42.8734L40.7203 37.4047C40.3016 36.9875 39.6984 36.75 39.0625 36.75ZM20.3125 39.0625C9.97344 39.0625 1.5625 30.6516 1.5625 20.3125C1.5625 9.97344 9.97344 1.5625 20.3125 1.5625C30.6516 1.5625 39.0625 9.97344 39.0625 20.3125C39.0625 30.6516 30.6516 39.0625 20.3125 39.0625ZM20.3125 3.125C10.8359 3.125 3.125 10.8359 3.125 20.3125C3.125 29.7891 10.8359 37.5 20.3125 37.5C29.7891 37.5 37.5 29.7891 37.5 20.3125C37.5 10.8359 29.7891 3.125 20.3125 3.125Z" fill="currentColor"/>
|
||||||
|
<path d="M21.875 25.0328C21.4438 25.0328 21.0938 24.6828 21.0938 24.2516C21.0938 23.8219 20.7437 23.4703 20.3125 23.4703C19.8813 23.4703 19.5312 23.8219 19.5312 24.2516C19.5312 24.6828 19.1812 25.0328 18.75 25.0328C18.3188 25.0328 17.9688 24.6828 17.9688 24.2516C17.9688 22.9594 19.0203 21.9078 20.3125 21.9078C21.6047 21.9078 22.6562 22.9594 22.6562 24.2516C22.6562 24.6828 22.3062 25.0328 21.875 25.0328ZM27.3438 22.6562C26.4969 22.6562 25.7812 21.9406 25.7812 21.0937C25.7812 20.2469 26.4969 19.5312 27.3438 19.5312C28.1906 19.5312 28.9062 20.2469 28.9062 21.0937C28.9062 21.9406 28.1906 22.6562 27.3438 22.6562ZM13.2812 22.6562C12.4344 22.6562 11.7188 21.9406 11.7188 21.0937C11.7188 20.2469 12.4344 19.5312 13.2812 19.5312C14.1281 19.5312 14.8438 20.2469 14.8438 21.0937C14.8438 21.9406 14.1281 22.6562 13.2812 22.6562Z" fill="currentColor"/>
|
||||||
|
<path d="M36.6094 37.3451C36.4094 37.3451 36.2094 37.2686 36.0562 37.117L32.5938 33.6545C32.2891 33.3498 32.2891 32.8545 32.5938 32.5498C32.8984 32.2451 33.3937 32.2451 33.6984 32.5498L37.1609 36.0123C37.4656 36.317 37.4656 36.8123 37.1609 37.117C37.0094 37.2701 36.8094 37.3451 36.6094 37.3451Z" fill="currentColor"/>
|
||||||
|
<path d="M44.5312 48.4375C43.4891 48.4375 42.5078 48.0312 41.7688 47.2937L36.3 41.825C35.5625 41.0859 35.1562 40.1062 35.1562 39.0625C35.1562 38.0188 35.5625 37.0391 36.3 36.3C37.7156 34.8844 40.4078 34.8844 41.8234 36.3L47.2922 41.7688C48.0313 42.5078 48.4375 43.4875 48.4375 44.5312C48.4375 45.575 48.0312 46.5547 47.2937 47.2937C46.5562 48.0328 45.5734 48.4375 44.5312 48.4375ZM39.0625 36.75C38.4266 36.75 37.8234 36.9875 37.4047 37.4063C36.9625 37.8484 36.7188 38.4375 36.7188 39.0625C36.7188 39.6875 36.9625 40.2766 37.4047 40.7203L42.8734 46.1891C43.7594 47.0734 45.3016 47.0734 46.1875 46.1891C46.6312 45.7453 46.875 45.1563 46.875 44.5312C46.875 43.9062 46.6313 43.3172 46.1891 42.8734L40.7203 37.4047C40.3016 36.9875 39.6984 36.75 39.0625 36.75ZM20.3125 39.0625C9.97344 39.0625 1.5625 30.6516 1.5625 20.3125C1.5625 9.97344 9.97344 1.5625 20.3125 1.5625C30.6516 1.5625 39.0625 9.97344 39.0625 20.3125C39.0625 30.6516 30.6516 39.0625 20.3125 39.0625ZM20.3125 3.125C10.8359 3.125 3.125 10.8359 3.125 20.3125C3.125 29.7891 10.8359 37.5 20.3125 37.5C29.7891 37.5 37.5 29.7891 37.5 20.3125C37.5 10.8359 29.7891 3.125 20.3125 3.125Z" fill="currentColor"/>
|
||||||
|
<path d="M21.875 25.0328C21.4438 25.0328 21.0938 24.6828 21.0938 24.2516C21.0938 23.8219 20.7437 23.4703 20.3125 23.4703C19.8813 23.4703 19.5312 23.8219 19.5312 24.2516C19.5312 24.6828 19.1812 25.0328 18.75 25.0328C18.3188 25.0328 17.9688 24.6828 17.9688 24.2516C17.9688 22.9594 19.0203 21.9078 20.3125 21.9078C21.6047 21.9078 22.6562 22.9594 22.6562 24.2516C22.6562 24.6828 22.3062 25.0328 21.875 25.0328ZM27.3438 22.6562C26.4969 22.6562 25.7812 21.9406 25.7812 21.0937C25.7812 20.2469 26.4969 19.5312 27.3438 19.5312C28.1906 19.5312 28.9062 20.2469 28.9062 21.0937C28.9062 21.9406 28.1906 22.6562 27.3438 22.6562ZM13.2812 22.6562C12.4344 22.6562 11.7188 21.9406 11.7188 21.0937C11.7188 20.2469 12.4344 19.5312 13.2812 19.5312C14.1281 19.5312 14.8438 20.2469 14.8438 21.0937C14.8438 21.9406 14.1281 22.6562 13.2812 22.6562Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="text-text dark:text-text-dark font-semibold">{t('noSearchResults')}</h2>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<></>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// single sender account
|
||||||
|
isLoading ?
|
||||||
|
<div className="p-5 flex justify-start items-center gap-3">
|
||||||
|
<CircularProgress color="secondary" size={24} />
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
listContent.flat().map((v : any , i) => {
|
||||||
|
return (
|
||||||
|
<div key={i} className="w-full flex gap-3 justify-between items-center p-5 font-semibold h-[70px] border-b-[1px] border-secondary-light/30">
|
||||||
|
<span className="w-full text-start whitespace-nowrap text-ellipsis overflow-hidden lg:block hidden">{i + 1}</span>
|
||||||
|
<span className="w-full text-start whitespace-nowrap text-ellipsis overflow-hidden">{v.firstName}</span>
|
||||||
|
<span className="w-full text-start flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden lg:block hidden">{v.lastName}</span>
|
||||||
|
<span className="w-full text-start flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden lg:block hidden">{t(v.gendre+'Gendre')}</span>
|
||||||
|
<span className="w-full text-start flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden lg:block hidden">{v.payMonth} {appGeneralSettings.currencySymbol}</span>
|
||||||
|
<span className="w-full text-start flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden lg:block hidden">{v.planDelay + ' ' + t('months')}</span>
|
||||||
|
<span className="w-full text-start flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden">
|
||||||
|
{v.planExpAt_unix - (Date.now() / 1000) > 259200 ?
|
||||||
|
<span className="flex gap-1 text-success">
|
||||||
|
<svg width="6" height="6" viewBox="0 0 6 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect y="2.79895" width="3.95833" height="3.95833" rx="1" transform="rotate(-45 0 2.79895)" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
{t('trueActive')}
|
||||||
|
</span>
|
||||||
|
:
|
||||||
|
(
|
||||||
|
v.planExpAt_unix - (Date.now() / 1000) > 0 ?
|
||||||
|
<span className="flex gap-1 text-warning">
|
||||||
|
<svg width="6" height="6" viewBox="0 0 6 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect y="2.79895" width="3.95833" height="3.95833" rx="1" transform="rotate(-45 0 2.79895)" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
{t('expireVerySoon')}
|
||||||
|
</span>
|
||||||
|
:
|
||||||
|
<span className="flex gap-1 text-error">
|
||||||
|
<svg width="6" height="6" viewBox="0 0 6 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect y="2.79895" width="3.95833" height="3.95833" rx="1" transform="rotate(-45 0 2.79895)" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
{t('falseActive')}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<span className="lg:w-full w-[100px] h-full text-start flex justify-start items-center gap-3">
|
||||||
|
<button onClick={async() => {
|
||||||
|
dispatch(setUpdateSubPopUp({
|
||||||
|
status: true,
|
||||||
|
data: v
|
||||||
|
}))
|
||||||
|
}} className="[&_*]:fill-success [&_*]:dark:fill-success">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4.72703 14.3043C4.74067 14.3151 4.7562 14.3231 4.76993 14.3339C4.8441 14.3912 4.92435 14.4392 5.00077 14.4933C5.17192 14.6158 5.3433 14.7375 5.52457 14.8424C5.53027 14.8456 5.53643 14.8479 5.54213 14.8512C5.76923 14.9817 6.0012 15.1034 6.24248 15.2052L6.3216 15.2381C6.43365 15.285 6.54277 15.3303 6.6666 15.3736C6.98595 15.4806 7.25767 15.5545 7.5177 15.6072C7.6839 15.6416 7.84177 15.6675 7.992 15.6872C8.01495 15.6903 8.03842 15.6911 8.06145 15.6939C8.1666 15.7064 8.27168 15.7186 8.36588 15.7236C8.54077 15.7413 8.7246 15.75 8.89965 15.75C12.6218 15.75 15.6496 12.7222 15.6496 9.00005C15.6496 8.58552 15.3142 8.25005 14.8996 8.25005C14.4851 8.25005 14.1496 8.58552 14.1496 9.00005C14.1496 11.8946 11.7942 14.25 8.89965 14.25C8.7282 14.25 8.55937 14.2326 8.39062 14.2161C8.26853 14.2062 8.13667 14.1855 8.00348 14.1629C7.8855 14.1423 7.76775 14.1204 7.65195 14.0919C7.56645 14.0712 7.48478 14.0478 7.4025 14.0235C7.01085 13.9062 6.6327 13.7503 6.2802 13.5474C6.2784 13.5463 6.27652 13.5456 6.27472 13.5446C6.13912 13.4664 6.01222 13.3743 5.88405 13.2843C5.81752 13.2369 5.74793 13.1961 5.6832 13.1452C5.61105 13.0893 5.54588 13.0247 5.47658 12.9651C5.37068 12.8729 5.26268 12.7826 5.1627 12.6833C5.11305 12.6331 5.06993 12.5763 5.02222 12.5241L6.36555 12.282C6.7728 12.2081 7.04377 11.8184 6.9705 11.4105C6.89655 11.0032 6.51202 10.7322 6.09892 10.8055L2.96707 11.3702C2.77155 11.4054 2.59793 11.5174 2.48438 11.6808C2.37083 11.8441 2.32687 12.0455 2.36205 12.2418L2.92672 15.3729C2.99265 15.7354 3.30833 15.9896 3.66428 15.9896C3.70823 15.9896 3.75293 15.9859 3.7983 15.9779C4.20555 15.9039 4.47652 15.5142 4.40325 15.1063L4.16925 13.8101C4.17495 13.8156 4.18155 13.8202 4.18725 13.8258C4.37498 14.0076 4.55175 14.1669 4.72703 14.3043ZM13.1726 3.69582C13.1594 3.68532 13.1445 3.67767 13.1312 3.66725C13.0534 3.6071 12.9693 3.55655 12.889 3.49992C12.7215 3.38052 12.5542 3.26127 12.377 3.15867C12.3641 3.15117 12.3499 3.14585 12.3368 3.13842C12.116 3.01257 11.8914 2.89377 11.6573 2.795L11.5781 2.76207C11.4661 2.7152 11.3569 2.66982 11.2331 2.62655C10.913 2.5196 10.6413 2.4449 10.3813 2.39292C10.2159 2.35857 10.0582 2.3327 9.90795 2.3129C9.88508 2.30975 9.86182 2.30907 9.83895 2.30615C9.73373 2.2937 9.62857 2.28147 9.534 2.27645C9.35887 2.25882 9.17505 2.25005 9 2.25005C5.27782 2.25005 2.25 5.27787 2.25 9.00005C2.25 9.41457 2.58548 9.75005 3 9.75005C3.41453 9.75005 3.75 9.41457 3.75 9.00005C3.75 6.1055 6.10545 3.75005 9 3.75005C9.17145 3.75005 9.34028 3.76745 9.50903 3.78402C9.63113 3.79385 9.76297 3.81462 9.89617 3.8372C10.0142 3.85782 10.1319 3.87965 10.2477 3.90815C10.3332 3.92892 10.4149 3.95232 10.4971 3.97662C11.0449 4.14065 11.5594 4.39362 12.0246 4.72182C12.0874 4.76675 12.1535 4.8053 12.2146 4.85337C12.2863 4.90887 12.3511 4.9733 12.42 5.03247C12.5275 5.12607 12.6368 5.21772 12.7393 5.31965C12.788 5.369 12.8305 5.42472 12.8773 5.4761L11.5342 5.71805C11.1269 5.792 10.856 6.1817 10.9292 6.58962C10.9951 6.95217 11.3108 7.20635 11.6668 7.20635C11.7107 7.20635 11.7554 7.20267 11.8008 7.19465L14.9326 6.62997C15.1282 6.5948 15.3018 6.48275 15.4153 6.3194C15.5289 6.15605 15.5729 5.95467 15.5377 5.7584L14.973 2.6273C14.8991 2.22005 14.513 1.94765 14.1014 2.02235C13.6942 2.0963 13.4232 2.486 13.4965 2.89392L13.7305 4.18992C13.7215 4.18092 13.7109 4.17372 13.7017 4.1648C13.5185 3.98787 13.3452 3.8318 13.1726 3.69582Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button onClick={async() => {
|
||||||
|
dispatch(setUpdatePopUp({
|
||||||
|
status: true,
|
||||||
|
data: v
|
||||||
|
}))
|
||||||
|
}} className="[&_*]:fill-success [&_*]:dark:fill-success">
|
||||||
|
<svg width="15" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6.36069 6.61884L5.39925 9.91891C5.3176 10.1996 5.39219 10.5041 5.59355 10.7111C5.74073 10.8626 5.93856 10.9446 6.14093 10.9446C6.21527 10.9446 6.29012 10.9337 6.36321 10.9109L9.56961 9.92151C9.69234 9.88363 9.80423 9.81515 9.89471 9.72177L15.7732 3.67103C15.9184 3.52161 16.0001 3.31876 16.0001 3.10761C16.0001 2.89646 15.9184 2.6936 15.7732 2.54419L13.528 0.23346C13.2256 -0.0778202 12.7357 -0.0778202 12.4333 0.23346L6.55499 6.28421C6.46426 6.37759 6.39774 6.49225 6.36069 6.61884ZM7.78786 7.26889L12.9806 1.92371L14.1308 3.10761L8.93806 8.45279L7.29541 8.95966L7.78786 7.26889Z" fill="#4DB33D"/>
|
||||||
|
<path d="M15.2258 7.4375C14.7981 7.4375 14.4516 7.79444 14.4516 8.23438V13.5469C14.4516 14.2794 13.8727 14.875 13.1613 14.875H2.83871C2.12727 14.875 1.54839 14.2794 1.54839 13.5469V2.92188C1.54839 2.18933 2.12727 1.59375 2.83871 1.59375H8C8.42767 1.59375 8.77419 1.23681 8.77419 0.796875C8.77419 0.356936 8.42767 0 8 0H2.83871C1.27344 0 0 1.311 0 2.92188V13.5469C0 15.1577 1.27344 16.4688 2.83871 16.4688H13.1613C14.7266 16.4688 16 15.1577 16 13.5469V8.23438C16 7.79444 15.6535 7.4375 15.2258 7.4375Z" fill="#4DB33D"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button onClick={async() => {
|
||||||
|
await dispatch(delete_({
|
||||||
|
id: v._id
|
||||||
|
}))
|
||||||
|
}} className="[&_*]:fill-error [&_*]:dark:fill-error">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M2.0625 4.625C2.0625 4.27983 2.32153 4.00001 2.64107 4.00001L4.63925 3.99968C5.03626 3.98881 5.38652 3.71612 5.52161 3.31268C5.52516 3.30208 5.52924 3.28899 5.54389 3.24152L5.62999 2.96245C5.68267 2.79134 5.72857 2.64227 5.79281 2.50902C6.04657 1.98262 6.51606 1.61708 7.0586 1.52349C7.19593 1.4998 7.34136 1.4999 7.50832 1.50002H10.1168C10.2838 1.4999 10.4292 1.4998 10.5665 1.52349C11.1091 1.61708 11.5786 1.98262 11.8323 2.50902C11.8966 2.64227 11.9425 2.79134 11.9951 2.96245L12.0812 3.24152C12.0959 3.28899 12.1 3.30208 12.1035 3.31268C12.2387 3.71612 12.6584 3.98915 13.0553 4.00001H14.9839C15.3034 4.00001 15.5625 4.27983 15.5625 4.625C15.5625 4.97018 15.3034 5.25 14.9839 5.25H2.64107C2.32153 5.25 2.0625 4.97018 2.0625 4.625Z" fill="currentColor"/>
|
||||||
|
<path opacity="0.5" d="M8.7051 16.4997H9.29528C11.3259 16.4997 12.3412 16.4997 13.0013 15.8523C13.6615 15.2049 13.7291 14.143 13.8641 12.0191L14.0588 8.95863C14.132 7.80626 14.1687 7.23003 13.8375 6.86489C13.5063 6.49976 12.9471 6.49976 11.8286 6.49976H6.17179C5.05329 6.49976 4.49403 6.49976 4.16286 6.86489C3.83169 7.23003 3.86833 7.80626 3.94162 8.95863L4.13625 12.0191C4.27133 14.143 4.33887 15.2049 4.99901 15.8523C5.65915 16.4997 6.67446 16.4997 8.7051 16.4997Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button onClick={async() => {
|
||||||
|
dispatch(setDetailsPopUp({
|
||||||
|
status: true,
|
||||||
|
data: v ? v : null
|
||||||
|
}))
|
||||||
|
}} className="[&_*]:fill-info [&_*]:dark:fill-info">
|
||||||
|
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M13.3336 13.3333H1.66641V1.66672H6.875V0H1.66641C0.749649 0 0 0.749883 0 1.66672V13.3333C0 14.2501 0.749649 15 1.66641 15H13.3336C14.2504 15 15 14.2501 15 13.3333V8.125H13.3336V13.3333ZM8.75 0V1.66672H12.167L3.74996 10.0833L4.9166 11.25L13.3336 2.83316V6.25H15V0H8.75Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
<ListPagination />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { useDispatch } from "react-redux"
|
||||||
|
import { setAddPopUp } from "@/redux/features/members-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
export default function AddNewButton()
|
||||||
|
{
|
||||||
|
// setup needed varibles
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const t = useTranslations('members');
|
||||||
|
// handle the open of the pop up
|
||||||
|
function handleClickOpen()
|
||||||
|
{
|
||||||
|
dispatch(setAddPopUp(true))
|
||||||
|
}
|
||||||
|
// return the component ui
|
||||||
|
return (
|
||||||
|
<button onClick={handleClickOpen}
|
||||||
|
className="
|
||||||
|
bg-secondary-light dark:bg-primary text-primary-light dark:text-text
|
||||||
|
fixed z-50 bottom-7 ltr:right-7 rtl:left-7
|
||||||
|
w-auto h-18 p-3 rounded-md drop-shadow-lg
|
||||||
|
flex justify-between items-center gap-3
|
||||||
|
">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 49 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M25 0.5C26.3807 0.5 27.5 1.61929 27.5 3L27.5 46C27.5 47.3807 26.3807 48.5 25 48.5C23.6193 48.5 22.5 47.3807 22.5 46L22.5 3C22.5 1.61929 23.6193 0.5 25 0.5Z" fill="currentColor"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.5 25C48.5 26.3807 47.3807 27.5 46 27.5L3 27.5C1.61929 27.5 0.5 26.3807 0.5 25C0.5 23.6193 1.61929 22.5 3 22.5L46 22.5C47.3807 22.5 48.5 23.6193 48.5 25Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h3>{t('addNewMember')}</h3>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
505
webapp/src/components/dashboard/members/parts/addPopUp.tsx
Normal file
505
webapp/src/components/dashboard/members/parts/addPopUp.tsx
Normal file
@ -0,0 +1,505 @@
|
|||||||
|
import { forwardRef , ReactElement , Ref, useEffect, useState } from "react"
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { AppDispatch, useAppSelector } from "@/redux/store";
|
||||||
|
import { setAddPopUp , add } from "@/redux/features/members-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import Slide from '@mui/material/Slide';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import { FormikWizard } from 'formik-wizard-form';
|
||||||
|
import * as Yup from "yup";
|
||||||
|
import Select from 'react-select'
|
||||||
|
import AsyncSelect from 'react-select/async';
|
||||||
|
import { components } from 'react-select';
|
||||||
|
import searchForServices from "@/functions/requests/members/searchForServices";
|
||||||
|
|
||||||
|
export default function AddPopUp()
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
// redux
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// intl-18
|
||||||
|
const t = useTranslations('members');
|
||||||
|
// mui
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
// get state from redux
|
||||||
|
const addPopUp = useAppSelector((state) => state.membersReducer.value.addPopUp)
|
||||||
|
const themeType = useAppSelector((state) => state.themeTypeReducer.value.themeType)
|
||||||
|
// setup the popup animation
|
||||||
|
const Transition = forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: ReactElement<any, any>;
|
||||||
|
},
|
||||||
|
ref: Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
// set the form init values
|
||||||
|
const [initialFormValues , setInitialFormValues] = useState<{
|
||||||
|
firstName: string | null,
|
||||||
|
lastName: string | null,
|
||||||
|
email: string | null,
|
||||||
|
phone: string | null,
|
||||||
|
address: string | null,
|
||||||
|
services: [string] | [],
|
||||||
|
planDelay: string | null,
|
||||||
|
gendre: string | null, // m or w
|
||||||
|
startBodyForm: string | null,
|
||||||
|
startWeight: string | null,
|
||||||
|
}>({
|
||||||
|
firstName : '',
|
||||||
|
lastName : '',
|
||||||
|
phone: '',
|
||||||
|
email: '',
|
||||||
|
address: '',
|
||||||
|
services: [],
|
||||||
|
planDelay: '',
|
||||||
|
gendre: 'm',
|
||||||
|
startBodyForm: '',
|
||||||
|
startWeight: '',
|
||||||
|
})
|
||||||
|
// first page
|
||||||
|
const FirstPaper = ({
|
||||||
|
values,
|
||||||
|
errors,
|
||||||
|
touched,
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
setFieldValue
|
||||||
|
} : any) => {
|
||||||
|
return (
|
||||||
|
<DialogContent>
|
||||||
|
{
|
||||||
|
// dialog text content contain a small description to the current form part
|
||||||
|
}
|
||||||
|
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
|
||||||
|
<h1 className="text-[18px] font-tajawal">{t('memberAddText1')}</h1>
|
||||||
|
</DialogContentText>
|
||||||
|
<div className="
|
||||||
|
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80
|
||||||
|
[&_*]:text-text/80 [&_*]:dark:text-text-dark/80
|
||||||
|
[&_*]:placeholder-text/80 [&_*]:dark:placeholder-text-dark/80
|
||||||
|
w-full h-auto mt-[36px] flex flex-col gap-3
|
||||||
|
">
|
||||||
|
<div className="w-full flex lg:flex-row flex-col lg:gap-12 gap-7">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('identificationInformation')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="firstName"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.firstName}
|
||||||
|
placeholder={t('firstName')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors.firstName && touched.firstName && t(errors.firstName)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="lastName"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.lastName}
|
||||||
|
placeholder={t('lastName')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors.lastName && touched.lastName && t(errors.lastName)}</p>
|
||||||
|
</div>
|
||||||
|
<h3 className="font-bold">{t('gendre')}</h3>
|
||||||
|
<div className="w-full flex justify-start items-center gap-5">
|
||||||
|
<input
|
||||||
|
id={`mGendre`}
|
||||||
|
type="checkbox"
|
||||||
|
className="form-checkbox p-3 border-none bg-primary-dark/10 dark:bg-primary/10 checked:bg-success-light dark:checked:bg-success-light checked:focus:bg-success-light checked:hover:bg-success-light rounded-md"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
checked={values.gendre == 'm' ? true : false}
|
||||||
|
onChange={() => {
|
||||||
|
setFieldValue("gendre" , "m")
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor={`mGendre`} className="w-full flex flex-col gap-3">
|
||||||
|
<span>
|
||||||
|
<h1 className="font-bold text-[16px]">{t("mGendre")}</h1>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex justify-start items-center gap-5">
|
||||||
|
<input
|
||||||
|
id={`wGendre`}
|
||||||
|
type="checkbox"
|
||||||
|
className="form-checkbox p-3 border-none bg-primary-dark/10 dark:bg-primary/10 checked:bg-success-light dark:checked:bg-success-light checked:focus:bg-success-light checked:hover:bg-success-light rounded-md"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
checked={values.gendre == 'w' ? true : false}
|
||||||
|
onChange={() => {
|
||||||
|
setFieldValue("gendre" , "w")
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor={`wGendre`} className="w-full flex flex-col gap-3">
|
||||||
|
<span>
|
||||||
|
<h1 className="font-bold text-[16px]">{t("wGendre")}</h1>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('contactInformations')}</h3>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<input
|
||||||
|
id="phone"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values?.phone}
|
||||||
|
placeholder={t('phone')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors.phone && touched.phone && t(errors.phone)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="email"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values?.email}
|
||||||
|
placeholder={t('email')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors.email && touched.email && t(errors.email)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
id="address"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values?.address}
|
||||||
|
placeholder={t('address')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors.address && touched.address && t(errors.address)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// second paper
|
||||||
|
const SecondPaper = ({
|
||||||
|
values,
|
||||||
|
errors,
|
||||||
|
touched,
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
setFieldValue
|
||||||
|
} : any) => {
|
||||||
|
async function loadServices(s : string) {
|
||||||
|
let docs = await searchForServices(s)
|
||||||
|
|
||||||
|
let options = docs.map((v , i) => {
|
||||||
|
return {value: v._id , label: v.name}
|
||||||
|
})
|
||||||
|
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
// body types options
|
||||||
|
const bodyTypesOptions = [
|
||||||
|
{ value: 'weak', label: t('weak') },
|
||||||
|
{ value: 'graceful', label: t('graceful') },
|
||||||
|
{ value: 'middle', label: t('middle') },
|
||||||
|
{ value: 'midhuge', label: t('midhuge') },
|
||||||
|
{ value: 'huge', label: t('huge') }
|
||||||
|
]
|
||||||
|
// contain the react select styles
|
||||||
|
const customStyles = {
|
||||||
|
container: styles => ({
|
||||||
|
...styles,
|
||||||
|
width: "100%",
|
||||||
|
backgroundColor: themeType == "light" ? theme.palette.primary.main : theme.palette.primary.dark,
|
||||||
|
margin: "7px 0 0 0",
|
||||||
|
fontFamily: 'Regular',
|
||||||
|
borderRadius: 5,
|
||||||
|
border: themeType == "light" ? "solid 2px black" : ""
|
||||||
|
}),
|
||||||
|
control: (styles : any, state : any) => ({
|
||||||
|
...styles,
|
||||||
|
minHeight: "50px",
|
||||||
|
height: "auto",
|
||||||
|
border: "none",
|
||||||
|
boxShadow: "none",
|
||||||
|
borderRadius: 5,
|
||||||
|
backgroundColor: themeType == "light" ? theme.palette.primary.main : theme.palette.primary.dark,
|
||||||
|
fontFamily: 'Regular',
|
||||||
|
color: "#000",
|
||||||
|
opacity: 0.8,
|
||||||
|
}),
|
||||||
|
option: (styles : any, { isDisable , isSelected } : {
|
||||||
|
isDisable: boolean,
|
||||||
|
isSelected: boolean
|
||||||
|
}) => ({
|
||||||
|
...styles,
|
||||||
|
zIndex: 999,
|
||||||
|
backgroundColor: isSelected ? theme.palette.primary.main : (themeType == "light" ? theme.palette.primary.main : theme.palette.primary.dark),
|
||||||
|
color: isSelected ? `#fff` : `#000` ,
|
||||||
|
padding: "15px",
|
||||||
|
cursor: "pointer",
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.secondary.light,
|
||||||
|
color: `#fff`,
|
||||||
|
},
|
||||||
|
'&:active': {
|
||||||
|
backgroundColor: `#eee`,
|
||||||
|
color: `#000`,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
menu: (styles : any, state : any) => ({
|
||||||
|
...styles,
|
||||||
|
backgroundColor: themeType == "light" ? theme.palette.primary.main : theme.palette.primary.dark,
|
||||||
|
borderRadius: 5,
|
||||||
|
}),
|
||||||
|
menuList: (styles : any, state : any) => ({
|
||||||
|
...styles,
|
||||||
|
backgroundColor: themeType == "light" ? theme.palette.primary.main : theme.palette.primary.dark,
|
||||||
|
height: '150px',
|
||||||
|
border: `2px solid #000`,
|
||||||
|
borderRadius: 5,
|
||||||
|
"::-webkit-scrollbar": {
|
||||||
|
width: "0px",
|
||||||
|
height: "0px",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
// no options react select component
|
||||||
|
const NoOptionsMessage = props => {
|
||||||
|
return (
|
||||||
|
<components.NoOptionsMessage {...props}>
|
||||||
|
<span className="custom-css-class">{t('searchForServices')}</span>
|
||||||
|
</components.NoOptionsMessage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogContent sx={{
|
||||||
|
overflowY: 'visible',
|
||||||
|
}}>
|
||||||
|
{
|
||||||
|
// dialog text content contain a small description to the current form part
|
||||||
|
}
|
||||||
|
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
|
||||||
|
<h1 className="text-[18px] font-tajawal">{t('memberAddText1')}</h1>
|
||||||
|
</DialogContentText>
|
||||||
|
<div className="
|
||||||
|
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80
|
||||||
|
[&_*]:text-text/80 [&_*]:dark:text-text-dark/80
|
||||||
|
[&_*]:placeholder-text/80 [&_*]:dark:placeholder-text-dark/80
|
||||||
|
w-full h-auto mt-[36px] flex flex-col gap-3
|
||||||
|
">
|
||||||
|
<div className="w-full flex lg:flex-row flex-col lg:gap-12 gap-7">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('subscriptionInformations')}</h3>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="planDelay"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values?.planDelay}
|
||||||
|
placeholder={t('planDelay')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors.planDelay && touched.planDelay && t(errors.planDelay)}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col lg:max-w-[350px] min-h-[150px] h-auto">
|
||||||
|
<AsyncSelect
|
||||||
|
cacheOptions
|
||||||
|
loadOptions={loadServices}
|
||||||
|
components={{ NoOptionsMessage }}
|
||||||
|
loadingMessage={() => t('search')}
|
||||||
|
isMulti
|
||||||
|
styles={customStyles}
|
||||||
|
placeholder={t("services")}
|
||||||
|
onChange={((e) => {
|
||||||
|
let ids = e.map((v,i) => {
|
||||||
|
return v.value
|
||||||
|
})
|
||||||
|
setFieldValue("services" , ids)
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors.services && touched.services && t(errors.services)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('healthInformations')}</h3>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="startWeight"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values?.startWeight}
|
||||||
|
placeholder={t('startWeight')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.startWeight && touched?.startWeight && t(errors?.startWeight)}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-5 lg:max-w-[350px] min-h-[150px] h-auto">
|
||||||
|
<Select
|
||||||
|
options={bodyTypesOptions}
|
||||||
|
styles={customStyles}
|
||||||
|
placeholder={t("bodyType")}
|
||||||
|
onChange={((e) => {
|
||||||
|
setFieldValue("startBodyForm" , e?.value)
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.startBodyForm && touched?.startBodyForm && t(errors?.startBodyForm)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// setup the function that close the popup
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch(setAddPopUp(false));
|
||||||
|
};
|
||||||
|
// we dont mount the pop up if we dont need it
|
||||||
|
if(!addPopUp) return <></>
|
||||||
|
// we mount the popup if we needed it
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
open={addPopUp}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
keepMounted
|
||||||
|
onClose={handleClose}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth={'md'}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// This is the title of the pop up
|
||||||
|
}
|
||||||
|
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
|
||||||
|
<h1 className="text-[25px] font-tajawal">{t('addNewMember')}</h1>
|
||||||
|
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</DialogTitle>
|
||||||
|
{
|
||||||
|
// This is the formik wizard component that contain multi papers each with specific inputs
|
||||||
|
}
|
||||||
|
<FormikWizard
|
||||||
|
initialValues={initialFormValues}// values type is same as initialFormValues type
|
||||||
|
onSubmit={async (values : any) => {
|
||||||
|
await dispatch(add(values))
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
validateOnNext
|
||||||
|
activeStepIndex={0}
|
||||||
|
steps={[
|
||||||
|
{
|
||||||
|
component: FirstPaper,
|
||||||
|
// this is the paper validation schema ( can`t go to next paper or submit ( for last paper ) if the validation schema return a false result )
|
||||||
|
validationSchema: Yup.object().shape({
|
||||||
|
firstName: Yup.string().required('thisFieldIsRequired'),
|
||||||
|
lastName: Yup.string().required('thisFieldIsRequired'),
|
||||||
|
phone: Yup.string().required('thisFieldIsRequired'),
|
||||||
|
email: Yup.string().email('invalidEmailAddress').required('thisFieldIsRequired'),
|
||||||
|
address: Yup.string().required('thisFieldIsRequired'),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: SecondPaper,
|
||||||
|
// this is the paper validation schema ( can`t go to next paper or submit if last paper if the validation schema return a false result )
|
||||||
|
validationSchema: Yup.object().shape({
|
||||||
|
planDelay: Yup.number().typeError('mustBeANumber').required('thisFieldIsRequired'),
|
||||||
|
services: Yup.array().min(1 , "selectAtLeastOneService"),
|
||||||
|
startBodyForm: Yup.string().required('thisFieldIsRequired'),
|
||||||
|
startWeight: Yup.number().typeError('mustBeANumber').required('thisFieldIsRequired'),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
renderComponent,
|
||||||
|
handlePrev,
|
||||||
|
handleNext,
|
||||||
|
isNextDisabled,
|
||||||
|
isPrevDisabled,
|
||||||
|
isLastStep,
|
||||||
|
isSubmitting,
|
||||||
|
currentStepIndex
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
{renderComponent()}
|
||||||
|
</div>
|
||||||
|
<DialogActions sx={{ padding: '16px 24px' }}>
|
||||||
|
{
|
||||||
|
isSubmitting?
|
||||||
|
<div className="w-full flex gap-3 justify-end items-start">
|
||||||
|
<CircularProgress color="secondary" size={24} />
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<div className="w-full flex gap-3 justify-end items-start">
|
||||||
|
{currentStepIndex != 0 ? <button className="btn font-semibold" type="button" onClick={handlePrev} disabled={isPrevDisabled}>
|
||||||
|
{t('back')}
|
||||||
|
</button> : <></>}
|
||||||
|
<button disabled={isNextDisabled} className="disabled:opacity-10 btn font-semibold" type="button" onClick={handleNext}>
|
||||||
|
{isLastStep ? t('done') : t('next')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</DialogActions>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</FormikWizard>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
260
webapp/src/components/dashboard/members/parts/detailsPopUp.tsx
Normal file
260
webapp/src/components/dashboard/members/parts/detailsPopUp.tsx
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
import { forwardRef , ReactElement , Ref } from "react"
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { AppDispatch, useAppSelector } from "@/redux/store";
|
||||||
|
import { setDetailsPopUp } from "@/redux/features/members-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import Slide from '@mui/material/Slide';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
|
||||||
|
export default function ServiceDetailsPopUp()
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
// redux
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// intl-18
|
||||||
|
const t = useTranslations('members');
|
||||||
|
// mui
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
// get state from redux
|
||||||
|
const detailsPopUp = useAppSelector((state) => state.membersReducer.value.detailsPopUp)
|
||||||
|
const detailsPopUpData = useAppSelector((state) => state.membersReducer.value.detailsPopUpData)
|
||||||
|
// setup the popup animation
|
||||||
|
const Transition = forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: ReactElement<any, any>;
|
||||||
|
},
|
||||||
|
ref: Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
// setup the function that close the popup
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch(setDetailsPopUp({
|
||||||
|
status: false,
|
||||||
|
data: null
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
// we dont mount the pop up if we dont need it
|
||||||
|
if(!detailsPopUp) return <></>
|
||||||
|
// convert unix into read able date
|
||||||
|
function UnixToReadAbleDate(unix_time : number)
|
||||||
|
{
|
||||||
|
let dateObj = new Date(unix_time * 1000)
|
||||||
|
// we got the year
|
||||||
|
let year = dateObj.getUTCFullYear(),
|
||||||
|
// get month
|
||||||
|
month = dateObj.getUTCMonth(),
|
||||||
|
// get day
|
||||||
|
day = dateObj.getUTCDay(),
|
||||||
|
// get the hours
|
||||||
|
hours = dateObj.getUTCHours(),
|
||||||
|
// minutes
|
||||||
|
minutes = dateObj.getUTCMinutes(),
|
||||||
|
// seconds
|
||||||
|
seconds = dateObj.getUTCSeconds();
|
||||||
|
// now we format the string
|
||||||
|
let formattedTime = year.toString()+ '-' + month.toString() + '-' + day.toString();
|
||||||
|
return formattedTime;
|
||||||
|
}
|
||||||
|
// we mount the popup if we needed it
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
open={detailsPopUp}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
keepMounted
|
||||||
|
onClose={handleClose}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth={'md'}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// This is the title of the pop up
|
||||||
|
}
|
||||||
|
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
|
||||||
|
<h1 className="text-[25px] font-tajawal">{t('memberDetails')}</h1>
|
||||||
|
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</DialogTitle>
|
||||||
|
{
|
||||||
|
// Formik form start from here
|
||||||
|
}
|
||||||
|
<Formik
|
||||||
|
// Initial values
|
||||||
|
initialValues={{ name : '' , description : '' , price: 0 , status: '' }}
|
||||||
|
// this function handle the submition of the formik form
|
||||||
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{
|
||||||
|
// dialog content contain the formik form
|
||||||
|
}
|
||||||
|
<DialogContent>
|
||||||
|
{
|
||||||
|
// dialog text content contain a small description to the current form part
|
||||||
|
}
|
||||||
|
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
|
||||||
|
<h1 className="text-[18px] font-tajawal">{t('memberDetailsText1')}</h1>
|
||||||
|
</DialogContentText>
|
||||||
|
<div className="
|
||||||
|
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80
|
||||||
|
[&_*]:text-text/80 [&_*]:dark:text-text-dark/80
|
||||||
|
[&_*]:placeholder-text/80 [&_*]:dark:placeholder-text-dark/80
|
||||||
|
w-full h-auto mt-[36px] flex flex-col gap-3
|
||||||
|
">
|
||||||
|
<div className="w-full flex lg:flex-row flex-col lg:gap-12 gap-7">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-3">
|
||||||
|
<h3 className="font-bold">{t('identificationInformation')}</h3>
|
||||||
|
<input
|
||||||
|
id="firstName"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={detailsPopUpData?.firstName ? detailsPopUpData.firstName : ''}
|
||||||
|
placeholder={t('firstName')}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
id="lastName"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={detailsPopUpData?.lastName ? detailsPopUpData.lastName : ''}
|
||||||
|
placeholder={t('lastName')}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<h3 className="font-bold">{t('gendre')}</h3>
|
||||||
|
<div className="w-full flex justify-start items-center gap-5">
|
||||||
|
<input
|
||||||
|
disabled
|
||||||
|
id={`active`}
|
||||||
|
type="checkbox"
|
||||||
|
className="form-checkbox p-3 border-none bg-primary-dark/10 dark:bg-primary/10 checked:bg-success-light dark:checked:bg-success-light checked:focus:bg-success-light checked:hover:bg-success-light rounded-md"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
checked={detailsPopUpData?.gendre == "m"}
|
||||||
|
/>
|
||||||
|
<label htmlFor={`active`} className="w-full flex flex-col gap-3">
|
||||||
|
<span>
|
||||||
|
<h1 className="font-bold text-[16px]">{t("mGendre")}</h1>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex justify-start items-center gap-5">
|
||||||
|
<input
|
||||||
|
disabled
|
||||||
|
id={`deactivate`}
|
||||||
|
type="checkbox"
|
||||||
|
className="form-checkbox p-3 border-none bg-primary-dark/10 dark:bg-primary/10 checked:bg-success-light dark:checked:bg-success-light checked:focus:bg-success-light checked:hover:bg-success-light rounded-md"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
checked={detailsPopUpData?.gendre != "m"}
|
||||||
|
/>
|
||||||
|
<label htmlFor={`deactivate`} className="w-full flex flex-col gap-3">
|
||||||
|
<span>
|
||||||
|
<h1 className="font-bold text-[16px]">{t("wGendre")}</h1>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-3">
|
||||||
|
<h3 className="font-bold">{t('contactInformations')}</h3>
|
||||||
|
<div className="flex flex-col gap-5">
|
||||||
|
<input
|
||||||
|
id="phone"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={detailsPopUpData?.phone ? detailsPopUpData.phone : ''}
|
||||||
|
placeholder={t('phone')}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
disabled
|
||||||
|
id="email"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={detailsPopUpData?.email ? detailsPopUpData.email : ''}
|
||||||
|
placeholder={t('email')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
id="address"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={detailsPopUpData?.address ? detailsPopUpData.address : ''}
|
||||||
|
placeholder={t('address')}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex text-start mt-7">
|
||||||
|
<p>{t('registerAt') + ' ' + UnixToReadAbleDate(parseInt(detailsPopUpData?.registerAt_unix))}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
{
|
||||||
|
// dialog actions contain buttons that effect the popup status
|
||||||
|
}
|
||||||
|
<DialogActions sx={{ padding: '16px 24px' }}>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button type="submit" className="btn font-semibold">
|
||||||
|
{t('close')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
import Pagination from '@mui/material/Pagination';
|
||||||
|
import { setCurrentPage } from '@/redux/features/members-slice';
|
||||||
|
import { load } from '@/redux/features/members-slice'
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { AppDispatch , useAppSelector } from '@/redux/store';
|
||||||
|
|
||||||
|
export default function ListPagination() {
|
||||||
|
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// load needed states
|
||||||
|
const currentPage = useAppSelector((state) => state.membersReducer.value.currentPage)
|
||||||
|
const total = useAppSelector((state) => state.membersReducer.value.total)
|
||||||
|
// handle the pagination
|
||||||
|
const handlePagination = (event: React.ChangeEvent<unknown>, value: number) => {
|
||||||
|
dispatch(load({page: value}));
|
||||||
|
dispatch(setCurrentPage(value));
|
||||||
|
};
|
||||||
|
// show the pagination ui
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{Math.ceil(total / 4) > 1 ? (
|
||||||
|
<div className="flex justify-center items-center py-12">
|
||||||
|
<Pagination
|
||||||
|
sx={{ direction: 'ltr' }}
|
||||||
|
count={Math.ceil(total / 4)}
|
||||||
|
page={currentPage}
|
||||||
|
onChange={handlePagination}
|
||||||
|
variant="outlined"
|
||||||
|
shape="rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
269
webapp/src/components/dashboard/members/parts/updatePopUp.tsx
Normal file
269
webapp/src/components/dashboard/members/parts/updatePopUp.tsx
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
import { forwardRef , ReactElement , Ref, useEffect } from "react"
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { AppDispatch, useAppSelector } from "@/redux/store";
|
||||||
|
import { setUpdatePopUp , update } from "@/redux/features/members-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import Slide from '@mui/material/Slide';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
|
||||||
|
export default function UpdatePopUp()
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
// redux
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// intl-18
|
||||||
|
const t = useTranslations('members');
|
||||||
|
// mui
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
// get state from redux
|
||||||
|
const updatePopUp = useAppSelector((state) => state.membersReducer.value.updatePopUp)
|
||||||
|
const updatePopUpData = useAppSelector((state) => state.membersReducer.value.updatePopUpData)
|
||||||
|
// setup the popup animation
|
||||||
|
const Transition = forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: ReactElement<any, any>;
|
||||||
|
},
|
||||||
|
ref: Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
// setup the function that close the popup
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch(setUpdatePopUp({
|
||||||
|
status: false,
|
||||||
|
data: null
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
// we dont mount the pop up if we dont need it
|
||||||
|
if(!updatePopUp) return <></>
|
||||||
|
// we mount the popup if we needed it
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
open={updatePopUp}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
keepMounted
|
||||||
|
onClose={handleClose}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth={'md'}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// This is the title of the pop up
|
||||||
|
}
|
||||||
|
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
|
||||||
|
<h1 className="text-[25px] font-tajawal">{t('updateMember')}</h1>
|
||||||
|
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</DialogTitle>
|
||||||
|
{
|
||||||
|
// Formik form start from here
|
||||||
|
}
|
||||||
|
<Formik
|
||||||
|
// Initial values
|
||||||
|
initialValues={{
|
||||||
|
_id: updatePopUpData?._id ? updatePopUpData?._id : '' ,
|
||||||
|
firstName : updatePopUpData?.firstName ? updatePopUpData?.firstName : '' ,
|
||||||
|
lastName : updatePopUpData?.lastName ? updatePopUpData?.lastName : '' ,
|
||||||
|
email : updatePopUpData?.email ? updatePopUpData?.email : '' ,
|
||||||
|
phone : updatePopUpData?.phone ? updatePopUpData?.phone : '' ,
|
||||||
|
//gendre: updatePopUpData?.gendre ? updatePopUpData?.gendre : 'm' ,
|
||||||
|
address : updatePopUpData?.address ? updatePopUpData?.address : '' ,
|
||||||
|
active: updatePopUpData?.active,
|
||||||
|
//payMonth: updatePopUpData?.payMonth ? updatePopUpData?.payMonth : 0,
|
||||||
|
//bodyState: updatePopUpData?.bodyState ? updatePopUpData?.bodyState : {},
|
||||||
|
//registerAt: updatePopUpData?.registerAt ? updatePopUpData?.registerAt : '',
|
||||||
|
//registerAt_unix: updatePopUpData?.registerAt_unix ? updatePopUpData?.registerAt_unix : '',
|
||||||
|
//planDelay: updatePopUpData?.planDelay ? updatePopUpData?.planDelay : 0,
|
||||||
|
//planDelay_unix: updatePopUpData?.planDelay_unix ? updatePopUpData?.planDelay_unix : 0,
|
||||||
|
//services: updatePopUpData?.services ? updatePopUpData?.services : [],
|
||||||
|
}}
|
||||||
|
// this function handle the submition of the formik form
|
||||||
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
|
// dispatch the update function
|
||||||
|
await dispatch(update(values))
|
||||||
|
// close the popup
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
values,
|
||||||
|
setFieldValue
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{
|
||||||
|
// dialog content contain the formik form
|
||||||
|
}
|
||||||
|
<DialogContent>
|
||||||
|
{
|
||||||
|
// dialog text content contain a small description to the current form part
|
||||||
|
}
|
||||||
|
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
|
||||||
|
<h1 className="text-[18px] font-tajawal">{t('memberUpdateText1')}</h1>
|
||||||
|
</DialogContentText>
|
||||||
|
<div className="
|
||||||
|
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80
|
||||||
|
[&_*]:text-text/80 [&_*]:dark:text-text-dark/80
|
||||||
|
[&_*]:placeholder-text/80 [&_*]:dark:placeholder-text-dark/80
|
||||||
|
w-full h-auto mt-[36px] flex flex-col gap-3
|
||||||
|
">
|
||||||
|
<div className="w-full flex lg:flex-row flex-col lg:gap-12 gap-7">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('identificationInformation')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="firstName"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.firstName}
|
||||||
|
placeholder={t('firstName')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="lastName"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.lastName}
|
||||||
|
placeholder={t('lastName')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h3 className="font-bold">{t('status')}</h3>
|
||||||
|
<div className="w-full flex justify-start items-center gap-5">
|
||||||
|
<input
|
||||||
|
id={`active`}
|
||||||
|
type="checkbox"
|
||||||
|
className="form-checkbox p-3 border-none bg-primary-dark/10 dark:bg-primary/10 checked:bg-success-light dark:checked:bg-success-light checked:focus:bg-success-light checked:hover:bg-success-light rounded-md"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
checked={values.active ? values.active : false}
|
||||||
|
onChange={() => {
|
||||||
|
setFieldValue("active" , true)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor={`active`} className="w-full flex flex-col gap-3">
|
||||||
|
<span>
|
||||||
|
<h1 className="font-bold text-[16px]">{t("trueActive")}</h1>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex justify-start items-center gap-5">
|
||||||
|
<input
|
||||||
|
id={`deactivate`}
|
||||||
|
type="checkbox"
|
||||||
|
className="form-checkbox p-3 border-none bg-primary-dark/10 dark:bg-primary/10 checked:bg-success-light dark:checked:bg-success-light checked:focus:bg-success-light checked:hover:bg-success-light rounded-md"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
checked={!values.active}
|
||||||
|
onChange={() => {
|
||||||
|
setFieldValue("active" , false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor={`deactivate`} className="w-full flex flex-col gap-3">
|
||||||
|
<span>
|
||||||
|
<h1 className="font-bold text-[16px]">{t("falseActive")}</h1>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('contactInformations')}</h3>
|
||||||
|
<div className="flex flex-col gap-5">
|
||||||
|
<input
|
||||||
|
id="phone"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values?.phone}
|
||||||
|
placeholder={t('phone')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="email"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values?.email}
|
||||||
|
placeholder={t('email')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
id="address"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values?.address}
|
||||||
|
placeholder={t('address')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
{
|
||||||
|
// dialog actions contain buttons that effect the popup status
|
||||||
|
}
|
||||||
|
<DialogActions sx={{ padding: '16px 24px' }}>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button type="submit" className="btn font-semibold">
|
||||||
|
{
|
||||||
|
isSubmitting ?
|
||||||
|
<CircularProgress color="secondary" size={24} />
|
||||||
|
:
|
||||||
|
t('save')
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
325
webapp/src/components/dashboard/members/parts/updateSubPopUp.tsx
Normal file
325
webapp/src/components/dashboard/members/parts/updateSubPopUp.tsx
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
import { forwardRef , ReactElement , Ref, useEffect } from "react"
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { AppDispatch, useAppSelector } from "@/redux/store";
|
||||||
|
import { setUpdateSubPopUp , updateSub } from "@/redux/features/members-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import Slide from '@mui/material/Slide';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import Select from 'react-select'
|
||||||
|
import { search as searchForServices , load as loadServices } from '@/redux/features/services-slice'
|
||||||
|
import { components } from 'react-select';
|
||||||
|
import AsyncSelect from 'react-select/async';
|
||||||
|
|
||||||
|
export default function UpdateSubPopUp()
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
// redux
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// intl-18
|
||||||
|
const t = useTranslations('members');
|
||||||
|
// mui
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
// get state from redux
|
||||||
|
const updateSubPopUp = useAppSelector((state) => state.membersReducer.value.updateSubPopUp)
|
||||||
|
const updateSubPopUpData = useAppSelector((state) => state.membersReducer.value.updateSubPopUpData)
|
||||||
|
const themeType = useAppSelector((state) => state.themeTypeReducer.value.themeType)
|
||||||
|
// setup the popup animation
|
||||||
|
const Transition = forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: ReactElement<any, any>;
|
||||||
|
},
|
||||||
|
ref: Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
// setup the function that close the popup
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch(setUpdateSubPopUp({
|
||||||
|
status: false,
|
||||||
|
data: null
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
// setup multi select
|
||||||
|
// no options react select component
|
||||||
|
const NoOptionsMessage = props => {
|
||||||
|
return (
|
||||||
|
<components.NoOptionsMessage {...props}>
|
||||||
|
<span className="custom-css-class">{t('searchForServices')}</span>
|
||||||
|
</components.NoOptionsMessage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
// body types options
|
||||||
|
const bodyTypesOptions = [
|
||||||
|
{ value: 'weak', label: t('weak') },
|
||||||
|
{ value: 'graceful', label: t('graceful') },
|
||||||
|
{ value: 'middle', label: t('middle') },
|
||||||
|
{ value: 'midhuge', label: t('midhuge') },
|
||||||
|
{ value: 'huge', label: t('huge') }
|
||||||
|
]
|
||||||
|
// contain the react select styles
|
||||||
|
const customStyles = {
|
||||||
|
container: styles => ({
|
||||||
|
...styles,
|
||||||
|
width: "100%",
|
||||||
|
backgroundColor: themeType == "light" ? theme.palette.primary.main : theme.palette.primary.dark,
|
||||||
|
margin: "7px 0 0 0",
|
||||||
|
fontFamily: 'Regular',
|
||||||
|
borderRadius: 5,
|
||||||
|
border: themeType == "light" ? "solid 2px black" : ""
|
||||||
|
}),
|
||||||
|
control: (styles : any, state : any) => ({
|
||||||
|
...styles,
|
||||||
|
minHeight: "50px",
|
||||||
|
height: "auto",
|
||||||
|
border: "none",
|
||||||
|
boxShadow: "none",
|
||||||
|
borderRadius: 5,
|
||||||
|
backgroundColor: themeType == "light" ? theme.palette.primary.main : theme.palette.primary.dark,
|
||||||
|
fontFamily: 'Regular',
|
||||||
|
color: "#000",
|
||||||
|
opacity: 0.8,
|
||||||
|
}),
|
||||||
|
option: (styles : any, { isDisable , isSelected } : {
|
||||||
|
isDisable: boolean,
|
||||||
|
isSelected: boolean
|
||||||
|
}) => ({
|
||||||
|
...styles,
|
||||||
|
zIndex: 999,
|
||||||
|
backgroundColor: isSelected ? theme.palette.primary.main : (themeType == "light" ? theme.palette.primary.main : theme.palette.primary.dark),
|
||||||
|
color: isSelected ? `#fff` : `#000` ,
|
||||||
|
padding: "15px",
|
||||||
|
cursor: "pointer",
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.secondary.light,
|
||||||
|
color: `#fff`,
|
||||||
|
},
|
||||||
|
'&:active': {
|
||||||
|
backgroundColor: `#eee`,
|
||||||
|
color: `#000`,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
menu: (styles : any, state : any) => ({
|
||||||
|
...styles,
|
||||||
|
backgroundColor: themeType == "light" ? theme.palette.primary.main : theme.palette.primary.dark,
|
||||||
|
borderRadius: 5,
|
||||||
|
}),
|
||||||
|
menuList: (styles : any, state : any) => ({
|
||||||
|
...styles,
|
||||||
|
backgroundColor: themeType == "light" ? theme.palette.primary.main : theme.palette.primary.dark,
|
||||||
|
height: '150px',
|
||||||
|
border: `2px solid #000`,
|
||||||
|
borderRadius: 5,
|
||||||
|
"::-webkit-scrollbar": {
|
||||||
|
width: "0px",
|
||||||
|
height: "0px",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
function ServicesMultiSelect ({setFieldValue , errors , touched} : {setFieldValue : any , errors : any , touched : any}) {
|
||||||
|
// load services
|
||||||
|
const listContent = useAppSelector((state) => state.servicesReducer.value.listContent)
|
||||||
|
async function loadServices (searchKeyword : string) {
|
||||||
|
if(searchKeyword)
|
||||||
|
{
|
||||||
|
await dispatch(searchForServices({searchKeyword: searchKeyword}))
|
||||||
|
}
|
||||||
|
let options = listContent.map((v , i) => {
|
||||||
|
return {value: v._id , label: v.name}
|
||||||
|
})
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col max-w-[350px] min-h-[150px] h-auto">
|
||||||
|
<AsyncSelect
|
||||||
|
cacheOptions
|
||||||
|
loadOptions={loadServices}
|
||||||
|
components={{ NoOptionsMessage }}
|
||||||
|
loadingMessage={() => t('search')}
|
||||||
|
isMulti
|
||||||
|
styles={customStyles}
|
||||||
|
placeholder={t("services")}
|
||||||
|
onChange={((e) => {
|
||||||
|
let ids = e.map((v,i) => {
|
||||||
|
return v.value
|
||||||
|
})
|
||||||
|
setFieldValue("services" , ids)
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors.services && touched.services && t(errors.services)}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// we dont mount the pop up if we dont need it
|
||||||
|
if(!updateSubPopUp) return <></>
|
||||||
|
// we mount the popup if we needed it
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
open={updateSubPopUp}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
keepMounted
|
||||||
|
onClose={handleClose}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth={'md'}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// This is the title of the pop up
|
||||||
|
}
|
||||||
|
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
|
||||||
|
<h1 className="text-[25px] font-tajawal">{t('memberSubscriptionRenewal')}</h1>
|
||||||
|
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</DialogTitle>
|
||||||
|
{
|
||||||
|
// Formik form start from here
|
||||||
|
}
|
||||||
|
<Formik
|
||||||
|
// Initial values
|
||||||
|
initialValues={{
|
||||||
|
_id: updateSubPopUpData?._id ? updateSubPopUpData?._id : '' ,
|
||||||
|
//firstName : updatePopUpData?.firstName ? updatePopUpData?.firstName : '' ,
|
||||||
|
//lastName : updatePopUpData?.lastName ? updatePopUpData?.lastName : '' ,
|
||||||
|
//email : updatePopUpData?.email ? updatePopUpData?.email : '' ,
|
||||||
|
//phone : updatePopUpData?.phone ? updatePopUpData?.phone : '' ,
|
||||||
|
//gendre: updatePopUpData?.gendre ? updatePopUpData?.gendre : 'm' ,
|
||||||
|
//address : updatePopUpData?.address ? updatePopUpData?.address : '' ,
|
||||||
|
//active: updatePopUpData?.active,
|
||||||
|
//payMonth: updatePopUpData?.payMonth ? updatePopUpData?.payMonth : 0,
|
||||||
|
currentBodyForm: '0',
|
||||||
|
currentWeight: '0',
|
||||||
|
//registerAt: updatePopUpData?.registerAt ? updatePopUpData?.registerAt : '',
|
||||||
|
//registerAt_unix: updatePopUpData?.registerAt_unix ? updatePopUpData?.registerAt_unix : '',
|
||||||
|
planDelay: 0,
|
||||||
|
//planDelay_unix: updatePopUpData?.planDelay_unix ? updatePopUpData?.planDelay_unix : 0,
|
||||||
|
services: [],
|
||||||
|
}}
|
||||||
|
// this function handle the submition of the formik form
|
||||||
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
|
// dispatch the update function
|
||||||
|
await dispatch(updateSub(values))
|
||||||
|
// close the popup
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
values,
|
||||||
|
setFieldValue,
|
||||||
|
errors,
|
||||||
|
touched
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{
|
||||||
|
// dialog content contain the formik form
|
||||||
|
}
|
||||||
|
<DialogContent sx={{
|
||||||
|
overflowY: 'visible',
|
||||||
|
}}>
|
||||||
|
{
|
||||||
|
// dialog text content contain a small description to the current form part
|
||||||
|
}
|
||||||
|
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
|
||||||
|
<h1 className="text-[18px] font-tajawal">{t('memberSubscriptionRenewalText1')}</h1>
|
||||||
|
</DialogContentText>
|
||||||
|
<div className="
|
||||||
|
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80
|
||||||
|
[&_*]:text-text/80 [&_*]:dark:text-text-dark/80
|
||||||
|
[&_*]:placeholder-text/80 [&_*]:dark:placeholder-text-dark/80
|
||||||
|
w-full h-auto mt-[36px] flex flex-col gap-3
|
||||||
|
">
|
||||||
|
<div className="w-full flex lg:flex-row flex-col lg:gap-12 gap-7">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('planDetails')}</h3>
|
||||||
|
<div className="flex flex-col gap-5">
|
||||||
|
<input
|
||||||
|
id="planDelay"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values?.planDelay}
|
||||||
|
placeholder={t('planDelay')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ServicesMultiSelect setFieldValue={setFieldValue} errors={errors} touched={touched}/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('BodyStateInformations')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="currentWeight"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[350px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.currentWeight}
|
||||||
|
placeholder={t('currentWeight')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-5 max-w-[350px] min-h-[150px] h-auto">
|
||||||
|
<Select
|
||||||
|
options={bodyTypesOptions}
|
||||||
|
styles={customStyles}
|
||||||
|
placeholder={t("bodyType")}
|
||||||
|
onChange={((e) => {
|
||||||
|
setFieldValue("currentBodyForm" , e?.value)
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.currentBodyForm && touched?.currentBodyForm && t(errors?.currentBodyForm)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
{
|
||||||
|
// dialog actions contain buttons that effect the popup status
|
||||||
|
}
|
||||||
|
<DialogActions sx={{ padding: '16px 24px' }}>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button type="submit" className="btn font-semibold">
|
||||||
|
{
|
||||||
|
isSubmitting ?
|
||||||
|
<CircularProgress color="secondary" size={24} />
|
||||||
|
:
|
||||||
|
t('save')
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
16
webapp/src/components/dashboard/members/popUpsWrapper.tsx
Normal file
16
webapp/src/components/dashboard/members/popUpsWrapper.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import AddPopUp from './parts/addPopUp'
|
||||||
|
import DetailsPopUp from './parts/detailsPopUp'
|
||||||
|
import UpdatePopUp from './parts/updatePopUp'
|
||||||
|
import UpdateSubPopUp from './parts/updateSubPopUp'
|
||||||
|
|
||||||
|
export default function PopUpWrapper()
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<UpdatePopUp />
|
||||||
|
<AddPopUp />
|
||||||
|
<DetailsPopUp />
|
||||||
|
<UpdateSubPopUp />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { useDispatch } from "react-redux"
|
||||||
|
import { setAddPopUp } from "@/redux/features/products-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
export default function AddNewButton()
|
||||||
|
{
|
||||||
|
// setup needed varibles
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const t = useTranslations('products');
|
||||||
|
// handle the open of the pop up
|
||||||
|
function handleClickOpen()
|
||||||
|
{
|
||||||
|
dispatch(setAddPopUp(true))
|
||||||
|
}
|
||||||
|
// return the component ui
|
||||||
|
return (
|
||||||
|
<button onClick={handleClickOpen}
|
||||||
|
className="
|
||||||
|
bg-secondary-light dark:bg-primary text-primary-light dark:text-text
|
||||||
|
fixed z-50 bottom-7 ltr:right-7 rtl:left-7
|
||||||
|
w-auto h-18 p-3 rounded-md drop-shadow-lg
|
||||||
|
flex justify-between items-center gap-3
|
||||||
|
">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 49 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M25 0.5C26.3807 0.5 27.5 1.61929 27.5 3L27.5 46C27.5 47.3807 26.3807 48.5 25 48.5C23.6193 48.5 22.5 47.3807 22.5 46L22.5 3C22.5 1.61929 23.6193 0.5 25 0.5Z" fill="currentColor"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.5 25C48.5 26.3807 47.3807 27.5 46 27.5L3 27.5C1.61929 27.5 0.5 26.3807 0.5 25C0.5 23.6193 1.61929 22.5 3 22.5L46 22.5C47.3807 22.5 48.5 23.6193 48.5 25Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h3>{t('addNewProduct')}</h3>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
212
webapp/src/components/dashboard/products/parts/addPopUp.tsx
Normal file
212
webapp/src/components/dashboard/products/parts/addPopUp.tsx
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
import { forwardRef , ReactElement , Ref } from "react"
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { AppDispatch, useAppSelector } from "@/redux/store";
|
||||||
|
import { setAddPopUp , add } from "@/redux/features/products-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import Slide from '@mui/material/Slide';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import * as Yup from "yup";
|
||||||
|
|
||||||
|
export default function AddPopUp()
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
// redux
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// intl-18
|
||||||
|
const t = useTranslations('products');
|
||||||
|
// mui
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
// get state from redux
|
||||||
|
const addPopUp = useAppSelector((state) => state.productsReducer.value.addPopUp)
|
||||||
|
// setup the popup animation
|
||||||
|
const Transition = forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: ReactElement<any, any>;
|
||||||
|
},
|
||||||
|
ref: Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
// setup the function that close the popup
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch(setAddPopUp(false));
|
||||||
|
};
|
||||||
|
// we dont mount the pop up if we dont need it
|
||||||
|
if(!addPopUp) return <></>
|
||||||
|
// we mount the popup if we needed it
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
open={addPopUp}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
keepMounted
|
||||||
|
onClose={handleClose}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth={'md'}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// This is the title of the pop up
|
||||||
|
}
|
||||||
|
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
|
||||||
|
<h1 className="text-[25px] font-tajawal">{t('addNewProduct')}</h1>
|
||||||
|
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</DialogTitle>
|
||||||
|
{
|
||||||
|
// Formik form start from here
|
||||||
|
}
|
||||||
|
<Formik
|
||||||
|
// Initial values
|
||||||
|
initialValues={{ name : '' , description : '' , price: 0 , quantity: 0 }}
|
||||||
|
// validat the inputs
|
||||||
|
validationSchema={Yup.object().shape({
|
||||||
|
name: Yup.string().required('thisFieldIsRequired'),
|
||||||
|
description: Yup.string().required('thisFieldIsRequired'),
|
||||||
|
price: Yup.number().typeError('mustBeANumber').required('thisFieldIsRequired'),
|
||||||
|
quantity: Yup.number().typeError('mustBeANumber').required('thisFieldIsRequired'),
|
||||||
|
})}
|
||||||
|
// this function handle the submition of the formik form
|
||||||
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
|
await dispatch(add(values))
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
values,
|
||||||
|
errors,
|
||||||
|
touched
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{
|
||||||
|
// dialog content contain the formik form
|
||||||
|
}
|
||||||
|
<DialogContent>
|
||||||
|
{
|
||||||
|
// dialog text content contain a small description to the current form part
|
||||||
|
}
|
||||||
|
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
|
||||||
|
<h1 className="text-[18px] font-tajawal">{t('productAddText1')}</h1>
|
||||||
|
</DialogContentText>
|
||||||
|
<div className="
|
||||||
|
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80
|
||||||
|
[&_*]:text-text/80 [&_*]:dark:text-text-dark/80
|
||||||
|
[&_*]:placeholder-text/80 [&_*]:dark:placeholder-text-dark/80
|
||||||
|
w-full h-auto mt-[36px] flex flex-col gap-3
|
||||||
|
">
|
||||||
|
<div className="w-full flex lg:flex-row flex-col lg:gap-12 gap-7">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('productInformations')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.name}
|
||||||
|
placeholder={t('name')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.name && touched?.name && t(errors?.name)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.description}
|
||||||
|
placeholder={t('description')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.description && touched?.description && t(errors?.description)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('price&quantity')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="price"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.price == 0 ? '' : values.price}
|
||||||
|
placeholder={t('price')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.price && touched?.price && t(errors?.price)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="quantity"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.quantity == 0 ? '' : values.quantity}
|
||||||
|
placeholder={t('quantity')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.quantity && touched?.quantity && t(errors?.quantity)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
{
|
||||||
|
// dialog actions contain buttons that effect the popup status
|
||||||
|
}
|
||||||
|
<DialogActions sx={{ padding: '16px 24px' }}>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button type="submit" className="btn font-semibold">
|
||||||
|
{
|
||||||
|
isSubmitting ?
|
||||||
|
<CircularProgress color="secondary" size={24} />
|
||||||
|
:
|
||||||
|
t('done')
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
195
webapp/src/components/dashboard/products/parts/detailsPopUp.tsx
Normal file
195
webapp/src/components/dashboard/products/parts/detailsPopUp.tsx
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
import { forwardRef , ReactElement , Ref } from "react"
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { AppDispatch, useAppSelector } from "@/redux/store";
|
||||||
|
import { setDetailsPopUp } from "@/redux/features/products-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import Slide from '@mui/material/Slide';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
|
||||||
|
export default function ServiceDetailsPopUp()
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
// redux
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// intl-18
|
||||||
|
const t = useTranslations('products');
|
||||||
|
// mui
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
// get state from redux
|
||||||
|
const detailsPopUp = useAppSelector((state) => state.productsReducer.value.detailsPopUp)
|
||||||
|
const detailsPopUpData = useAppSelector((state) => state.productsReducer.value.detailsPopUpData)
|
||||||
|
// setup the popup animation
|
||||||
|
const Transition = forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: ReactElement<any, any>;
|
||||||
|
},
|
||||||
|
ref: Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
// setup the function that close the popup
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch(setDetailsPopUp({
|
||||||
|
status: false,
|
||||||
|
data: null
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
// we dont mount the pop up if we dont need it
|
||||||
|
if(!detailsPopUp) return <></>
|
||||||
|
// we mount the popup if we needed it
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
open={detailsPopUp}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
keepMounted
|
||||||
|
onClose={handleClose}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth={'md'}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// This is the title of the pop up
|
||||||
|
}
|
||||||
|
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
|
||||||
|
<h1 className="text-[25px] font-tajawal">{t('productDetails')}</h1>
|
||||||
|
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</DialogTitle>
|
||||||
|
{
|
||||||
|
// Formik form start from here
|
||||||
|
}
|
||||||
|
<Formik
|
||||||
|
// Initial values
|
||||||
|
initialValues={{ name : '' , description : '' , price : 0 , quantity : 0 }}
|
||||||
|
// this function handle the submition of the formik form
|
||||||
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{
|
||||||
|
// dialog content contain the formik form
|
||||||
|
}
|
||||||
|
<DialogContent>
|
||||||
|
{
|
||||||
|
// dialog text content contain a small description to the current form part
|
||||||
|
}
|
||||||
|
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
|
||||||
|
<h1 className="text-[18px] font-tajawal">{t('productDetailsText1')}</h1>
|
||||||
|
</DialogContentText>
|
||||||
|
<div className="
|
||||||
|
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80
|
||||||
|
[&_*]:text-text/80 [&_*]:dark:text-text-dark/80
|
||||||
|
[&_*]:placeholder-text/80 [&_*]:dark:placeholder-text-dark/80
|
||||||
|
w-full h-auto mt-[36px] flex flex-col gap-3
|
||||||
|
">
|
||||||
|
<div className="w-full flex lg:flex-row flex-col lg:gap-12 gap-7">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('productInformations')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={detailsPopUpData?.name ? detailsPopUpData.name : ''}
|
||||||
|
placeholder={t('name')}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={detailsPopUpData?.description ? detailsPopUpData.description : ''}
|
||||||
|
placeholder={t('description')}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-3">
|
||||||
|
<h3 className="font-bold">{t('price&quantity')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
disabled
|
||||||
|
id="price"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={detailsPopUpData?.price == 0 ? '' : detailsPopUpData.price}
|
||||||
|
placeholder={t('price')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
disabled
|
||||||
|
id="quantity"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={detailsPopUpData?.quantity ? detailsPopUpData.quantity : ''}
|
||||||
|
placeholder={t('quantity')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
{
|
||||||
|
// dialog actions contain buttons that effect the popup status
|
||||||
|
}
|
||||||
|
<DialogActions sx={{ padding: '16px 24px' }}>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button type="submit" className="btn font-semibold">
|
||||||
|
{t('close')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
import Pagination from '@mui/material/Pagination';
|
||||||
|
import { setCurrentPage } from '@/redux/features/products-slice';
|
||||||
|
import { load } from '@/redux/features/products-slice'
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { AppDispatch , useAppSelector } from '@/redux/store';
|
||||||
|
|
||||||
|
export default function ListPagination() {
|
||||||
|
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// load needed states
|
||||||
|
const currentPage = useAppSelector((state) => state.productsReducer.value.currentPage)
|
||||||
|
const total = useAppSelector((state) => state.productsReducer.value.total)
|
||||||
|
// handle the pagination
|
||||||
|
const handlePagination = (event: React.ChangeEvent<unknown>, value: number) => {
|
||||||
|
dispatch(load({page:value}));
|
||||||
|
dispatch(setCurrentPage(value));
|
||||||
|
};
|
||||||
|
// show the pagination ui
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{Math.ceil(total / 4) > 1 ? (
|
||||||
|
<div className="flex justify-center items-center py-12">
|
||||||
|
<Pagination
|
||||||
|
sx={{ direction: 'ltr' }}
|
||||||
|
count={Math.ceil(total / 4)}
|
||||||
|
page={currentPage}
|
||||||
|
onChange={handlePagination}
|
||||||
|
variant="outlined"
|
||||||
|
shape="rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
225
webapp/src/components/dashboard/products/parts/updatePopUp.tsx
Normal file
225
webapp/src/components/dashboard/products/parts/updatePopUp.tsx
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
import { forwardRef , ReactElement , Ref, useEffect } from "react"
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { AppDispatch, useAppSelector } from "@/redux/store";
|
||||||
|
import { setUpdatePopUp , update } from "@/redux/features/products-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import Slide from '@mui/material/Slide';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
|
||||||
|
export default function UpdatePopUp()
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
// redux
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// intl-18
|
||||||
|
const t = useTranslations('products');
|
||||||
|
// mui
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
// get state from redux
|
||||||
|
const updatePopUp = useAppSelector((state) => state.productsReducer.value.updatePopUp)
|
||||||
|
const updatePopUpData = useAppSelector((state) => state.productsReducer.value.updatePopUpData)
|
||||||
|
// setup the popup animation
|
||||||
|
const Transition = forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: ReactElement<any, any>;
|
||||||
|
},
|
||||||
|
ref: Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
// setup the function that close the popup
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch(setUpdatePopUp({
|
||||||
|
status: false,
|
||||||
|
data: null
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
// we dont mount the pop up if we dont need it
|
||||||
|
if(!updatePopUp) return <></>
|
||||||
|
// we mount the popup if we needed it
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
open={updatePopUp}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
keepMounted
|
||||||
|
onClose={handleClose}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth={'md'}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// This is the title of the pop up
|
||||||
|
}
|
||||||
|
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
|
||||||
|
<h1 className="text-[25px] font-tajawal">{t('updateProduct')}</h1>
|
||||||
|
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</DialogTitle>
|
||||||
|
{
|
||||||
|
// Formik form start from here
|
||||||
|
}
|
||||||
|
<Formik
|
||||||
|
// Initial values
|
||||||
|
initialValues={{
|
||||||
|
_id: updatePopUpData?._id ? updatePopUpData?._id : '' ,
|
||||||
|
name : updatePopUpData?.name ? updatePopUpData?.name : '' ,
|
||||||
|
description : updatePopUpData?.description ? updatePopUpData?.description : '' ,
|
||||||
|
price: updatePopUpData?.price ? updatePopUpData?.price : 0 ,
|
||||||
|
quantity: updatePopUpData?.quantity ? updatePopUpData?.quantity : 0,
|
||||||
|
}}
|
||||||
|
// this function handle the submition of the formik form
|
||||||
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
|
// dispatch the update function
|
||||||
|
await dispatch(update(values))
|
||||||
|
// close the popup
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
values,
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{
|
||||||
|
// dialog content contain the formik form
|
||||||
|
}
|
||||||
|
<DialogContent>
|
||||||
|
{
|
||||||
|
// dialog text content contain a small description to the current form part
|
||||||
|
}
|
||||||
|
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
|
||||||
|
<h1 className="text-[18px] font-tajawal">{t('productUpdateText1')}</h1>
|
||||||
|
</DialogContentText>
|
||||||
|
<div className="
|
||||||
|
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80
|
||||||
|
[&_*]:text-text/80 [&_*]:dark:text-text-dark/80
|
||||||
|
[&_*]:placeholder-text/80 [&_*]:dark:placeholder-text-dark/80
|
||||||
|
w-full h-auto mt-[36px] flex flex-col gap-3
|
||||||
|
">
|
||||||
|
<div className="w-full flex lg:flex-row flex-col lg:gap-12 gap-7">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('productInformations')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.name}
|
||||||
|
placeholder={t('name')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.description}
|
||||||
|
placeholder={t('description')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="price"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.price}
|
||||||
|
placeholder={t('price')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('price&quantity')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="price"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.price == 0 ? '' : values.price}
|
||||||
|
placeholder={t('price')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="quantity"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.quantity == 0 ? '' : values.quantity}
|
||||||
|
placeholder={t('quantity')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
{
|
||||||
|
// dialog actions contain buttons that effect the popup status
|
||||||
|
}
|
||||||
|
<DialogActions sx={{ padding: '16px 24px' }}>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button type="submit" className="btn font-semibold">
|
||||||
|
{
|
||||||
|
isSubmitting ?
|
||||||
|
<CircularProgress color="secondary" size={24} />
|
||||||
|
:
|
||||||
|
t('save')
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
14
webapp/src/components/dashboard/products/popUpsWrapper.tsx
Normal file
14
webapp/src/components/dashboard/products/popUpsWrapper.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import AddPopUp from './parts/addPopUp'
|
||||||
|
import DetailsPopUp from './parts/detailsPopUp'
|
||||||
|
import UpdatePopUp from './parts/updatePopUp'
|
||||||
|
|
||||||
|
export default function PopUpWrapper()
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<UpdatePopUp />
|
||||||
|
<AddPopUp />
|
||||||
|
<DetailsPopUp />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
166
webapp/src/components/dashboard/products/productsList.tsx
Normal file
166
webapp/src/components/dashboard/products/productsList.tsx
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { useAppSelector } from '@/redux/store';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import { AppDispatch } from '@/redux/store';
|
||||||
|
import { setSearchKeyword ,load , search , delete_ } from '@/redux/features/products-slice'
|
||||||
|
import ListPagination from './parts/productsListPagination'
|
||||||
|
import { setDetailsPopUp , setUpdatePopUp , setCurrentPage } from '@/redux/features/products-slice';
|
||||||
|
|
||||||
|
export default function List()
|
||||||
|
{
|
||||||
|
const t = useTranslations('products');
|
||||||
|
const dispatch = useDispatch<AppDispatch>()
|
||||||
|
|
||||||
|
const listContent = useAppSelector((state) => state.productsReducer.value.listContent)
|
||||||
|
const isLoading = useAppSelector((state) => state.productsReducer.value.isLoading)
|
||||||
|
const loadedFirstTime = useAppSelector((state) => state.productsReducer.value.loadedFirstTime)
|
||||||
|
const searchKeyword = useAppSelector((state) => state.productsReducer.value.searchKeyword)
|
||||||
|
const currentPage = useAppSelector((state) => state.productsReducer.value.currentPage)
|
||||||
|
const appGeneralSettings = useAppSelector((state) => state.settingsReducer.value.appGeneralSettings)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(loadedFirstTime) return
|
||||||
|
if(isLoading) return
|
||||||
|
async function a()
|
||||||
|
{
|
||||||
|
await dispatch(load({page : 1}))
|
||||||
|
}
|
||||||
|
a()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-3 w-full w-auto">
|
||||||
|
<header className="w-full flex lg:flex-row flex-col lg:justify-between lg:items-center gap-3">
|
||||||
|
<h1 className="font-semibold text-[20px] text-text dark:text-text-dark">{t('products')}</h1>
|
||||||
|
<label htmlFor="email" className="relative text-gray-400 focus-within:text-gray-600 block">
|
||||||
|
<svg className="text-secondary dark:text-primary/50 cursor-pointer w-[15px] h-[15px] absolute top-1/2 transform -translate-y-1/2 rtl:left-3 ltr:right-3" width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M18.4479 16.9688L14.9063 13.4375C16.0489 11.9817 16.669 10.184 16.6667 8.33334C16.6667 6.68516 16.1779 5.074 15.2623 3.70359C14.3466 2.33318 13.0451 1.26507 11.5224 0.634341C9.99965 0.00361068 8.32409 -0.161417 6.70759 0.160126C5.09108 0.48167 3.60622 1.27534 2.44078 2.44078C1.27534 3.60622 0.48167 5.09108 0.160126 6.70759C-0.161417 8.32409 0.00361068 9.99965 0.634341 11.5224C1.26507 13.0451 2.33318 14.3466 3.70359 15.2623C5.074 16.1779 6.68516 16.6667 8.33334 16.6667C10.184 16.669 11.9817 16.0489 13.4375 14.9063L16.9688 18.4479C17.0656 18.5456 17.1808 18.6231 17.3077 18.6759C17.4347 18.7288 17.5708 18.756 17.7083 18.756C17.8459 18.756 17.982 18.7288 18.1089 18.6759C18.2359 18.6231 18.3511 18.5456 18.4479 18.4479C18.5456 18.3511 18.6231 18.2359 18.6759 18.1089C18.7288 17.982 18.756 17.8459 18.756 17.7083C18.756 17.5708 18.7288 17.4347 18.6759 17.3077C18.6231 17.1808 18.5456 17.0656 18.4479 16.9688ZM2.08334 8.33334C2.08334 7.0972 2.44989 5.88883 3.13665 4.86102C3.82341 3.83322 4.79953 3.03214 5.94157 2.55909C7.0836 2.08604 8.34027 1.96227 9.55265 2.20343C10.765 2.44459 11.8787 3.03984 12.7528 3.91392C13.6268 4.788 14.2221 5.90164 14.4632 7.11402C14.7044 8.3264 14.5806 9.58307 14.1076 10.7251C13.6345 11.8671 12.8335 12.8433 11.8057 13.53C10.7778 14.2168 9.56947 14.5833 8.33334 14.5833C6.67573 14.5833 5.08602 13.9249 3.91392 12.7528C2.74182 11.5807 2.08334 9.99094 2.08334 8.33334Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<input onChange={(async(e) => {
|
||||||
|
if(e.target.value.length == 0)
|
||||||
|
{
|
||||||
|
dispatch(setCurrentPage(1));
|
||||||
|
}
|
||||||
|
dispatch(setSearchKeyword(e.target.value))
|
||||||
|
await dispatch(search({
|
||||||
|
searchKeyword: e.target.value
|
||||||
|
}))
|
||||||
|
})}
|
||||||
|
value={searchKeyword}
|
||||||
|
className="lg:w-[300px] w-full text-text dark:text-text-dark rounded-[5px] px-3 h-[50px] border-[1px] !border-secondary-light bg-primary-dark/5 placeholder:font-semibold" type="text" placeholder={t('searchForProducts')}></input>
|
||||||
|
</label>
|
||||||
|
</header>
|
||||||
|
<div className="panel !p-0 mb-7 dark:bg-primary-dark">
|
||||||
|
<header className="w-full lg:flex gap-3 hidden justify-between items-center p-5 opacity-50 font-semibold border-b-[1px] border-secondary-light">
|
||||||
|
<span className="w-full text-start">#</span>
|
||||||
|
<span className="w-full text-start">{t('name')}</span>
|
||||||
|
<span className="w-full text-start">{t('price')}</span>
|
||||||
|
<span className="w-full text-start">{t('quantity')}</span>
|
||||||
|
<span className="w-full text-start">{t('actions')}</span>
|
||||||
|
</header>
|
||||||
|
<main className="flex flex-col justify-center items-center">
|
||||||
|
{
|
||||||
|
!isLoading && listContent.flat().length == 0 && currentPage >= 2 && searchKeyword.length == 0 ?
|
||||||
|
<div className="p-5 py-10 flex justify-start items-center gap-3 [&_*]:fill-warning">
|
||||||
|
<svg width="25" height="25" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path opacity="0.5" d="M15.2069 2.85079C15.6886 1.98507 16.3858 1.26524 17.2277 0.76451C18.0696 0.263778 19.0261 0 20 0C20.9739 0 21.9304 0.263778 22.7723 0.76451C23.6142 1.26524 24.3114 1.98507 24.7931 2.85079L39.309 29.3012C39.77 30.1312 40.0083 31.0711 39.9998 32.0257C39.9913 32.9802 39.7363 33.9155 39.2607 34.7368C38.7781 35.5842 38.085 36.286 37.2518 36.7713C36.4186 37.2565 35.4747 37.5078 34.5159 37.4998H5.48407C4.52527 37.5078 3.58143 37.2565 2.74819 36.7713C1.91496 36.286 1.22192 35.5842 0.739307 34.7368C0.263677 33.9155 0.00870576 32.9802 0.000218482 32.0257C-0.0082688 31.0711 0.230027 30.1312 0.690974 29.3012L15.2069 2.85079ZM20 30.6818C20.3296 30.6818 20.6518 30.5819 20.9259 30.3946C21.2 30.2073 21.4136 29.9411 21.5397 29.6296C21.6659 29.3182 21.6989 28.9754 21.6346 28.6448C21.5703 28.3142 21.4115 28.0104 21.1785 27.7721C20.9454 27.5337 20.6484 27.3713 20.3251 27.3056C20.0018 27.2398 19.6668 27.2736 19.3622 27.4026C19.0577 27.5316 18.7974 27.7501 18.6143 28.0304C18.4312 28.3107 18.3334 28.6402 18.3334 28.9773C18.3334 29.4294 18.509 29.8629 18.8215 30.1826C19.1341 30.5022 19.558 30.6818 20 30.6818ZM18.3334 22.1593C18.3334 22.6114 18.509 23.0449 18.8215 23.3646C19.1341 23.6843 19.558 23.8638 20 23.8638C20.442 23.8638 20.8659 23.6843 21.1785 23.3646C21.491 23.0449 21.6666 22.6114 21.6666 22.1593V11.9324C21.6666 11.4803 21.491 11.0467 21.1785 10.7271C20.8659 10.4074 20.442 10.2279 20 10.2279C19.558 10.2279 19.1341 10.4074 18.8215 10.7271C18.509 11.0467 18.3334 11.4803 18.3334 11.9324V22.1593Z" fill="currentColor"/>
|
||||||
|
<circle cx="20" cy="29" r="2" fill="currentColor"/>
|
||||||
|
<rect x="18" y="10" width="4" height="14" rx="2" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="text-text dark:text-text-dark font-semibold">{t('thisPageIsEmpty')}</h2>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<></>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!isLoading && listContent.flat().length == 0 && currentPage < 2 && searchKeyword.length == 0 ?
|
||||||
|
<div className="p-5 py-10 flex justify-start items-center gap-3 [&_*]:fill-warning">
|
||||||
|
<svg width="25" height="25" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path opacity="0.5" d="M15.2069 2.85079C15.6886 1.98507 16.3858 1.26524 17.2277 0.76451C18.0696 0.263778 19.0261 0 20 0C20.9739 0 21.9304 0.263778 22.7723 0.76451C23.6142 1.26524 24.3114 1.98507 24.7931 2.85079L39.309 29.3012C39.77 30.1312 40.0083 31.0711 39.9998 32.0257C39.9913 32.9802 39.7363 33.9155 39.2607 34.7368C38.7781 35.5842 38.085 36.286 37.2518 36.7713C36.4186 37.2565 35.4747 37.5078 34.5159 37.4998H5.48407C4.52527 37.5078 3.58143 37.2565 2.74819 36.7713C1.91496 36.286 1.22192 35.5842 0.739307 34.7368C0.263677 33.9155 0.00870576 32.9802 0.000218482 32.0257C-0.0082688 31.0711 0.230027 30.1312 0.690974 29.3012L15.2069 2.85079ZM20 30.6818C20.3296 30.6818 20.6518 30.5819 20.9259 30.3946C21.2 30.2073 21.4136 29.9411 21.5397 29.6296C21.6659 29.3182 21.6989 28.9754 21.6346 28.6448C21.5703 28.3142 21.4115 28.0104 21.1785 27.7721C20.9454 27.5337 20.6484 27.3713 20.3251 27.3056C20.0018 27.2398 19.6668 27.2736 19.3622 27.4026C19.0577 27.5316 18.7974 27.7501 18.6143 28.0304C18.4312 28.3107 18.3334 28.6402 18.3334 28.9773C18.3334 29.4294 18.509 29.8629 18.8215 30.1826C19.1341 30.5022 19.558 30.6818 20 30.6818ZM18.3334 22.1593C18.3334 22.6114 18.509 23.0449 18.8215 23.3646C19.1341 23.6843 19.558 23.8638 20 23.8638C20.442 23.8638 20.8659 23.6843 21.1785 23.3646C21.491 23.0449 21.6666 22.6114 21.6666 22.1593V11.9324C21.6666 11.4803 21.491 11.0467 21.1785 10.7271C20.8659 10.4074 20.442 10.2279 20 10.2279C19.558 10.2279 19.1341 10.4074 18.8215 10.7271C18.509 11.0467 18.3334 11.4803 18.3334 11.9324V22.1593Z" fill="currentColor"/>
|
||||||
|
<circle cx="20" cy="29" r="2" fill="currentColor"/>
|
||||||
|
<rect x="18" y="10" width="4" height="14" rx="2" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="text-text dark:text-text-dark font-semibold">{t('noData')}</h2>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<></>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!isLoading && listContent.flat().length == 0 && searchKeyword.length != 0 ?
|
||||||
|
<div className="p-5 py-10 flex justify-start items-center gap-3 [&_*]:fill-warning">
|
||||||
|
<svg width="25" height="25" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M27.3438 20.3125C27.7656 20.3125 28.125 20.6719 28.125 21.0938C28.125 21.5156 27.7656 21.875 27.3438 21.875C26.9219 21.875 26.5625 21.5156 26.5625 21.0938C26.5625 20.6719 26.9219 20.3125 27.3438 20.3125ZM13.2812 20.3125C13.7031 20.3125 14.0625 20.6719 14.0625 21.0938C14.0625 21.5156 13.7031 21.875 13.2812 21.875C12.8594 21.875 12.5 21.5156 12.5 21.0938C12.5 20.6719 12.8594 20.3125 13.2812 20.3125Z" fill="currentColor"/>
|
||||||
|
<path d="M36.6094 37.3451C36.4094 37.3451 36.2094 37.2686 36.0562 37.117L32.5938 33.6545C32.2891 33.3498 32.2891 32.8545 32.5938 32.5498C32.8984 32.2451 33.3937 32.2451 33.6984 32.5498L37.1609 36.0123C37.4656 36.317 37.4656 36.8123 37.1609 37.117C37.0094 37.2701 36.8094 37.3451 36.6094 37.3451Z" fill="currentColor"/>
|
||||||
|
<path d="M44.5312 48.4375C43.4891 48.4375 42.5078 48.0312 41.7688 47.2937L36.3 41.825C35.5625 41.0859 35.1562 40.1062 35.1562 39.0625C35.1562 38.0188 35.5625 37.0391 36.3 36.3C37.7156 34.8844 40.4078 34.8844 41.8234 36.3L47.2922 41.7688C48.0313 42.5078 48.4375 43.4875 48.4375 44.5312C48.4375 45.575 48.0312 46.5547 47.2937 47.2937C46.5562 48.0328 45.5734 48.4375 44.5312 48.4375ZM39.0625 36.75C38.4266 36.75 37.8234 36.9875 37.4047 37.4063C36.9625 37.8484 36.7188 38.4375 36.7188 39.0625C36.7188 39.6875 36.9625 40.2766 37.4047 40.7203L42.8734 46.1891C43.7594 47.0734 45.3016 47.0734 46.1875 46.1891C46.6312 45.7453 46.875 45.1563 46.875 44.5312C46.875 43.9062 46.6313 43.3172 46.1891 42.8734L40.7203 37.4047C40.3016 36.9875 39.6984 36.75 39.0625 36.75ZM20.3125 39.0625C9.97344 39.0625 1.5625 30.6516 1.5625 20.3125C1.5625 9.97344 9.97344 1.5625 20.3125 1.5625C30.6516 1.5625 39.0625 9.97344 39.0625 20.3125C39.0625 30.6516 30.6516 39.0625 20.3125 39.0625ZM20.3125 3.125C10.8359 3.125 3.125 10.8359 3.125 20.3125C3.125 29.7891 10.8359 37.5 20.3125 37.5C29.7891 37.5 37.5 29.7891 37.5 20.3125C37.5 10.8359 29.7891 3.125 20.3125 3.125Z" fill="currentColor"/>
|
||||||
|
<path d="M21.875 25.0328C21.4438 25.0328 21.0938 24.6828 21.0938 24.2516C21.0938 23.8219 20.7437 23.4703 20.3125 23.4703C19.8813 23.4703 19.5312 23.8219 19.5312 24.2516C19.5312 24.6828 19.1812 25.0328 18.75 25.0328C18.3188 25.0328 17.9688 24.6828 17.9688 24.2516C17.9688 22.9594 19.0203 21.9078 20.3125 21.9078C21.6047 21.9078 22.6562 22.9594 22.6562 24.2516C22.6562 24.6828 22.3062 25.0328 21.875 25.0328ZM27.3438 22.6562C26.4969 22.6562 25.7812 21.9406 25.7812 21.0937C25.7812 20.2469 26.4969 19.5312 27.3438 19.5312C28.1906 19.5312 28.9062 20.2469 28.9062 21.0937C28.9062 21.9406 28.1906 22.6562 27.3438 22.6562ZM13.2812 22.6562C12.4344 22.6562 11.7188 21.9406 11.7188 21.0937C11.7188 20.2469 12.4344 19.5312 13.2812 19.5312C14.1281 19.5312 14.8438 20.2469 14.8438 21.0937C14.8438 21.9406 14.1281 22.6562 13.2812 22.6562Z" fill="currentColor"/>
|
||||||
|
<path d="M36.6094 37.3451C36.4094 37.3451 36.2094 37.2686 36.0562 37.117L32.5938 33.6545C32.2891 33.3498 32.2891 32.8545 32.5938 32.5498C32.8984 32.2451 33.3937 32.2451 33.6984 32.5498L37.1609 36.0123C37.4656 36.317 37.4656 36.8123 37.1609 37.117C37.0094 37.2701 36.8094 37.3451 36.6094 37.3451Z" fill="currentColor"/>
|
||||||
|
<path d="M44.5312 48.4375C43.4891 48.4375 42.5078 48.0312 41.7688 47.2937L36.3 41.825C35.5625 41.0859 35.1562 40.1062 35.1562 39.0625C35.1562 38.0188 35.5625 37.0391 36.3 36.3C37.7156 34.8844 40.4078 34.8844 41.8234 36.3L47.2922 41.7688C48.0313 42.5078 48.4375 43.4875 48.4375 44.5312C48.4375 45.575 48.0312 46.5547 47.2937 47.2937C46.5562 48.0328 45.5734 48.4375 44.5312 48.4375ZM39.0625 36.75C38.4266 36.75 37.8234 36.9875 37.4047 37.4063C36.9625 37.8484 36.7188 38.4375 36.7188 39.0625C36.7188 39.6875 36.9625 40.2766 37.4047 40.7203L42.8734 46.1891C43.7594 47.0734 45.3016 47.0734 46.1875 46.1891C46.6312 45.7453 46.875 45.1563 46.875 44.5312C46.875 43.9062 46.6313 43.3172 46.1891 42.8734L40.7203 37.4047C40.3016 36.9875 39.6984 36.75 39.0625 36.75ZM20.3125 39.0625C9.97344 39.0625 1.5625 30.6516 1.5625 20.3125C1.5625 9.97344 9.97344 1.5625 20.3125 1.5625C30.6516 1.5625 39.0625 9.97344 39.0625 20.3125C39.0625 30.6516 30.6516 39.0625 20.3125 39.0625ZM20.3125 3.125C10.8359 3.125 3.125 10.8359 3.125 20.3125C3.125 29.7891 10.8359 37.5 20.3125 37.5C29.7891 37.5 37.5 29.7891 37.5 20.3125C37.5 10.8359 29.7891 3.125 20.3125 3.125Z" fill="currentColor"/>
|
||||||
|
<path d="M21.875 25.0328C21.4438 25.0328 21.0938 24.6828 21.0938 24.2516C21.0938 23.8219 20.7437 23.4703 20.3125 23.4703C19.8813 23.4703 19.5312 23.8219 19.5312 24.2516C19.5312 24.6828 19.1812 25.0328 18.75 25.0328C18.3188 25.0328 17.9688 24.6828 17.9688 24.2516C17.9688 22.9594 19.0203 21.9078 20.3125 21.9078C21.6047 21.9078 22.6562 22.9594 22.6562 24.2516C22.6562 24.6828 22.3062 25.0328 21.875 25.0328ZM27.3438 22.6562C26.4969 22.6562 25.7812 21.9406 25.7812 21.0937C25.7812 20.2469 26.4969 19.5312 27.3438 19.5312C28.1906 19.5312 28.9062 20.2469 28.9062 21.0937C28.9062 21.9406 28.1906 22.6562 27.3438 22.6562ZM13.2812 22.6562C12.4344 22.6562 11.7188 21.9406 11.7188 21.0937C11.7188 20.2469 12.4344 19.5312 13.2812 19.5312C14.1281 19.5312 14.8438 20.2469 14.8438 21.0937C14.8438 21.9406 14.1281 22.6562 13.2812 22.6562Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="text-text dark:text-text-dark font-semibold">{t('noSearchResults')}</h2>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<></>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// single sender account
|
||||||
|
isLoading ?
|
||||||
|
<div className="p-5 flex justify-start items-center gap-3">
|
||||||
|
<CircularProgress color="secondary" size={24} />
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
listContent.flat().map((v , i) => {
|
||||||
|
return (
|
||||||
|
<div key={i} className="w-full flex gap-3 justify-between items-center p-5 font-semibold h-[70px] border-b-[1px] border-secondary-light/30">
|
||||||
|
<span className="w-full text-start whitespace-nowrap text-ellipsis overflow-hidden lg:block hidden">{i + 1}</span>
|
||||||
|
<span className="w-full text-start whitespace-nowrap text-ellipsis overflow-hidden">{v.name}</span>
|
||||||
|
<span className="w-full text-start flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden">{v.price} {appGeneralSettings.currencySymbol}</span>
|
||||||
|
<span className="w-full text-start flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden">{v.quantity}</span>
|
||||||
|
|
||||||
|
<span className="lg:w-full w-[80px] h-full text-start flex justify-start items-center gap-3">
|
||||||
|
<button onClick={async() => {
|
||||||
|
dispatch(setUpdatePopUp({
|
||||||
|
status: true,
|
||||||
|
data: v
|
||||||
|
}))
|
||||||
|
}} className="[&_*]:fill-success [&_*]:dark:fill-success">
|
||||||
|
<svg width="15" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6.36069 6.61884L5.39925 9.91891C5.3176 10.1996 5.39219 10.5041 5.59355 10.7111C5.74073 10.8626 5.93856 10.9446 6.14093 10.9446C6.21527 10.9446 6.29012 10.9337 6.36321 10.9109L9.56961 9.92151C9.69234 9.88363 9.80423 9.81515 9.89471 9.72177L15.7732 3.67103C15.9184 3.52161 16.0001 3.31876 16.0001 3.10761C16.0001 2.89646 15.9184 2.6936 15.7732 2.54419L13.528 0.23346C13.2256 -0.0778202 12.7357 -0.0778202 12.4333 0.23346L6.55499 6.28421C6.46426 6.37759 6.39774 6.49225 6.36069 6.61884ZM7.78786 7.26889L12.9806 1.92371L14.1308 3.10761L8.93806 8.45279L7.29541 8.95966L7.78786 7.26889Z" fill="#4DB33D"/>
|
||||||
|
<path d="M15.2258 7.4375C14.7981 7.4375 14.4516 7.79444 14.4516 8.23438V13.5469C14.4516 14.2794 13.8727 14.875 13.1613 14.875H2.83871C2.12727 14.875 1.54839 14.2794 1.54839 13.5469V2.92188C1.54839 2.18933 2.12727 1.59375 2.83871 1.59375H8C8.42767 1.59375 8.77419 1.23681 8.77419 0.796875C8.77419 0.356936 8.42767 0 8 0H2.83871C1.27344 0 0 1.311 0 2.92188V13.5469C0 15.1577 1.27344 16.4688 2.83871 16.4688H13.1613C14.7266 16.4688 16 15.1577 16 13.5469V8.23438C16 7.79444 15.6535 7.4375 15.2258 7.4375Z" fill="#4DB33D"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button onClick={async() => {
|
||||||
|
await dispatch(delete_({
|
||||||
|
id: v._id
|
||||||
|
}))
|
||||||
|
}} className="[&_*]:fill-error [&_*]:dark:fill-error">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M2.0625 4.625C2.0625 4.27983 2.32153 4.00001 2.64107 4.00001L4.63925 3.99968C5.03626 3.98881 5.38652 3.71612 5.52161 3.31268C5.52516 3.30208 5.52924 3.28899 5.54389 3.24152L5.62999 2.96245C5.68267 2.79134 5.72857 2.64227 5.79281 2.50902C6.04657 1.98262 6.51606 1.61708 7.0586 1.52349C7.19593 1.4998 7.34136 1.4999 7.50832 1.50002H10.1168C10.2838 1.4999 10.4292 1.4998 10.5665 1.52349C11.1091 1.61708 11.5786 1.98262 11.8323 2.50902C11.8966 2.64227 11.9425 2.79134 11.9951 2.96245L12.0812 3.24152C12.0959 3.28899 12.1 3.30208 12.1035 3.31268C12.2387 3.71612 12.6584 3.98915 13.0553 4.00001H14.9839C15.3034 4.00001 15.5625 4.27983 15.5625 4.625C15.5625 4.97018 15.3034 5.25 14.9839 5.25H2.64107C2.32153 5.25 2.0625 4.97018 2.0625 4.625Z" fill="currentColor"/>
|
||||||
|
<path opacity="0.5" d="M8.7051 16.4997H9.29528C11.3259 16.4997 12.3412 16.4997 13.0013 15.8523C13.6615 15.2049 13.7291 14.143 13.8641 12.0191L14.0588 8.95863C14.132 7.80626 14.1687 7.23003 13.8375 6.86489C13.5063 6.49976 12.9471 6.49976 11.8286 6.49976H6.17179C5.05329 6.49976 4.49403 6.49976 4.16286 6.86489C3.83169 7.23003 3.86833 7.80626 3.94162 8.95863L4.13625 12.0191C4.27133 14.143 4.33887 15.2049 4.99901 15.8523C5.65915 16.4997 6.67446 16.4997 8.7051 16.4997Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button onClick={async() => {
|
||||||
|
dispatch(setDetailsPopUp({
|
||||||
|
status: true,
|
||||||
|
data: v ? v : null
|
||||||
|
}))
|
||||||
|
}} className="[&_*]:fill-info [&_*]:dark:fill-info">
|
||||||
|
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M13.3336 13.3333H1.66641V1.66672H6.875V0H1.66641C0.749649 0 0 0.749883 0 1.66672V13.3333C0 14.2501 0.749649 15 1.66641 15H13.3336C14.2504 15 15 14.2501 15 13.3333V8.125H13.3336V13.3333ZM8.75 0V1.66672H12.167L3.74996 10.0833L4.9166 11.25L13.3336 2.83316V6.25H15V0H8.75Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
<ListPagination />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { useDispatch } from "react-redux"
|
||||||
|
import { setAddPopUp } from "@/redux/features/services-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
export default function AddNewButton()
|
||||||
|
{
|
||||||
|
// setup needed varibles
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const t = useTranslations('services');
|
||||||
|
// handle the open of the pop up
|
||||||
|
function handleClickOpen()
|
||||||
|
{
|
||||||
|
dispatch(setAddPopUp(true))
|
||||||
|
}
|
||||||
|
// return the component ui
|
||||||
|
return (
|
||||||
|
<button onClick={handleClickOpen}
|
||||||
|
className="
|
||||||
|
bg-secondary-light dark:bg-primary text-primary-light dark:text-text
|
||||||
|
fixed z-50 bottom-7 ltr:right-7 rtl:left-7
|
||||||
|
w-auto h-18 p-3 rounded-md drop-shadow-lg
|
||||||
|
flex justify-between items-center gap-3
|
||||||
|
">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 49 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M25 0.5C26.3807 0.5 27.5 1.61929 27.5 3L27.5 46C27.5 47.3807 26.3807 48.5 25 48.5C23.6193 48.5 22.5 47.3807 22.5 46L22.5 3C22.5 1.61929 23.6193 0.5 25 0.5Z" fill="currentColor"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.5 25C48.5 26.3807 47.3807 27.5 46 27.5L3 27.5C1.61929 27.5 0.5 26.3807 0.5 25C0.5 23.6193 1.61929 22.5 3 22.5L46 22.5C47.3807 22.5 48.5 23.6193 48.5 25Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h3>{t('addNewService')}</h3>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
230
webapp/src/components/dashboard/services/parts/addPopUp.tsx
Normal file
230
webapp/src/components/dashboard/services/parts/addPopUp.tsx
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
import { forwardRef , ReactElement , Ref } from "react"
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { AppDispatch, useAppSelector } from "@/redux/store";
|
||||||
|
import { setAddPopUp , add } from "@/redux/features/services-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import Slide from '@mui/material/Slide';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import * as Yup from "yup";
|
||||||
|
|
||||||
|
export default function AddPopUp()
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
// redux
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// intl-18
|
||||||
|
const t = useTranslations('services');
|
||||||
|
// mui
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
// get state from redux
|
||||||
|
const addPopUp = useAppSelector((state) => state.servicesReducer.value.addPopUp)
|
||||||
|
// setup the popup animation
|
||||||
|
const Transition = forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: ReactElement<any, any>;
|
||||||
|
},
|
||||||
|
ref: Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
// setup the function that close the popup
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch(setAddPopUp(false));
|
||||||
|
};
|
||||||
|
// we dont mount the pop up if we dont need it
|
||||||
|
if(!addPopUp) return <></>
|
||||||
|
// we mount the popup if we needed it
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
open={addPopUp}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
keepMounted
|
||||||
|
onClose={handleClose}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth={'sm'}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// This is the title of the pop up
|
||||||
|
}
|
||||||
|
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
|
||||||
|
<h1 className="text-[25px] font-tajawal">{t('addNewService')}</h1>
|
||||||
|
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</DialogTitle>
|
||||||
|
{
|
||||||
|
// Formik form start from here
|
||||||
|
}
|
||||||
|
<Formik
|
||||||
|
// Initial values
|
||||||
|
initialValues={{ name : '' , description : '' , price: '' , status: 'active' }}
|
||||||
|
// validat the inputs
|
||||||
|
validationSchema={Yup.object().shape({
|
||||||
|
name: Yup.string().required('thisFieldIsRequired'),
|
||||||
|
description: Yup.string().required('thisFieldIsRequired'),
|
||||||
|
price: Yup.number().typeError('mustBeANumber').required('thisFieldIsRequired'),
|
||||||
|
})}
|
||||||
|
// this function handle the submition of the formik form
|
||||||
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
|
await dispatch(add(values))
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
values,
|
||||||
|
setFieldValue,
|
||||||
|
errors,
|
||||||
|
touched
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{
|
||||||
|
// dialog content contain the formik form
|
||||||
|
}
|
||||||
|
<DialogContent>
|
||||||
|
{
|
||||||
|
// dialog text content contain a small description to the current form part
|
||||||
|
}
|
||||||
|
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
|
||||||
|
<h1 className="text-[18px] font-tajawal">{t('serviceAddText1')}</h1>
|
||||||
|
</DialogContentText>
|
||||||
|
<div className="
|
||||||
|
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80
|
||||||
|
[&_*]:text-text/80 [&_*]:dark:text-text-dark/80
|
||||||
|
[&_*]:placeholder-text/80 [&_*]:dark:placeholder-text-dark/80
|
||||||
|
w-full h-auto mt-[36px] flex flex-col gap-3
|
||||||
|
">
|
||||||
|
<div className="w-full flex lg:flex-row flex-col lg:gap-12 gap-7">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('serviceInformations')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.name}
|
||||||
|
placeholder={t('name')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.name && touched?.name && t(errors?.name)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.description}
|
||||||
|
placeholder={t('description')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.description && touched?.description && t(errors?.description)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="price"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.price}
|
||||||
|
placeholder={t('price')}
|
||||||
|
/>
|
||||||
|
<p className="!text-error text-sm">{errors?.price && touched?.price && t(errors?.price)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-3">
|
||||||
|
<h3 className="font-bold">{t('status')}</h3>
|
||||||
|
<div className="w-full flex justify-start items-center gap-5">
|
||||||
|
<input
|
||||||
|
id={`active`}
|
||||||
|
type="checkbox"
|
||||||
|
className="form-checkbox p-3 border-none bg-primary-dark/10 dark:bg-primary/10 checked:bg-success-light dark:checked:bg-success-light checked:focus:bg-success-light checked:hover:bg-success-light rounded-md"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
checked={values.status == 'active' ? true : false}
|
||||||
|
onChange={() => {
|
||||||
|
setFieldValue("status" , "active")
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor={`active`} className="w-full flex flex-col gap-3">
|
||||||
|
<span>
|
||||||
|
<h1 className="font-bold text-[16px]">{t("active")}</h1>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex justify-start items-center gap-5">
|
||||||
|
<input
|
||||||
|
id={`deactivate`}
|
||||||
|
type="checkbox"
|
||||||
|
className="form-checkbox p-3 border-none bg-primary-dark/10 dark:bg-primary/10 checked:bg-success-light dark:checked:bg-success-light checked:focus:bg-success-light checked:hover:bg-success-light rounded-md"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
checked={values.status == 'active' ? false : true}
|
||||||
|
onChange={() => {
|
||||||
|
setFieldValue("status" , "deactivate")
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor={`deactivate`} className="w-full flex flex-col gap-3">
|
||||||
|
<span>
|
||||||
|
<h1 className="font-bold text-[16px]">{t("deactivate")}</h1>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
{
|
||||||
|
// dialog actions contain buttons that effect the popup status
|
||||||
|
}
|
||||||
|
<DialogActions sx={{ padding: '16px 24px' }}>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button type="submit" className="btn font-semibold">
|
||||||
|
{
|
||||||
|
isSubmitting ?
|
||||||
|
<CircularProgress color="secondary" size={24} />
|
||||||
|
:
|
||||||
|
t('done')
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
210
webapp/src/components/dashboard/services/parts/detailsPopUp.tsx
Normal file
210
webapp/src/components/dashboard/services/parts/detailsPopUp.tsx
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
import { forwardRef , ReactElement , Ref } from "react"
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { AppDispatch, useAppSelector } from "@/redux/store";
|
||||||
|
import { setDetailsPopUp } from "@/redux/features/services-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import Slide from '@mui/material/Slide';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
|
||||||
|
export default function ServiceDetailsPopUp()
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
// redux
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// intl-18
|
||||||
|
const t = useTranslations('services');
|
||||||
|
// mui
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
// get state from redux
|
||||||
|
const detailsPopUp = useAppSelector((state) => state.servicesReducer.value.detailsPopUp)
|
||||||
|
const detailsPopUpData = useAppSelector((state) => state.servicesReducer.value.detailsPopUpData)
|
||||||
|
// setup the popup animation
|
||||||
|
const Transition = forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: ReactElement<any, any>;
|
||||||
|
},
|
||||||
|
ref: Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
// setup the function that close the popup
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch(setDetailsPopUp({
|
||||||
|
status: false,
|
||||||
|
data: null
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
// we dont mount the pop up if we dont need it
|
||||||
|
if(!detailsPopUp) return <></>
|
||||||
|
// we mount the popup if we needed it
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
open={detailsPopUp}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
keepMounted
|
||||||
|
onClose={handleClose}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth={'sm'}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// This is the title of the pop up
|
||||||
|
}
|
||||||
|
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
|
||||||
|
<h1 className="text-[25px] font-tajawal">{t('serviceDetails')}</h1>
|
||||||
|
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</DialogTitle>
|
||||||
|
{
|
||||||
|
// Formik form start from here
|
||||||
|
}
|
||||||
|
<Formik
|
||||||
|
// Initial values
|
||||||
|
initialValues={{ name : '' , description : '' , price: 0 , status: '' }}
|
||||||
|
// this function handle the submition of the formik form
|
||||||
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{
|
||||||
|
// dialog content contain the formik form
|
||||||
|
}
|
||||||
|
<DialogContent>
|
||||||
|
{
|
||||||
|
// dialog text content contain a small description to the current form part
|
||||||
|
}
|
||||||
|
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
|
||||||
|
<h1 className="text-[18px] font-tajawal">{t('serviceDetailsText1')}</h1>
|
||||||
|
</DialogContentText>
|
||||||
|
<div className="
|
||||||
|
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80
|
||||||
|
[&_*]:text-text/80 [&_*]:dark:text-text-dark/80
|
||||||
|
[&_*]:placeholder-text/80 [&_*]:dark:placeholder-text-dark/80
|
||||||
|
w-full h-auto mt-[36px] flex flex-col gap-3
|
||||||
|
">
|
||||||
|
<div className="w-full flex lg:flex-row flex-col lg:gap-12 gap-7">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('serviceInformations')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={detailsPopUpData?.name ? detailsPopUpData.name : ''}
|
||||||
|
placeholder={t('name')}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={detailsPopUpData?.description ? detailsPopUpData.description : ''}
|
||||||
|
placeholder={t('description')}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
disabled
|
||||||
|
id="price"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={detailsPopUpData?.price ? detailsPopUpData.price : ''}
|
||||||
|
placeholder={t('price')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-3">
|
||||||
|
<h3 className="font-bold">{t('status')}</h3>
|
||||||
|
<div className="w-full flex justify-start items-center gap-5">
|
||||||
|
<input
|
||||||
|
disabled
|
||||||
|
id={`active`}
|
||||||
|
type="checkbox"
|
||||||
|
className="form-checkbox p-3 border-none bg-primary-dark/10 dark:bg-primary/10 checked:bg-success-light dark:checked:bg-success-light checked:focus:bg-success-light checked:hover:bg-success-light rounded-md"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
checked={detailsPopUpData?.status == "active"}
|
||||||
|
/>
|
||||||
|
<label htmlFor={`active`} className="w-full flex flex-col gap-3">
|
||||||
|
<span>
|
||||||
|
<h1 className="font-bold text-[16px]">{t("active")}</h1>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex justify-start items-center gap-5">
|
||||||
|
<input
|
||||||
|
disabled
|
||||||
|
id={`deactivate`}
|
||||||
|
type="checkbox"
|
||||||
|
className="form-checkbox p-3 border-none bg-primary-dark/10 dark:bg-primary/10 checked:bg-success-light dark:checked:bg-success-light checked:focus:bg-success-light checked:hover:bg-success-light rounded-md"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
checked={detailsPopUpData?.status != "active"}
|
||||||
|
/>
|
||||||
|
<label htmlFor={`deactivate`} className="w-full flex flex-col gap-3">
|
||||||
|
<span>
|
||||||
|
<h1 className="font-bold text-[16px]">{t("deactivate")}</h1>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
{
|
||||||
|
// dialog actions contain buttons that effect the popup status
|
||||||
|
}
|
||||||
|
<DialogActions sx={{ padding: '16px 24px' }}>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button type="submit" className="btn font-semibold">
|
||||||
|
{t('close')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
import Pagination from '@mui/material/Pagination';
|
||||||
|
import { setCurrentPage } from '@/redux/features/services-slice';
|
||||||
|
import { load } from '@/redux/features/services-slice'
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { AppDispatch , useAppSelector } from '@/redux/store';
|
||||||
|
|
||||||
|
export default function ListPagination() {
|
||||||
|
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// load needed states
|
||||||
|
const currentPage = useAppSelector((state) => state.servicesReducer.value.currentPage)
|
||||||
|
const total = useAppSelector((state) => state.servicesReducer.value.total)
|
||||||
|
// handle the pagination
|
||||||
|
const handlePagination = (event: React.ChangeEvent<unknown>, value: number) => {
|
||||||
|
dispatch(load({page:value}));
|
||||||
|
dispatch(setCurrentPage(value));
|
||||||
|
};
|
||||||
|
// show the pagination ui
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{Math.ceil(total / 4) > 1 ? (
|
||||||
|
<div className="flex justify-center items-center py-12">
|
||||||
|
<Pagination
|
||||||
|
sx={{ direction: 'ltr' }}
|
||||||
|
count={Math.ceil(total / 4)}
|
||||||
|
page={currentPage}
|
||||||
|
onChange={handlePagination}
|
||||||
|
variant="outlined"
|
||||||
|
shape="rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
233
webapp/src/components/dashboard/services/parts/updatePopUp.tsx
Normal file
233
webapp/src/components/dashboard/services/parts/updatePopUp.tsx
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
import { forwardRef , ReactElement , Ref, useEffect } from "react"
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { AppDispatch, useAppSelector } from "@/redux/store";
|
||||||
|
import { setUpdatePopUp , update } from "@/redux/features/services-slice"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import Slide from '@mui/material/Slide';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
|
||||||
|
export default function UpdatePopUp()
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
// redux
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// intl-18
|
||||||
|
const t = useTranslations('services');
|
||||||
|
// mui
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
// get state from redux
|
||||||
|
const updatePopUp = useAppSelector((state) => state.servicesReducer.value.updatePopUp)
|
||||||
|
const updatePopUpData = useAppSelector((state) => state.servicesReducer.value.updatePopUpData)
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(updatePopUp , "updatePopUp")
|
||||||
|
}, [updatePopUp])
|
||||||
|
// setup the popup animation
|
||||||
|
const Transition = forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: ReactElement<any, any>;
|
||||||
|
},
|
||||||
|
ref: Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
// setup the function that close the popup
|
||||||
|
const handleClose = () => {
|
||||||
|
dispatch(setUpdatePopUp({
|
||||||
|
status: false,
|
||||||
|
data: null
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
// we dont mount the pop up if we dont need it
|
||||||
|
if(!updatePopUp) return <></>
|
||||||
|
// we mount the popup if we needed it
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
fullScreen={fullScreen}
|
||||||
|
open={updatePopUp}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
keepMounted
|
||||||
|
onClose={handleClose}
|
||||||
|
fullWidth={true}
|
||||||
|
maxWidth={'sm'}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// This is the title of the pop up
|
||||||
|
}
|
||||||
|
<DialogTitle sx={{ color: theme.palette.text.primary , display:'flex' , justifyContent: 'space-between' , flexDirection: 'row' , alignItems: 'center' }}>
|
||||||
|
<h1 className="text-[25px] font-tajawal">{t('updateService')}</h1>
|
||||||
|
<span onClick={() => handleClose()} className="fill-primary-dark dark:fill-primary cursor-pointer">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5851 8.99904L17.6675 1.92716C17.8792 1.71545 17.9981 1.42831 17.9981 1.12891C17.9981 0.829501 17.8792 0.54236 17.6675 0.330649C17.4558 0.118938 17.1687 0 16.8693 0C16.5699 0 16.2828 0.118938 16.0711 0.330649L9 7.41377L1.92888 0.330649C1.71719 0.118938 1.43008 2.65824e-07 1.1307 2.68055e-07C0.831331 2.70286e-07 0.54422 0.118938 0.332532 0.330649C0.120844 0.54236 0.0019188 0.829501 0.0019188 1.12891C0.0019188 1.42831 0.120844 1.71545 0.332532 1.92716L7.4149 8.99904L0.332532 16.0709C0.227164 16.1754 0.143531 16.2998 0.0864579 16.4368C0.0293845 16.5738 0 16.7208 0 16.8692C0 17.0176 0.0293845 17.1645 0.0864579 17.3016C0.143531 17.4386 0.227164 17.5629 0.332532 17.6674C0.43704 17.7728 0.561376 17.8565 0.698368 17.9135C0.83536 17.9706 0.982298 18 1.1307 18C1.27911 18 1.42605 17.9706 1.56304 17.9135C1.70003 17.8565 1.82437 17.7728 1.92888 17.6674L9 10.5843L16.0711 17.6674C16.1756 17.7728 16.3 17.8565 16.437 17.9135C16.574 17.9706 16.7209 18 16.8693 18C17.0177 18 17.1646 17.9706 17.3016 17.9135C17.4386 17.8565 17.563 17.7728 17.6675 17.6674C17.7728 17.5629 17.8565 17.4386 17.9135 17.3016C17.9706 17.1645 18 17.0176 18 16.8692C18 16.7208 17.9706 16.5738 17.9135 16.4368C17.8565 16.2998 17.7728 16.1754 17.6675 16.0709L10.5851 8.99904Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</DialogTitle>
|
||||||
|
{
|
||||||
|
// Formik form start from here
|
||||||
|
}
|
||||||
|
<Formik
|
||||||
|
// Initial values
|
||||||
|
initialValues={{
|
||||||
|
_id: updatePopUpData?._id ? updatePopUpData?._id : '' ,
|
||||||
|
name : updatePopUpData?.name ? updatePopUpData?.name : '' ,
|
||||||
|
description : updatePopUpData?.description ? updatePopUpData?.description : '' ,
|
||||||
|
price: updatePopUpData?.price ? updatePopUpData?.price : 0 ,
|
||||||
|
status: updatePopUpData?.status ? updatePopUpData?.status : 'active'
|
||||||
|
}}
|
||||||
|
// this function handle the submition of the formik form
|
||||||
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
|
// dispatch the update function
|
||||||
|
await dispatch(update(values))
|
||||||
|
// close the popup
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
values,
|
||||||
|
setFieldValue
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{
|
||||||
|
// dialog content contain the formik form
|
||||||
|
}
|
||||||
|
<DialogContent>
|
||||||
|
{
|
||||||
|
// dialog text content contain a small description to the current form part
|
||||||
|
}
|
||||||
|
<DialogContentText sx={{color: theme.palette.text.primary }} id="alert-dialog-slide-description">
|
||||||
|
<h1 className="text-[18px] font-tajawal">{t('serviceUpdateText1')}</h1>
|
||||||
|
</DialogContentText>
|
||||||
|
<div className="
|
||||||
|
[&_*]:fill-text/80 [&_*]:dark:fill-text-dark/80
|
||||||
|
[&_*]:text-text/80 [&_*]:dark:text-text-dark/80
|
||||||
|
[&_*]:placeholder-text/80 [&_*]:dark:placeholder-text-dark/80
|
||||||
|
w-full h-auto mt-[36px] flex flex-col gap-3
|
||||||
|
">
|
||||||
|
<div className="w-full flex lg:flex-row flex-col lg:gap-12 gap-7">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5">
|
||||||
|
<h3 className="font-bold">{t('serviceInformations')}</h3>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.name}
|
||||||
|
placeholder={t('name')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10 max-h-[250px]
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.description}
|
||||||
|
placeholder={t('description')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="price"
|
||||||
|
className="
|
||||||
|
disabled:bg-primary-dark/10
|
||||||
|
input lg:w-[300px] w-full border-2 border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-dark/40 focus:outline-none
|
||||||
|
"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.price}
|
||||||
|
placeholder={t('price')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-3">
|
||||||
|
<h3 className="font-bold">{t('status')}</h3>
|
||||||
|
<div className="w-full flex justify-start items-center gap-5">
|
||||||
|
<input
|
||||||
|
id={`active`}
|
||||||
|
type="checkbox"
|
||||||
|
className="form-checkbox p-3 border-none bg-primary-dark/10 dark:bg-primary/10 checked:bg-success-light dark:checked:bg-success-light checked:focus:bg-success-light checked:hover:bg-success-light rounded-md"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
checked={values.status == 'active' ? true : false}
|
||||||
|
onChange={() => {
|
||||||
|
setFieldValue("status" , "active")
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor={`active`} className="w-full flex flex-col gap-3">
|
||||||
|
<span>
|
||||||
|
<h1 className="font-bold text-[16px]">{t("active")}</h1>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex justify-start items-center gap-5">
|
||||||
|
<input
|
||||||
|
id={`deactivate`}
|
||||||
|
type="checkbox"
|
||||||
|
className="form-checkbox p-3 border-none bg-primary-dark/10 dark:bg-primary/10 checked:bg-success-light dark:checked:bg-success-light checked:focus:bg-success-light checked:hover:bg-success-light rounded-md"
|
||||||
|
onBlur={handleBlur}
|
||||||
|
checked={values.status == 'active' ? false : true}
|
||||||
|
onChange={() => {
|
||||||
|
setFieldValue("status" , "deactivate")
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor={`deactivate`} className="w-full flex flex-col gap-3">
|
||||||
|
<span>
|
||||||
|
<h1 className="font-bold text-[16px]">{t("deactivate")}</h1>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
{
|
||||||
|
// dialog actions contain buttons that effect the popup status
|
||||||
|
}
|
||||||
|
<DialogActions sx={{ padding: '16px 24px' }}>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button type="submit" className="btn font-semibold">
|
||||||
|
{
|
||||||
|
isSubmitting ?
|
||||||
|
<CircularProgress color="secondary" size={24} />
|
||||||
|
:
|
||||||
|
t('save')
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
14
webapp/src/components/dashboard/services/popUpsWrapper.tsx
Normal file
14
webapp/src/components/dashboard/services/popUpsWrapper.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import AddPopUp from './parts/addPopUp'
|
||||||
|
import DetailsPopUp from './parts/detailsPopUp'
|
||||||
|
import UpdatePopUp from './parts/updatePopUp'
|
||||||
|
|
||||||
|
export default function ServicesPopUpWrapper()
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<UpdatePopUp />
|
||||||
|
<AddPopUp />
|
||||||
|
<DetailsPopUp />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
168
webapp/src/components/dashboard/services/servicesList.tsx
Normal file
168
webapp/src/components/dashboard/services/servicesList.tsx
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { useAppSelector } from '@/redux/store';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import { AppDispatch } from '@/redux/store';
|
||||||
|
import { setSearchKeyword ,load , search , delete_ } from '@/redux/features/services-slice'
|
||||||
|
import ListPagination from './parts/servicesListPagination'
|
||||||
|
import { setDetailsPopUp , setUpdatePopUp , setCurrentPage } from '@/redux/features/services-slice';
|
||||||
|
|
||||||
|
export default function List()
|
||||||
|
{
|
||||||
|
const t = useTranslations('services');
|
||||||
|
const dispatch = useDispatch<AppDispatch>()
|
||||||
|
|
||||||
|
const listContent = useAppSelector((state) => state.servicesReducer.value.listContent)
|
||||||
|
const isLoading = useAppSelector((state) => state.servicesReducer.value.isLoading)
|
||||||
|
const loadedFirstTime = useAppSelector((state) => state.servicesReducer.value.loadedFirstTime)
|
||||||
|
const searchKeyword = useAppSelector((state) => state.servicesReducer.value.searchKeyword)
|
||||||
|
const currentPage = useAppSelector((state) => state.servicesReducer.value.currentPage)
|
||||||
|
const appGeneralSettings = useAppSelector((state) => state.settingsReducer.value.appGeneralSettings)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(loadedFirstTime) return
|
||||||
|
if(isLoading) return
|
||||||
|
async function a()
|
||||||
|
{
|
||||||
|
await dispatch(load({page : 1}))
|
||||||
|
}
|
||||||
|
a()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-3 w-full w-auto">
|
||||||
|
<header className="w-full flex lg:flex-row flex-col lg:justify-between lg:items-center gap-3">
|
||||||
|
<h1 className="font-semibold text-[20px] text-text dark:text-text-dark">{t('services')}</h1>
|
||||||
|
<label htmlFor="email" className="relative text-gray-400 focus-within:text-gray-600 block">
|
||||||
|
<svg className="text-secondary dark:text-primary/50 cursor-pointer w-[15px] h-[15px] absolute top-1/2 transform -translate-y-1/2 rtl:left-3 ltr:right-3" width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M18.4479 16.9688L14.9063 13.4375C16.0489 11.9817 16.669 10.184 16.6667 8.33334C16.6667 6.68516 16.1779 5.074 15.2623 3.70359C14.3466 2.33318 13.0451 1.26507 11.5224 0.634341C9.99965 0.00361068 8.32409 -0.161417 6.70759 0.160126C5.09108 0.48167 3.60622 1.27534 2.44078 2.44078C1.27534 3.60622 0.48167 5.09108 0.160126 6.70759C-0.161417 8.32409 0.00361068 9.99965 0.634341 11.5224C1.26507 13.0451 2.33318 14.3466 3.70359 15.2623C5.074 16.1779 6.68516 16.6667 8.33334 16.6667C10.184 16.669 11.9817 16.0489 13.4375 14.9063L16.9688 18.4479C17.0656 18.5456 17.1808 18.6231 17.3077 18.6759C17.4347 18.7288 17.5708 18.756 17.7083 18.756C17.8459 18.756 17.982 18.7288 18.1089 18.6759C18.2359 18.6231 18.3511 18.5456 18.4479 18.4479C18.5456 18.3511 18.6231 18.2359 18.6759 18.1089C18.7288 17.982 18.756 17.8459 18.756 17.7083C18.756 17.5708 18.7288 17.4347 18.6759 17.3077C18.6231 17.1808 18.5456 17.0656 18.4479 16.9688ZM2.08334 8.33334C2.08334 7.0972 2.44989 5.88883 3.13665 4.86102C3.82341 3.83322 4.79953 3.03214 5.94157 2.55909C7.0836 2.08604 8.34027 1.96227 9.55265 2.20343C10.765 2.44459 11.8787 3.03984 12.7528 3.91392C13.6268 4.788 14.2221 5.90164 14.4632 7.11402C14.7044 8.3264 14.5806 9.58307 14.1076 10.7251C13.6345 11.8671 12.8335 12.8433 11.8057 13.53C10.7778 14.2168 9.56947 14.5833 8.33334 14.5833C6.67573 14.5833 5.08602 13.9249 3.91392 12.7528C2.74182 11.5807 2.08334 9.99094 2.08334 8.33334Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<input onChange={(async(e) => {
|
||||||
|
if(e.target.value.length == 0)
|
||||||
|
{
|
||||||
|
dispatch(setCurrentPage(1));
|
||||||
|
}
|
||||||
|
dispatch(setSearchKeyword(e.target.value))
|
||||||
|
await dispatch(search({
|
||||||
|
searchKeyword: e.target.value
|
||||||
|
}))
|
||||||
|
})}
|
||||||
|
value={searchKeyword}
|
||||||
|
className="lg:w-[300px] w-full text-text dark:text-text-dark rounded-[5px] px-3 h-[50px] border-[1px] !border-secondary-light bg-primary-dark/5 placeholder:font-semibold" type="text" placeholder={t('searchForServices')}></input>
|
||||||
|
</label>
|
||||||
|
</header>
|
||||||
|
<div className="panel !p-0 mb-7 dark:bg-primary-dark">
|
||||||
|
<header className="w-full lg:flex gap-3 hidden justify-between items-center p-5 opacity-50 font-semibold border-b-[1px] border-secondary-light">
|
||||||
|
<span className="w-full text-start">#</span>
|
||||||
|
<span className="w-full text-start">{t('name')}</span>
|
||||||
|
<span className="w-full text-start">{t('price')}</span>
|
||||||
|
<span className="w-full text-start">{t('totalSubscribers')}</span>
|
||||||
|
<span className="w-full text-start">{t('status')}</span>
|
||||||
|
<span className="w-full text-start">{t('actions')}</span>
|
||||||
|
</header>
|
||||||
|
<main className="flex flex-col justify-center items-center">
|
||||||
|
{
|
||||||
|
!isLoading && listContent.flat().length == 0 && currentPage >= 2 && searchKeyword.length == 0 ?
|
||||||
|
<div className="p-5 py-10 flex justify-start items-center gap-3 [&_*]:fill-warning">
|
||||||
|
<svg width="25" height="25" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path opacity="0.5" d="M15.2069 2.85079C15.6886 1.98507 16.3858 1.26524 17.2277 0.76451C18.0696 0.263778 19.0261 0 20 0C20.9739 0 21.9304 0.263778 22.7723 0.76451C23.6142 1.26524 24.3114 1.98507 24.7931 2.85079L39.309 29.3012C39.77 30.1312 40.0083 31.0711 39.9998 32.0257C39.9913 32.9802 39.7363 33.9155 39.2607 34.7368C38.7781 35.5842 38.085 36.286 37.2518 36.7713C36.4186 37.2565 35.4747 37.5078 34.5159 37.4998H5.48407C4.52527 37.5078 3.58143 37.2565 2.74819 36.7713C1.91496 36.286 1.22192 35.5842 0.739307 34.7368C0.263677 33.9155 0.00870576 32.9802 0.000218482 32.0257C-0.0082688 31.0711 0.230027 30.1312 0.690974 29.3012L15.2069 2.85079ZM20 30.6818C20.3296 30.6818 20.6518 30.5819 20.9259 30.3946C21.2 30.2073 21.4136 29.9411 21.5397 29.6296C21.6659 29.3182 21.6989 28.9754 21.6346 28.6448C21.5703 28.3142 21.4115 28.0104 21.1785 27.7721C20.9454 27.5337 20.6484 27.3713 20.3251 27.3056C20.0018 27.2398 19.6668 27.2736 19.3622 27.4026C19.0577 27.5316 18.7974 27.7501 18.6143 28.0304C18.4312 28.3107 18.3334 28.6402 18.3334 28.9773C18.3334 29.4294 18.509 29.8629 18.8215 30.1826C19.1341 30.5022 19.558 30.6818 20 30.6818ZM18.3334 22.1593C18.3334 22.6114 18.509 23.0449 18.8215 23.3646C19.1341 23.6843 19.558 23.8638 20 23.8638C20.442 23.8638 20.8659 23.6843 21.1785 23.3646C21.491 23.0449 21.6666 22.6114 21.6666 22.1593V11.9324C21.6666 11.4803 21.491 11.0467 21.1785 10.7271C20.8659 10.4074 20.442 10.2279 20 10.2279C19.558 10.2279 19.1341 10.4074 18.8215 10.7271C18.509 11.0467 18.3334 11.4803 18.3334 11.9324V22.1593Z" fill="currentColor"/>
|
||||||
|
<circle cx="20" cy="29" r="2" fill="currentColor"/>
|
||||||
|
<rect x="18" y="10" width="4" height="14" rx="2" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="text-text dark:text-text-dark font-semibold">{t('thisPageIsEmpty')}</h2>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<></>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!isLoading && listContent.flat().length == 0 && currentPage < 2 && searchKeyword.length == 0 ?
|
||||||
|
<div className="p-5 py-10 flex justify-start items-center gap-3 [&_*]:fill-warning">
|
||||||
|
<svg width="25" height="25" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path opacity="0.5" d="M15.2069 2.85079C15.6886 1.98507 16.3858 1.26524 17.2277 0.76451C18.0696 0.263778 19.0261 0 20 0C20.9739 0 21.9304 0.263778 22.7723 0.76451C23.6142 1.26524 24.3114 1.98507 24.7931 2.85079L39.309 29.3012C39.77 30.1312 40.0083 31.0711 39.9998 32.0257C39.9913 32.9802 39.7363 33.9155 39.2607 34.7368C38.7781 35.5842 38.085 36.286 37.2518 36.7713C36.4186 37.2565 35.4747 37.5078 34.5159 37.4998H5.48407C4.52527 37.5078 3.58143 37.2565 2.74819 36.7713C1.91496 36.286 1.22192 35.5842 0.739307 34.7368C0.263677 33.9155 0.00870576 32.9802 0.000218482 32.0257C-0.0082688 31.0711 0.230027 30.1312 0.690974 29.3012L15.2069 2.85079ZM20 30.6818C20.3296 30.6818 20.6518 30.5819 20.9259 30.3946C21.2 30.2073 21.4136 29.9411 21.5397 29.6296C21.6659 29.3182 21.6989 28.9754 21.6346 28.6448C21.5703 28.3142 21.4115 28.0104 21.1785 27.7721C20.9454 27.5337 20.6484 27.3713 20.3251 27.3056C20.0018 27.2398 19.6668 27.2736 19.3622 27.4026C19.0577 27.5316 18.7974 27.7501 18.6143 28.0304C18.4312 28.3107 18.3334 28.6402 18.3334 28.9773C18.3334 29.4294 18.509 29.8629 18.8215 30.1826C19.1341 30.5022 19.558 30.6818 20 30.6818ZM18.3334 22.1593C18.3334 22.6114 18.509 23.0449 18.8215 23.3646C19.1341 23.6843 19.558 23.8638 20 23.8638C20.442 23.8638 20.8659 23.6843 21.1785 23.3646C21.491 23.0449 21.6666 22.6114 21.6666 22.1593V11.9324C21.6666 11.4803 21.491 11.0467 21.1785 10.7271C20.8659 10.4074 20.442 10.2279 20 10.2279C19.558 10.2279 19.1341 10.4074 18.8215 10.7271C18.509 11.0467 18.3334 11.4803 18.3334 11.9324V22.1593Z" fill="currentColor"/>
|
||||||
|
<circle cx="20" cy="29" r="2" fill="currentColor"/>
|
||||||
|
<rect x="18" y="10" width="4" height="14" rx="2" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="text-text dark:text-text-dark font-semibold">{t('noData')}</h2>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<></>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!isLoading && listContent.flat().length == 0 && searchKeyword.length != 0 ?
|
||||||
|
<div className="p-5 py-10 flex justify-start items-center gap-3 [&_*]:fill-warning">
|
||||||
|
<svg width="25" height="25" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M27.3438 20.3125C27.7656 20.3125 28.125 20.6719 28.125 21.0938C28.125 21.5156 27.7656 21.875 27.3438 21.875C26.9219 21.875 26.5625 21.5156 26.5625 21.0938C26.5625 20.6719 26.9219 20.3125 27.3438 20.3125ZM13.2812 20.3125C13.7031 20.3125 14.0625 20.6719 14.0625 21.0938C14.0625 21.5156 13.7031 21.875 13.2812 21.875C12.8594 21.875 12.5 21.5156 12.5 21.0938C12.5 20.6719 12.8594 20.3125 13.2812 20.3125Z" fill="currentColor"/>
|
||||||
|
<path d="M36.6094 37.3451C36.4094 37.3451 36.2094 37.2686 36.0562 37.117L32.5938 33.6545C32.2891 33.3498 32.2891 32.8545 32.5938 32.5498C32.8984 32.2451 33.3937 32.2451 33.6984 32.5498L37.1609 36.0123C37.4656 36.317 37.4656 36.8123 37.1609 37.117C37.0094 37.2701 36.8094 37.3451 36.6094 37.3451Z" fill="currentColor"/>
|
||||||
|
<path d="M44.5312 48.4375C43.4891 48.4375 42.5078 48.0312 41.7688 47.2937L36.3 41.825C35.5625 41.0859 35.1562 40.1062 35.1562 39.0625C35.1562 38.0188 35.5625 37.0391 36.3 36.3C37.7156 34.8844 40.4078 34.8844 41.8234 36.3L47.2922 41.7688C48.0313 42.5078 48.4375 43.4875 48.4375 44.5312C48.4375 45.575 48.0312 46.5547 47.2937 47.2937C46.5562 48.0328 45.5734 48.4375 44.5312 48.4375ZM39.0625 36.75C38.4266 36.75 37.8234 36.9875 37.4047 37.4063C36.9625 37.8484 36.7188 38.4375 36.7188 39.0625C36.7188 39.6875 36.9625 40.2766 37.4047 40.7203L42.8734 46.1891C43.7594 47.0734 45.3016 47.0734 46.1875 46.1891C46.6312 45.7453 46.875 45.1563 46.875 44.5312C46.875 43.9062 46.6313 43.3172 46.1891 42.8734L40.7203 37.4047C40.3016 36.9875 39.6984 36.75 39.0625 36.75ZM20.3125 39.0625C9.97344 39.0625 1.5625 30.6516 1.5625 20.3125C1.5625 9.97344 9.97344 1.5625 20.3125 1.5625C30.6516 1.5625 39.0625 9.97344 39.0625 20.3125C39.0625 30.6516 30.6516 39.0625 20.3125 39.0625ZM20.3125 3.125C10.8359 3.125 3.125 10.8359 3.125 20.3125C3.125 29.7891 10.8359 37.5 20.3125 37.5C29.7891 37.5 37.5 29.7891 37.5 20.3125C37.5 10.8359 29.7891 3.125 20.3125 3.125Z" fill="currentColor"/>
|
||||||
|
<path d="M21.875 25.0328C21.4438 25.0328 21.0938 24.6828 21.0938 24.2516C21.0938 23.8219 20.7437 23.4703 20.3125 23.4703C19.8813 23.4703 19.5312 23.8219 19.5312 24.2516C19.5312 24.6828 19.1812 25.0328 18.75 25.0328C18.3188 25.0328 17.9688 24.6828 17.9688 24.2516C17.9688 22.9594 19.0203 21.9078 20.3125 21.9078C21.6047 21.9078 22.6562 22.9594 22.6562 24.2516C22.6562 24.6828 22.3062 25.0328 21.875 25.0328ZM27.3438 22.6562C26.4969 22.6562 25.7812 21.9406 25.7812 21.0937C25.7812 20.2469 26.4969 19.5312 27.3438 19.5312C28.1906 19.5312 28.9062 20.2469 28.9062 21.0937C28.9062 21.9406 28.1906 22.6562 27.3438 22.6562ZM13.2812 22.6562C12.4344 22.6562 11.7188 21.9406 11.7188 21.0937C11.7188 20.2469 12.4344 19.5312 13.2812 19.5312C14.1281 19.5312 14.8438 20.2469 14.8438 21.0937C14.8438 21.9406 14.1281 22.6562 13.2812 22.6562Z" fill="currentColor"/>
|
||||||
|
<path d="M36.6094 37.3451C36.4094 37.3451 36.2094 37.2686 36.0562 37.117L32.5938 33.6545C32.2891 33.3498 32.2891 32.8545 32.5938 32.5498C32.8984 32.2451 33.3937 32.2451 33.6984 32.5498L37.1609 36.0123C37.4656 36.317 37.4656 36.8123 37.1609 37.117C37.0094 37.2701 36.8094 37.3451 36.6094 37.3451Z" fill="currentColor"/>
|
||||||
|
<path d="M44.5312 48.4375C43.4891 48.4375 42.5078 48.0312 41.7688 47.2937L36.3 41.825C35.5625 41.0859 35.1562 40.1062 35.1562 39.0625C35.1562 38.0188 35.5625 37.0391 36.3 36.3C37.7156 34.8844 40.4078 34.8844 41.8234 36.3L47.2922 41.7688C48.0313 42.5078 48.4375 43.4875 48.4375 44.5312C48.4375 45.575 48.0312 46.5547 47.2937 47.2937C46.5562 48.0328 45.5734 48.4375 44.5312 48.4375ZM39.0625 36.75C38.4266 36.75 37.8234 36.9875 37.4047 37.4063C36.9625 37.8484 36.7188 38.4375 36.7188 39.0625C36.7188 39.6875 36.9625 40.2766 37.4047 40.7203L42.8734 46.1891C43.7594 47.0734 45.3016 47.0734 46.1875 46.1891C46.6312 45.7453 46.875 45.1563 46.875 44.5312C46.875 43.9062 46.6313 43.3172 46.1891 42.8734L40.7203 37.4047C40.3016 36.9875 39.6984 36.75 39.0625 36.75ZM20.3125 39.0625C9.97344 39.0625 1.5625 30.6516 1.5625 20.3125C1.5625 9.97344 9.97344 1.5625 20.3125 1.5625C30.6516 1.5625 39.0625 9.97344 39.0625 20.3125C39.0625 30.6516 30.6516 39.0625 20.3125 39.0625ZM20.3125 3.125C10.8359 3.125 3.125 10.8359 3.125 20.3125C3.125 29.7891 10.8359 37.5 20.3125 37.5C29.7891 37.5 37.5 29.7891 37.5 20.3125C37.5 10.8359 29.7891 3.125 20.3125 3.125Z" fill="currentColor"/>
|
||||||
|
<path d="M21.875 25.0328C21.4438 25.0328 21.0938 24.6828 21.0938 24.2516C21.0938 23.8219 20.7437 23.4703 20.3125 23.4703C19.8813 23.4703 19.5312 23.8219 19.5312 24.2516C19.5312 24.6828 19.1812 25.0328 18.75 25.0328C18.3188 25.0328 17.9688 24.6828 17.9688 24.2516C17.9688 22.9594 19.0203 21.9078 20.3125 21.9078C21.6047 21.9078 22.6562 22.9594 22.6562 24.2516C22.6562 24.6828 22.3062 25.0328 21.875 25.0328ZM27.3438 22.6562C26.4969 22.6562 25.7812 21.9406 25.7812 21.0937C25.7812 20.2469 26.4969 19.5312 27.3438 19.5312C28.1906 19.5312 28.9062 20.2469 28.9062 21.0937C28.9062 21.9406 28.1906 22.6562 27.3438 22.6562ZM13.2812 22.6562C12.4344 22.6562 11.7188 21.9406 11.7188 21.0937C11.7188 20.2469 12.4344 19.5312 13.2812 19.5312C14.1281 19.5312 14.8438 20.2469 14.8438 21.0937C14.8438 21.9406 14.1281 22.6562 13.2812 22.6562Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="text-text dark:text-text-dark font-semibold">{t('noSearchResults')}</h2>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<></>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// single sender account
|
||||||
|
isLoading ?
|
||||||
|
<div className="p-5 flex justify-start items-center gap-3">
|
||||||
|
<CircularProgress color="secondary" size={24} />
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
listContent.flat().map((v , i) => {
|
||||||
|
return (
|
||||||
|
<div key={i} className="w-full flex gap-3 justify-between items-center p-5 font-semibold h-[70px] border-b-[1px] border-secondary-light/30">
|
||||||
|
<span className="w-full text-start whitespace-nowrap text-ellipsis overflow-hidden lg:block hidden">{i + 1}</span>
|
||||||
|
<span className="w-full text-start whitespace-nowrap text-ellipsis overflow-hidden">{v.name}</span>
|
||||||
|
<span className="w-full text-start flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden">{appGeneralSettings.currencySymbol} {v.price}</span>
|
||||||
|
<span className="w-full text-start flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden">{v.totalSubscribers}</span>
|
||||||
|
<span className="w-full text-start lg:block hidden flex gap-2 whitespace-nowrap text-ellipsis overflow-hidden">{t(v.status+'Service')}</span>
|
||||||
|
|
||||||
|
<span className="lg:w-full w-[80px] h-full text-start flex justify-start items-center gap-3">
|
||||||
|
<button onClick={async() => {
|
||||||
|
dispatch(setUpdatePopUp({
|
||||||
|
status: true,
|
||||||
|
data: v
|
||||||
|
}))
|
||||||
|
}} className="[&_*]:fill-success [&_*]:dark:fill-success">
|
||||||
|
<svg width="15" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6.36069 6.61884L5.39925 9.91891C5.3176 10.1996 5.39219 10.5041 5.59355 10.7111C5.74073 10.8626 5.93856 10.9446 6.14093 10.9446C6.21527 10.9446 6.29012 10.9337 6.36321 10.9109L9.56961 9.92151C9.69234 9.88363 9.80423 9.81515 9.89471 9.72177L15.7732 3.67103C15.9184 3.52161 16.0001 3.31876 16.0001 3.10761C16.0001 2.89646 15.9184 2.6936 15.7732 2.54419L13.528 0.23346C13.2256 -0.0778202 12.7357 -0.0778202 12.4333 0.23346L6.55499 6.28421C6.46426 6.37759 6.39774 6.49225 6.36069 6.61884ZM7.78786 7.26889L12.9806 1.92371L14.1308 3.10761L8.93806 8.45279L7.29541 8.95966L7.78786 7.26889Z" fill="#4DB33D"/>
|
||||||
|
<path d="M15.2258 7.4375C14.7981 7.4375 14.4516 7.79444 14.4516 8.23438V13.5469C14.4516 14.2794 13.8727 14.875 13.1613 14.875H2.83871C2.12727 14.875 1.54839 14.2794 1.54839 13.5469V2.92188C1.54839 2.18933 2.12727 1.59375 2.83871 1.59375H8C8.42767 1.59375 8.77419 1.23681 8.77419 0.796875C8.77419 0.356936 8.42767 0 8 0H2.83871C1.27344 0 0 1.311 0 2.92188V13.5469C0 15.1577 1.27344 16.4688 2.83871 16.4688H13.1613C14.7266 16.4688 16 15.1577 16 13.5469V8.23438C16 7.79444 15.6535 7.4375 15.2258 7.4375Z" fill="#4DB33D"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button onClick={async() => {
|
||||||
|
await dispatch(delete_({
|
||||||
|
id: v._id
|
||||||
|
}))
|
||||||
|
}} className="[&_*]:fill-error [&_*]:dark:fill-error">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M2.0625 4.625C2.0625 4.27983 2.32153 4.00001 2.64107 4.00001L4.63925 3.99968C5.03626 3.98881 5.38652 3.71612 5.52161 3.31268C5.52516 3.30208 5.52924 3.28899 5.54389 3.24152L5.62999 2.96245C5.68267 2.79134 5.72857 2.64227 5.79281 2.50902C6.04657 1.98262 6.51606 1.61708 7.0586 1.52349C7.19593 1.4998 7.34136 1.4999 7.50832 1.50002H10.1168C10.2838 1.4999 10.4292 1.4998 10.5665 1.52349C11.1091 1.61708 11.5786 1.98262 11.8323 2.50902C11.8966 2.64227 11.9425 2.79134 11.9951 2.96245L12.0812 3.24152C12.0959 3.28899 12.1 3.30208 12.1035 3.31268C12.2387 3.71612 12.6584 3.98915 13.0553 4.00001H14.9839C15.3034 4.00001 15.5625 4.27983 15.5625 4.625C15.5625 4.97018 15.3034 5.25 14.9839 5.25H2.64107C2.32153 5.25 2.0625 4.97018 2.0625 4.625Z" fill="currentColor"/>
|
||||||
|
<path opacity="0.5" d="M8.7051 16.4997H9.29528C11.3259 16.4997 12.3412 16.4997 13.0013 15.8523C13.6615 15.2049 13.7291 14.143 13.8641 12.0191L14.0588 8.95863C14.132 7.80626 14.1687 7.23003 13.8375 6.86489C13.5063 6.49976 12.9471 6.49976 11.8286 6.49976H6.17179C5.05329 6.49976 4.49403 6.49976 4.16286 6.86489C3.83169 7.23003 3.86833 7.80626 3.94162 8.95863L4.13625 12.0191C4.27133 14.143 4.33887 15.2049 4.99901 15.8523C5.65915 16.4997 6.67446 16.4997 8.7051 16.4997Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button onClick={async() => {
|
||||||
|
dispatch(setDetailsPopUp({
|
||||||
|
status: true,
|
||||||
|
data: v ? v : null
|
||||||
|
}))
|
||||||
|
}} className="[&_*]:fill-info [&_*]:dark:fill-info">
|
||||||
|
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M13.3336 13.3333H1.66641V1.66672H6.875V0H1.66641C0.749649 0 0 0.749883 0 1.66672V13.3333C0 14.2501 0.749649 15 1.66641 15H13.3336C14.2504 15 15 14.2501 15 13.3333V8.125H13.3336V13.3333ZM8.75 0V1.66672H12.167L3.74996 10.0833L4.9166 11.25L13.3336 2.83316V6.25H15V0H8.75Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
<ListPagination />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
403
webapp/src/components/dashboard/settings/generalSettings.tsx
Normal file
403
webapp/src/components/dashboard/settings/generalSettings.tsx
Normal file
@ -0,0 +1,403 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import Select from 'react-select'
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { AppDispatch, useAppSelector } from "@/redux/store";
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { update } from '@/redux/features/settings-slice'
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import * as Yup from "yup";
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
|
||||||
|
export default function GeneralSettings()
|
||||||
|
{
|
||||||
|
// declare the needed variables
|
||||||
|
// redux
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
// intl-18
|
||||||
|
const t = useTranslations('settings');
|
||||||
|
// mui
|
||||||
|
const theme = useTheme();
|
||||||
|
// load needed states
|
||||||
|
const appGeneralSettings = useAppSelector((state) => state.settingsReducer.value.appGeneralSettings)
|
||||||
|
const isLoadingSettings = useAppSelector((state) => state.settingsReducer.value.isLoadingSettings)
|
||||||
|
const loadedFirstTime = useAppSelector((state) => state.settingsReducer.value.loadedFirstTime)
|
||||||
|
const themeType = useAppSelector((state) => state.themeTypeReducer.value.themeType)
|
||||||
|
|
||||||
|
// handle edite mode
|
||||||
|
const [edite , setEdite] = useState(false)
|
||||||
|
function handleEdite()
|
||||||
|
{
|
||||||
|
setEdite(!edite)
|
||||||
|
}
|
||||||
|
// body types options
|
||||||
|
const bodyTypesOptions = [
|
||||||
|
{ value: true, label: t('show') },
|
||||||
|
{ value: false, label: t('dontShow') },
|
||||||
|
]
|
||||||
|
// extract svg from svg file
|
||||||
|
const readFile = (file : Blob) => {
|
||||||
|
// this function return a promise
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// read the file
|
||||||
|
const reader = new FileReader();
|
||||||
|
// whene file fully readed fire this
|
||||||
|
reader.onload = (event) => {
|
||||||
|
const content = event?.target?.result;
|
||||||
|
resolve(content);
|
||||||
|
};
|
||||||
|
// this fire whene error raised
|
||||||
|
reader.onerror = (error) => {
|
||||||
|
reject(error);
|
||||||
|
};
|
||||||
|
// read the file as a text thene fire onload listener
|
||||||
|
reader.readAsText(file);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// handle the changes in the file input
|
||||||
|
const handleFileChange = async (event : any , setFieldValue : any) => {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
try {
|
||||||
|
// get the file content
|
||||||
|
const content = await readFile(file);
|
||||||
|
// set the field value with with new file content
|
||||||
|
setFieldValue('logo' , content)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error reading file:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// contain the react select styles
|
||||||
|
const customStyles = {
|
||||||
|
container: styles => ({
|
||||||
|
...styles,
|
||||||
|
width: "100%",
|
||||||
|
backgroundColor: theme.palette.primary.light,
|
||||||
|
fontFamily: 'Regular',
|
||||||
|
borderRadius: 5,
|
||||||
|
border: themeType == "light" ? "solid 2px black" : ""
|
||||||
|
}),
|
||||||
|
control: (styles : any, state : any) => ({
|
||||||
|
...styles,
|
||||||
|
minHeight: "50px",
|
||||||
|
height: "auto",
|
||||||
|
border: "none",
|
||||||
|
boxShadow: "none",
|
||||||
|
borderRadius: 5,
|
||||||
|
backgroundColor: theme.palette.primary.light,
|
||||||
|
fontFamily: 'Regular',
|
||||||
|
color: "#000",
|
||||||
|
opacity: 0.8,
|
||||||
|
}),
|
||||||
|
option: (styles : any, { isDisable , isSelected } : {
|
||||||
|
isDisable: boolean,
|
||||||
|
isSelected: boolean
|
||||||
|
}) => ({
|
||||||
|
...styles,
|
||||||
|
zIndex: 999,
|
||||||
|
backgroundColor: isSelected ? theme.palette.secondary.main : theme.palette.primary.light,
|
||||||
|
color: isSelected ? theme.palette.primary.light : theme.palette.primary.dark ,
|
||||||
|
padding: "15px",
|
||||||
|
cursor: "pointer",
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.secondary.light,
|
||||||
|
color: `#fff`,
|
||||||
|
},
|
||||||
|
'&:active': {
|
||||||
|
backgroundColor: `#eee`,
|
||||||
|
color: `#000`,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
menu: (styles : any, state : any) => ({
|
||||||
|
...styles,
|
||||||
|
backgroundColor: theme.palette.primary.light,
|
||||||
|
borderRadius: 5,
|
||||||
|
}),
|
||||||
|
menuList: (styles : any, state : any) => ({
|
||||||
|
...styles,
|
||||||
|
backgroundColor: theme.palette.primary.light,
|
||||||
|
height: '150px',
|
||||||
|
border: `2px solid #000`,
|
||||||
|
borderRadius: 5,
|
||||||
|
"::-webkit-scrollbar": {
|
||||||
|
width: "0px",
|
||||||
|
height: "0px",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
!loadedFirstTime ?
|
||||||
|
<div className="w-full h-[100vh] flex items-center justify-center gap-5">
|
||||||
|
<CircularProgress color="secondary" size={22} />
|
||||||
|
<h1 className="dark:text-text-light text-text">{t('loading')}...</h1>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<>
|
||||||
|
<Formik
|
||||||
|
// Initial values
|
||||||
|
initialValues={{
|
||||||
|
appName : appGeneralSettings.appName,
|
||||||
|
appNameEN : appGeneralSettings.appNameEN,
|
||||||
|
gymName : appGeneralSettings.gymName,
|
||||||
|
email : appGeneralSettings.email,
|
||||||
|
phone : appGeneralSettings.phone,
|
||||||
|
address : appGeneralSettings.address,
|
||||||
|
showLogo : appGeneralSettings.showLogo,
|
||||||
|
logo : appGeneralSettings.logo,
|
||||||
|
currencySymbol : appGeneralSettings.currencySymbol
|
||||||
|
}}
|
||||||
|
// validat the inputs
|
||||||
|
validationSchema={Yup.object().shape({
|
||||||
|
appName : Yup.string().required('thisFieldIsRequired'),
|
||||||
|
appNameEN : Yup.string().required('thisFieldIsRequired'),
|
||||||
|
gymName : Yup.string().required('thisFieldIsRequired'),
|
||||||
|
email : Yup.string().required('thisFieldIsRequired'),
|
||||||
|
phone : Yup.string().required('thisFieldIsRequired'),
|
||||||
|
address : Yup.string().required('thisFieldIsRequired'),
|
||||||
|
showLogo : Yup.string().required('thisFieldIsRequired'),
|
||||||
|
logo : Yup.string().required('thisFieldIsRequired'),
|
||||||
|
currencySymbol : Yup.string().required('thisFieldIsRequired'),
|
||||||
|
})}
|
||||||
|
// this function handle the submition of the formik form
|
||||||
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
|
await dispatch(update(values))
|
||||||
|
handleEdite()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
values,
|
||||||
|
setFieldValue,
|
||||||
|
resetForm
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<section className="w-full h-auto min-h-[100vh] pb-[50px] flex flex-col gap-5">
|
||||||
|
<header className="[&_*]:dark:text-text-light [&_*]:text-text w-full flex flex-col text-start">
|
||||||
|
<h3 className="font-bold text-md">{t('general')}</h3>
|
||||||
|
<h4 className="font-medium text-[15px]">{t('gymInformations')}</h4>
|
||||||
|
</header>
|
||||||
|
<div className="w-full flex lg:flex-row flex-col gap-5">
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5 mt-[36px]">
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<label className="text-sm font-bold text-text dark:text-text-light">{t('appName')}</label>
|
||||||
|
<input
|
||||||
|
id="appName"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.appName}
|
||||||
|
placeholder={t('appName')}
|
||||||
|
disabled={!edite}
|
||||||
|
className="
|
||||||
|
disabled:cursor-not-allowed
|
||||||
|
disabled:bg-primary-dark/10 disabled:dark:bg-primary-light/10
|
||||||
|
disabled:dark:text-text-light/50 text-text
|
||||||
|
disabled:text-text/50 input w-full border-2 border-primary-dark
|
||||||
|
dark:border-primary-dark p-[11px] rounded-md bg-primary
|
||||||
|
dark:bg-primary-light focus:outline-none
|
||||||
|
"></input>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<label className="text-sm font-bold text-text dark:text-text-light">{t('appNameEN')}</label>
|
||||||
|
<input
|
||||||
|
id="appNameEN"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.appNameEN}
|
||||||
|
placeholder={t('appNameEN')}
|
||||||
|
disabled={!edite}
|
||||||
|
className="
|
||||||
|
disabled:cursor-not-allowed
|
||||||
|
disabled:bg-primary-dark/10 disabled:dark:bg-primary-light/10
|
||||||
|
disabled:dark:text-text-light/50 text-text
|
||||||
|
disabled:text-text/50 input w-full border-2 border-primary-dark
|
||||||
|
dark:border-primary-dark p-[11px] rounded-md bg-primary
|
||||||
|
dark:bg-primary-light focus:outline-none
|
||||||
|
"></input>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<label className="text-sm font-bold text-text dark:text-text-light">{t('gymName')}</label>
|
||||||
|
<input
|
||||||
|
id="gymName"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.gymName}
|
||||||
|
placeholder={t('gymName')}
|
||||||
|
|
||||||
|
disabled={!edite}
|
||||||
|
className="
|
||||||
|
disabled:cursor-not-allowed disabled:dark:text-text-light/50
|
||||||
|
text-text disabled:text-text/50
|
||||||
|
disabled:bg-primary-dark/10 disabled:dark:bg-primary-light/10
|
||||||
|
input w-full border-2 border-primary-dark dark:border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-light focus:outline-none
|
||||||
|
"></input>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<label className="text-sm font-bold text-text dark:text-text-light">{t('email')}</label>
|
||||||
|
<input
|
||||||
|
id="email"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.email}
|
||||||
|
placeholder={t('email')}
|
||||||
|
|
||||||
|
disabled={!edite}
|
||||||
|
className="
|
||||||
|
disabled:cursor-not-allowed disabled:dark:text-text-light/50
|
||||||
|
text-text disabled:text-text/50 focus:outline-none
|
||||||
|
disabled:bg-primary-dark/10 disabled:dark:bg-primary-light/10
|
||||||
|
input w-full border-2 border-primary-dark dark:border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-light
|
||||||
|
"></input>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<label className="text-sm font-bold text-text dark:text-text-light">{t('phone')}</label>
|
||||||
|
<input
|
||||||
|
id="phone"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.phone}
|
||||||
|
placeholder={t('phone')}
|
||||||
|
disabled={!edite}
|
||||||
|
className="
|
||||||
|
disabled:cursor-not-allowed disabled:dark:text-text-light/50
|
||||||
|
text-text disabled:text-text/50
|
||||||
|
disabled:bg-primary-dark/10 disabled:dark:bg-primary-light/10
|
||||||
|
input w-full border-2 border-primary-dark dark:border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-light focus:outline-none
|
||||||
|
"></input>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<label className="text-sm font-bold text-text dark:text-text-light">{t('address')}</label>
|
||||||
|
<input
|
||||||
|
id="address"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.address}
|
||||||
|
placeholder={t('address')}
|
||||||
|
|
||||||
|
disabled={!edite}
|
||||||
|
className="
|
||||||
|
disabled:cursor-not-allowed disabled:dark:text-text-light/50
|
||||||
|
text-text disabled:text-text/50
|
||||||
|
disabled:bg-primary-dark/10 disabled:dark:bg-primary-light/10
|
||||||
|
input w-full border-2 border-primary-dark dark:border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-light focus:outline-none
|
||||||
|
"></input>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<label className="text-sm font-bold text-text dark:text-text-light">{t('currencySymbol')}</label>
|
||||||
|
<input
|
||||||
|
id="currencySymbol"
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
value={values.currencySymbol}
|
||||||
|
placeholder={t('currencySymbol')}
|
||||||
|
|
||||||
|
disabled={!edite}
|
||||||
|
className="
|
||||||
|
disabled:cursor-not-allowed disabled:dark:text-text-light/50
|
||||||
|
text-text disabled:text-text/50
|
||||||
|
disabled:bg-primary-dark/10 disabled:dark:bg-primary-light/10
|
||||||
|
input w-full border-2 border-primary-dark dark:border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-light focus:outline-none
|
||||||
|
"></input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="lg:w-1/2 w-full flex flex-col gap-5 mt-[36px]">
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<label className="text-sm font-bold text-text dark:text-text-light">{t('logo')}</label>
|
||||||
|
<label htmlFor="logoCatcher" className={`
|
||||||
|
w-full h-[143px] ${ edite ? "cursor-pointer bg-primary-light dark:bg-primary-light"
|
||||||
|
: "cursor-not-allowed bg-primary-dark/10 dark:bg-primary-light/10 "}
|
||||||
|
rounded-md flex items-center
|
||||||
|
justify-center border-2 border-primary-dark
|
||||||
|
dark:border-primary-dark rounded-md
|
||||||
|
overflow-hidden flex items-center justify-center
|
||||||
|
`}>
|
||||||
|
<div className="overflow-hidden [&_svg]:max-w-[100px] [&_svg]:max-h-[100px]" dangerouslySetInnerHTML={{ __html: values.logo }} />
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
onChange={((e) => {
|
||||||
|
handleFileChange(e , setFieldValue)
|
||||||
|
})}
|
||||||
|
accept=".svg"
|
||||||
|
disabled={!edite}
|
||||||
|
className="invisible w-0 h-0 hidden"
|
||||||
|
type="file"
|
||||||
|
id="logoCatcher"
|
||||||
|
></input>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<label className="text-sm font-bold text-text dark:text-text-light">{t('showLogo')}</label>
|
||||||
|
{
|
||||||
|
!edite ?
|
||||||
|
<input
|
||||||
|
disabled={!edite}
|
||||||
|
placeholder={t(appGeneralSettings.showLogo ? 'show' : 'dontShow')}
|
||||||
|
className="
|
||||||
|
disabled:cursor-not-allowed disabled:dark:text-text-light/50
|
||||||
|
text-text disabled:text-text/50
|
||||||
|
disabled:bg-primary-dark/10 disabled:dark:bg-primary-light/10
|
||||||
|
input w-full border-2 border-primary-dark dark:border-primary-dark p-[11px]
|
||||||
|
rounded-md bg-primary dark:bg-primary-light focus:outline-none
|
||||||
|
"></input>
|
||||||
|
:
|
||||||
|
<Select
|
||||||
|
disabled
|
||||||
|
menuPlacement="auto"
|
||||||
|
options={bodyTypesOptions}
|
||||||
|
styles={customStyles}
|
||||||
|
placeholder={t("showLogo")}
|
||||||
|
onChange={((e) => {
|
||||||
|
setFieldValue("showLogo" , e?.value)
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-5 items-center justify-end">
|
||||||
|
{
|
||||||
|
edite ?
|
||||||
|
<button onClick={(() => {
|
||||||
|
handleEdite()
|
||||||
|
resetForm()
|
||||||
|
})} type="button" className="font-bold min-w-[50px] text-text dark:text-text-light">
|
||||||
|
{t('ignore')}
|
||||||
|
</button>
|
||||||
|
:
|
||||||
|
<button type="button" onClick={handleEdite} className="font-bold min-w-[50px] text-text dark:text-text-light">
|
||||||
|
{t('edite')}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
<button type="submit" disabled={!edite} className="disabled:opacity-[0.2] font-bold min-w-[50px] text-text dark:text-text-light">
|
||||||
|
{
|
||||||
|
isSubmitting ?
|
||||||
|
<CircularProgress color="secondary" size={20} />
|
||||||
|
:
|
||||||
|
<>
|
||||||
|
{t('save')}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
130
webapp/src/components/dashboard/sidebar.tsx
Normal file
130
webapp/src/components/dashboard/sidebar.tsx
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import { useRef } from 'react';
|
||||||
|
import PerfectScrollbar from 'react-perfect-scrollbar';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { toggleSidebar } from '@/redux/features/sidebar-slice';
|
||||||
|
import AnimateHeight from 'react-animate-height';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { useAppSelector } from "@/redux/store";
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
import { AppDispatch } from '@/redux/store';
|
||||||
|
import Cookies from 'universal-cookie';
|
||||||
|
|
||||||
|
export default function Sidebar () {
|
||||||
|
|
||||||
|
// init classess
|
||||||
|
const cookies = new Cookies({ path: '/' });
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
const t = useTranslations('sidebar');
|
||||||
|
const pathname = usePathname()
|
||||||
|
const prevPathName = useRef(pathname)
|
||||||
|
const local = cookies.get('NEXT_LOCALE');
|
||||||
|
// get states
|
||||||
|
const sidebarState = useAppSelector((state) => state.sidebarReducer.value.state);
|
||||||
|
const sidebarItems = useAppSelector((state) => state.sidebarReducer.value.items);
|
||||||
|
const appGeneralSettings = useAppSelector((state) => state.settingsReducer.value.appGeneralSettings)
|
||||||
|
// handle sidebar menus states
|
||||||
|
const [currentMenu, setCurrentMenu] = useState<string>('');
|
||||||
|
const toggleMenu = (value: string) => {
|
||||||
|
setCurrentMenu((oldValue) => {
|
||||||
|
return oldValue === value ? '' : value;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
if(prevPathName.current == pathname) return
|
||||||
|
if(!sidebarState) return
|
||||||
|
dispatch(toggleSidebar())
|
||||||
|
prevPathName.current == pathname
|
||||||
|
}, [pathname])
|
||||||
|
// return sidebar ui
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<nav className={`fixed top-0 ltr:left-0 rtl:right-0 lg:relative shadow-[5px_0_25px_0_rgba(94,92,154,0.1)] z-[99] h-screen bg-primary-dark dark:bg-primary-dark duration-300 ${sidebarState ? 'min-w-[260px] max-w-[260px] ' : 'ltr:translate-x-[-260px] rtl:translate-x-[260px] w-0'}`}>
|
||||||
|
<header className="w-full max-h-[64px] flex justify-between items-center px-[18px] py-[14px] [&_*]:text-text-light bg-secondary-dark">
|
||||||
|
<div className="flex justify-center items-center flex gap-[15px]">
|
||||||
|
<div className={`w-12 h-16 flex items-center justify-center relative ${appGeneralSettings.showLogo ? '' : 'hidden'}`} dangerouslySetInnerHTML={{ __html: appGeneralSettings.logo }} />
|
||||||
|
<h1 className="text-[25px] font-light">{local == 'ar' ? appGeneralSettings.appName : appGeneralSettings.appNameEN}</h1>
|
||||||
|
</div>
|
||||||
|
<button onClick={() => {dispatch(toggleSidebar())}} className="rounded-full rtl:rotate-180 hover:bg-primary/5 p-[6px]">
|
||||||
|
<svg width="23" height="23" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M18.4166 26.9166L9.91663 17L18.4166 7.08331" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path opacity="0.5" d="M24.083 26.9166L15.583 17L24.083 7.08331" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
<main className="[&_*]:text-text-light dark:[&_*]:text-text-light bg-primary-dark dark:bg-primary-dark">
|
||||||
|
<PerfectScrollbar className="relative h-[calc(100vh-80px)]">
|
||||||
|
<div className="w-full pt-[33px] px-[16px] flex flex-col gap-2 [&_button]:px-[6px] [&_button]:py-[6px]">
|
||||||
|
{
|
||||||
|
sidebarItems.map((v : {
|
||||||
|
name: string,
|
||||||
|
icon: string,
|
||||||
|
link?: string,
|
||||||
|
items?: {name: string, link: string}[],
|
||||||
|
} , i : number) => {
|
||||||
|
if(v.items)
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<div key={i} className="flex flex-col gap-2">
|
||||||
|
<button onClick={() => toggleMenu(v.name)} className={`h-12 w-12 hover:bg-primary-light/10 w-full rounded-md flex items-center justify-between text-[18px] font-bold`}>
|
||||||
|
<div className="flex items-center w-full gap-[12px]">
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: v.icon }} />
|
||||||
|
{t(v.name)}
|
||||||
|
</div>
|
||||||
|
<div className={currentMenu === v.name ? 'rotate-90' : 'rtl:rotate-180'}>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M9 5L15 12L9 19" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<AnimateHeight duration={300} height={currentMenu === v.name ? 'auto' : 0}>
|
||||||
|
<ul className="w-full h-auto [&_li]:px-[18px] [&_li]:py-[14px] [&_li]:cursor-pointer [&_li]:cursor-pointer flex flex-col gap-2">
|
||||||
|
{
|
||||||
|
v.items.map((v2 : {name: string, link: string} , i2 : number) => {
|
||||||
|
return (
|
||||||
|
<Link key={i2} href={v2.link}>
|
||||||
|
<li key={i2} className={`h-12 w-full hover:bg-primary-light/10 flex items-center gap-[12px] text-[18px] font-bold rounded-md ${pathname === v2.link || (pathname.match(/^\/(ar|en)/) && pathname.replace(/^\/(ar|en)/, '') === v2.link) ? 'bg-secondary-light' : ''}`}>
|
||||||
|
{t(v2.name)}
|
||||||
|
</li>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</AnimateHeight>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<Link key={i} href={v.link || ''}>
|
||||||
|
<button className={`h-12 w-12 hover:bg-primary-light/10 w-full rounded-md flex items-center text-[18px] font-bold ${pathname === v.link || (pathname.match(/^\/(ar|en)/) && pathname.replace(/^\/(ar|en)/, '') === v.link) ? 'bg-secondary-light' : ''}`}>
|
||||||
|
<div className="flex items-center w-full gap-[12px]">
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: v.icon }} />
|
||||||
|
{t(v.name)}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="absolute bottom-0 w-full py-[33px] px-[16px] flex flex-col gap-2 [&_button]:px-[6px] [&_button]:py-[6px] border-t-2 border-primary-light/5">
|
||||||
|
<Link href="/dashboard/settings">
|
||||||
|
<button className={`h-12 w-12 hover:bg-primary-light/10 w-full rounded-md flex items-center text-[18px] font-bold ${pathname === "/dashboard/settings" || (pathname.match(/^\/(ar|en)/) && pathname.replace(/^\/(ar|en)/, '') === "/dashboard/settings") ? 'bg-secondary-light' : ''}`}>
|
||||||
|
<div className="flex items-center w-full gap-[12px]">
|
||||||
|
<svg className="shrink-0 group-hover:!text-primary" width="28" height="28" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path opacity="0.2" d="M13 15.4C13 13.3258 13 12.2887 13.659 11.6444C14.318 11 15.3787 11 17.5 11C19.6213 11 20.682 11 21.341 11.6444C22 12.2887 22 13.3258 22 15.4V17.6C22 19.6742 22 20.7113 21.341 21.3556C20.682 22 19.6213 22 17.5 22C15.3787 22 14.318 22 13.659 21.3556C13 20.7113 13 19.6742 13 17.6V15.4Z" fill="currentColor"></path> <path d="M2 8.6C2 10.6742 2 11.7113 2.65901 12.3556C3.31802 13 4.37868 13 6.5 13C8.62132 13 9.68198 13 10.341 12.3556C11 11.7113 11 10.6742 11 8.6V6.4C11 4.32582 11 3.28873 10.341 2.64437C9.68198 2 8.62132 2 6.5 2C4.37868 2 3.31802 2 2.65901 2.64437C2 3.28873 2 4.32582 2 6.4V8.6Z" fill="currentColor"></path> <path d="M13 5.5C13 4.4128 13 3.8692 13.1713 3.44041C13.3996 2.86867 13.8376 2.41443 14.389 2.17761C14.8024 2 15.3266 2 16.375 2H18.625C19.6734 2 20.1976 2 20.611 2.17761C21.1624 2.41443 21.6004 2.86867 21.8287 3.44041C22 3.8692 22 4.4128 22 5.5C22 6.5872 22 7.1308 21.8287 7.55959C21.6004 8.13133 21.1624 8.58557 20.611 8.82239C20.1976 9 19.6734 9 18.625 9H16.375C15.3266 9 14.8024 9 14.389 8.82239C13.8376 8.58557 13.3996 8.13133 13.1713 7.55959C13 7.1308 13 6.5872 13 5.5Z" fill="currentColor"></path> <path opacity="0.5" d="M2 18.5C2 19.5872 2 20.1308 2.17127 20.5596C2.39963 21.1313 2.83765 21.5856 3.38896 21.8224C3.80245 22 4.32663 22 5.375 22H7.625C8.67337 22 9.19755 22 9.61104 21.8224C10.1624 21.5856 10.6004 21.1313 10.8287 20.5596C11 20.1308 11 19.5872 11 18.5C11 17.4128 11 16.8692 10.8287 16.4404C10.6004 15.8687 10.1624 15.4144 9.61104 15.1776C9.19755 15 8.67337 15 7.625 15H5.375C4.32663 15 3.80245 15 3.38896 15.1776C2.83765 15.4144 2.39963 15.8687 2.17127 16.4404C2 16.8692 2 17.4128 2 18.5Z" fill="currentColor"></path> </svg>
|
||||||
|
{t('settings')}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</PerfectScrollbar>
|
||||||
|
</main>
|
||||||
|
</nav>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user