diff --git a/package-lock.json b/package-lock.json index 903c4905a06547275034bc153eb13c91c13b58e5..595db5a15c82a7ca2f0974000ead8e9df641aa35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "geolib": "^3.3.4", "leaflet": "^1.9.4", "lucide-react": "^0.517.0", + "moment": "^2.30.1", "next": "15.3.3", "next-auth": "^4.24.11", "next-pwa": "^5.6.0", @@ -5593,6 +5594,15 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/package.json b/package.json index e80bd23a4512905f39c7be5cfd6064f909cddff1..d20af8b97ccfddb5ecd0f3ccc1484c7090c50483 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "geolib": "^3.3.4", "leaflet": "^1.9.4", "lucide-react": "^0.517.0", + "moment": "^2.30.1", "next": "15.3.3", "next-auth": "^4.24.11", "next-pwa": "^5.6.0", diff --git a/public/assets/logo-login-new.svg b/public/assets/logo-login-new.svg new file mode 100644 index 0000000000000000000000000000000000000000..379fdbadcd412f6caa3e7ac9c66a5a71a20a93ea --- /dev/null +++ b/public/assets/logo-login-new.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/models/face_landmark_68_model-shard1 b/public/models/face_landmark_68_model-shard1 new file mode 100644 index 0000000000000000000000000000000000000000..fcaca474f001923870e24940a926d9f01b4ac9e9 Binary files /dev/null and b/public/models/face_landmark_68_model-shard1 differ diff --git a/public/models/face_landmark_68_model-weights_manifest.json b/public/models/face_landmark_68_model-weights_manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..0fe27075f26c5ca65afa82254f01d85ff4fd6511 --- /dev/null +++ b/public/models/face_landmark_68_model-weights_manifest.json @@ -0,0 +1 @@ +[{"weights":[{"name":"dense0/conv0/filters","shape":[3,3,3,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004853619781194949,"min":-0.5872879935245888}},{"name":"dense0/conv0/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004396426443960153,"min":-0.7298067896973853}},{"name":"dense0/conv1/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00635151559231328,"min":-0.5589333721235686}},{"name":"dense0/conv1/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.009354315552057004,"min":-1.2628325995276957}},{"name":"dense0/conv1/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0029380727048013726,"min":-0.5846764682554731}},{"name":"dense0/conv2/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0049374802439820535,"min":-0.6171850304977566}},{"name":"dense0/conv2/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.009941946758943446,"min":-1.3421628124573652}},{"name":"dense0/conv2/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0030300481062309416,"min":-0.5272283704841838}},{"name":"dense0/conv3/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005672684837790097,"min":-0.7431217137505026}},{"name":"dense0/conv3/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010712201455060173,"min":-1.5639814124387852}},{"name":"dense0/conv3/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0030966934035806097,"min":-0.3839899820439956}},{"name":"dense1/conv0/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0039155554537679636,"min":-0.48161332081345953}},{"name":"dense1/conv0/pointwise_filter","shape":[1,1,32,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01023082966898002,"min":-1.094698774580862}},{"name":"dense1/conv0/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0027264176630506327,"min":-0.3871513081531898}},{"name":"dense1/conv1/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004583378632863362,"min":-0.5454220573107401}},{"name":"dense1/conv1/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00915846403907327,"min":-1.117332612766939}},{"name":"dense1/conv1/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003091680419211294,"min":-0.5966943209077797}},{"name":"dense1/conv2/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005407439727409214,"min":-0.708374604290607}},{"name":"dense1/conv2/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00946493943532308,"min":-1.2399070660273235}},{"name":"dense1/conv2/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004409168514550901,"min":-0.9788354102303}},{"name":"dense1/conv3/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004478132958505668,"min":-0.6493292789833219}},{"name":"dense1/conv3/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.011063695888893277,"min":-1.2501976354449402}},{"name":"dense1/conv3/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003909627596537272,"min":-0.6646366914113363}},{"name":"dense2/conv0/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003213915404151468,"min":-0.3374611174359041}},{"name":"dense2/conv0/pointwise_filter","shape":[1,1,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010917326048308728,"min":-1.4520043644250609}},{"name":"dense2/conv0/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002800439152063108,"min":-0.38085972468058266}},{"name":"dense2/conv1/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0050568851770139206,"min":-0.6927932692509071}},{"name":"dense2/conv1/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01074961213504567,"min":-1.3222022926106174}},{"name":"dense2/conv1/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0030654204242369708,"min":-0.5487102559384177}},{"name":"dense2/conv2/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00591809165244009,"min":-0.917304206128214}},{"name":"dense2/conv2/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01092823346455892,"min":-1.366029183069865}},{"name":"dense2/conv2/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002681120470458386,"min":-0.36463238398234055}},{"name":"dense2/conv3/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0048311497650894465,"min":-0.5797379718107336}},{"name":"dense2/conv3/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.011227761062921263,"min":-1.4483811771168429}},{"name":"dense2/conv3/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0034643323982463162,"min":-0.3360402426298927}},{"name":"dense3/conv0/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003394978887894574,"min":-0.49227193874471326}},{"name":"dense3/conv0/pointwise_filter","shape":[1,1,128,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010051267287310432,"min":-1.2765109454884247}},{"name":"dense3/conv0/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003142924752889895,"min":-0.4588670139219247}},{"name":"dense3/conv1/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00448304671867221,"min":-0.5872791201460595}},{"name":"dense3/conv1/pointwise_filter","shape":[1,1,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.016063522357566685,"min":-2.3613377865623026}},{"name":"dense3/conv1/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00287135781026354,"min":-0.47664539650374765}},{"name":"dense3/conv2/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006002906724518421,"min":-0.7923836876364315}},{"name":"dense3/conv2/pointwise_filter","shape":[1,1,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.017087187019048954,"min":-1.6061955797906016}},{"name":"dense3/conv2/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003124481205846749,"min":-0.46242321846531886}},{"name":"dense3/conv3/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006576311588287353,"min":-1.0193282961845398}},{"name":"dense3/conv3/pointwise_filter","shape":[1,1,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.015590153955945782,"min":-1.99553970636106}},{"name":"dense3/conv3/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004453541601405424,"min":-0.6546706154065973}},{"name":"fc/weights","shape":[256,136],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010417488509533453,"min":-1.500118345372817}},{"name":"fc/bias","shape":[136],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0025084222648658005,"min":0.07683877646923065}}],"paths":["face_landmark_68_model-shard1"]}] \ No newline at end of file diff --git a/src/app/beranda/page.js b/src/app/beranda/page.js index 88df5e26031ae7c8fdb7965c48120ec879e7e4ae..1905f4d93400db1847b3635a7824e6e0302c6163 100644 --- a/src/app/beranda/page.js +++ b/src/app/beranda/page.js @@ -21,6 +21,7 @@ export default function Beranda() { const [loadingAbsenToday, setLoadingAbsenToday] = useState(true); const [typePresenceIn, setTypePresenceIn] = useState('Datang'); const [typePresenceOut, setTypePresenceOut] = useState('Pulang'); + const [loadingPresenceType, setLoadingPresenceType] = useState(null); // 'Datang' atau 'Pulang' const router = useRouter(); useEffect(() => { @@ -42,17 +43,25 @@ export default function Beranda() { const fetchedData = response?.data ?? []; const absenData = fetchedData.data[0]; setDataAbsenToday(fetchedData.data[0]); - console.log(absenData); + + // console.log(absenData, 'dataAbsen'); + if (absenData?.presence_type === 'masuk') { document.cookie = 'hasCheckedIn=true; path=/;'; } else { - document.cookie = 'hasCheckedIn=; Max-Age=0; path=/;'; // hapus cookie jika belum absen + document.cookie = 'hasCheckedIn=; Max-Age=0; path=/;'; } if (absenData?.presence?.presence_type === 'pulang') { document.cookie = 'hasCheckedOut=true; path=/;'; } else { - document.cookie = 'hasCheckedOut=; Max-Age=0; path=/;'; // hapus cookie jika belum absen + document.cookie = 'hasCheckedOut=; Max-Age=0; path=/;'; + } + + if (absenData === undefined){ + document.cookie = 'hasAbsen=true; path=/;'; + } else { + document.cookie = 'hasAbsen=; Max-Age=0; path=/;'; } } catch (error) { @@ -85,6 +94,12 @@ export default function Beranda() { setLoadingAbsenHistory(false); } + const handlePresenceClick = (type) => { + setLoadingPresenceType(type); + const targetRoute = type === 'Datang' ? '/check-in' : '/check-out'; + router.push(targetRoute); + }; + return ( @@ -109,8 +124,23 @@ export default function Beranda() {
- - + {/* + */} + handlePresenceClick('Datang')} + /> + + handlePresenceClick('Pulang')} +/>
diff --git a/src/app/check-out-telat/[parent_id]/page.js b/src/app/check-out-telat/[parent_id]/page.js new file mode 100644 index 0000000000000000000000000000000000000000..c6d9d96f887fd89a9770501f6d9d5caa663773cf --- /dev/null +++ b/src/app/check-out-telat/[parent_id]/page.js @@ -0,0 +1,322 @@ +'use client' + +import LoadingComponent from '@/components/LoadingComponent'; +import axiosInstance from '@/lib/axios'; +import { Button, Col, message, Radio, Row } from 'antd'; +import TextArea from 'antd/es/input/TextArea'; +import { useSession } from 'next-auth/react'; +import dynamic from 'next/dynamic'; +import { use, useEffect, useState } from 'react'; +import { getLocationAddress } from '@/utils/locationUtils'; +import { ReloadOutlined } from '@ant-design/icons'; +import { useRouter } from 'next/navigation'; + +const DynamicMap = dynamic(() => import('@/components/RealTimeMapComponent'), { + ssr: false, +}); + +export default function CheckIn({params}) { + const [messageApi, contextHolder] = message.useMessage(); + const { data: session } = useSession(); + const [dataMasterLocation, setDataMasterLocation] = useState([]); + const [loadingMasterLocation, setLoadingMasterLocation] = useState(true); + const [userLat, setUserLat] = useState(null); + const [userLng, setUserLng] = useState(null); + const [selectedLocation, setSelectedLocation] = useState(null); + const [radiusTarget, setRadiusTarget] = useState(0); + const [userLocationName, setUserLocationName] = useState(''); + const [refreshingLocation, setRefreshingLocation] = useState(false); + const [loadingLocation, setLoadingLocation] = useState(false); + const router = useRouter(); + const { parent_id } = use(params); + + + const [dataAbsen, setDataAbsen] = useState({ + latitude_longitude: '', + presence_type: 'pulang', + presence_condition: '-', + add_information_type: '-', + add_information_condition: '', + location_validation_status: '', + office_id : '-', + office_name : '-', + location_name: '-', + parent_id: '-' + }); + + useEffect(() => { + if (!parent_id) { + alert('Data check-in tidak ditemukan'); + } else { + setDataAbsen({ + ...dataAbsen, + parent_id: parent_id + }) + } + }, [parent_id]); + + const refreshLocation = async () => { + setLoadingLocation(true); + if (!navigator.geolocation) { + messageApi.error('Geolocation tidak didukung oleh browser Anda.'); + return; + } + + setRefreshingLocation(true); + + try { + const position = await new Promise((resolve, reject) => { + navigator.geolocation.getCurrentPosition(resolve, reject, { + enableHighAccuracy: true, + maximumAge: 0, + timeout: 10000, + }); + }); + + const lat = position.coords.latitude; + const lng = position.coords.longitude; + + setUserLat(lat); + setUserLng(lng); + + const address = await getLocationAddress(lat, lng); + setUserLocationName(address); + setDataAbsen((prev) => ({ + ...prev, + latitude_longitude: `${lat}, ${lng}`, + location_name: address || '', + })); + } catch (error) { + console.error('Gagal ambil lokasi:', error); + messageApi.error('Gagal ambil lokasi: ' + error.message); + } finally { + setRefreshingLocation(false); + setLoadingLocation(false); + } + }; + + + console.log(parent_id); + + useEffect(() => { + if (session?.accessToken) { + getMasterLocation(session.accessToken); + } + }, [session]); + + useEffect(() => { + let watchId; + if (typeof window !== 'undefined' && navigator.geolocation) { + watchId = navigator.geolocation.watchPosition( + async (position) => { + const lat = position.coords.latitude; + const lng = position.coords.longitude; + setUserLat(lat); + setUserLng(lng); + + const address = await getLocationAddress(lat, lng); + + setDataAbsen((prev) => ({ + ...prev, + latitude_longitude: `${lat}, ${lng}`, + location_name: address || '', + })); + + setUserLocationName(address); + }, + (error) => { + messageApi.error('Gagal ambil lokasi: ' + error.message); + }, + { enableHighAccuracy: true, maximumAge: 0, timeout: 5000 } + ); + } + return () => { + if (watchId) navigator.geolocation.clearWatch(watchId); + }; + }, []); + + const getMasterLocation = async (token) => { + setLoadingMasterLocation(true); + try { + const response = await axiosInstance.get('/user/mlp', { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + const fetchedData = response?.data?.data ?? []; + setDataMasterLocation(fetchedData.data); + } catch (error) { + messageApi.error('Gagal mengambil data Location'); + } + setLoadingMasterLocation(false); + }; + + const onChangeRadio = (e) => { + const selected = dataMasterLocation.find((loc) => loc.name_location === e.target.value); + + if (e.target.value === 'Dinas Luar') { + setDataAbsen({ + ...dataAbsen, + presence_condition: e.target.value, + office_name: e.target.value, + office_id: '-', + location_validation_status: 0, + + }); + setSelectedLocation(null); + setRadiusTarget(0); + } else if (selected) { + setSelectedLocation(selected); + setRadiusTarget(selected.radius); + setDataAbsen({ + ...dataAbsen, + presence_condition: 'WFO', + office_name: selected.name_location, + office_id: selected.id, + location_validation_status: 1, + add_information_condition: '' + }); + } + }; + + const onChangeCatatan = (e) => { + setDataAbsen({ + ...dataAbsen, + add_information_condition: e.target.value, + }); + }; + + const handleSendData = () => { + if (!userLat || !userLng ) { + messageApi.warning('Lokasi pengguna tidak tersedia'); + return; + } + + if(dataAbsen.office_name !== 'Dinas Luar') { + const [targetLat, targetLng] = selectedLocation.latitude_longitude.split(', ').map(Number); + + const R = 6371; + const dLat = (targetLat - userLat) * (Math.PI / 180); + const dLon = (targetLng - userLng) * (Math.PI / 180); + const a = + Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(userLat * (Math.PI / 180)) * + Math.cos(targetLat * (Math.PI / 180)) * + Math.sin(dLon / 2) * + Math.sin(dLon / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + const distance = R * c * 1000; + + const inRadius = distance <= radiusTarget; + setDataAbsen((prev) => ({ + ...prev, + location_validation_status: inRadius ? 1 : 0, + })); + + if (inRadius) { + messageApi.success('Anda berada di dalam radius. Data siap dikirim.'); + console.log('dataSend', dataAbsen); + localStorage.setItem('pendingAbsensi', JSON.stringify(dataAbsen)); + window.location.href = '/foto'; + } else { + messageApi.warning('Anda berada di luar radius kantor !'); + } + } else { + messageApi.success('Anda berada di Luar Kantor. Data siap dikirim.'); + console.log('dataSend', dataAbsen); + localStorage.setItem('pendingAbsensi', JSON.stringify(dataAbsen)); + window.location.href = '/foto'; + } + + }; + + const isCanCheckin = + (dataAbsen.presence_condition === 'Dinas Luar' && dataAbsen.add_information_condition.length >= 10) || + (dataAbsen.presence_condition === 'WFO'); + + return ( +
+ {contextHolder} +
+
+
PRESENSI DATANG
+
+
+
+
+
+ +
+ {loadingLocation ? +
+ Memuat lokasi... +
: + + } +
+ {loadingMasterLocation ? ( + + ) : ( + <> + {userLat && userLng && ( +
+ Lokasi Anda : {userLat}, {userLng}
+ Nama Tempat : {userLocationName || ' Mencari...'} +
+ )} + +
LOKASI
+ + + + {dataMasterLocation.map((location) => ( + + {location.name_location} + + ))} + + Dinas Luar + + + + + + {dataAbsen.presence_condition === 'Dinas Luar' && ( +
+
CATATAN
+