Compare commits
5 Commits
a835e75f66
...
19f8189fc3
| Author | SHA1 | Date | |
|---|---|---|---|
| 19f8189fc3 | |||
| 970c195ca5 | |||
| d944d3a389 | |||
| e0c7eee1fd | |||
| 6baa2896c2 |
@ -4,6 +4,7 @@ import { ToastContainer } from "react-toastify";
|
|||||||
import { QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||||
import { queryClient } from "./layouts/AuthLayout";
|
import { queryClient } from "./layouts/AuthLayout";
|
||||||
|
import ModalProvider from "./ModalProvider";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -11,6 +12,7 @@ const App = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="app">
|
<div className="app">
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<ModalProvider/>
|
||||||
<DireProvider>
|
<DireProvider>
|
||||||
<AppRoutes />
|
<AppRoutes />
|
||||||
</DireProvider>
|
</DireProvider>
|
||||||
|
|||||||
@ -1,112 +0,0 @@
|
|||||||
import React, {
|
|
||||||
createContext,
|
|
||||||
useContext,
|
|
||||||
useState,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
} from "react";
|
|
||||||
|
|
||||||
const ModalContext = createContext();
|
|
||||||
|
|
||||||
export const useModal = () => useContext(ModalContext);
|
|
||||||
|
|
||||||
// ModalProvider to manage modal state and expose functionality to the rest of the app
|
|
||||||
export const ModalProvider = ({ children }) => {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const [modalContent, setModalContent] = useState(null);
|
|
||||||
const [onSubmit, setOnSubmit] = useState(null);
|
|
||||||
const [modalSize, setModalSize] = useState("lg");
|
|
||||||
|
|
||||||
// Ref to track the modal content element
|
|
||||||
const modalRef = useRef(null);
|
|
||||||
|
|
||||||
const openModal = (content, onSubmitCallback, size = "lg") => {
|
|
||||||
setModalContent(content); // Set modal content dynamically
|
|
||||||
setOnSubmit(() => onSubmitCallback); // Set the submit handler dynamically
|
|
||||||
setIsOpen(true); // Open the modal
|
|
||||||
setModalSize(size); // Set the modal size
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to close the modal
|
|
||||||
const closeModal = () => {
|
|
||||||
setIsOpen(false); // Close the modal
|
|
||||||
setModalContent(null); // Clear modal content
|
|
||||||
setOnSubmit(null); // Clear the submit callback
|
|
||||||
setModalSize("lg"); // Reset modal size
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleEscape = (event) => {
|
|
||||||
if (event.key === "Escape") {
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.addEventListener("keydown", handleEscape);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("keydown", handleEscape);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleClickOutside = (event) => {
|
|
||||||
if (modalRef.current && !modalRef.current.contains(event.target)) {
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.addEventListener("mousedown", handleClickOutside);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("mousedown", handleClickOutside);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModalContext.Provider
|
|
||||||
value={{
|
|
||||||
isOpen,
|
|
||||||
openModal,
|
|
||||||
closeModal,
|
|
||||||
modalContent,
|
|
||||||
modalSize,
|
|
||||||
onSubmit,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
|
|
||||||
{isOpen && (
|
|
||||||
<div style={overlayStyles}>
|
|
||||||
<div
|
|
||||||
ref={modalRef}
|
|
||||||
style={{
|
|
||||||
...modalStyles,
|
|
||||||
maxWidth: modalSize === "sm" ? "400px" : "800px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div>{modalContent}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</ModalContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const overlayStyles = {
|
|
||||||
position: "fixed",
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
zIndex: 1050,
|
|
||||||
};
|
|
||||||
|
|
||||||
const modalStyles = {
|
|
||||||
backgroundColor: "white",
|
|
||||||
padding: "20px",
|
|
||||||
borderRadius: "5px",
|
|
||||||
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
|
|
||||||
width: "90%",
|
|
||||||
maxWidth: "800px",
|
|
||||||
};
|
|
||||||
14
src/ModalProvider.jsx
Normal file
14
src/ModalProvider.jsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import Modal from './components/common/Modal'
|
||||||
|
import ManageOrganization from './components/Organization/ManageOrganization'
|
||||||
|
|
||||||
|
const ModalProvider = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
|
||||||
|
<ManageOrganization/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModalProvider
|
||||||
@ -4,7 +4,6 @@ import Avatar from "../common/Avatar";
|
|||||||
import { convertShortTime } from "../../utils/dateUtils";
|
import { convertShortTime } from "../../utils/dateUtils";
|
||||||
import RenderAttendanceStatus from "./RenderAttendanceStatus";
|
import RenderAttendanceStatus from "./RenderAttendanceStatus";
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { fetchAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice";
|
|
||||||
import DateRangePicker from "../common/DateRangePicker";
|
import DateRangePicker from "../common/DateRangePicker";
|
||||||
import { clearCacheKey, getCachedData, useSelectedProject } from "../../slices/apiDataManager";
|
import { clearCacheKey, getCachedData, useSelectedProject } from "../../slices/apiDataManager";
|
||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
import TimePicker from "../common/TimePicker";
|
import TimePicker from "../common/TimePicker";
|
||||||
import { usePositionTracker } from "../../hooks/usePositionTracker";
|
import { usePositionTracker } from "../../hooks/usePositionTracker";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { markAttendance } from "../../slices/apiSlice/attedanceLogsSlice";
|
|
||||||
import showToast from "../../services/toastService";
|
import showToast from "../../services/toastService";
|
||||||
import { checkIfCurrentDate } from "../../utils/dateUtils";
|
import { checkIfCurrentDate } from "../../utils/dateUtils";
|
||||||
import { useMarkAttendance } from "../../hooks/useAttendance";
|
import { useMarkAttendance } from "../../hooks/useAttendance";
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
import React, { act, useEffect, useState } from 'react'
|
import React, { act, useEffect, useState } from 'react'
|
||||||
import useAttendanceStatus, { ACTIONS } from '../../hooks/useAttendanceStatus';
|
import useAttendanceStatus, { ACTIONS } from '../../hooks/useAttendanceStatus';
|
||||||
// import AttendanceRepository from '../../repositories/AttendanceRepository';
|
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { usePositionTracker } from '../../hooks/usePositionTracker';
|
import { usePositionTracker } from '../../hooks/usePositionTracker';
|
||||||
import {markCurrentAttendance} from '../../slices/apiSlice/attendanceAllSlice';
|
|
||||||
import {cacheData, getCachedData, useSelectedProject} from '../../slices/apiDataManager';
|
import {cacheData, getCachedData, useSelectedProject} from '../../slices/apiDataManager';
|
||||||
import showToast from '../../services/toastService';
|
import showToast from '../../services/toastService';
|
||||||
import { useMarkAttendance } from '../../hooks/useAttendance';
|
import { useMarkAttendance } from '../../hooks/useAttendance';
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import React, { useState, useEffect } from "react";
|
|||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import DateRangePicker, { DateRangePicker1 } from "../common/DateRangePicker";
|
import DateRangePicker, { DateRangePicker1 } from "../common/DateRangePicker";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { fetchEmployeeAttendanceData } from "../../slices/apiSlice/employeeAttendanceSlice";
|
|
||||||
import usePagination from "../../hooks/usePagination";
|
import usePagination from "../../hooks/usePagination";
|
||||||
import Avatar from "../common/Avatar";
|
import Avatar from "../common/Avatar";
|
||||||
import { convertShortTime } from "../../utils/dateUtils";
|
import { convertShortTime } from "../../utils/dateUtils";
|
||||||
|
|||||||
85
src/components/Organization/ManageOrganization.jsx
Normal file
85
src/components/Organization/ManageOrganization.jsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import React from "react";
|
||||||
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
|
import {
|
||||||
|
defaultOrganizationValues,
|
||||||
|
organizationSchema,
|
||||||
|
} from "./OrganizationSchema";
|
||||||
|
import Modal from "../common/Modal";
|
||||||
|
import { useOrganization } from "../../hooks/useDirectory";
|
||||||
|
import { useOrganizationModal } from "../../hooks/useOrganization";
|
||||||
|
import Label from "../common/Label";
|
||||||
|
import SelectMultiple from "../common/SelectMultiple";
|
||||||
|
|
||||||
|
const ManageOrganization = () => {
|
||||||
|
const orgModal = useOrganizationModal();
|
||||||
|
const method = useForm({
|
||||||
|
resolver: zodResolver(organizationSchema),
|
||||||
|
defaultValues: defaultOrganizationValues,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { handleSubmit, watch, register } = method;
|
||||||
|
|
||||||
|
const onSubmit = () => {};
|
||||||
|
|
||||||
|
const contentBody = (
|
||||||
|
<FormProvider {...method}>
|
||||||
|
<form className="form" onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<div className="mb-1 text-start">
|
||||||
|
<Label htmlFor="organization" required>
|
||||||
|
Organization Name
|
||||||
|
</Label>
|
||||||
|
<input className="form-control form-control-sm" />
|
||||||
|
</div>
|
||||||
|
<div className="mb-1 text-start">
|
||||||
|
<Label htmlFor="organization" required>
|
||||||
|
Contact Person
|
||||||
|
</Label>
|
||||||
|
<input className="form-control form-control-sm" />
|
||||||
|
</div>
|
||||||
|
<div className="mb-1 text-start">
|
||||||
|
<Label htmlFor="organization" required>
|
||||||
|
Contact Number
|
||||||
|
</Label>
|
||||||
|
<input className="form-control form-control-sm" />
|
||||||
|
</div>
|
||||||
|
<div className="mb-1 text-start">
|
||||||
|
<Label htmlFor="organization" required>
|
||||||
|
Email Address
|
||||||
|
</Label>
|
||||||
|
<input className="form-control form-control-sm" />
|
||||||
|
</div>
|
||||||
|
<div className="mb-1 text-start">
|
||||||
|
<SelectMultiple
|
||||||
|
name="serviceIds"
|
||||||
|
label="Services"
|
||||||
|
required={true}
|
||||||
|
valueKey="id"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-1 text-start">
|
||||||
|
<Label htmlFor="organization" required>
|
||||||
|
Address
|
||||||
|
</Label>
|
||||||
|
<textarea className="form-control form-control-sm" rows={2} />
|
||||||
|
</div>
|
||||||
|
<div className="d-flex justify-content-end gap-2 my-2">
|
||||||
|
<button className="btn btn-sm btn-secondary">Cancel</button>
|
||||||
|
<button className="btn btn-sm btn-primary">Submit</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={orgModal.isOpen}
|
||||||
|
onClose={orgModal.onClose}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
title={"Manage Organization"}
|
||||||
|
actionLabel={"Submit"}
|
||||||
|
body={contentBody}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ManageOrganization;
|
||||||
32
src/components/Organization/OrganizationSchema.js
Normal file
32
src/components/Organization/OrganizationSchema.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { array, z } from "zod";
|
||||||
|
|
||||||
|
const phoneRegex = /^\+?[1-9]\d{6,14}$/;
|
||||||
|
|
||||||
|
export const organizationSchema = z.object({
|
||||||
|
organizationName: z.string().min(1, { message: "Name is required" }),
|
||||||
|
contactPhone: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(7)
|
||||||
|
.max(20)
|
||||||
|
.regex(phoneRegex, { message: "Invalid phone number" }),
|
||||||
|
contactPerson: z.string().min(1, { message: "Person name required" }),
|
||||||
|
address: z.string().min(1, { message: "Address is required!" }),
|
||||||
|
email: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: "Email is required" })
|
||||||
|
.email("Invalid email address"),
|
||||||
|
listOfServiceId: z
|
||||||
|
.array(z.string())
|
||||||
|
.min(1, { message: "Please insert service id" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export const defaultOrganizationValues = {
|
||||||
|
organizationSchema:"",
|
||||||
|
contactPhone:"",
|
||||||
|
contactPerson:"",
|
||||||
|
address:"",
|
||||||
|
email:"",
|
||||||
|
listOfServiceId:[]
|
||||||
|
}
|
||||||
55
src/components/common/Modal.jsx
Normal file
55
src/components/common/Modal.jsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
|
||||||
|
import { useCallback } from "react";
|
||||||
|
|
||||||
|
const Modal = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
disabled,
|
||||||
|
size="md",
|
||||||
|
position="top",
|
||||||
|
}) => {
|
||||||
|
const handleClose = useCallback(() => {
|
||||||
|
if (disabled) return;
|
||||||
|
onClose();
|
||||||
|
}, [disabled, onClose]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="modal fade show"
|
||||||
|
style={{ display: "block", backgroundColor: "rgba(0,0,0,0.6)" }}
|
||||||
|
tabIndex="-1"
|
||||||
|
role="dialog"
|
||||||
|
>
|
||||||
|
<div className={`modal-dialog modal-${size} modal-dialog-${position}`} role="document">
|
||||||
|
<div className="modal-content text-white shadow-lg">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="modal-header pb-2 border-0">
|
||||||
|
<h5 className="modal-title">{title}</h5>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn-close btn-close-white"
|
||||||
|
onClick={handleClose}
|
||||||
|
aria-label="Close"
|
||||||
|
>
|
||||||
|
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Body */}
|
||||||
|
<div className="modal-body pt-0">{body}</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Modal;
|
||||||
@ -2,15 +2,17 @@ import React, { useState, useEffect, useRef } from "react";
|
|||||||
import { useFormContext } from "react-hook-form";
|
import { useFormContext } from "react-hook-form";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import "./MultiSelectDropdown.css";
|
import "./MultiSelectDropdown.css";
|
||||||
|
import Label from "./Label";
|
||||||
|
|
||||||
const SelectMultiple = ({
|
const SelectMultiple = ({
|
||||||
name,
|
name,
|
||||||
options = [],
|
options = [],
|
||||||
label = "Select options",
|
label = "Select options",
|
||||||
labelKey = "name", // Can now be a function or a string
|
labelKey = "name",
|
||||||
valueKey = "id",
|
valueKey = "id",
|
||||||
placeholder = "Please select...",
|
placeholder = "Please select...",
|
||||||
IsLoading = false,
|
IsLoading = false,
|
||||||
|
required = false,
|
||||||
}) => {
|
}) => {
|
||||||
const { setValue, watch } = useFormContext();
|
const { setValue, watch } = useFormContext();
|
||||||
const selectedValues = watch(name) || [];
|
const selectedValues = watch(name) || [];
|
||||||
@ -20,7 +22,11 @@ const SelectMultiple = ({
|
|||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
const dropdownRef = useRef(null);
|
const dropdownRef = useRef(null);
|
||||||
|
|
||||||
const [dropdownStyles, setDropdownStyles] = useState({ top: 0, left: 0, width: 0 });
|
const [dropdownStyles, setDropdownStyles] = useState({
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: 0,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (e) => {
|
const handleClickOutside = (e) => {
|
||||||
@ -100,8 +106,14 @@ const SelectMultiple = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={valueVal}
|
key={valueVal}
|
||||||
className={`multi-select-dropdown-option ${isChecked ? "selected" : ""}`}
|
className={`multi-select-dropdown-option ${
|
||||||
style={{ display: "flex", alignItems: "center", padding: "4px 8px" }}
|
isChecked ? "selected" : ""
|
||||||
|
}`}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: "4px 8px",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@ -130,8 +142,14 @@ const SelectMultiple = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div ref={containerRef} className="multi-select-dropdown-container" style={{ position: "relative" }}>
|
<div
|
||||||
<label className="form-label mb-1">{label}</label>
|
ref={containerRef}
|
||||||
|
className="multi-select-dropdown-container"
|
||||||
|
style={{ position: "relative" }}
|
||||||
|
>
|
||||||
|
<Label htmlFor={name} required={required}>
|
||||||
|
{label}
|
||||||
|
</Label>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="multi-select-dropdown-header"
|
className="multi-select-dropdown-header"
|
||||||
@ -140,7 +158,9 @@ const SelectMultiple = ({
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={
|
className={
|
||||||
selectedValues.length > 0 ? "placeholder-style-selected" : "placeholder-style"
|
selectedValues.length > 0
|
||||||
|
? "placeholder-style-selected"
|
||||||
|
: "placeholder-style"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="selected-badges-container">
|
<div className="selected-badges-container">
|
||||||
@ -149,7 +169,10 @@ const SelectMultiple = ({
|
|||||||
const found = options.find((opt) => opt[valueKey] === val);
|
const found = options.find((opt) => opt[valueKey] === val);
|
||||||
const label = found ? getLabel(found) : "";
|
const label = found ? getLabel(found) : "";
|
||||||
return (
|
return (
|
||||||
<span key={val} className="badge badge-selected-item mx-1 mb-1">
|
<span
|
||||||
|
key={val}
|
||||||
|
className="badge badge-selected-item mx-1 mb-1"
|
||||||
|
>
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
16
src/hooks/useOrganization.js
Normal file
16
src/hooks/useOrganization.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
|
import { toggleOrgModal,openOrgModal,closeOrgModal } from "../slices/localVariablesSlice";
|
||||||
|
|
||||||
|
|
||||||
|
export const useOrganizationModal = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const isOpen = useSelector((state) => state.localVariables.OrganizationModal.isOpen);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isOpen,
|
||||||
|
onOpen: () => dispatch(openOrgModal()),
|
||||||
|
onClose: () => dispatch(closeOrgModal()),
|
||||||
|
Togggle:()=> dispatch(toggleOrgModal(isOpen))
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
@ -2,11 +2,9 @@
|
|||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
// import { MasterDataProvider } from "./provider/MasterDataContext";
|
|
||||||
|
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { store } from './store/store';
|
import { store } from './store/store';
|
||||||
import { ModalProvider } from './ModalContext.jsx';
|
|
||||||
import { ChangePasswordProvider } from './components/Context/ChangePasswordContext.jsx';
|
import { ChangePasswordProvider } from './components/Context/ChangePasswordContext.jsx';
|
||||||
import { ModalProvider1 } from './pages/Gallary/ModalContext.jsx';
|
import { ModalProvider1 } from './pages/Gallary/ModalContext.jsx';
|
||||||
|
|
||||||
@ -15,15 +13,12 @@ createRoot(document.getElementById('root')!).render(
|
|||||||
// <StrictMode>
|
// <StrictMode>
|
||||||
// <MasterDataProvider>
|
// <MasterDataProvider>
|
||||||
<Provider store={ store }>
|
<Provider store={ store }>
|
||||||
<ModalProvider>
|
|
||||||
<ChangePasswordProvider >
|
<ChangePasswordProvider >
|
||||||
<ModalProvider1>
|
<ModalProvider1>
|
||||||
<App />
|
<App />
|
||||||
</ModalProvider1>
|
</ModalProvider1>
|
||||||
</ChangePasswordProvider>
|
</ChangePasswordProvider>
|
||||||
</ModalProvider>
|
|
||||||
</Provider>
|
</Provider>
|
||||||
// </MasterDataProvider>
|
|
||||||
|
|
||||||
// </StrictMode>,
|
// </StrictMode>,
|
||||||
)
|
)
|
||||||
|
|||||||
43
src/pages/Organization/OrganizationPage.jsx
Normal file
43
src/pages/Organization/OrganizationPage.jsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||||
|
import { useOrganizationModal } from "../../hooks/useOrganization";
|
||||||
|
|
||||||
|
const OrganizationPage = () => {
|
||||||
|
const orgModal = useOrganizationModal()
|
||||||
|
return (
|
||||||
|
<div className="container-fluid">
|
||||||
|
<Breadcrumb
|
||||||
|
data={[{ label: "Home", link: "/" }, { label: "Organizations" }]}
|
||||||
|
/>
|
||||||
|
<div className="card my-3 px-sm-2 px-0">
|
||||||
|
<div className="card-body py-2 px-3">
|
||||||
|
<div className="row align-items-center">
|
||||||
|
<div className="col-6 ">
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
className="form-control form-control-sm w-auto"
|
||||||
|
placeholder="Search Organization"
|
||||||
|
aria-describedby="search-label"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-6 text-end mt-2 mt-sm-0">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="p-1 me-1 m-sm-0 bg-primary rounded-circle"
|
||||||
|
title="Add New Organization"
|
||||||
|
onClick={()=>orgModal.onOpen()}
|
||||||
|
>
|
||||||
|
<i className="bx bx-plus fs-4 text-white"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrganizationPage;
|
||||||
@ -49,6 +49,8 @@ import MainResetPasswordPage from "../pages/authentication/MainResetPasswordPage
|
|||||||
import TenantPage from "../pages/Tenant/TenantPage";
|
import TenantPage from "../pages/Tenant/TenantPage";
|
||||||
import { Navigate } from "react-router-dom";
|
import { Navigate } from "react-router-dom";
|
||||||
import CreateTenant from "../pages/Tenant/CreateTenant";
|
import CreateTenant from "../pages/Tenant/CreateTenant";
|
||||||
|
;
|
||||||
|
import OrganizationPage from "../pages/Organization/OrganizationPage";
|
||||||
import LandingPage from "../pages/Home/LandingPage";
|
import LandingPage from "../pages/Home/LandingPage";
|
||||||
const router = createBrowserRouter(
|
const router = createBrowserRouter(
|
||||||
[
|
[
|
||||||
@ -96,6 +98,7 @@ const router = createBrowserRouter(
|
|||||||
{ path: "/tenants/new-tenant", element: <CreateTenant /> },
|
{ path: "/tenants/new-tenant", element: <CreateTenant /> },
|
||||||
{ path: "/tenant/:tenantId", element: <SuperTenantDetails /> },
|
{ path: "/tenant/:tenantId", element: <SuperTenantDetails /> },
|
||||||
{ path: "/tenant/self", element: <SelfTenantDetails /> },
|
{ path: "/tenant/self", element: <SelfTenantDetails /> },
|
||||||
|
{ path: "/organizations", element: <OrganizationPage /> },
|
||||||
{ path: "/help/support", element: <Support /> },
|
{ path: "/help/support", element: <Support /> },
|
||||||
{ path: "/help/docs", element: <Documentation /> },
|
{ path: "/help/docs", element: <Documentation /> },
|
||||||
{ path: "/help/connect", element: <Connect /> },
|
{ path: "/help/connect", element: <Connect /> },
|
||||||
|
|||||||
@ -1,93 +0,0 @@
|
|||||||
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
|
||||||
import AttendanceRepository from '../../repositories/AttendanceRepository';
|
|
||||||
import {clearCacheKey} from '../apiDataManager';
|
|
||||||
|
|
||||||
// Fetch attendance data
|
|
||||||
export const fetchAttendanceData = createAsyncThunk(
|
|
||||||
'attendanceLogs/fetchAttendanceData',
|
|
||||||
async ( {projectId, fromDate, toDate}, thunkAPI ) =>
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
const response = await AttendanceRepository.getAttendanceFilteredByDate(projectId, fromDate, toDate);
|
|
||||||
return response?.data?.filter((log) => log.checkInTime !== null && log.activity !== 0);
|
|
||||||
} catch (error) {
|
|
||||||
return thunkAPI.rejectWithValue(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
export const markAttendance = createAsyncThunk(
|
|
||||||
'attendanceLogs/markAttendance', // Updated action type prefix
|
|
||||||
async ( formData, thunkAPI ) =>
|
|
||||||
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
let newRecordAttendance = {
|
|
||||||
Id: formData.id || null,
|
|
||||||
comment: formData.description,
|
|
||||||
employeeID: formData.employeeId,
|
|
||||||
projectID: formData.projectId,
|
|
||||||
date: new Date().toISOString(),
|
|
||||||
markTime: formData.markTime,
|
|
||||||
latitude: formData.latitude.toString(),
|
|
||||||
longitude: formData.longitude.toString(),
|
|
||||||
action: formData.action,
|
|
||||||
image: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await AttendanceRepository.markAttendance( newRecordAttendance );
|
|
||||||
return response.data;
|
|
||||||
} catch ( error )
|
|
||||||
{
|
|
||||||
const message = error?.response?.data?.message || error.message || "Error Occured During Api Call";
|
|
||||||
return thunkAPI.rejectWithValue(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Attendance Logs Slice
|
|
||||||
const attendanceLogsSlice = createSlice({
|
|
||||||
name: 'attendanceLogs', // Updated slice name
|
|
||||||
initialState: {
|
|
||||||
data: [],
|
|
||||||
loading: false,
|
|
||||||
error: null,
|
|
||||||
},
|
|
||||||
reducers: {
|
|
||||||
setAttendanceData: (state, action) => {
|
|
||||||
state.data = action.payload;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
extraReducers: (builder) => {
|
|
||||||
builder
|
|
||||||
// Fetch attendance data
|
|
||||||
.addCase(fetchAttendanceData.pending, (state) => {
|
|
||||||
state.loading = true;
|
|
||||||
})
|
|
||||||
.addCase(fetchAttendanceData.fulfilled, (state, action) => {
|
|
||||||
state.loading = false;
|
|
||||||
state.data = action.payload;
|
|
||||||
})
|
|
||||||
.addCase(fetchAttendanceData.rejected, (state, action) => {
|
|
||||||
state.loading = false;
|
|
||||||
state.error = action.payload;
|
|
||||||
})
|
|
||||||
|
|
||||||
// Mark attendance - log attenace data
|
|
||||||
.addCase(markAttendance.fulfilled, (state, action) => {
|
|
||||||
const updatedRecord = action.payload;
|
|
||||||
const index = state.data.findIndex(item => item.id === updatedRecord.id);
|
|
||||||
|
|
||||||
if (index !== -1) {
|
|
||||||
state.data[index] = { ...state.data[index], ...updatedRecord };
|
|
||||||
} else {
|
|
||||||
state.data.push(updatedRecord);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const { setAttendanceData } = attendanceLogsSlice.actions;
|
|
||||||
export default attendanceLogsSlice.reducer;
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
|
||||||
import AttendanceRepository from '../../repositories/AttendanceRepository';
|
|
||||||
import {clearCacheKey} from '../apiDataManager';
|
|
||||||
|
|
||||||
export const markCurrentAttendance = createAsyncThunk(
|
|
||||||
'attendanceCurrentDate/markAttendance',
|
|
||||||
async ( formData, {getState, dispatch, rejectWithValue} ) =>
|
|
||||||
{
|
|
||||||
|
|
||||||
const { projectId } = getState().localVariables
|
|
||||||
try
|
|
||||||
{
|
|
||||||
|
|
||||||
// Create the new attendance record
|
|
||||||
const newRecordAttendance = {
|
|
||||||
Id: formData.id || null,
|
|
||||||
comment: formData.description,
|
|
||||||
employeeID: formData.employeeId,
|
|
||||||
projectId: projectId,
|
|
||||||
date: new Date().toISOString(),
|
|
||||||
markTime: formData.markTime,
|
|
||||||
latitude: formData.latitude.toString(),
|
|
||||||
longitude: formData.longitude.toString(),
|
|
||||||
action: formData.action,
|
|
||||||
image: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await AttendanceRepository.markAttendance(newRecordAttendance);
|
|
||||||
const markedAttendance = response.data
|
|
||||||
clearCacheKey("AttendanceLogs")
|
|
||||||
return markedAttendance;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error marking attendance:', error);
|
|
||||||
return rejectWithValue(error.message); // Reject with error message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
|
||||||
import AttendanceRepository from '../../repositories/AttendanceRepository';
|
|
||||||
import { markAttendance } from './attedanceLogsSlice';
|
|
||||||
|
|
||||||
export const fetchEmployeeAttendanceData = createAsyncThunk(
|
|
||||||
'employeeAttendance/fetchEmployeeAttendanceData',
|
|
||||||
async ( {employeeId, fromDate, toDate}, thunkAPI ) =>
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
const response = await AttendanceRepository.getAttendanceByEmployee( employeeId, fromDate, toDate );
|
|
||||||
// return response?.data?.filter((log) => log.checkInTime !== null && log.activity !== 0);
|
|
||||||
return response.data
|
|
||||||
} catch (error) {
|
|
||||||
return thunkAPI.rejectWithValue(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
const employeeAttendancesSlice = createSlice({
|
|
||||||
name: 'employeeAttendance', // Updated slice name
|
|
||||||
initialState: {
|
|
||||||
data: [],
|
|
||||||
loading: false,
|
|
||||||
error: null,
|
|
||||||
},
|
|
||||||
reducers: {
|
|
||||||
setEmployeeAttendanceData: (state, action) => {
|
|
||||||
state.data = action.payload;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
extraReducers: (builder) => {
|
|
||||||
builder
|
|
||||||
// Fetch attendance data
|
|
||||||
.addCase(fetchEmployeeAttendanceData.pending, (state) => {
|
|
||||||
state.loading = true;
|
|
||||||
})
|
|
||||||
.addCase(fetchEmployeeAttendanceData.fulfilled, (state, action) => {
|
|
||||||
state.loading = false;
|
|
||||||
state.data = action.payload;
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
.addCase(fetchEmployeeAttendanceData.rejected, (state, action) => {
|
|
||||||
state.loading = false;
|
|
||||||
state.error = action.payload;
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const { setEmployeeAttendanceData } = employeeAttendancesSlice.actions;
|
|
||||||
export default employeeAttendancesSlice.reducer;
|
|
||||||
@ -10,7 +10,11 @@ const localVariablesSlice = createSlice({
|
|||||||
endDate: null,
|
endDate: null,
|
||||||
},
|
},
|
||||||
projectId: null,
|
projectId: null,
|
||||||
reload:false
|
reload:false,
|
||||||
|
|
||||||
|
OrganizationModal:{
|
||||||
|
isOpen:false
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
@ -32,8 +36,18 @@ const localVariablesSlice = createSlice({
|
|||||||
setDefaultDateRange: (state, action) => {
|
setDefaultDateRange: (state, action) => {
|
||||||
state.defaultDateRange = action.payload;
|
state.defaultDateRange = action.payload;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
openOrgModal: (state) => {
|
||||||
|
state.OrganizationModal.isOpen = true;
|
||||||
|
},
|
||||||
|
closeOrgModal: (state) => {
|
||||||
|
state.OrganizationModal.isOpen = false;
|
||||||
|
},
|
||||||
|
toggleOrgModal: (state) => {
|
||||||
|
state.OrganizationModal.isOpen = !state.OrganizationModal.isOpen;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { changeMaster ,updateRegularizationCount,setProjectId,refreshData,setDefaultDateRange} = localVariablesSlice.actions;
|
export const { changeMaster ,updateRegularizationCount,setProjectId,refreshData,setDefaultDateRange,openOrgModal,closeOrgModal,toggleOrgModal} = localVariablesSlice.actions;
|
||||||
export default localVariablesSlice.reducer;
|
export default localVariablesSlice.reducer;
|
||||||
|
|||||||
@ -2,15 +2,12 @@ import { configureStore } from "@reduxjs/toolkit";
|
|||||||
import apiCacheReducer from "../slices/apiCacheSlice";
|
import apiCacheReducer from "../slices/apiCacheSlice";
|
||||||
import globalVariablesReducer from "../slices/globalVariablesSlice";
|
import globalVariablesReducer from "../slices/globalVariablesSlice";
|
||||||
import localVariableRducer from "../slices/localVariablesSlice"
|
import localVariableRducer from "../slices/localVariablesSlice"
|
||||||
import attendanceReducer from "../slices/apiSlice/attedanceLogsSlice"
|
|
||||||
import employeeAttendanceReducer from "../slices/apiSlice/employeeAttendanceSlice"
|
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
apiCache: apiCacheReducer,
|
apiCache: apiCacheReducer,
|
||||||
globalVariables: globalVariablesReducer,
|
globalVariables: globalVariablesReducer,
|
||||||
localVariables:localVariableRducer,
|
localVariables:localVariableRducer,
|
||||||
attendanceLogs: attendanceReducer,
|
|
||||||
employeeAttendance: employeeAttendanceReducer,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user