Skip to content

Commit a33bd49

Browse files
committed
picture presence update
1 parent 0367dcc commit a33bd49

File tree

9 files changed

+198
-88
lines changed

9 files changed

+198
-88
lines changed

package-lock.json

+26-16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"react-paginate": "^8.2.0",
2222
"react-query": "^3.39.3",
2323
"react-router-dom": "^6.16.0",
24+
"react-webcam": "^7.2.0",
2425
"swiper": "^11.0.3"
2526
},
2627
"devDependencies": {

src/components/ExcuseModal.jsx

+7-50
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,24 @@
11
import React, { createRef, useState } from "react";
22
import { Icon } from "@iconify/react";
3-
import axiosClient from "../axios-client";
4-
import { useMutation, useQuery, useQueryClient } from "react-query";
53

6-
const ExcuseModal = ({ setMessage }) => {
7-
const queryClient = useQueryClient();
4+
const ExcuseModal = ({mutation, time, setMessage}) => {
85
const descriptionRef = createRef();
96
const attachmentRef = createRef();
107
const [description, setDescription] = useState("");
118
const [id, setId] = useState(3);
12-
const now = new Date();
13-
const formattedTime = now.toLocaleTimeString("en-US", {
14-
hour12: false,
15-
hour: "2-digit",
16-
minute: "2-digit",
17-
second: "2-digit",
18-
});
19-
20-
const { data: companyDetails } = useQuery("companyDetails", async () => {
21-
const response = await axiosClient.get("/appliances/accepted");
22-
return response.data.appliances[0];
23-
});
24-
25-
const { data: activity } = useQuery(
26-
["activity", companyDetails?.intern_date.company_id],
27-
async () => {
28-
const response = await axiosClient.get(
29-
`/today-activities?company=${companyDetails?.intern_date.company_id}`
30-
);
31-
return response.data;
32-
},
33-
{ enabled: !!companyDetails?.intern_date.company_id }
34-
);
35-
36-
const mutation = useMutation(
37-
(formData) =>
38-
axiosClient.put(`/presences/${activity.presence.id}`, formData),
39-
{
40-
onSuccess: () => {
41-
queryClient.invalidateQueries("presences");
42-
queryClient.invalidateQueries("activity");
43-
document.getElementById("modal").close();
44-
setMessage("Berhasil izin!");
45-
},
46-
onError: (err) => {
47-
const response = err.response;
48-
if (response && response.status === 422) {
49-
console.log(response.data);
50-
}
51-
},
52-
}
53-
);
549

5510
const onSubmit = (ev) => {
5611
ev.preventDefault();
5712

5813
const formData = new FormData();
59-
formData.append("check_in", formattedTime);
14+
formData.append("check_in", time);
6015
formData.append("attachment", attachmentRef.current.files[0]);
6116
formData.append("description", descriptionRef.current.value);
6217
formData.append("presence_status_id", id);
6318

6419
mutation.mutate(formData);
20+
document.getElementById("izin").close();
21+
setMessage("Berhasil izin!");
6522
};
6623

6724
return (
@@ -85,7 +42,7 @@ const ExcuseModal = ({ setMessage }) => {
8542
name='radio-10'
8643
className='radio checked:bg-red-500'
8744
onChange={() => setId(3)}
88-
checked
45+
checked={id === 3}
8946
/>
9047
</label>
9148
</div>
@@ -101,7 +58,7 @@ const ExcuseModal = ({ setMessage }) => {
10158
</label>
10259
</div>
10360
</div>
104-
<label className='form-control w-full max-w-xs'>
61+
<label className='form-control w-full'>
10562
<label className='label'>
10663
<span className='label-text text-dark transition duration-300 dark:text-lightOne text-base'>
10764
Lampiran
@@ -111,7 +68,7 @@ const ExcuseModal = ({ setMessage }) => {
11168
type='file'
11269
name='attachment'
11370
accept='image/*'
114-
className='file-input file-input-bordered w-full max-w-xs'
71+
className='file-input file-input-bordered w-full'
11572
ref={attachmentRef}
11673
/>
11774
</label>

src/components/index.jsx

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export { default as Footer } from "./Footer";
1717
export { default as Title } from "./Title";
1818
export { default as PresenceButton } from "./presence/PresenceButton";
1919
export { default as PresenceModal } from "./presence/PresenceModal";
20+
export { default as PresenceCamera } from "./presence/PresenceCamera";
2021
export { default as UploadCv } from "./UploadCv";
2122
export { default as Activity } from "./activity/Activity";
2223
export { default as NoActivity } from "./activity/NoActivity";

src/components/intern/MyInternDetail.jsx

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const MyIntern = ({ vacancy }) => {
3434
if (appliancesData) {
3535
setStartDate(appliancesData.start_date || "");
3636
setEndDate(appliancesData.end_date || "");
37+
setExtend(appliancesData.extend);
3738
}
3839
}, [appliancesData]);
3940

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import React, { useCallback, useRef, useState } from "react";
2+
import Webcam from "react-webcam";
3+
import { Icon } from "@iconify/react";
4+
5+
const PresenceCamera = ({ onClose, mutation, time, setMessage }) => {
6+
const webcamRef = useRef(null);
7+
const [imageSrc, setImageSrc] = useState(null);
8+
9+
// Convert base64 string to Blob
10+
const base64ToBlob = (base64, mime) => {
11+
const byteString = atob(base64.split(",")[1]);
12+
const mimeString = mime || base64.split(";")[0].split(":")[1];
13+
const ab = new ArrayBuffer(byteString.length);
14+
const ia = new Uint8Array(ab);
15+
for (let i = 0; i < byteString.length; i++) {
16+
ia[i] = byteString.charCodeAt(i);
17+
}
18+
return new Blob([ab], { type: mimeString });
19+
};
20+
21+
const capture = useCallback(() => {
22+
const imageSrc = webcamRef.current.getScreenshot();
23+
setImageSrc(imageSrc);
24+
}, [webcamRef]);
25+
26+
const onSubmit = (ev) => {
27+
ev.preventDefault(); // Prevent the default form submission behavior
28+
29+
const blob = base64ToBlob(imageSrc, "image/jpeg");
30+
const file = new File([blob], "capture.jpg", { type: "image/jpeg" });
31+
32+
const formData = new FormData();
33+
formData.append("check_in", time);
34+
formData.append("attachment", file); // Use the File object
35+
formData.append("presence_status_id", 1);
36+
37+
mutation.mutate(formData);
38+
document.getElementById("checkin").close();
39+
setMessage("Berhasil absen masuk!");
40+
};
41+
42+
return (
43+
<dialog id='checkin' className='modal'>
44+
<div className='modal-box bg-lightOne dark:bg-dark'>
45+
<div className='flex justify-between items-center mb-3'>
46+
<h3 className='font-bold text-lg'>Absen Masuk</h3>
47+
<form method='dialog'>
48+
<button
49+
className='text-2xl p-3 hover:drop-shadow-xl hover:bg-light-gray rounded-full'
50+
onClick={onClose}
51+
>
52+
<Icon icon='ic:round-close' color='#99abb4' />
53+
</button>
54+
</form>
55+
</div>
56+
57+
{imageSrc ? (
58+
<img src={imageSrc} alt='Captured' />
59+
) : (
60+
<>
61+
<Webcam
62+
audio={false}
63+
ref={webcamRef}
64+
screenshotFormat='image/jpeg'
65+
width={640}
66+
height={480}
67+
/>
68+
</>
69+
)}
70+
<div className='modal-action'>
71+
<form onSubmit={onSubmit} method='dialog'>
72+
{imageSrc ? (
73+
<div>
74+
<button
75+
className='btn bg-red-700 text-lightOne hover:drop-shadow-xl rounded-md'
76+
onClick={(e) => {
77+
e.stopPropagation();
78+
setImageSrc(null);
79+
}}
80+
>
81+
Retake photo
82+
</button>
83+
<button
84+
type='submit'
85+
className='btn bg-main text-lightOne hover:drop-shadow-xl rounded-md'
86+
>
87+
submit
88+
</button>
89+
</div>
90+
) : (
91+
<button
92+
type='button'
93+
className='btn bg-main text-lightOne hover:drop-shadow-xl rounded-md'
94+
onClick={(e) => {
95+
e.stopPropagation();
96+
capture();
97+
}}
98+
>
99+
Take photo
100+
</button>
101+
)}
102+
</form>
103+
</div>
104+
</div>
105+
</dialog>
106+
);
107+
};
108+
109+
export default PresenceCamera;

0 commit comments

Comments
 (0)