Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scouts attendance #50

Merged
merged 13 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions client/src/assets/styles/components/Button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
color: var(--grey-900);
}

&--success-light {
background: var(--Green-200, #bcf0da);
color: #000;
}

&--danger {
background-color: var(--red-500);
color: var(--grey-900);
Expand Down
47 changes: 47 additions & 0 deletions client/src/assets/styles/components/Inputs.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,50 @@
direction: rtl;
}
}

input[type="checkbox"] {
--size: 1.5em;
--mark-size: 0.8em;
-webkit-appearance: none;
appearance: none;
background-color: var(--grey-800);
margin: 0;
font: inherit;
color: currentColor;
width: var(--size);
height: var(--size);
border: 0.15em solid var(--grey-700);
border-radius: 0.15em;
transform: translateY(-0.075em);
display: grid;
place-content: center;

&::before {
content: "";
width: var(--mark-size);
height: var(--mark-size);
transform: scale(0);
transition: 120ms transform ease-in-out;
box-shadow: inset 1em 1em var(--grey-100);
transform-origin: bottom left;
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
}
&:checked {
background-color: var(--primary-600);
border-color: var(--primary-600);
outline-color: var(--primary-600);
}
&:checked::before {
transform: scale(1);
}
&:focus {
outline: max(2px, 0.15em) solid currentColor;
outline-offset: max(2px, 0.15em);
}
&:disabled {
color: var(--grey-700);
background-color: var(--grey-700);
outline-color: var(--grey-600);
cursor: not-allowed;
}
}
224 changes: 224 additions & 0 deletions client/src/components/captains-attendance/CaptainAttendance.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import { useEffect, useState } from "react";
import CustomSelect from "../common/CustomSelect";
import PageTitle from "../common/PageTitle";
import "../scouts-attendance/ScoutsAttendance.scss";
import InfoBox from "../common/InfoBox";
import Button from "../common/Button";
import { useGetAllWeeksQuery } from "../../redux/slices/termApiSlice";
import { useSelector } from "react-redux";
import { toast } from "react-toastify";
import {
useGetUnitAttendanceQuery,
useUpsertUnitAttendanceMutation,
} from "../../redux/slices/attendanceApiSlice";

export default function CaptainsAttendance() {
const [attendance, setAttendance] = useState([]);
const [chosenWeek, setChosenWeek] = useState("");

let {
data: weeks,
isLoading: isLoadingWeeks,
isFetching: isFetchingWeeks,
isSuccess: isSuccessWeeks,
} = useGetAllWeeksQuery();

const { userInfo } = useSelector((state) => state.auth);

const [upsertAttendance, { isLoading: isLoadingUpsertAttendance }] =
useUpsertUnitAttendanceMutation();

if (isSuccessWeeks && !isLoadingWeeks && !isFetchingWeeks) {
weeks = weeks?.body;
weeks = weeks.map((week) => ({
...week,
display:
week?.weekNumber +
" - " +
new Date(week?.startDate).toLocaleDateString(),
}));

// console.log(weeks);
}

let {
data: scouts,
isLoading: isLoadingScouts,
isFetching: isFetchingScouts,
isSuccess: isSuccessScouts,
refetch: refetchScouts,
} = useGetUnitAttendanceQuery({
weekNumber: parseInt(chosenWeek),
termNumber: weeks?.find((week) => week.weekNumber === parseInt(chosenWeek))
?.termNumber,
baseName: userInfo?.rSectorBaseName,
suffixName: userInfo?.rSectorSuffixName,
});

if (isSuccessScouts && !isLoadingScouts && !isFetchingScouts) {
scouts = scouts?.body;
scouts = scouts.map((scout) => ({
...scout,
present: scout?.attendanceStatus === "attended",
excused: scout?.attendanceStatus === "execused",
id: scout.scoutId,
name: scout.firstName + " " + scout.middleName + " " + scout.lastName,
}));
// console.log({ scouts });
}

useEffect(() => {
if (scouts) {
setAttendance(scouts);
}
}, [isSuccessScouts]);

const handleCheckboxChange = (scoutId, checkboxType) => {
setAttendance((prevState) => {
return prevState.map((scout) => {
return scoutId === scout.id
? { ...scout, [checkboxType]: !scout[checkboxType] }
: scout;
});
});
};

const handleSubmit = async (e) => {
e.preventDefault();

const attendanceReqBody = attendance.map((scout) => ({
...scout,
attendanceStatus: scout.present
? "attended"
: scout.excused
? "execused"
: "absent",
weekNumber: parseInt(chosenWeek),
termNumber: weeks.find((week) => week.weekNumber === parseInt(chosenWeek))
?.termNumber,
sectorBaseName: userInfo?.rSectorBaseName,
sectorSuffixName: userInfo?.rSectorSuffixName,
}));

console.log({ attendanceReqBody });

try {
const res = await upsertAttendance({
attendanceRecords: attendanceReqBody,
}).unwrap();
// if (!res.ok)
// throw new Error("Something went wrong while inserting attendance");
toast.success("تم تسجيل الغياب بنجاح");
console.log(res.body);
} catch (err) {
toast.error("حدث خطأ أثناء تسجيل الغياب");
console.log(JSON.stringify(err));
toast.error(JSON.stringify(err));
}
};

// if (!userInfo?.rSectorBaseName || !userInfo?.rSectorSuffixName) {
// return (
// <div className="container">
// <h2>لا يمكنك تسجيل الغياب</h2>
// <p>يرجى تعيين القطاع الخاص بك للقيام بذلك</p>
// </div>
// );
// }

return (
<form onSubmit={handleSubmit} className="scouts-attendance-page container">
<PageTitle title="تسجيل الغياب" />

<div className="choose-week">
<CustomSelect
label="تغيير الأسبوع"
data={weeks ? weeks : []}
displayMember="display"
valueMember="weekNumber"
selectedValue={chosenWeek}
onChange={(e) => {
setChosenWeek(e.target.value);
refetchScouts();
}}
required={true}
/>
{isLoadingWeeks && <p>جاري التحميل...</p>}
</div>

<div className="record-attendance">
<table className="simple-table-for-checkboxes">
<thead>
<tr>
<th className="num-col">#</th>
<th>الاسم</th>
<th className="check-col">حاضر</th>
<th className="check-col">معتذر</th>
</tr>
</thead>
<tbody>
{attendance.map((scout) => (
<tr key={scout.id}>
<td className="num-col">{scout.id}</td>
<td>{scout.name}</td>
<td className="check-col">
<input
type="checkbox"
checked={scout?.present}
onChange={() => handleCheckboxChange(scout.id, "present")}
disabled={scout?.excused}
/>
</td>
<td className="check-col">
<input
type="checkbox"
checked={scout?.excused}
onChange={() => handleCheckboxChange(scout.id, "excused")}
disabled={scout?.present}
/>
</td>
</tr>
))}
</tbody>
</table>
{isFetchingScouts && <p>جاري التحميل</p>}
<div className="info-section attendance-info-section">
<InfoBox title="العدد الكلي" value={attendance.length} />
<InfoBox
title="الحضور"
value={attendance.filter((scout) => scout.present).length}
/>
<InfoBox
title="نسبة الحضور"
value={
attendance.length > 0
? Math.round(
(attendance.filter((scout) => scout.present).length /
attendance.length) *
100
) + "%"
: "0%"
}
/>
<InfoBox
title="الغياب"
value={attendance.filter((scout) => !scout.present).length}
/>
</div>
</div>

<Button className="Button--medium Button--success-light" type="submit">
تسليم
</Button>
{isLoadingUpsertAttendance && (
<p
style={{
direction: "rtl",
}}
>
جاري التحميل
</p>
)}
</form>
);
}
4 changes: 2 additions & 2 deletions client/src/components/common/Inputs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ TextInput.propTypes = {
label: PropTypes.string.isRequired,
type: PropTypes.string,
name: PropTypes.string,
value: PropTypes.string,
value: PropTypes.string || PropTypes.number,
onChange: PropTypes.func,
placeholder: PropTypes.string,
required: PropTypes.bool,
Expand All @@ -48,7 +48,7 @@ function RadioInput({ label, name, required, valuesArr, onChange }) {
onChange={onChange}
required={required}
/>
<span>{value}</span>
<span>{value}</span>
</div>
))}
</div>
Expand Down
4 changes: 1 addition & 3 deletions client/src/components/common/UserActions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const ActionRoutes = {
Sessions: "/sessions",
"Start New Term": "/start-new-term",
Sectors: "/sectors",
"Record Captain Absence": "/record-captain-absence",
"Record Captain Absence": "/record-captains-absence",
"Record Scouts Absence": "/record-scouts-absence",
Scores: "/scores",
Sector: "/sector",
Expand Down Expand Up @@ -173,7 +173,6 @@ export default function UserActions() {
القطاعات
</Button>
<Button
disabled
linkTo={ActionRoutes["Record Captain Absence"]}
className="Button--medium span-2-cols Button--success"
>
Expand Down Expand Up @@ -236,7 +235,6 @@ export default function UserActions() {
القطاع
</Button>
<Button
disabled
linkTo={ActionRoutes["Record Scouts Absence"]}
className="Button--medium span-2-cols Button--success"
>
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/common/nav.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function Nav() {
const dispatch = useDispatch();
const navigate = useNavigate();

const [logout, { isLoading, error }] = useLogoutMutation();
const [logout, { isLoading }] = useLogoutMutation();

useEffect(() => {
if (userInfo) {
Expand All @@ -41,13 +41,13 @@ export default function Nav() {
try {
await logout().unwrap();
toast.dark("تم تسجيل الخروج بنجاح");
dispatch(clearCredentials());
navigate("/");
} catch (err) {
toast.dark("حدث خطأ ما");
toast.error(err?.data?.message || err.error || JSON.stringify(err));
console.error(err);
}
dispatch(clearCredentials());
};

return (
Expand Down
4 changes: 0 additions & 4 deletions client/src/components/landingpage/LandingPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ import PageTitle from "../common/PageTitle";

import "./LandingPage.scss";

import TestTable from "../testing/TestTable";


import {
FolderIcon,
AcademicCapIcon,
Expand Down Expand Up @@ -85,7 +82,6 @@ export default function LandingPage() {

{/* uncomment to test statistics Table */}
{/* <TestTable /> */}

</div>
);
}
Loading