diff --git a/next.config.js b/next.config.js index b866ef000ee2682cb81ded2ad967f6c160822bbb..108fc63fb16c3b2d7b0b7fc41541e77ae37488ee 100644 --- a/next.config.js +++ b/next.config.js @@ -3,7 +3,7 @@ const withPWA = require('next-pwa')({ dest: 'public', register: true, skipWaiting: true, - // disable: process.env.NODE_ENV === 'development', // opsional + disable: process.env.NODE_ENV === 'development', // opsional }); /** @type {import('next').NextConfig} */ diff --git a/package-lock.json b/package-lock.json index 595db5a15c82a7ca2f0974000ead8e9df641aa35..a8d9c1b2ea56ca0e8db95612c050b1f8d20768aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@vladmandic/face-api": "^1.7.15", "antd": "^5.26.1", "axios": "^1.10.0", + "file-saver": "^2.0.5", "geolib": "^3.3.4", "leaflet": "^1.9.4", "lucide-react": "^0.517.0", @@ -26,6 +27,7 @@ "react-dom": "^19.0.0", "react-leaflet": "^5.0.0", "react-webcam": "^7.2.0", + "xlsx": "^0.18.5", "zustand": "^5.0.5" }, "devDependencies": { @@ -2819,6 +2821,15 @@ "node": ">=0.4.0" } }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3399,6 +3410,19 @@ ], "license": "CC-BY-4.0" }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3489,6 +3513,15 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -3618,6 +3651,18 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4221,6 +4266,12 @@ "reusify": "^1.0.4" } }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==", + "license": "MIT" + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -4361,6 +4412,15 @@ "node": ">= 6" } }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -7684,6 +7744,18 @@ "deprecated": "Please use @jridgewell/sourcemap-codec instead", "license": "MIT" }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -8837,6 +8909,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/workbox-background-sync": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.6.0.tgz", @@ -9211,6 +9301,27 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index d20af8b97ccfddb5ecd0f3ccc1484c7090c50483..e71cf475b3de369307291987da9ce004bf1dc014 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@vladmandic/face-api": "^1.7.15", "antd": "^5.26.1", "axios": "^1.10.0", + "file-saver": "^2.0.5", "geolib": "^3.3.4", "leaflet": "^1.9.4", "lucide-react": "^0.517.0", @@ -27,6 +28,7 @@ "react-dom": "^19.0.0", "react-leaflet": "^5.0.0", "react-webcam": "^7.2.0", + "xlsx": "^0.18.5", "zustand": "^5.0.5" }, "devDependencies": { diff --git a/public/assets/profill.png b/public/assets/profill.png new file mode 100644 index 0000000000000000000000000000000000000000..b5da2c05a201d3103689c0180de2c155935b0be6 Binary files /dev/null and b/public/assets/profill.png differ diff --git a/src/app/attendance/detail/page.js b/src/app/attendance/detail/page.js index bde04b5227c8f37fc8aa55450f7de76b7797200b..d67603fd1e8bbdbda93327aa607fc7936b6131ab 100644 --- a/src/app/attendance/detail/page.js +++ b/src/app/attendance/detail/page.js @@ -33,7 +33,6 @@ export default function AbsensiDetail() { }); } - console.log(presenceData, 'data') if (loading) { return ; } diff --git a/src/app/attendance/page.js b/src/app/attendance/page.js index fbf2b5eeff5255a5fd7feddab8bc11cb2f07c564..75e73857ef0485cae6045d0c36c265f2dc3ef459 100644 --- a/src/app/attendance/page.js +++ b/src/app/attendance/page.js @@ -3,25 +3,30 @@ import DetailPresenceCardComponent from "@/components/DetailPresenceCardComponent"; import LoadingComponent from "@/components/LoadingComponent"; import axiosInstance from "@/lib/axios"; +import { DatePicker } from "antd"; import { useSession } from "next-auth/react" import { useEffect, useState } from "react"; +import dayjs from "dayjs"; +import LoadComponent from "@/components/LoadComponent"; +import ExportExcelButton from "@/components/ExportExcelButton"; export default function attendance(){ const {data : session} = useSession() const [dataAbsenHistory, setDataAbsenHistory] = useState([]); + const [selectedMonth, setSelectedMonth] = useState(dayjs()); const [loadingAbsenHistory, setLoadingAbsenHistory] = useState(true); useEffect(() => { if (session?.accessToken) { - getDataAbsenHistory(session.accessToken); + getDataAbsenHistory(session.accessToken, 'all'); } - }, [session]); + }, [session, selectedMonth]); - const getDataAbsenHistory = async (token) => { + const getDataAbsenHistory = async (token, date) => { setLoadingAbsenHistory(true); try { - const response = await axiosInstance.get('/user/get-absen-history-parent?limit=31&page=1', { + const response = await axiosInstance.get(`/user/get-absen-history-parent?limit=31&page=1&monthyear=${date}`, { headers: { Authorization: `Bearer ${token}`, }, @@ -35,20 +40,37 @@ export default function attendance(){ } setLoadingAbsenHistory(false); }; + + const onChange = (date, dateString) => { + setSelectedMonth(date); + }; return( -
+
Kehadiran
- {loadingAbsenHistory ? <> : dataAbsenHistory.map((item) => ( - - ))} + + +
+ +
+ + {loadingAbsenHistory ? +
+ + : + dataAbsenHistory.map((item) => ( + + ))}
) diff --git a/src/app/beranda/page.js b/src/app/beranda/page.js index 1905f4d93400db1847b3635a7824e6e0302c6163..89e2758ffae1c9ed68aad1fe941ddd163527ddd5 100644 --- a/src/app/beranda/page.js +++ b/src/app/beranda/page.js @@ -10,6 +10,7 @@ import React, { useEffect, useState } from 'react'; import dayjs from 'dayjs'; import 'dayjs/locale/id'; import { useRouter } from 'next/navigation'; +import LoadComponent from '@/components/LoadComponent'; dayjs.locale('id'); @@ -18,6 +19,7 @@ export default function Beranda() { const [dataAbsenToday, setDataAbsenToday] = useState([]); const [dataAbsenHistory, setDataAbsenHistory] = useState([]); const [loadingAbsenHistory, setLoadingAbsenHistory] = useState(true); + const [loadingDetail, setLoadingDetail] = useState(); const [loadingAbsenToday, setLoadingAbsenToday] = useState(true); const [typePresenceIn, setTypePresenceIn] = useState('Datang'); const [typePresenceOut, setTypePresenceOut] = useState('Pulang'); @@ -100,6 +102,12 @@ export default function Beranda() { router.push(targetRoute); }; + const handleDetailClick = (item) => { + setLoadingDetail(true); + console.log(item) + // router.push(`/check-out-telat/${presences[0]?.id}`); + } + return ( @@ -152,11 +160,14 @@ export default function Beranda() {
- {loadingAbsenHistory ? <> : dataAbsenHistory.map((item) => ( + {loadingAbsenHistory ? <> : + loadingDetail ? <> : + dataAbsenHistory.map((item) => ( handleDetailClick(item)} /> ))}
diff --git a/src/app/check-in/page.js b/src/app/check-in/page.js index ddcc64e5f4941cfe24efb4bd557a4cf4094cc2d0..7b0043d8c1053de03ebc9175c56afd0e7f0bf4cd 100644 --- a/src/app/check-in/page.js +++ b/src/app/check-in/page.js @@ -26,6 +26,7 @@ export default function CheckIn() { const [userLocationName, setUserLocationName] = useState(''); const [refreshingLocation, setRefreshingLocation] = useState(false); const [loadingLocation, setLoadingLocation] = useState(false); + const [loadingSendData, setLoadingSendData] = useState(false); const [dataAbsen, setDataAbsen] = useState({ @@ -170,6 +171,7 @@ export default function CheckIn() { }; const handleSendData = () => { + setLoadingSendData(true); if (!userLat || !userLng ) { messageApi.warning('Lokasi pengguna tidak tersedia'); return; @@ -198,7 +200,6 @@ export default function CheckIn() { 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 { @@ -293,7 +294,7 @@ export default function CheckIn() { )}
-
diff --git a/src/app/foto/page.js b/src/app/foto/page.js index 82ad83611f3bcd99718e273512a0198ce75a3d56..3039313ec45d25f39d339d9d5134af44f94a1eb0 100644 --- a/src/app/foto/page.js +++ b/src/app/foto/page.js @@ -11,9 +11,10 @@ export default function FotoPage() { const [dataAbsen, setDataAbsen] = useState(null); const [imageSrc, setImageSrc] = useState(null); const [messageApi, contextHolder] = message.useMessage(); - const router = useRouter(); const [absenTime, setAbsenTime] = useState(null); + const [loading, setLoading] = useState(false); const { data: session } = useSession(); + const router = useRouter(); useEffect(() => { @@ -51,6 +52,7 @@ export default function FotoPage() { }; const handleSubmit = async () => { + setLoading(true); try { const blob = dataURLtoBlob(imageSrc); const formData = new FormData(); @@ -108,7 +110,7 @@ export default function FotoPage() { {imageSrc && ( - )} diff --git a/src/app/login/page.js b/src/app/login/page.js index 1beb248b56b68da7b87d77b4547334c8a1f2dafc..359daa81b8760c763242c150249325da7a141e99 100644 --- a/src/app/login/page.js +++ b/src/app/login/page.js @@ -1,7 +1,7 @@ 'use client' import React, { Suspense,useEffect, useState } from 'react'; -import { Button, Form, Image, Input, message } from 'antd'; +import { Button, Form, Image, Input, message, notification } from 'antd'; import { signIn } from 'next-auth/react'; import { useRouter, useSearchParams } from 'next/navigation'; @@ -11,6 +11,15 @@ function LoginForm() { const error = searchParams.get('error'); const [loading, setLoading] = useState(false); const [messageApi, contextHolder] = message.useMessage(); + const [show, context] = notification.useNotification(); + const [notificationShown, setNotificationShown] = useState(false); + + useEffect(() => { + if (!notificationShown) { + openNotification(); + setNotificationShown(true); + } + }, [notificationShown]); useEffect(() => { if (error) { @@ -31,13 +40,26 @@ function LoginForm() { router.push("/beranda"); } else { messageApi.error("Login Gagal, Cek kembali username dan password"); + setLoading(false); } - setLoading(false); + }; + + const pesan = + "Apabila Anda mengalami kendala pada aplikasi, mohon untuk menghubungi layanan helpdesk IT."; + + const openNotification = () => { + show.open({ + type: "info", + message: "Informasi Penting", + description: pesan, + duration: 5, + }); }; return (
{contextHolder} + {context}
@@ -62,7 +84,7 @@ function LoginForm() { + ); +}; + +export default ExportExcelButton; diff --git a/src/components/LoadComponent.js b/src/components/LoadComponent.js new file mode 100644 index 0000000000000000000000000000000000000000..12b302a6df6ad2c75cf742450ae140c93f988ea3 --- /dev/null +++ b/src/components/LoadComponent.js @@ -0,0 +1,10 @@ +import { LoadingOutlined } from '@ant-design/icons'; +import { Skeleton, Spin } from 'antd'; + +export default function LoadComponent() { + return ( +
+ +
+ ); +} diff --git a/src/components/ProfileCardComponent.js b/src/components/ProfileCardComponent.js index 6caadb2bfa1be88036a405d5d0d6aa0a1c788a08..b64d2dd3e60f1c52012e2fb9da943f3adf665c5d 100644 --- a/src/components/ProfileCardComponent.js +++ b/src/components/ProfileCardComponent.js @@ -15,7 +15,7 @@ export default function ProfileCardComponent({ name, image }) {
{session?.user?.name || '-'}
{session?.nip || 'NIP'}
- +
);