Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
P
presence
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Packages
Packages
Container Registry
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
jorke
presence
Commits
5f9b580a
Commit
5f9b580a
authored
Jul 07, 2025
by
jorke
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Initial commit
parent
fef8775c
Changes
21
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
946 additions
and
249 deletions
+946
-249
package-lock.json
package-lock.json
+10
-0
package.json
package.json
+1
-0
public/assets/logo-login-new.svg
public/assets/logo-login-new.svg
+32
-0
public/models/face_landmark_68_model-shard1
public/models/face_landmark_68_model-shard1
+0
-0
public/models/face_landmark_68_model-weights_manifest.json
public/models/face_landmark_68_model-weights_manifest.json
+1
-0
src/app/beranda/page.js
src/app/beranda/page.js
+35
-5
src/app/check-out-telat/[parent_id]/page.js
src/app/check-out-telat/[parent_id]/page.js
+322
-0
src/app/check-out/page.js
src/app/check-out/page.js
+1
-1
src/app/foto/page.js
src/app/foto/page.js
+2
-3
src/app/layout.js
src/app/layout.js
+1
-1
src/app/login/page.js
src/app/login/page.js
+35
-26
src/components/BottomMenuComponent.js
src/components/BottomMenuComponent.js
+45
-21
src/components/CameraComponent.js
src/components/CameraComponent.js
+102
-57
src/components/CameraFaceComponent-bak.js
src/components/CameraFaceComponent-bak.js
+153
-0
src/components/CameraFaceComponent.js
src/components/CameraFaceComponent.js
+68
-35
src/components/DetailPresenceCardComponent.js
src/components/DetailPresenceCardComponent.js
+85
-32
src/components/PresenceCardComponent.js
src/components/PresenceCardComponent.js
+39
-64
src/components/ProfileCardComponent.js
src/components/ProfileCardComponent.js
+1
-1
src/middleware.js
src/middleware.js
+7
-0
src/utils/loadFaceApi.js
src/utils/loadFaceApi.js
+4
-1
src/utils/locationUtils.js
src/utils/locationUtils.js
+2
-2
No files found.
package-lock.json
View file @
5f9b580a
...
...
@@ -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"
,
...
...
package.json
View file @
5f9b580a
...
...
@@ -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
"
,
...
...
public/assets/logo-login-new.svg
0 → 100644
View file @
5f9b580a
<svg
width=
"3310"
height=
"2041"
viewBox=
"0 0 3310 2041"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<g
filter=
"url(#filter0_d_1_3988)"
>
<path
d=
"M2005.3 480.986C1846.22 275.364 1510.26 275.364 1351.18 480.986C1224.57 644.644 1224.57 852.937 1351.18 1016.6C1510.26 1222.22 1846.22 1222.22 2005.3 1016.6C2131.92 852.937 2131.92 644.644 2005.3 480.986Z"
fill=
"white"
/>
<path
d=
"M2005.3 411.174C1819.12 236.275 1530.77 229.503 1351.18 411.174C1164.03 600.5 1166.53 877.5 1351.18 1083C1507.32 1256.76 1847.45 1296.4 2079.63 991.995M1351.18 480.986C1510.26 275.364 1846.22 275.364 2005.3 480.986C2131.92 644.644 2131.92 852.937 2005.3 1016.6C1846.22 1222.22 1510.26 1222.22 1351.18 1016.6C1224.57 852.937 1224.57 644.644 1351.18 480.986Z"
stroke=
"url(#paint0_linear_1_3988)"
stroke-width=
"50"
/>
<path
d=
"M1890.44 571.97C1922.27 674.552 1794.77 926.309 1720.85 1082.56C1705.26 1115.52 1651.24 1116.49 1634.32 1084.19C1553.36 929.599 1415.67 685.253 1453.78 571.97C1524.1 362.939 1825.96 364.168 1890.44 571.97Z"
fill=
"#040A44"
/>
<path
d=
"M1803.28 499.842C1730.05 426.608 1611.31 426.608 1538.08 499.842C1464.85 573.075 1464.85 691.81 1538.08 765.044C1611.31 838.277 1730.05 838.277 1803.28 765.044C1876.52 691.81 1876.52 573.075 1803.28 499.842Z"
fill=
"url(#paint1_linear_1_3988)"
/>
<path
d=
"M1568.27 616.926L1634.47 709.02C1635.18 710.015 1636.61 710.139 1637.49 709.281L1779.74 569.934"
stroke=
"#040A44"
stroke-width=
"40"
stroke-linecap=
"round"
/>
<path
d=
"M857.992 1779.5C843.159 1779.5 827.909 1778.25 812.242 1775.75C796.742 1773.42 782.159 1770.33 768.492 1766.5C754.992 1762.67 743.992 1758.67 735.492 1754.5L741.492 1682.25C752.992 1688.75 764.992 1694.33 777.492 1699C790.159 1703.5 802.742 1707 815.242 1709.5C827.909 1712 839.992 1713.25 851.492 1713.25C862.992 1713.25 872.992 1711.83 881.492 1709C889.992 1706.17 896.576 1701.92 901.242 1696.25C905.909 1690.42 908.242 1682.92 908.242 1673.75C908.242 1664.42 905.159 1656.75 898.992 1650.75C892.992 1644.58 884.326 1639.25 872.992 1634.75C861.659 1630.08 848.159 1625.5 832.492 1621C810.826 1614.67 792.326 1607.33 776.992 1599C761.659 1590.5 749.909 1579.67 741.742 1566.5C733.742 1553.17 729.742 1536.08 729.742 1515.25C729.742 1494.42 734.326 1476.08 743.492 1460.25C752.659 1444.25 766.492 1431.75 784.992 1422.75C803.492 1413.75 826.909 1409.25 855.242 1409.25C866.409 1409.25 877.576 1410 888.742 1411.5C899.909 1412.83 910.742 1414.67 921.242 1417C931.909 1419.33 941.742 1422 950.742 1425C959.742 1428 967.576 1431.17 974.242 1434.5L967.992 1507C957.326 1500.33 946.242 1494.75 934.742 1490.25C923.242 1485.75 911.992 1482.33 900.992 1480C889.992 1477.67 879.992 1476.5 870.992 1476.5C860.826 1476.5 851.576 1477.83 843.242 1480.5C835.076 1483.17 828.576 1487.25 823.742 1492.75C818.909 1498.25 816.492 1505.17 816.492 1513.5C816.492 1521.67 818.742 1528.58 823.242 1534.25C827.909 1539.75 834.992 1544.67 844.492 1549C854.159 1553.17 866.576 1557.5 881.742 1562C910.242 1570.5 932.742 1579.42 949.242 1588.75C965.742 1597.92 977.409 1609 984.242 1622C991.242 1635 994.742 1651.33 994.742 1671C994.742 1692.83 989.992 1711.92 980.492 1728.25C970.992 1744.42 956.159 1757 935.992 1766C915.826 1775 889.826 1779.5 857.992 1779.5ZM1045.29 1773C1051.62 1753.83 1058.2 1733.67 1065.04 1712.5C1072.04 1691.33 1078.62 1671.25 1084.79 1652.25L1125.04 1530C1132.2 1507.83 1138.7 1487.92 1144.54 1470.25C1150.37 1452.42 1156.2 1434.5 1162.04 1416.5H1268.04C1274.04 1435.17 1279.87 1453.33 1285.54 1471C1291.37 1488.67 1297.87 1508.33 1305.04 1530L1344.79 1652.5C1351.29 1672.17 1357.87 1692.42 1364.54 1713.25C1371.37 1734.08 1377.87 1754 1384.04 1773H1299.04C1293.7 1754 1288.2 1734.58 1282.54 1714.75C1276.87 1694.75 1271.45 1675.83 1266.29 1658L1217.29 1486.25H1211.29L1161.79 1656.25C1156.45 1674.75 1150.79 1694.17 1144.79 1714.5C1138.95 1734.67 1133.29 1754.17 1127.79 1773H1045.29ZM1131.29 1698.5L1140.04 1637H1299.29L1306.29 1698.5H1131.29ZM1454.41 1773C1454.41 1753.83 1454.41 1735.25 1454.41 1717.25C1454.41 1699.25 1454.41 1679.08 1454.41 1656.75V1535.75C1454.41 1512.75 1454.41 1492 1454.41 1473.5C1454.41 1455 1454.41 1436 1454.41 1416.5H1536.66C1536.66 1436 1536.66 1455 1536.66 1473.5C1536.66 1492 1536.66 1512.75 1536.66 1535.75V1643.5C1536.66 1665.83 1536.66 1686 1536.66 1704C1536.66 1722 1536.66 1740.58 1536.66 1759.75L1508.16 1701.25H1577.91C1595.24 1701.25 1610.41 1701.25 1623.41 1701.25C1636.58 1701.25 1648.74 1701.25 1659.91 1701.25C1671.24 1701.25 1682.83 1701.25 1694.66 1701.25V1773H1454.41ZM1759.2 1773C1761.7 1754.33 1764.12 1735.67 1766.45 1717C1768.78 1698.33 1771.28 1678.08 1773.95 1656.25L1788.7 1536.75C1791.53 1515.25 1794.12 1494.92 1796.45 1475.75C1798.78 1456.58 1801.2 1436.83 1803.7 1416.5H1886.95C1894.95 1436.83 1902.53 1456.33 1909.7 1475C1917.03 1493.5 1924.28 1511.92 1931.45 1530.25L1973.95 1639.25H1979.45L2020.7 1530.5C2027.7 1512 2034.62 1493.67 2041.45 1475.5C2048.45 1457.33 2055.78 1437.67 2063.45 1416.5H2145.7C2148.2 1436.5 2150.62 1456.08 2152.95 1475.25C2155.45 1494.42 2158.12 1515 2160.95 1537L2176.2 1657C2178.87 1678.67 2181.37 1698.67 2183.7 1717C2186.2 1735.33 2188.62 1754 2190.95 1773H2112.7C2109.7 1748 2106.78 1724.25 2103.95 1701.75C2101.12 1679.25 2098.53 1658.17 2096.2 1638.5L2085.7 1554H2080.2L2049.2 1644.75C2041.7 1667.08 2034.12 1689.25 2026.45 1711.25C2018.95 1733.08 2011.95 1753.67 2005.45 1773H1950.95C1946.28 1760.5 1941.2 1747.08 1935.7 1732.75C1930.37 1718.42 1924.87 1703.83 1919.2 1689C1913.53 1674 1907.95 1659.33 1902.45 1645L1867.95 1554H1862.45L1852.2 1638C1849.87 1657.83 1847.28 1679.08 1844.45 1701.75C1841.78 1724.25 1838.95 1748 1835.95 1773H1759.2ZM2251.63 1773C2257.97 1753.83 2264.55 1733.67 2271.38 1712.5C2278.38 1691.33 2284.97 1671.25 2291.13 1652.25L2331.38 1530C2338.55 1507.83 2345.05 1487.92 2350.88 1470.25C2356.72 1452.42 2362.55 1434.5 2368.38 1416.5H2474.38C2480.38 1435.17 2486.22 1453.33 2491.88 1471C2497.72 1488.67 2504.22 1508.33 2511.38 1530L2551.13 1652.5C2557.63 1672.17 2564.22 1692.42 2570.88 1713.25C2577.72 1734.08 2584.22 1754 2590.38 1773H2505.38C2500.05 1754 2494.55 1734.58 2488.88 1714.75C2483.22 1694.75 2477.8 1675.83 2472.63 1658L2423.63 1486.25H2417.63L2368.13 1656.25C2362.8 1674.75 2357.13 1694.17 2351.13 1714.5C2345.3 1734.67 2339.63 1754.17 2334.13 1773H2251.63ZM2337.63 1698.5L2346.38 1637H2505.63L2512.63 1698.5H2337.63Z"
fill=
"#DA630D"
/>
</g>
<defs>
<filter
id=
"filter0_d_1_3988"
x=
"-4"
y=
"0"
width=
"3318"
height=
"2049"
filterUnits=
"userSpaceOnUse"
color-interpolation-filters=
"sRGB"
>
<feFlood
flood-opacity=
"0"
result=
"BackgroundImageFix"
/>
<feColorMatrix
in=
"SourceAlpha"
type=
"matrix"
values=
"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result=
"hardAlpha"
/>
<feOffset
dy=
"4"
/>
<feGaussianBlur
stdDeviation=
"2"
/>
<feComposite
in2=
"hardAlpha"
operator=
"out"
/>
<feColorMatrix
type=
"matrix"
values=
"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"
/>
<feBlend
mode=
"normal"
in2=
"BackgroundImageFix"
result=
"effect1_dropShadow_1_3988"
/>
<feBlend
mode=
"normal"
in=
"SourceGraphic"
in2=
"effect1_dropShadow_1_3988"
result=
"shape"
/>
</filter>
<linearGradient
id=
"paint0_linear_1_3988"
x1=
"1376.07"
y1=
"379.609"
x2=
"2417.47"
y2=
"1086.88"
gradientUnits=
"userSpaceOnUse"
>
<stop
stop-color=
"#F4A549"
/>
<stop
offset=
"0.437953"
stop-color=
"#FBAB33"
/>
<stop
offset=
"0.947917"
stop-color=
"#DA630D"
/>
</linearGradient>
<linearGradient
id=
"paint1_linear_1_3988"
x1=
"1552.51"
y1=
"485.683"
x2=
"1976.19"
y2=
"790.065"
gradientUnits=
"userSpaceOnUse"
>
<stop
stop-color=
"#F4A549"
/>
<stop
offset=
"0.437953"
stop-color=
"#FBAB33"
/>
<stop
offset=
"0.947917"
stop-color=
"#DA630D"
/>
</linearGradient>
</defs>
</svg>
public/models/face_landmark_68_model-shard1
0 → 100644
View file @
5f9b580a
File added
public/models/face_landmark_68_model-weights_manifest.json
0 → 100644
View file @
5f9b580a
[{
"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
src/app/beranda/page.js
View file @
5f9b580a
...
...
@@ -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() {
<
/div
>
<
/div
>
<
div
className
=
"
grid grid-cols-2 gap-4
"
>
<
PresenceCard
presenceType
=
{
typePresenceIn
}
presenceData
=
{
dataAbsenToday
}
/
>
<
PresenceCard
presenceType
=
{
typePresenceOut
}
presenceData
=
{
dataAbsenToday
}
/
>
{
/* <PresenceCard presenceType={typePresenceIn} presenceData={dataAbsenToday}/>
<PresenceCard presenceType={typePresenceOut} presenceData={dataAbsenToday}/> */
}
<
PresenceCard
presenceType
=
{
typePresenceIn
}
presenceData
=
{
dataAbsenToday
}
presenceUrl
=
{
null
}
loading
=
{
loadingPresenceType
===
'
Datang
'
}
onClick
=
{()
=>
handlePresenceClick
(
'
Datang
'
)}
/
>
<
PresenceCard
presenceType
=
{
typePresenceOut
}
presenceData
=
{
dataAbsenToday
}
presenceUrl
=
{
null
}
loading
=
{
loadingPresenceType
===
'
Pulang
'
}
onClick
=
{()
=>
handlePresenceClick
(
'
Pulang
'
)}
/
>
<
/div
>
<
/div
>
<
div
className
=
"
grid gap-4 mt-4
"
>
...
...
src/app/check-out-telat/[parent_id]/page.js
0 → 100644
View file @
5f9b580a
This diff is collapsed.
Click to expand it.
src/app/check-out/page.js
View file @
5f9b580a
...
...
@@ -222,7 +222,7 @@ export default function CheckIn() {
{
contextHolder
}
<
div
className
=
"
p-4 card-head
"
>
<
div
className
=
"
grid gap-4 mt-4
"
>
<
div
className
=
"
col-start font-bold
"
>
PRESENSI
DAT
ANG
<
/div
>
<
div
className
=
"
col-start font-bold
"
>
PRESENSI
PUL
ANG
<
/div
>
<
div
className
=
"
col-end-3 text-end text-blue-600
"
>
<
div
>
<
Button
...
...
src/app/foto/page.js
View file @
5f9b580a
...
...
@@ -3,7 +3,6 @@
import
{
useEffect
,
useState
}
from
'
react
'
;
import
{
Button
,
message
}
from
'
antd
'
;
import
{
useRouter
}
from
'
next/navigation
'
;
import
CameraComponent
from
'
@/components/CameraComponent
'
;
import
axiosInstance
from
'
@/lib/axios
'
;
import
{
useSession
}
from
'
next-auth/react
'
;
import
CameraFaceComponent
from
'
@/components/CameraFaceComponent
'
;
...
...
@@ -57,7 +56,6 @@ export default function FotoPage() {
const
formData
=
new
FormData
();
formData
.
append
(
'
picture
'
,
blob
);
// formData.append('picture', imageSrc); // base64 string
formData
.
append
(
'
latitude_longitude
'
,
dataAbsen
.
latitude_longitude
);
formData
.
append
(
'
presence_type
'
,
dataAbsen
.
presence_type
);
formData
.
append
(
'
presence_condition
'
,
dataAbsen
.
presence_condition
);
...
...
@@ -67,6 +65,7 @@ export default function FotoPage() {
formData
.
append
(
'
office_id
'
,
dataAbsen
.
office_id
);
formData
.
append
(
'
office_name
'
,
dataAbsen
.
office_name
);
formData
.
append
(
'
location_name
'
,
dataAbsen
.
location_name
);
dataAbsen
?.
parent_id
===
undefined
?
''
:
formData
.
append
(
'
parent_id
'
,
dataAbsen
?.
parent_id
)
await
axiosInstance
.
post
(
'
/user/presence
'
,
formData
,
{
headers
:
{
...
...
@@ -104,8 +103,8 @@ export default function FotoPage() {
)}
<div className='mt-4'>
{/* <CameraComponent onCapture={(img) => setImageSrc(img)}/> */}
<CameraFaceComponent onCapture={(img) => setImageSrc(img)}/>
{/* <CameraEyeDetection onCapture={(img) => setImageSrc(img)}/> */}
</div>
{imageSrc && (
...
...
src/app/layout.js
View file @
5f9b580a
...
...
@@ -2,7 +2,7 @@ import '../../styles/globals.css';
import
{
AntdRegistry
}
from
'
@ant-design/nextjs-registry
'
;
import
React
from
'
react
'
;
import
{
DM_Sans
}
from
'
next/font/google
'
import
{
ConfigProvider
}
from
'
antd
'
;
import
{
ConfigProvider
,
App
}
from
'
antd
'
;
import
theme
from
'
../../theme.config
'
;
import
'
@ant-design/v5-patch-for-react-19
'
;
import
ConditionalBottomMenu
from
'
@/components/ConditionalBottomMenu
'
;
...
...
src/app/login/page.js
View file @
5f9b580a
'
use client
'
import
React
,
{
useEffect
,
useState
}
from
'
react
'
;
import
{
Button
,
Form
,
I
nput
}
from
'
antd
'
;
import
{
Button
,
Form
,
I
mage
,
Input
,
message
}
from
'
antd
'
;
import
{
signIn
}
from
'
next-auth/react
'
;
import
{
useRouter
,
useSearchParams
}
from
'
next/navigation
'
;
...
...
@@ -11,6 +11,7 @@ export default function Login() {
const
searchParams
=
useSearchParams
();
const
error
=
searchParams
.
get
(
'
error
'
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
messageApi
,
contextHolder
]
=
message
.
useMessage
();
useEffect
(()
=>
{
if
(
error
)
{
...
...
@@ -27,41 +28,49 @@ export default function Login() {
});
if
(
result
.
ok
)
{
messageApi
.
success
(
'
Login Berhasil
'
)
router
.
push
(
"
/beranda
"
);
}
else
{
alert
(
"
Login gagal
"
);
messageApi
.
error
(
"
Login Gagal, Cek kembali username dan password
"
);
}
setLoading
(
false
);
};
return
(
<
div
className
=
"
justify-center p-4
"
>
<
div
className
=
"
pt-[155px]
"
>
<
Form
initialValues
=
{{
remember
:
true
}}
onFinish
=
{
handleSubmit
}
autoComplete
=
"
off
"
>
<
Form
.
Item
name
=
"
username
"
rules
=
{[{
required
:
true
,
message
:
'
Please input your username!
'
}]}
<
div
>
{
contextHolder
}
<
div
className
=
"
min-h-screen flex items-center justify-center px-4
"
>
<
div
className
=
"
max-w-md w-full p-6
"
>
<
Image
preview
=
{
false
}
src
=
'
/assets/logo-login-new.svg
'
/>
<
Form
initialValues
=
{{
remember
:
true
}}
onFinish
=
{
handleSubmit
}
autoComplete
=
"
off
"
>
<
Input
placeholder
=
"
Email
"
/>
<
/Form.Item
>
<
Form
.
Item
name
=
"
username
"
rules
=
{[{
required
:
true
,
message
:
'
Mohon masukan username anda!
'
}]}
>
<
Input
placeholder
=
"
Username
"
/>
<
/Form.Item
>
<
Form
.
Item
name
=
"
password
"
rules
=
{[{
required
:
true
,
message
:
'
Please input your password
!
'
}]}
>
<
Input
.
Password
placeholder
=
"
Password
"
/>
<
/Form.Item
>
<
Form
.
Item
name
=
"
password
"
rules
=
{[{
required
:
true
,
message
:
'
Mohon masukan password anda
!
'
}]}
>
<
Input
.
Password
placeholder
=
"
Password
"
/>
<
/Form.Item
>
<
Form
.
Item
>
<
Button
type
=
"
primary
"
htmlType
=
"
submit
"
block
loading
=
{
loading
}
>
Submit
<
/Button
>
<
/Form.Item
>
<
/Form
>
<
Form
.
Item
>
<
Button
type
=
"
primary
"
htmlType
=
"
submit
"
block
loading
=
{
loading
}
>
Submit
<
/Button
>
<
/Form.Item
>
<
Button
type
=
"
primary
"
block
disabled
>
Login
Azure
<
/Button
>
<
/Form
>
<
/div
>
<
/div
>
<
/div
>
);
...
...
src/components/BottomMenuComponent.js
View file @
5f9b580a
...
...
@@ -3,10 +3,13 @@
import
{
usePathname
,
useRouter
}
from
'
next/navigation
'
;
import
{
CalendarCheck
,
Home
,
User
}
from
'
lucide-react
'
;
import
classNames
from
'
classnames
'
;
import
{
useEffect
,
useState
}
from
'
react
'
;
import
{
Spin
}
from
'
antd
'
;
export
default
function
BottomMenuComponent
()
{
const
pathname
=
usePathname
();
const
router
=
useRouter
();
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
menus
=
[
{
...
...
@@ -26,28 +29,49 @@ export default function BottomMenuComponent() {
},
];
const
handleNavigation
=
(
path
)
=>
{
if
(
pathname
===
path
)
return
;
setLoading
(
true
);
router
.
push
(
path
);
};
// Matikan loading saat path berubah
useEffect
(()
=>
{
setLoading
(
false
);
},
[
pathname
]);
return
(
<
div
className
=
"
fixed bottom-0 left-0 right-0 z-40 bg-white border-t border-gray-200 shadow-md
"
>
<
div
className
=
"
flex justify-around items-center h-16
"
>
{
menus
.
map
((
menu
)
=>
{
const
isActive
=
pathname
===
menu
.
path
;
return
(
<
button
key
=
{
menu
.
path
}
onClick
=
{()
=>
router
.
push
(
menu
.
path
)}
className
=
{
classNames
(
'
flex flex-col items-center justify-center text-sm
'
,
isActive
?
'
text-blue-600 font-semibold
'
:
'
text-gray-500
'
)}
>
{
menu
.
icon
(
isActive
)}
<
span
className
=
"
text-xs mt-1
"
>
{
menu
.
label
}
<
/span
>
{
isActive
&&
<
div
className
=
"
w-1.5 h-1.5 bg-blue-600 rounded-full mt-1
"
/>
}
<
/button
>
);
})}
<>
{
/* Loading Overlay */
}
{
loading
&&
(
<
div
className
=
"
fixed inset-0 z-[9999] bg-white bg-opacity-80 flex items-center justify-center
"
>
<
Spin
size
=
"
large
"
/>
<
/div
>
)}
{
/* Bottom Menu */
}
<
div
className
=
"
fixed bottom-0 left-0 right-0 z-40 bg-white border-t border-gray-200 shadow-md
"
>
<
div
className
=
"
flex justify-around items-center h-16
"
>
{
menus
.
map
((
menu
)
=>
{
const
isActive
=
pathname
===
menu
.
path
;
return
(
<
button
key
=
{
menu
.
path
}
onClick
=
{()
=>
handleNavigation
(
menu
.
path
)}
className
=
{
classNames
(
'
flex flex-col items-center justify-center text-sm
'
,
isActive
?
'
text-blue-600 font-semibold
'
:
'
text-gray-500
'
)}
>
{
menu
.
icon
(
isActive
)}
<
span
className
=
"
text-xs mt-1
"
>
{
menu
.
label
}
<
/span
>
{
isActive
&&
<
div
className
=
"
w-1.5 h-1.5 bg-blue-600 rounded-full mt-1
"
/>
}
<
/button
>
);
})}
<
/div
>
<
/div
>
<
/
div
>
<
/
>
);
}
src/components/CameraComponent.js
View file @
5f9b580a
'
use client
'
;
import
{
useRef
,
useState
}
from
'
react
'
;
import
{
use
Effect
,
use
Ref
,
useState
}
from
'
react
'
;
import
Webcam
from
'
react-webcam
'
;
import
{
Button
,
message
}
from
'
antd
'
;
import
{
loadFaceApi
}
from
'
@/utils/loadFaceApi
'
;
// Fungsi kompresi gambar agar < 500KB
export
const
compressImage
=
async
(
base64
,
maxSizeKB
=
500
,
quality
=
0.7
)
=>
{
return
new
Promise
((
resolve
)
=>
{
const
img
=
new
Image
();
img
.
src
=
base64
;
export
default
function
CameraComponent
({
onCapture
})
{
const
webcamRef
=
useRef
(
null
);
const
canvasRef
=
useRef
(
null
);
const
[
faceDetected
,
setFaceDetected
]
=
useState
(
false
);
const
[
imageSrc
,
setImageSrc
]
=
useState
(
null
);
const
[
messageApi
,
contextHolder
]
=
message
.
useMessage
();
const
intervalRef
=
useRef
(
null
);
img
.
onload
=
()
=>
{
const
canvas
=
document
.
createElement
(
'
canvas
'
);
const
ctx
=
canvas
.
getContext
(
'
2d
'
);
useEffect
(()
=>
{
initFaceDetection
();
return
()
=>
{
if
(
intervalRef
.
current
)
clearInterval
(
intervalRef
.
current
);
};
},
[]);
canvas
.
width
=
img
.
width
;
canvas
.
height
=
img
.
height
;
const
initFaceDetection
=
async
()
=>
{
const
faceapi
=
await
loadFaceApi
()
;
// Flip secara horizontal (mirror)
ctx
.
translate
(
canvas
.
width
,
0
);
ctx
.
scale
(
-
1
,
1
);
ctx
.
drawImage
(
img
,
0
,
0
);
intervalRef
.
current
=
setInterval
(
async
()
=>
{
if
(
webcamRef
.
current
&&
webcamRef
.
current
.
video
&&
webcamRef
.
current
.
video
.
readyState
===
4
)
{
const
video
=
webcamRef
.
current
.
video
;
const
canvas
=
canvasRef
.
current
;
const
ctx
=
canvas
.
getContext
(
'
2d
'
);
let
compressedBase64
=
canvas
.
toDataURL
(
'
image/jpeg
'
,
quality
);
let
sizeInKB
=
Math
.
round
((
compressedBase64
.
length
*
(
3
/
4
))
/
1024
);
const
displaySize
=
{
width
:
video
.
videoWidth
,
height
:
video
.
videoHeight
,
};
// Loop jika masih > maxSizeKB
while
(
sizeInKB
>
maxSizeKB
&&
quality
>
0.1
)
{
quality
-=
0.05
;
compressedBase64
=
canvas
.
toDataURL
(
'
image/jpeg
'
,
quality
);
sizeInKB
=
Math
.
round
((
compressedBase64
.
length
*
(
3
/
4
))
/
1024
);
}
// Samakan ukuran canvas dengan video
canvas
.
width
=
displaySize
.
width
;
canvas
.
height
=
displaySize
.
height
;
resolve
(
compressedBase64
);
};
});
};
const
detections
=
await
faceapi
.
detectAllFaces
(
video
,
new
faceapi
.
TinyFaceDetectorOptions
({
inputSize
:
416
,
scoreThreshold
:
0.5
,
})
);
export
default
function
CameraComponent
({
onCapture
})
{
const
webcamRef
=
useRef
(
null
);
const
[
imageSrc
,
setImageSrc
]
=
useState
(
null
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
messageApi
,
contextHolder
]
=
message
.
useMessage
();
setFaceDetected
(
detections
.
length
>
0
);
const
resized
=
faceapi
.
resizeResults
(
detections
,
displaySize
);
const
capture
=
async
()
=>
{
if
(
!
webcamRef
.
current
)
return
;
// Gambar bounding box mirror
ctx
.
clearRect
(
0
,
0
,
canvas
.
width
,
canvas
.
height
);
ctx
.
save
();
ctx
.
scale
(
-
1
,
1
);
ctx
.
translate
(
-
canvas
.
width
,
0
);
faceapi
.
draw
.
drawDetections
(
canvas
,
resized
);
ctx
.
restore
();
}
},
400
);
};
const
capture
=
()
=>
{
const
screenshot
=
webcamRef
.
current
.
getScreenshot
();
if
(
!
screenshot
)
{
messageApi
.
error
(
'
Gagal mengambil gambar
'
);
return
;
}
setLoading
(
true
);
const
compressed
=
await
compressImage
(
screenshot
);
setImageSrc
(
compressed
);
setLoading
(
false
);
onCapture
(
compressed
);
setImageSrc
(
screenshot
);
onCapture
(
screenshot
);
};
const
reset
=
()
=>
{
...
...
@@ -65,18 +77,51 @@ export default function CameraComponent({ onCapture }) {
};
return
(
<
div
>
<
div
className
=
"
relative w-full
"
>
{
contextHolder
}
{
!
imageSrc
?
(
<>
<
Webcam
ref
=
{
webcamRef
}
audio
=
{
false
}
screenshotFormat
=
"
image/jpeg
"
width
=
"
100%
"
videoConstraints
=
{{
facingMode
:
'
user
'
}}
style
=
{{
transform
:
'
scaleX(-1)
'
}}
/
>
<
Button
type
=
"
primary
"
block
onClick
=
{
capture
}
className
=
"
mt-4
"
loading
=
{
loading
}
>
<
div
className
=
"
relative w-full
"
>
<
Webcam
ref
=
{
webcamRef
}
audio
=
{
false
}
screenshotFormat
=
"
image/jpeg
"
width
=
"
100%
"
videoConstraints
=
{{
facingMode
:
'
user
'
,
width
:
{
ideal
:
640
},
height
:
{
ideal
:
480
}
}}
// style={{ transform: 'scaleX(-1)' }}
/
>
<
canvas
ref
=
{
canvasRef
}
className
=
"
absolute top-0 left-0 z-10
"
style
=
{{
transform
:
'
scaleX(-1)
'
,
width
:
'
100%
'
,
height
:
'
100%
'
,
}}
/
>
<
/div
>
<
div
className
=
"
text-center mt-2 text-sm
"
>
{
faceDetected
?
(
<
span
className
=
"
text-green-600
"
>
Wajah
terdeteksi
✅
<
/span
>
)
:
(
<
span
className
=
"
text-red-500
"
>
Arahkan
wajah
ke
kamera
❌
<
/span
>
)}
<
/div
>
<
Button
type
=
"
primary
"
block
className
=
"
mt-4
"
onClick
=
{
capture
}
disabled
=
{
!
faceDetected
}
>
Ambil
Foto
<
/Button
>
<
/
>
...
...
@@ -84,10 +129,10 @@ export default function CameraComponent({ onCapture }) {
<>
<
img
src
=
{
imageSrc
}
alt
=
"
Hasil
Foto
"
className
=
"
w-64 h-64 object-cover
border rounded mx-auto
"
alt
=
"
Foto
"
className
=
"
w-64 h-64 object-cover
mx-auto border rounded
"
/>
<
Button
type
=
"
primary
"
block
className
=
"
mt-4
"
onClick
=
{
reset
}
>
<
Button
block
className
=
"
mt-4
"
onClick
=
{
reset
}
>
Ulangi
Foto
<
/Button
>
<
/
>
...
...
src/components/CameraFaceComponent-bak.js
0 → 100644
View file @
5f9b580a
'
use client
'
;
import
{
useEffect
,
useRef
,
useState
}
from
'
react
'
;
import
Webcam
from
'
react-webcam
'
;
import
{
Button
,
message
}
from
'
antd
'
;
import
{
loadFaceApi
}
from
'
@/utils/loadFaceApi
'
;
export
default
function
CameraFaceComponent
({
onCapture
})
{
const
webcamRef
=
useRef
(
null
);
const
canvasRef
=
useRef
(
null
);
const
[
eyesDetected
,
setEyesDetected
]
=
useState
(
false
);
const
[
imageSrc
,
setImageSrc
]
=
useState
(
null
);
const
[
messageApi
,
contextHolder
]
=
message
.
useMessage
();
const
intervalRef
=
useRef
(
null
);
useEffect
(()
=>
{
initFaceDetection
();
return
()
=>
{
if
(
intervalRef
.
current
)
clearInterval
(
intervalRef
.
current
);
};
},
[]);
const
initFaceDetection
=
async
()
=>
{
const
faceapi
=
await
loadFaceApi
();
intervalRef
.
current
=
setInterval
(
async
()
=>
{
if
(
webcamRef
.
current
&&
webcamRef
.
current
.
video
&&
webcamRef
.
current
.
video
.
readyState
===
4
)
{
const
video
=
webcamRef
.
current
.
video
;
const
canvas
=
canvasRef
.
current
;
const
ctx
=
canvas
.
getContext
(
'
2d
'
);
const
displaySize
=
{
width
:
video
.
videoWidth
,
height
:
video
.
videoHeight
,
};
canvas
.
width
=
displaySize
.
width
;
canvas
.
height
=
displaySize
.
height
;
const
detections
=
await
faceapi
.
detectAllFaces
(
video
,
new
faceapi
.
TinyFaceDetectorOptions
({
inputSize
:
416
,
scoreThreshold
:
0.5
,
})
)
.
withFaceLandmarks
();
ctx
.
clearRect
(
0
,
0
,
canvas
.
width
,
canvas
.
height
);
ctx
.
save
();
ctx
.
scale
(
-
1
,
1
);
ctx
.
translate
(
-
canvas
.
width
,
0
);
faceapi
.
draw
.
drawDetections
(
canvas
,
faceapi
.
resizeResults
(
detections
,
displaySize
));
// faceapi.draw.drawFaceLandmarks(canvas, faceapi.resizeResults(detections, displaySize));
ctx
.
restore
();
let
isEyesVisible
=
false
;
if
(
detections
.
length
>
0
)
{
const
landmarks
=
detections
[
0
].
landmarks
;
const
leftEye
=
landmarks
.
getLeftEye
();
const
rightEye
=
landmarks
.
getRightEye
();
// ✅ Satu mata pun cukup
isEyesVisible
=
leftEye
.
length
>
0
||
rightEye
.
length
>
0
;
}
setEyesDetected
(
isEyesVisible
);
}
else
{
setEyesDetected
(
false
);
}
},
400
);
};
const
capture
=
()
=>
{
const
screenshot
=
webcamRef
.
current
.
getScreenshot
();
setImageSrc
(
screenshot
);
onCapture
(
screenshot
);
};
const
reset
=
()
=>
{
setImageSrc
(
null
);
onCapture
(
null
);
};
return
(
<
div
className
=
"
relative w-full
"
>
{
contextHolder
}
{
!
imageSrc
?
(
<>
<
div
className
=
"
relative w-full
"
>
<
Webcam
ref
=
{
webcamRef
}
audio
=
{
false
}
screenshotFormat
=
"
image/jpeg
"
width
=
"
100%
"
videoConstraints
=
{{
facingMode
:
'
user
'
,
width
:
{
ideal
:
640
},
height
:
{
ideal
:
480
},
}}
/
>
<
canvas
ref
=
{
canvasRef
}
className
=
"
absolute top-0 left-0 z-10
"
style
=
{{
transform
:
'
scaleX(-1)
'
,
width
:
'
100%
'
,
height
:
'
100%
'
,
}}
/
>
<
/div
>
<
div
className
=
"
text-center mt-2 text-sm
"
>
{
eyesDetected
?
(
<
span
className
=
"
text-green-600
"
>
Wajah
terdeteksi
✅
<
/span
>
)
:
(
<
span
className
=
"
text-red-500
"
>
Arahkan
wajah
agar
terlihat
❌
<
/span
>
)}
<
/div
>
<
Button
type
=
"
primary
"
block
className
=
"
mt-4
"
onClick
=
{
capture
}
disabled
=
{
!
eyesDetected
}
>
Ambil
Foto
<
/Button
>
<
/
>
)
:
(
<>
<
img
src
=
{
imageSrc
}
alt
=
"
Foto
"
className
=
"
w-64 h-64 object-cover mx-auto border rounded
"
/>
<
Button
block
className
=
"
mt-4
"
onClick
=
{
reset
}
>
Ulangi
Foto
<
/Button
>
<
/
>
)}
<
/div
>
);
}
src/components/CameraFaceComponent.js
View file @
5f9b580a
...
...
@@ -8,7 +8,8 @@ import { loadFaceApi } from '@/utils/loadFaceApi';
export
default
function
CameraFaceComponent
({
onCapture
})
{
const
webcamRef
=
useRef
(
null
);
const
canvasRef
=
useRef
(
null
);
const
[
faceDetected
,
setFaceDetected
]
=
useState
(
false
);
const
[
eyesDetected
,
setEyesDetected
]
=
useState
(
false
);
const
[
faceBox
,
setFaceBox
]
=
useState
(
null
);
const
[
imageSrc
,
setImageSrc
]
=
useState
(
null
);
const
[
messageApi
,
contextHolder
]
=
message
.
useMessage
();
const
intervalRef
=
useRef
(
null
);
...
...
@@ -38,38 +39,72 @@ export default function CameraFaceComponent({ onCapture }) {
height
:
video
.
videoHeight
,
};
// Samakan ukuran canvas dengan video
canvas
.
width
=
displaySize
.
width
;
canvas
.
height
=
displaySize
.
height
;
const
detections
=
await
faceapi
.
detectAllFaces
(
video
,
new
faceapi
.
TinyFaceDetectorOptions
({
inputSize
:
416
,
scoreThreshold
:
0.5
,
})
);
const
detections
=
await
faceapi
.
detectAllFaces
(
video
,
new
faceapi
.
TinyFaceDetectorOptions
({
inputSize
:
416
,
scoreThreshold
:
0.5
,
})
)
.
withFaceLandmarks
();
setFaceDetected
(
detections
.
length
>
0
);
const
resized
=
faceapi
.
resizeResults
(
detections
,
displaySize
);
// Gambar bounding box mirror
ctx
.
clearRect
(
0
,
0
,
canvas
.
width
,
canvas
.
height
);
ctx
.
save
();
ctx
.
scale
(
-
1
,
1
);
ctx
.
translate
(
-
canvas
.
width
,
0
);
faceapi
.
draw
.
drawDetections
(
canvas
,
resized
);
faceapi
.
draw
.
drawDetections
(
canvas
,
faceapi
.
resizeResults
(
detections
,
displaySize
)
);
ctx
.
restore
();
let
isEyesVisible
=
false
;
let
boundingBox
=
null
;
if
(
detections
.
length
>
0
)
{
const
detection
=
detections
[
0
];
const
landmarks
=
detection
.
landmarks
;
const
leftEye
=
landmarks
.
getLeftEye
();
const
rightEye
=
landmarks
.
getRightEye
();
isEyesVisible
=
leftEye
.
length
>
0
||
rightEye
.
length
>
0
;
boundingBox
=
detection
.
detection
.
box
;
}
setEyesDetected
(
isEyesVisible
);
setFaceBox
(
boundingBox
);
}
else
{
setEyesDetected
(
false
);
setFaceBox
(
null
);
}
},
400
);
};
const
capture
=
()
=>
{
const
screenshot
=
webcamRef
.
current
.
getScreenshot
();
setImageSrc
(
screenshot
);
onCapture
(
screenshot
);
};
const
video
=
webcamRef
.
current
.
video
;
if
(
!
eyesDetected
)
{
messageApi
.
error
(
'
Wajah tidak terdeteksi!
'
);
return
;
}
const
canvas
=
document
.
createElement
(
'
canvas
'
);
const
ctx
=
canvas
.
getContext
(
'
2d
'
);
const
videoWidth
=
video
.
videoWidth
;
const
videoHeight
=
video
.
videoHeight
;
canvas
.
width
=
videoWidth
;
canvas
.
height
=
videoHeight
;
ctx
.
drawImage
(
video
,
0
,
0
,
videoWidth
,
videoHeight
);
const
fullImage
=
canvas
.
toDataURL
(
'
image/jpeg
'
);
setImageSrc
(
fullImage
);
onCapture
(
fullImage
);
};
const
reset
=
()
=>
{
setImageSrc
(
null
);
...
...
@@ -89,29 +124,27 @@ export default function CameraFaceComponent({ onCapture }) {
screenshotFormat
=
"
image/jpeg
"
width
=
"
100%
"
videoConstraints
=
{{
facingMode
:
'
user
'
,
width
:
{
ideal
:
640
},
height
:
{
ideal
:
480
}
}}
// style={{ transform: 'scaleX(-1)' }}
facingMode
:
'
user
'
,
width
:
{
ideal
:
640
},
height
:
{
ideal
:
480
},
}}
/
>
<
canvas
ref
=
{
canvasRef
}
className
=
"
absolute top-0 left-0 z-10
"
style
=
{{
transform
:
'
scaleX(-1)
'
,
width
:
'
100%
'
,
height
:
'
100%
'
,
}}
ref
=
{
canvasRef
}
className
=
"
absolute top-0 left-0 z-10
"
style
=
{{
transform
:
'
scaleX(-1)
'
,
width
:
'
100%
'
,
height
:
'
100%
'
,
}}
/
>
<
/div
>
<
div
className
=
"
text-center mt-2 text-sm
"
>
{
face
Detected
?
(
{
eyes
Detected
?
(
<
span
className
=
"
text-green-600
"
>
Wajah
terdeteksi
✅
<
/span
>
)
:
(
<
span
className
=
"
text-red-500
"
>
Arahkan
wajah
ke
kamera
❌
<
/span
>
<
span
className
=
"
text-red-500
"
>
Arahkan
wajah
agar
terlihat
❌
<
/span
>
)}
<
/div
>
...
...
@@ -120,7 +153,7 @@ export default function CameraFaceComponent({ onCapture }) {
block
className
=
"
mt-4
"
onClick
=
{
capture
}
disabled
=
{
!
faceDetected
}
disabled
=
{
!
eyesDetected
||
!
faceBox
}
>
Ambil
Foto
<
/Button
>
...
...
src/components/DetailPresenceCardComponent.js
View file @
5f9b580a
import
React
from
'
react
'
;
import
React
,
{
useState
}
from
'
react
'
;
import
{
Card
,
Avatar
,
Row
,
Col
,
Flex
,
Badge
,
Button
}
from
'
antd
'
;
import
{
CloseCircleOutlined
,
LoginOutlined
,
LogoutOutlined
}
from
'
@ant-design/icons
'
;
import
{
LogIn
,
LogOut
}
from
'
lucide-react
'
;
import
{
useRouter
}
from
'
next/navigation
'
;
import
usePresenceStore
from
'
@/stores/usePresenceStore
'
;
import
LoadingComponent
from
'
./LoadingComponent
'
;
import
moment
from
"
moment
"
;
const
{
Meta
}
=
Card
;
export
default
function
DetailPresenceCardComponent
({
date
,
presences
})
{
const
router
=
useRouter
();
const
setPresenceData
=
usePresenceStore
((
state
)
=>
state
.
setPresenceData
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
loadingCheckOutTelat
,
setLoadingCheckOutTelat
]
=
useState
(
false
);
function
getDayName
(
dateString
)
{
...
...
@@ -27,11 +31,12 @@ export default function DetailPresenceCardComponent({ date, presences }) {
}
const
handleClick
=
()
=>
{
setLoading
(
true
);
setPresenceData
({
date
,
presences
});
router
.
push
(
'
/attendance/detail
'
);
setLoading
(
false
);
}
const
checkDayWork
=
(
date
,
presences
)
=>
{
if
(
getDayName
(
date
)
===
'
Sabtu
'
||
getDayName
(
date
)
===
'
Minggu
'
){
if
(
!
presences
[
0
])
{
...
...
@@ -44,38 +49,86 @@ export default function DetailPresenceCardComponent({ date, presences }) {
}
}
// getDayName(date) === 'Sabtu' || getDayName(date) === 'Minggu' ? !presences[0] ? 'Hari Libur' : 'Hari Kerja' : 'Hari Kerja'
const
handelCheckOutDelay
=
(
presences
)
=>
{
setLoadingCheckOutTelat
(
true
);
router
.
push
(
`/check-out-telat/
${
presences
[
0
]?.
id
}
`);
setLoadingCheckOutTelat(false);
}
const startTime = moment(moment(presences[0]?.date).format(`
YYYY
MM
DD
$
{
presences
[
0
]?.
time
}
`), "YYYY-MM-DD HH:mm:ss");
// Tanggal dan waktu akhir
const endTime = moment(moment(), "YYYY-MM-DD HH:mm:ss");
// Menghitung selisih waktu
const duration = moment.duration(endTime.diff(startTime));
const hours = duration.asHours();
return (
<
div
className
=
'
mt-4
'
>
<
Badge
.
Ribbon
text
=
{
getDayName
(
date
)}
style
=
{
checkDayWork
(
date
,
presences
)
===
'
Hari Kerja
'
?
{
width
:
80
,
textAlign
:
'
center
'
,
backgroundColor
:
'
#D98324
'
}
:
{
width
:
80
,
textAlign
:
'
center
'
,
backgroundColor
:
'
#D98324
'
}
}
>
<
div
className
=
'
card-shadow cursor-pointer hover:shadow-lg transition
'
onClick
=
{
handleClick
}
>
<
div
className
=
'
font-bold
'
>
{
formatTanggalIndo
(
date
)}
<
/div
>
<
div
className
=
'
mt-4 mb-4
'
>
<
Row
className
=
"
gutter-row
"
span
=
{
12
}
>
<
Col
span
=
{
12
}
>
<
Flex
align
=
'
center
'
gap
=
{
'
small
'
}
>
<
Avatar
size
=
"
middle
"
src
=
{
<
LogIn
size
=
{
16
}
style
=
{{
color
:
"
#443627
"
}}
/>} style={{ background: '#EFDCAB'}} /
>
<
div
className
=
'
text-md
'
>
{
presences
[
0
]?.
time
||
'
00:00:00
'
}
WIB
<
/div
>
<
/Flex
>
<
/Col
>
<
Col
span
=
{
12
}
>
<
Flex
align
=
'
center
'
gap
=
{
'
small
'
}
>
<
Avatar
size
=
"
middle
"
src
=
{
<
LogOut
size
=
{
16
}
style
=
{{
color
:
"
#443627
"
,
marginLeft
:
4
}}
/>} style={{ background: '#EFDCAB'}} /
>
<
div
className
=
'
text-md
'
>
{
presences
[
0
]?.
presence
?
presences
[
0
]?.
presence
?.
time
:
'
00:00:00
'
}
WIB
<
/div
>
<
/Flex
>
<
/Col
>
<
/Row
>
<div>
{loading ?
<>
<LoadingComponent />
</> :
<div className='mt-4' >
<Badge.Ribbon text={getDayName(date)}
style={checkDayWork(date, presences) === 'Hari Kerja' ?
{ width: 80, textAlign: 'center', backgroundColor:'#D98324' } :
{ width: 80, textAlign: 'center', backgroundColor:'#D98324' }
}
>
<div className='card-shadow cursor-pointer hover:shadow-lg transition'
onClick={handleClick}>
<div className='font-bold'>
{formatTanggalIndo(date)}
</div>
<div className='mt-4 mb-4'>
<Row className="gutter-row" span={12}>
<Col span={12}>
<Flex align='center' gap={'small'}>
<Avatar size="middle" src={<LogIn size={16} style={{ color:"#443627" }} />} style={{ background: '#EFDCAB'}} />
<div className='text-md'>{presences[0]?.time || '00:00:00'} WIB</div>
</Flex>
</Col>
<Col span={12}>
<Flex align='center' gap={'small'}>
<Avatar size="middle" src={<LogOut size={16} style={{ color:"#443627", marginLeft: 4 }} />} style={{ background: '#EFDCAB'}} />
<div className='text-md'>{presences[0]?.presence ? presences[0]?.presence?.time : '00:00:00'} WIB</div>
</Flex>
{moment().subtract(1, "days").format("DD MMMM YYYY") == moment(presences[0]?.date).format("DD MMMM YYYY") ?
presences[0]?.presence === null ?
hours <= 23 ?
// hours <= 16 ?
<div className='mt-2'>
<Button
className="text-12"
type='primary'
block
size='small'
onClick={(e) => {
e.stopPropagation()
handelCheckOutDelay(presences)
}}
loading={loadingCheckOutTelat}
>
<div className='text-sm mt-1'>
Pulang
</div>
</Button>
</div>
: ""
: ""
: ""
}
</Col>
</Row>
</div>
</div>
<
/div
>
<
/Badge.Ribbon
>
</Badge.Ribbon>
</div>
}
</div>
);
}
src/components/PresenceCardComponent.js
View file @
5f9b580a
import
React
,
{
useEffect
,
useState
}
from
'
react
'
;
import
{
Card
,
Avatar
,
Row
,
Col
,
Flex
,
Badge
,
Button
,
Tag
}
from
'
antd
'
;
import
Icon
,
{
CloseCircleOutlined
,
CloseCircleTwoTone
,
LoginOutlined
,
LogoutOutlined
}
from
'
@ant-design/icons
'
;
import
{
Avatar
,
Button
,
Flex
}
from
'
antd
'
;
import
{
LogIn
,
LogOut
}
from
'
lucide-react
'
;
import
{
useRouter
}
from
'
next/navigation
'
;
const
{
Meta
}
=
Card
;
export
default
function
PresenceCardComponent
({
presenceType
,
presenceUrl
,
presenceData
})
{
const
router
=
useRouter
();
const
handleClick
=
(
type
)
=>
{
if
(
type
===
'
Datang
'
){
router
.
push
(
'
/check-in
'
);
}
else
{
router
.
push
(
'
/check-out
'
);
}
}
// console.log(presenceData?.presence?.time)
export
default
function
PresenceCardComponent
({
presenceType
,
presenceUrl
,
presenceData
,
loading
,
onClick
})
{
return
(
<
div
className
=
'
mt-4
'
>
<
div
className
=
'
card bg-white
'
>
<
div
>
<
Flex
justify
=
'
flex-start
'
align
=
'
center
'
gap
=
{
'
small
'
}
>
<
div
className
=
'
max-w-sm
'
>
{
!
presenceUrl
?
presenceType
===
'
Datang
'
?
<
Avatar
size
=
"
middle
"
src
=
{
<
LogIn
size
=
{
16
}
style
=
{{
color
:
"
#443627
"
}}
/>} style={{ background: '#EFDCAB'}} /
>
:
<
Avatar
size
=
"
middle
"
src
=
{
<
LogOut
size
=
{
16
}
style
=
{{
color
:
"
#443627
"
,
marginLeft
:
4
}}
/>} style={{ background: '#EFDCAB'}} /
>
:
<
Avatar
shape
=
'
square
'
size
=
"
middle
"
src
=
"
/bg/prof.jpg
"
/>
}
<
/div
>
<
div
className
=
'
text-sm
'
>
{
presenceType
===
'
Datang
'
?
<
div
className
=
'
font-bold text-sm
'
>
{
presenceData
?.
time
||
'
00:00
'
}
WIB
<
/div>
:
<
div
className
=
'
font-bold text-sm
'
>
{
presenceData
?.
presence
?.
time
||
'
00:00
'
}
WIB
<
/div
>
}
<
/div
>
<
/Flex
>
<
div
className
=
'
mt-2
'
>
{
presenceType
===
"
Datang
"
?
(
<
Button
size
=
'
small
'
type
=
'
primary
'
onClick
=
{()
=>
handleClick
(
presenceType
)}
block
disabled
=
{
presenceData
?.
presence_type
===
'
masuk
'
}
>
<
div
className
=
'
text-sm
'
>
Datang
<
/div
>
<
/Button>
)
:
(
<
Button
size
=
'
small
'
type
=
'
primary
'
onClick
=
{()
=>
handleClick
(
presenceType
)}
block
disabled
=
{
presenceData
?.
presence
?.
presence_type
===
'
pulang
'
||
!
presenceData
?
true
:
false
}
>
<
div
className
=
'
text-sm
'
>
Pulang
<
/div
>
<
/Button
>
)
}
<
/div
>
<
div
className
=
'
card bg-white
'
>
<
div
>
<
Flex
justify
=
'
flex-start
'
align
=
'
center
'
gap
=
{
'
small
'
}
>
<
div
className
=
'
max-w-sm
'
>
{
!
presenceUrl
?
(
presenceType
===
'
Datang
'
?
(
<
Avatar
size
=
"
middle
"
src
=
{
<
LogIn
size
=
{
16
}
style
=
{{
color
:
"
#443627
"
}}
/>} style={{ background: '#EFDCAB' }} /
>
)
:
(
<
Avatar
size
=
"
middle
"
src
=
{
<
LogOut
size
=
{
16
}
style
=
{{
color
:
"
#443627
"
,
marginLeft
:
4
}}
/>} style={{ background: '#EFDCAB' }} /
>
)
)
:
(
<
Avatar
shape
=
'
square
'
size
=
"
middle
"
src
=
"
/bg/prof.jpg
"
/>
)}
<
/div
>
<
div
className
=
'
text-sm font-bold
'
>
{
presenceType
===
'
Datang
'
?
`
${
presenceData
?.
time
||
'
00:00
'
}
WIB
`
: `
$
{
presenceData
?.
presence
?.
time
||
'
00:00
'
}
WIB
`}
</div>
</Flex>
<div className='mt-2'>
<Button
size='small'
type='primary'
block
loading={loading}
onClick={onClick}
disabled={
presenceType === 'Datang'
? presenceData?.presence_type === 'masuk'
: presenceData?.presence?.presence_type === 'pulang' || !presenceData
}
>
<div className='text-sm'>{presenceType}</div>
</Button>
</div>
</div>
</div>
</div>
);
}
src/components/ProfileCardComponent.js
View file @
5f9b580a
...
...
@@ -13,7 +13,7 @@ export default function ProfileCardComponent({ name, image }) {
<
div
className
=
'
flex justify-between
'
>
<
div
>
<
div
className
=
'
font-bold
'
>
{
session
?.
user
?.
name
||
'
-
'
}
<
/div
>
<
div
>
{
session
?.
user
?.
nip
||
'
NIP
'
}
<
/div
>
<
div
>
{
session
?.
nip
||
'
NIP
'
}
<
/div
>
<
/div
>
<
Avatar
style
=
{{
border
:
'
2px solid #D98324
'
}}
size
=
{
'
large
'
}
src
=
{
"
/bg/prof.jpg
"
}
/
>
<
/div
>
...
...
src/middleware.js
View file @
5f9b580a
...
...
@@ -37,6 +37,13 @@ export async function middleware(req) {
}
}
if
(
currentPath
===
"
/check-out
"
)
{
const
hasAbsen
=
req
.
cookies
.
get
(
"
hasAbsen
"
)?.
value
;
if
(
hasAbsen
===
"
true
"
)
{
return
NextResponse
.
redirect
(
new
URL
(
"
/
"
,
req
.
url
));
}
}
return
NextResponse
.
next
();
}
...
...
src/utils/loadFaceApi.js
View file @
5f9b580a
...
...
@@ -5,7 +5,10 @@ export const loadFaceApi = async () => {
const
mod
=
await
import
(
'
@vladmandic/face-api
'
);
faceapi
=
mod
;
await
faceapi
.
nets
.
tinyFaceDetector
.
loadFromUri
(
'
/models
'
);
await
Promise
.
all
([
faceapi
.
nets
.
tinyFaceDetector
.
loadFromUri
(
'
/models
'
),
faceapi
.
nets
.
faceLandmark68Net
.
loadFromUri
(
'
/models
'
),
]);
}
return
faceapi
;
};
src/utils/locationUtils.js
View file @
5f9b580a
...
...
@@ -10,12 +10,12 @@ export const getLocationAddress = async (lat, lon) => {
}
else
{
// Jika error dari server (misal rate limit, invalid key, dll)
const
errorData
=
await
response
.
json
();
console
.
error
(
'
Error fetching location:
'
,
errorData
);
//
console.error('Error fetching location:', errorData);
return
'
Alamat tidak terdeteksi
'
;
}
}
catch
(
error
)
{
// Jika error jaringan atau timeout
console
.
error
(
'
Network error:
'
,
error
);
//
console.error('Network error:', error);
return
'
Alamat tidak terdeteksi
'
;
}
};
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment