Styles of Group Profile

Download as txt, pdf, or txt
Download as txt, pdf, or txt
You are on page 1of 11

import React from "react";

import {
CModal,
CModalHeader,
CModalTitle,
CModalBody,
CModalFooter,
CButton,
} from "@coreui/react-pro";
import { useFormik } from "formik";
import * as Yup from "yup";

const GroupProfileForm = ({
modalVisible,
setModalVisible,
newActivity,
handleFormSubmit,
}) => {
const validationSchema = Yup.object({
activityDate: Yup.date().required("Activity Date is required"),
maleBeneficiaryCount: Yup.number()
.min(0, "Must be greater than or equal to 0")
.required("Male Beneficiary Count is required"),
femaleBeneficiaryCount: Yup.number()
.min(0, "Must be greater than or equal to 0")
.required("Female Beneficiary Count is required"),
othersBeneficiaryCount: Yup.number()
.min(0, "Must be greater than or equal to 0")
.required("Others Beneficiary Count is required"),
});

const formik = useFormik({


initialValues: newActivity,
validationSchema,
onSubmit: (values) => {
handleFormSubmit(values);
},
});

return (
<CModal
alignment="center"
visible={modalVisible}
onClose={() => setModalVisible(false)}
>
<CModalHeader>
<CModalTitle>Add New Activity</CModalTitle>
</CModalHeader>
<CModalBody>
<form onSubmit={formik.handleSubmit}>
<div className="mb-3">
<label htmlFor="activityDate" className="form-label">
Activity Date
</label>
<input
type="date"
className="form-control"
id="activityDate"
name="activityDate"
value={formik.values.activityDate}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
{formik.touched.activityDate && formik.errors.activityDate ? (
<div className="text-danger">{formik.errors.activityDate}</div>
) : null}
</div>
<div className="mb-3">
<label htmlFor="maleBeneficiaryCount" className="form-label">
Male Beneficiary Count
</label>
<input
type="number"
className="form-control"
id="maleBeneficiaryCount"
name="maleBeneficiaryCount"
value={formik.values.maleBeneficiaryCount}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
{formik.touched.maleBeneficiaryCount &&
formik.errors.maleBeneficiaryCount ? (
<div className="text-danger">
{formik.errors.maleBeneficiaryCount}
</div>
) : null}
</div>
<div className="mb-3">
<label htmlFor="femaleBeneficiaryCount" className="form-label">
Female Beneficiary Count
</label>
<input
type="number"
className="form-control"
id="femaleBeneficiaryCount"
name="femaleBeneficiaryCount"
value={formik.values.femaleBeneficiaryCount}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
{formik.touched.femaleBeneficiaryCount &&
formik.errors.femaleBeneficiaryCount ? (
<div className="text-danger">
{formik.errors.femaleBeneficiaryCount}
</div>
) : null}
</div>
<div className="mb-3">
<label htmlFor="othersBeneficiaryCount" className="form-label">
Others Beneficiary Count
</label>
<input
type="number"
className="form-control"
id="othersBeneficiaryCount"
name="othersBeneficiaryCount"
value={formik.values.othersBeneficiaryCount}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
{formik.touched.othersBeneficiaryCount &&
formik.errors.othersBeneficiaryCount ? (
<div className="text-danger">
{formik.errors.othersBeneficiaryCount}
</div>
) : null}
</div>
<CModalFooter>
<CButton color="success text-white" type="submit">
Save
</CButton>
<CButton color="secondary" onClick={() => setModalVisible(false)}>
Cancel
</CButton>
</CModalFooter>
</form>
</CModalBody>
</CModal>
);
};

export default GroupProfileForm;

import React, { useState, useEffect } from "react";


import { useParams, useLocation } from "react-router-dom";
import {
CCard,
CCardBody,
CCardHeader,
CRow,
CCol,
CAlert,
CSpinner,
CFormSelect,
CAccordion,
CAccordionItem,
CAccordionHeader,
CAccordionBody,
} from "@coreui/react-pro";
import CIcon from "@coreui/icons-react";
import { getData, postData, deleteData } from "src/apiHandler/apiHandler";
import GroupCheckBox from "./GroupCheckBox";
import GroupProfileForm from "./GroupProfileForm";
import { cilGroup } from "@coreui/icons";

// Custom hook to fetch data from an API


const useFetchData = (endpoint, setData, setError) => {
useEffect(() => {
const fetchData = async () => {
try {
const response = await getData(endpoint);
if (Array.isArray(response?.data)) {
setData(response.data);
} else {
throw new Error(`Invalid data from ${endpoint}.`);
}
} catch (error) {
setError(error.message);
}
};
fetchData();
}, [endpoint, setData, setError]);
};

// Component to display group details


const GroupDetails = ({ groupName, groupType, address }) => (
<CCard className="primary" style={{ width: "100%" }}>
<CCardBody className="p-4">
<div className="d-flex bg-opacity-25 rounded">
<div className="col-4 d-flex justify-content-center align-items-center">
<CIcon icon={cilGroup} size="5xl" className="my-3" />
</div>
<div className="form-label d-flex flex-column">
<h3>{groupName}</h3>
<h6>
<strong>Group Type:</strong> {groupType}
</h6>
<h6>
<strong>Address:</strong> {address}
</h6>
</div>
</div>
</CCardBody>
</CCard>
);

const GroupProfile = () => {


const { groupId } = useParams();
const location = useLocation();
const { groupName = "N/A", groupType, address } = location.state || {};

const [allActivities, setAllActivities] = useState([]);


const [activities, setActivities] = useState([]);
const [fiscalYears, setFiscalYears] = useState([]);
const [selectedFiscalYear, setSelectedFiscalYear] = useState("");
const [selectedDates, setSelectedDates] = useState({});
const [checkedActivities, setCheckedActivities] = useState({});
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [selectedPhase, setSelectedPhase] = useState("");
const [modalVisible, setModalVisible] = useState(false);
const [newActivity, setNewActivity] = useState({
activityDate: "",
maleBeneficiaryCount: 0,
femaleBeneficiaryCount: 0,
othersBeneficiaryCount: 0,
});
const [currentActivity, setCurrentActivity] = useState(null);

// Fetch activities and fiscal years using custom hook


useFetchData("activities", setAllActivities, setError);
useFetchData("fiscalYears", setFiscalYears, setError);

// Handle fiscal year selection and fetch related activities


const handleFiscalYearChange = async (e) => {
const fiscalYearId = e.target.value;
setSelectedFiscalYear(fiscalYearId);
setSelectedPhase(""); // Reset phase selection
setError(null);

if (!fiscalYearId) {
setActivities([]);
setSelectedDates({});
setCheckedActivities({});
return;
}

try {
const response = await getData(
`groupFYActivities/${groupId}/${fiscalYearId}`,
);
if (Array.isArray(response?.data)) {
setActivities(response.data);
const newSelectedDates = {};
const newCheckedActivities = {};

response.data.forEach(
({ activityId, activityDate, groupFYActivityId }) => {
newSelectedDates[activityId] = activityDate;
newCheckedActivities[activityId] = {
checked: true,
groupFYActivityId,
};
},
);

setSelectedDates(newSelectedDates);
setCheckedActivities(newCheckedActivities);
} else {
throw new Error("Invalid activity data");
}
} catch {
setError("Error fetching activities");
}
};

// Handle date change for an activity


const handleDateChange = (activityId, date) => {
setSelectedDates((prev) => ({ ...prev, [activityId]: date }));
setCheckedActivities((prev) => ({
...prev,
[activityId]: { ...prev[activityId], checked: true },
}));

postData("groupFYActivities", {
fiscalYearId: selectedFiscalYear,
activityId,
groupId,
activityDate: date,
}).catch(() => setError("Error saving activity data"));
};

// Handle checkbox state change for activities


const handleCheckboxChange = async (activityId) => {
const isChecked = checkedActivities[activityId]?.checked;
const groupFYActivityId = checkedActivities[activityId]?.groupFYActivityId;

if (
isChecked &&
groupFYActivityId &&
window.confirm("Are you sure you want to delete this activity?")
) {
try {
await deleteData(`groupFYActivity/${groupFYActivityId}`);
setCheckedActivities((prev) => ({
...prev,
[activityId]: { checked: false },
}));
setSelectedDates((prev) => ({ ...prev, [activityId]: undefined }));
setActivities((prev) =>
prev.filter((a) => a.activityId !== activityId),
);
} catch {
setError("Error deleting activity");
}
}
};

// Merge activities with their status


const mergedActivities = allActivities.map((activity) => ({
...activity,
...activities.find((a) => a.activityId === activity.activityId),
}));

// Sort activities so that checked items come first


const sortedActivities = mergedActivities.sort((a, b) => {
const aChecked = checkedActivities[a.activityId]?.checked || false;
const bChecked = checkedActivities[b.activityId]?.checked || false;
return bChecked - aChecked;
});
// Group activities by phases and sectors
const groupedActivities = sortedActivities.reduce((acc, activity) => {
const phaseName = activity.sectors?.phases?.phaseName || "Unknown Phase";
const sectorName = activity.sectors?.sectorName || "Unknown Sector";

if (!acc[phaseName]) {
acc[phaseName] = {};
}

if (!acc[phaseName][sectorName]) {
acc[phaseName][sectorName] = [];
}

acc[phaseName][sectorName].push(activity);

return acc;
}, {});

// Handle phase selection


const handlePhaseChange = (e) => {
setSelectedPhase(e.target.value);
};

// Handle form input change


const handleInputChange = (e) => {
const { name, value } = e.target;
setNewActivity((prev) => ({ ...prev, [name]: value }));
};

const formatDate = (dateString) => {


const options = { year: "numeric", month: "2-digit", day: "2-digit" };
return new Date(dateString).toLocaleDateString(undefined, options);
};

// Handle form submission


const handleFormSubmit = async () => {
try {
// Save the new activity
const response = await postData("groupFYActivities", {
...newActivity,
fiscalYearId: selectedFiscalYear,
groupId,
activityId: currentActivity.activityId,
});

const { groupFYActivityId } = response.data;

// Update the activities state


setActivities((prev) => [
...prev,
{
...newActivity,
activityId: currentActivity.activityId,
groupFYActivityId,
}, // Assuming activityId is generated by the backend
]);

// Update the checked activities and selected dates


setCheckedActivities((prev) => ({
...prev,
[currentActivity.activityId]: { checked: true, groupFYActivityId },
}));
setSelectedDates((prev) => ({
...prev,
[currentActivity.activityId]: newActivity.activityDate,
}));

// Close the modal


setModalVisible(false);

// Reset the form


setNewActivity({
activityDate: "",
maleBeneficiaryCount: 0,
femaleBeneficiaryCount: 0,
othersBeneficiaryCount: 0,
});
} catch (error) {
setError("Error saving activity");
}
};

// Render error alert if group data is missing


if (!groupName || groupName === "N/A") {
return <CAlert color="danger">Error: Missing group data</CAlert>;
}

return (
<div>
{loading ? (
<CSpinner color="primary" className="d-flex justify-content-center" />
) : error ? (
<CAlert color="danger">{error}</CAlert>
) : (
<>
<CRow>
<CCol xl={12}>
<GroupDetails
groupName={groupName}
groupType={groupType}
address={address}
/>
</CCol>
</CRow>
<CRow className="mt-3 justify-content-end">
<CCol md="3">
<CFormSelect
size="md"
aria-label="Select fiscal year"
options={[
{ label: "Select Fiscal Year", value: "" },
...fiscalYears.map(({ fiscalYearName, fiscalYearId }) => ({
label: fiscalYearName,
value: fiscalYearId,
})),
]}
onChange={handleFiscalYearChange}
value={selectedFiscalYear}
/>
</CCol>
<CCol md="3">
<CFormSelect
size="md"
aria-label="Select phase"
options={[
{ label: "Select Phase", value: "" },
...Object.keys(groupedActivities).map((phaseName) => ({
label: phaseName,
value: phaseName,
})),
]}
onChange={handlePhaseChange}
value={selectedPhase}
/>
</CCol>
</CRow>
<CCol xl={12}>
<CCard className="mt-3">
<CCardHeader>
<h2>Activities</h2>
</CCardHeader>
<CCardBody>
{!selectedFiscalYear ? (
<CAlert color="warning">Please select a fiscal year</CAlert>
) : !selectedPhase ? (
<CAlert color="warning">Please select a phase</CAlert>
) : (
<CAccordion activeItemKey={1}>
{Object.entries(groupedActivities[selectedPhase]).map(
([sectorName, activities], index) => (
<CAccordionItem itemKey={index + 1} key={sectorName}>
<CAccordionHeader>
Sector: {sectorName}
</CAccordionHeader>
<CAccordionBody>
<CRow>
{activities.map((activity) => (
<GroupCheckBox
key={activity.activityId}
activity={activity}
isChecked={
checkedActivities[activity.activityId]
?.checked || false
}
onChange={() =>
handleCheckboxChange(activity.activityId)
}
selectedDate={
selectedDates[activity.activityId]
}
showDetails={true}
setModalVisible={setModalVisible}
setCurrentActivity={setCurrentActivity}
formatDate={formatDate} // Pass the formatDate
function
/>
))}
</CRow>
</CAccordionBody>
</CAccordionItem>
),
)}
</CAccordion>
)}
</CCardBody>
</CCard>
</CCol>
<GroupProfileForm
modalVisible={modalVisible}
setModalVisible={setModalVisible}
newActivity={newActivity}
handleInputChange={handleInputChange}
handleFormSubmit={handleFormSubmit}
/>
</>
)}
</div>
);
};

export default GroupProfile;

import React from "react";


import {
CButton,
CCol,
CCard,
CCardBody,
CCardHeader,
CFormCheck,
} from "@coreui/react-pro";
import CIcon from "@coreui/icons-react";
import { cilPlus } from "@coreui/icons";

const GroupCheckBox = ({
activity,
isChecked,
onChange,
selectedDate,
showDetails,
setModalVisible,
setCurrentActivity,
formatDate,
}) => (
<CCol xs="12" md="4">
<CCard className="mb-3">
<CCardHeader className="d-flex justify-content-between align-items-center">
<CFormCheck
id={`activity-${activity.activityId}`}
checked={isChecked}
onChange={onChange}
label={activity.activityName}
/>
{!isChecked && (
<CButton
color="primary"
// color="success text-white"
size="sm"
className="d-flex align-items-center text-white"
onClick={() => {
setCurrentActivity(activity);
setModalVisible(true);
}}
>
<CIcon icon={cilPlus} className="me-2" />
Add
</CButton>
)}
</CCardHeader>
{isChecked && (
<CCardBody>
{showDetails && (
<div>
<p>
<strong>Activity Date:</strong> {formatDate(selectedDate)}
</p>
<p>
<strong>Male Beneficiary Count:</strong>{" "}
{activity.maleBeneficiaryCount}
</p>
<p>
<strong>Female Beneficiary Count:</strong>{" "}
{activity.femaleBeneficiaryCount}
</p>
<p>
<strong>Others Beneficiary Count:</strong>{" "}
{activity.othersBeneficiaryCount}
</p>
</div>
)}
</CCardBody>
)}
</CCard>
</CCol>
);

export default GroupCheckBox;

You might also like