Compare commits
4 Commits
nico/9-sep
...
nico/16-se
| Author | SHA1 | Date | |
|---|---|---|---|
| 39e1e7b575 | |||
| 4ceea5203f | |||
| a5d841bb6b | |||
| 6a7bd386ae |
@@ -80,9 +80,11 @@
|
|||||||
"react-transition-group": "^4.4.5",
|
"react-transition-group": "^4.4.5",
|
||||||
"readdirp": "^4.1.1",
|
"readdirp": "^4.1.1",
|
||||||
"recharts": "^2.15.3",
|
"recharts": "^2.15.3",
|
||||||
|
"sharp": "^0.34.3",
|
||||||
"swr": "^2.3.2",
|
"swr": "^2.3.2",
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^11.1.0",
|
||||||
"valtio": "^2.1.3",
|
"valtio": "^2.1.3",
|
||||||
|
"zlib": "^1.0.5",
|
||||||
"zod": "^3.24.3"
|
"zod": "^3.24.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,51 +1,99 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"month": "Jan",
|
"month": "Jan",
|
||||||
"year": 2025,
|
"year": 2025,
|
||||||
"totalUnemployment": 160,
|
"totalUnemployment": 160,
|
||||||
"educatedUnemployment": 95,
|
"educatedUnemployment": 95,
|
||||||
"uneducatedUnemployment": 65,
|
"uneducatedUnemployment": 65,
|
||||||
"percentageChange": null
|
"percentageChange": 0.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "Feb",
|
"month": "Feb",
|
||||||
"year": 2025,
|
"year": 2025,
|
||||||
"totalUnemployment": 155,
|
"totalUnemployment": 158,
|
||||||
"educatedUnemployment": 90,
|
"educatedUnemployment": 93,
|
||||||
"uneducatedUnemployment": 65,
|
"uneducatedUnemployment": 65,
|
||||||
"percentageChange": -3.1
|
"percentageChange": -1.25
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "Mar",
|
"month": "Mar",
|
||||||
"year": 2025,
|
"year": 2025,
|
||||||
"totalUnemployment": 150,
|
"totalUnemployment": 155,
|
||||||
"educatedUnemployment": 88,
|
"educatedUnemployment": 91,
|
||||||
"uneducatedUnemployment": 62,
|
"uneducatedUnemployment": 64,
|
||||||
"percentageChange": -3.2
|
"percentageChange": -1.90
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "Apr",
|
"month": "Apr",
|
||||||
"year": 2025,
|
"year": 2025,
|
||||||
"totalUnemployment": 148,
|
"totalUnemployment": 152,
|
||||||
"educatedUnemployment": 85,
|
"educatedUnemployment": 89,
|
||||||
"uneducatedUnemployment": 63,
|
"uneducatedUnemployment": 63,
|
||||||
"percentageChange": -1.3
|
"percentageChange": -1.94
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "Mei",
|
"month": "Mei",
|
||||||
"year": 2025,
|
"year": 2025,
|
||||||
"totalUnemployment": 145,
|
"totalUnemployment": 150,
|
||||||
"educatedUnemployment": 82,
|
"educatedUnemployment": 88,
|
||||||
"uneducatedUnemployment": 63,
|
"uneducatedUnemployment": 62,
|
||||||
"percentageChange": -2.0
|
"percentageChange": -1.32
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"month": "Jun",
|
"month": "Jun",
|
||||||
"year": 2025,
|
"year": 2025,
|
||||||
"totalUnemployment": 140,
|
"totalUnemployment": 148,
|
||||||
"educatedUnemployment": 80,
|
"educatedUnemployment": 87,
|
||||||
"uneducatedUnemployment": 60,
|
"uneducatedUnemployment": 61,
|
||||||
"percentageChange": -3.4
|
"percentageChange": -1.33
|
||||||
}
|
},
|
||||||
]
|
{
|
||||||
|
"month": "Jul",
|
||||||
|
"year": 2025,
|
||||||
|
"totalUnemployment": 145,
|
||||||
|
"educatedUnemployment": 85,
|
||||||
|
"uneducatedUnemployment": 60,
|
||||||
|
"percentageChange": -2.03
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "Agu",
|
||||||
|
"year": 2025,
|
||||||
|
"totalUnemployment": 142,
|
||||||
|
"educatedUnemployment": 84,
|
||||||
|
"uneducatedUnemployment": 58,
|
||||||
|
"percentageChange": -2.07
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "Sep",
|
||||||
|
"year": 2025,
|
||||||
|
"totalUnemployment": 140,
|
||||||
|
"educatedUnemployment": 83,
|
||||||
|
"uneducatedUnemployment": 57,
|
||||||
|
"percentageChange": -1.41
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "Okt",
|
||||||
|
"year": 2025,
|
||||||
|
"totalUnemployment": 138,
|
||||||
|
"educatedUnemployment": 82,
|
||||||
|
"uneducatedUnemployment": 56,
|
||||||
|
"percentageChange": -1.43
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "Nov",
|
||||||
|
"year": 2025,
|
||||||
|
"totalUnemployment": 135,
|
||||||
|
"educatedUnemployment": 80,
|
||||||
|
"uneducatedUnemployment": 55,
|
||||||
|
"percentageChange": -2.17
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "Des",
|
||||||
|
"year": 2025,
|
||||||
|
"totalUnemployment": 132,
|
||||||
|
"educatedUnemployment": 78,
|
||||||
|
"uneducatedUnemployment": 54,
|
||||||
|
"percentageChange": -2.22
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
137
prisma/data/file-storage.json
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "cmff0rr4z0002vn0twp333m2",
|
||||||
|
"name": "S6RIjFaPvdQm3oq4rM4X9-desktop.webp",
|
||||||
|
"realName": "bares.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/S6RIjFaPvdQm3oq4rM4X9-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmff0tnf00003vn0t3kgzi0u0",
|
||||||
|
"name": "_pVNEmThU5ICGa8gv3gh_-desktop.webp",
|
||||||
|
"realName": "bicara-darma.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/_pVNEmThU5ICGa8gv3gh_-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmff0uykf0004vn0trmmxpgfh",
|
||||||
|
"name": "bv6rdKvjxkkjUSGLQ0lvB-desktop.webp",
|
||||||
|
"realName": "daves.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/bv6rdKvjxkkjUSGLQ0lvB-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmff0z34f0005vn0tjtvq519p",
|
||||||
|
"name": "Z4hWaV04CvoE20MjccQsV-desktop.webp",
|
||||||
|
"realName": "mangan.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/Z4hWaV04CvoE20MjccQsV-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmff38cyq000bvn0t9f01cz3f",
|
||||||
|
"name": "LvLAtOqWojx4sn6NjJWB9-desktop.webp",
|
||||||
|
"realName": "gelah-melah.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/LvLAtOqWojx4sn6NjJWB9-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmff0zqvd0007vn0tv6o5hjcq",
|
||||||
|
"name": "gR2mcvAQVgJ2-rM5coYJj-desktop.webp",
|
||||||
|
"realName": "inovasi-desa-darmasaba.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/gR2mcvAQVgJ2-rM5coYJj-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmff1013m0008vn0th7t0d64d",
|
||||||
|
"name": "JpL-9F8-IGztMn8E2ce02-desktop.webp",
|
||||||
|
"realName": "pdkt.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/JpL-9F8-IGztMn8E2ce02-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmff10cwq0009vn0tse8dzu3j",
|
||||||
|
"name": "bxAk4AsGbJTC705_IVdes-desktop.webp",
|
||||||
|
"realName": "sajjiana-dharma-raksaka.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/bxAk4AsGbJTC705_IVdes-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmff2w5ly000avn0telhct71k",
|
||||||
|
"name": "Vbj_osnMJUkGEQGDTLwV--desktop.webp",
|
||||||
|
"realName": "perbekel.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/Vbj_osnMJUkGEQGDTLwV--desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmff3joae0000vn6h8sgs0ilg",
|
||||||
|
"name": "7hox9spUxj56hY_EBYLnj-desktop.webp",
|
||||||
|
"realName": "youtube.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/7hox9spUxj56hY_EBYLnj-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmff3ll130001vn6hkhls3f5y",
|
||||||
|
"name": "ChihV7_1eS-AGtSg9UwMv-desktop.webp",
|
||||||
|
"realName": "gmail.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/ChihV7_1eS-AGtSg9UwMv-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmff3mtat0002vn6hs8vyyhdd",
|
||||||
|
"name": "z8v9ZREwOJHKGIRYauROt-desktop.webp",
|
||||||
|
"realName": "facebook.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/z8v9ZREwOJHKGIRYauROt-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmff3nv180003vn6h5jvedidq",
|
||||||
|
"name": "BLjMxTKoCNE31uOURR3IU-desktop.webp",
|
||||||
|
"realName": "telephone-call.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/BLjMxTKoCNE31uOURR3IU-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmff3oouh0004vn6hd94brzv9",
|
||||||
|
"name": "hkJYAeTNWK_vYaYS20w3I-desktop.webp",
|
||||||
|
"realName": "instagram.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/hkJYAeTNWK_vYaYS20w3I-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmff3q12g0005vn6h5ojov2qa",
|
||||||
|
"name": "6XEoZ9SFu59COpil03Gya-desktop.webp",
|
||||||
|
"realName": "tiktok.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/6XEoZ9SFu59COpil03Gya-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -2,31 +2,37 @@
|
|||||||
{
|
{
|
||||||
"id": "cmds8w2q60002vnbe6i8qhkuo",
|
"id": "cmds8w2q60002vnbe6i8qhkuo",
|
||||||
"name": "Telephone Desa Darmasaba",
|
"name": "Telephone Desa Darmasaba",
|
||||||
"iconUrl": "081239580000"
|
"iconUrl": "081239580000",
|
||||||
|
"imageId": "cmff3nv180003vn6h5jvedidq"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmds8z7u20005vnbegyyvnbk0",
|
"id": "cmds8z7u20005vnbegyyvnbk0",
|
||||||
"name": "Email Desa Darmasaba",
|
"name": "Email Desa Darmasaba",
|
||||||
"iconUrl": "desadarmasaba@badungkab.go.id"
|
"iconUrl": "desadarmasaba@badungkab.go.id",
|
||||||
|
"imageId": "cmff3ll130001vn6hkhls3f5y"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmds9023u0008vnbe3oxmhwyf",
|
"id": "cmds9023u0008vnbe3oxmhwyf",
|
||||||
"name": "Desa Darmasaba",
|
"name": "Desa Darmasaba",
|
||||||
"iconUrl": "https://www.youtube.com/channel/UCtPw9WOQO7d2HIKzKgel4Xg"
|
"iconUrl": "https://www.youtube.com/channel/UCtPw9WOQO7d2HIKzKgel4Xg",
|
||||||
|
"imageId": "cmff3joae0000vn6h8sgs0ilg"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmds90oul000bvnbe2bqkptoi",
|
"id": "cmds90oul000bvnbe2bqkptoi",
|
||||||
"name": "Pemerintah Desa Darmasaba",
|
"name": "Pemerintah Desa Darmasaba",
|
||||||
"iconUrl": "https://www.facebook.com/DarmasabaDesaku"
|
"iconUrl": "https://www.facebook.com/DarmasabaDesaku",
|
||||||
|
"imageId": "cmff3mtat0002vn6hs8vyyhdd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmds91i4e000evnbe8gtf1gub",
|
"id": "cmds91i4e000evnbe8gtf1gub",
|
||||||
"name": "ddarmasaba",
|
"name": "ddarmasaba",
|
||||||
"iconUrl": "https://www.instagram.com/ddarmasaba/"
|
"iconUrl": "https://www.instagram.com/ddarmasaba/",
|
||||||
|
"imageId": "cmff3oouh0004vn6hd94brzv9"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmds92de5000hvnbemlu6sq5x",
|
"id": "cmds92de5000hvnbemlu6sq5x",
|
||||||
"name": "desa.darmasaba",
|
"name": "desa.darmasaba",
|
||||||
"iconUrl": "https://www.tiktok.com/@desa.darmasaba?is_from_webapp=1&sender_device=pc"
|
"iconUrl": "https://www.tiktok.com/@desa.darmasaba?is_from_webapp=1&sender_device=pc",
|
||||||
|
"imageId": "cmff3q12g0005vn6h5ojov2qa"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
{
|
{
|
||||||
"id": "edit",
|
"id": "edit",
|
||||||
"name": "I.B Surya Prabhawa Manuaba, S.H., M.H.",
|
"name": "I.B Surya Prabhawa Manuaba, S.H., M.H.",
|
||||||
"position": "Perbekel Darmasaba periode 2021-2027"
|
"position": "Perbekel Darmasaba periode 2021-2027",
|
||||||
|
"imageId": "cmff2w5ly000avn0telhct71k"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,50 +1,51 @@
|
|||||||
[
|
[
|
||||||
{
|
|
||||||
"id": "cmdr7039z0002vn5rttctt9hn",
|
|
||||||
"name": "Davest",
|
|
||||||
"description": "Darmasaba Village Festval",
|
|
||||||
"link": "https://darmasaba.desa.id/berita/55862-rakor-davest-2024"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "cmdr755pf0005vn5rp8tyuubw",
|
"id": "cmdr755pf0005vn5rp8tyuubw",
|
||||||
"name": "Dmangan",
|
"name": "Dmangan",
|
||||||
"description": "Darmasaba Aman Pangan",
|
"description": "Darmasaba Aman Pangan",
|
||||||
"link": "https://darmasaba.desa.id/berita/61452-kader-d-mangan-berhasil-meraih-prestasi-dalam-ajang-lomba-banjar-bali-quis-bbq-tahun-2024"
|
"link": "https://darmasaba.desa.id/berita/61452-kader-d-mangan-berhasil-meraih-prestasi-dalam-ajang-lomba-banjar-bali-quis-bbq-tahun-2024",
|
||||||
|
"imageId" : "cmff0z34f0005vn0tjtvq519p"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdr76nqk0008vn5rdddvcxnr",
|
"id": "cmdr76nqk0008vn5rdddvcxnr",
|
||||||
"name": "Bicara Darmasaba",
|
"name": "Bicara Darmasaba",
|
||||||
"description": "Bicara Darmasaba",
|
"description": "Bicara Darmasaba",
|
||||||
"link": "https://darmasaba.desa.id/berita/42506-bicara-darmasaba"
|
"link": "https://darmasaba.desa.id/berita/42506-bicara-darmasaba",
|
||||||
|
"imageId" : "cmff0tnf00003vn0t3kgzi0u0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdr77vbw000bvn5rvpmoq31s",
|
"id": "cmdr77vbw000bvn5rvpmoq31s",
|
||||||
"name": "Bares",
|
"name": "Bares",
|
||||||
"description": "Darmasaba Recycling Stock/Exchange",
|
"description": "Darmasaba Recycling Stock/Exchange",
|
||||||
"link": "http://darmasaba.desa.id/berita/56722-bares"
|
"link": "http://darmasaba.desa.id/berita/56722-bares",
|
||||||
|
"imageId" : "cmff0rr4z0002vn0twp333m2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdr7bxtp000evn5rmy85wihx",
|
"id": "cmdr7bxtp000evn5rmy85wihx",
|
||||||
"name": "Sajjana Dharma Raksaka",
|
"name": "Sajjana Dharma Raksaka",
|
||||||
"description": "Sajjana Dharma Raksaka",
|
"description": "Sajjana Dharma Raksaka",
|
||||||
"link": "https://ppid.badungkab.go.id/storage/dokumen/5RS9dldGkrgzMQq6bKdZsqsVRHI8gffWv4PGfb3r.pdf"
|
"link": "https://ppid.badungkab.go.id/storage/dokumen/5RS9dldGkrgzMQq6bKdZsqsVRHI8gffWv4PGfb3r.pdf",
|
||||||
|
"imageId" : "cmff10cwq0009vn0tse8dzu3j"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdr7dlnk000hvn5r9lur3z35",
|
"id": "cmdr7dlnk000hvn5r9lur3z35",
|
||||||
"name": "PDKT",
|
"name": "PDKT",
|
||||||
"description": "Perangkat Desa Kuat Teknologi",
|
"description": "Perangkat Desa Kuat Teknologi",
|
||||||
"link": "https://darmasaba.desa.id/berita/53752-p-d-k-t"
|
"link": "https://darmasaba.desa.id/berita/53752-p-d-k-t",
|
||||||
|
"imageId" : "cmff1013m0008vn0th7t0d64d"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdr7ftob000mvn5rfhgdtg8v",
|
"id": "cmdr7ftob000mvn5rfhgdtg8v",
|
||||||
"name": "GM",
|
"name": "GM",
|
||||||
"description": "Galah Melah",
|
"description": "Galah Melah",
|
||||||
"link": "https://darmasaba.desa.id/berita/52880-galah-melah"
|
"link": "https://darmasaba.desa.id/berita/52880-galah-melah",
|
||||||
|
"imageId" : "cmff38cyq000bvn0t9f01cz3f"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdr7glue000pvn5r6onzslju",
|
"id": "cmdr7glue000pvn5r6onzslju",
|
||||||
"name": "Inovasi Desa Darmasaba",
|
"name": "Inovasi Desa Darmasaba",
|
||||||
"description": "Inovasi Desa Darmasaba",
|
"description": "Inovasi Desa Darmasaba",
|
||||||
"link": "https://darmasaba.desa.id/produk-lokal-desa"
|
"link": "https://darmasaba.desa.id/produk-lokal-desa",
|
||||||
|
"imageId" : "cmff0zqvd0007vn0tv6o5hjcq"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ model FileStorage {
|
|||||||
PejabatDesa PejabatDesa[]
|
PejabatDesa PejabatDesa[]
|
||||||
MediaSosial MediaSosial[]
|
MediaSosial MediaSosial[]
|
||||||
DesaAntiKorupsi DesaAntiKorupsi[]
|
DesaAntiKorupsi DesaAntiKorupsi[]
|
||||||
SDGSDesa SDGSDesa[]
|
SDGSDesa SdgsDesa[]
|
||||||
APBDesImage APBDes[] @relation("APBDesImage")
|
APBDesImage APBDes[] @relation("APBDesImage")
|
||||||
APBDesFile APBDes[] @relation("APBDesFile")
|
APBDesFile APBDes[] @relation("APBDesFile")
|
||||||
PrestasiDesa PrestasiDesa[]
|
PrestasiDesa PrestasiDesa[]
|
||||||
@@ -168,9 +168,9 @@ model KategoriDesaAntiKorupsi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//========================================= SDGS Desa ========================================= //
|
//========================================= SDGS Desa ========================================= //
|
||||||
model SDGSDesa {
|
model SdgsDesa {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String @unique
|
name String
|
||||||
jumlah String
|
jumlah String
|
||||||
image FileStorage? @relation(fields: [imageId], references: [id])
|
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||||
imageId String?
|
imageId String?
|
||||||
@@ -183,7 +183,7 @@ model SDGSDesa {
|
|||||||
//========================================= APBDes ========================================= //
|
//========================================= APBDes ========================================= //
|
||||||
model APBDes {
|
model APBDes {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String @unique
|
name String
|
||||||
jumlah String
|
jumlah String
|
||||||
image FileStorage? @relation("APBDesImage", fields: [imageId], references: [id])
|
image FileStorage? @relation("APBDesImage", fields: [imageId], references: [id])
|
||||||
imageId String?
|
imageId String?
|
||||||
@@ -198,7 +198,7 @@ model APBDes {
|
|||||||
//========================================= PRESTASI DESA ========================================= //
|
//========================================= PRESTASI DESA ========================================= //
|
||||||
model PrestasiDesa {
|
model PrestasiDesa {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String @unique
|
name String
|
||||||
deskripsi String @db.Text
|
deskripsi String @db.Text
|
||||||
kategori KategoriPrestasiDesa @relation(fields: [kategoriId], references: [id])
|
kategori KategoriPrestasiDesa @relation(fields: [kategoriId], references: [id])
|
||||||
kategoriId String
|
kategoriId String
|
||||||
@@ -1216,25 +1216,40 @@ model LayananPolsek {
|
|||||||
|
|
||||||
// ========================================= KONTAK DARURAT ========================================= //
|
// ========================================= KONTAK DARURAT ========================================= //
|
||||||
model KontakDaruratKeamanan {
|
model KontakDaruratKeamanan {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
nama String // contoh: "Layanan Darurat", "Fasilitas Kesehatan"
|
nama String
|
||||||
image FileStorage? @relation(fields: [imageId], references: [id])
|
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||||
imageId String?
|
imageId String?
|
||||||
kontakItems KontakItem[]
|
kategori KontakItem @relation(fields: [kategoriId], references: [id])
|
||||||
createdAt DateTime @default(now())
|
kategoriId String
|
||||||
updatedAt DateTime @updatedAt
|
kontakItems KontakDaruratToItem[]
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime?
|
||||||
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
model KontakItem {
|
model KontakItem {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
nama String // contoh: "Polisi", "Ambulans", "Puskesmas Darmasaba"
|
nama String
|
||||||
nomorTelepon String
|
nomorTelepon String
|
||||||
image FileStorage? @relation(fields: [imageId], references: [id])
|
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||||
imageId String?
|
imageId String?
|
||||||
kategori KontakDaruratKeamanan @relation(fields: [kategoriId], references: [id])
|
createdAt DateTime @default(now())
|
||||||
kategoriId String
|
updatedAt DateTime @updatedAt
|
||||||
createdAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
isActive Boolean @default(true)
|
||||||
|
KontakDaruratToItem KontakDaruratToItem[]
|
||||||
|
KontakDaruratKeamanan KontakDaruratKeamanan[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model KontakDaruratToItem {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
kontakDaruratId String
|
||||||
|
kontakItemId String
|
||||||
|
kontakDarurat KontakDaruratKeamanan @relation(fields: [kontakDaruratId], references: [id])
|
||||||
|
kontakItem KontakItem @relation(fields: [kontakItemId], references: [id])
|
||||||
|
createdAt DateTime @default(now())
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================= PENCEGAHAN KRIMINALITAS ========================================= //
|
// ========================================= PENCEGAHAN KRIMINALITAS ========================================= //
|
||||||
@@ -1401,6 +1416,9 @@ model PosisiOrganisasi {
|
|||||||
pegawai Pegawai[]
|
pegawai Pegawai[]
|
||||||
strukturOrganisasi StrukturOrganisasi[] // Relasi balik
|
strukturOrganisasi StrukturOrganisasi[] // Relasi balik
|
||||||
StrukturOrganisasiPPID StrukturOrganisasiPPID[]
|
StrukturOrganisasiPPID StrukturOrganisasiPPID[]
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
@@map("posisi_organisasi")
|
@@map("posisi_organisasi")
|
||||||
}
|
}
|
||||||
@@ -1469,7 +1487,7 @@ model ProgramKemiskinan {
|
|||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
nama String
|
nama String
|
||||||
deskripsi String
|
deskripsi String
|
||||||
ikonUrl String?
|
icon String
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
statistikId String? @unique
|
statistikId String? @unique
|
||||||
statistik StatistikKemiskinan? @relation(fields: [statistikId], references: [id])
|
statistik StatistikKemiskinan? @relation(fields: [statistikId], references: [id])
|
||||||
@@ -1551,7 +1569,7 @@ model DataDemografiPekerjaan {
|
|||||||
model DetailDataPengangguran {
|
model DetailDataPengangguran {
|
||||||
id String @id @default(uuid()) @db.Uuid
|
id String @id @default(uuid()) @db.Uuid
|
||||||
month String @db.VarChar(20)
|
month String @db.VarChar(20)
|
||||||
year DateTime
|
year Int
|
||||||
totalUnemployment Int
|
totalUnemployment Int
|
||||||
educatedUnemployment Int
|
educatedUnemployment Int
|
||||||
uneducatedUnemployment Int
|
uneducatedUnemployment Int
|
||||||
@@ -2106,18 +2124,18 @@ model KategoriBuku {
|
|||||||
// ========================================= USER ========================================= //
|
// ========================================= USER ========================================= //
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
username String
|
username String
|
||||||
nomor String @unique
|
nomor String @unique
|
||||||
role Role @relation(fields: [roleId], references: [id])
|
role Role @relation(fields: [roleId], references: [id])
|
||||||
roleId String @default("1")
|
roleId String @default("1")
|
||||||
instansi String?
|
instansi String?
|
||||||
UserSession UserSession? // Nama instansi (Puskesmas, Sekolah, dll)
|
UserSession UserSession? // Nama instansi (Puskesmas, Sekolah, dll)
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
lastLogin DateTime?
|
lastLogin DateTime?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime?
|
deletedAt DateTime?
|
||||||
}
|
}
|
||||||
|
|
||||||
model Role {
|
model Role {
|
||||||
|
|||||||
214
prisma/seed.ts
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import profilePejabatDesa from "./data/landing-page/profile/profile.json";
|
import profilePejabatDesa from "./data/landing-page/profile/profile.json";
|
||||||
import programInovasi from "./data/landing-page/profile/programInovasi.json";
|
import programInovasi from "./data/landing-page/profile/programInovasi.json";
|
||||||
@@ -15,7 +16,7 @@ import posisiOrganisasiPPID from "./data/ppid/struktur-ppid/posisi-organisasi-PP
|
|||||||
import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json";
|
import visiMisiPPID from "./data/ppid/visi-misi-ppid/visimisiPPID.json";
|
||||||
import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json";
|
import dasarHukumPPID from "./data/ppid/dasar-hukum-ppid/dasarhukumPPID.json";
|
||||||
import jenisKelamin from "./data/ppid/ikm/jenis-kelamin/jenis-kelamin.json";
|
import jenisKelamin from "./data/ppid/ikm/jenis-kelamin/jenis-kelamin.json";
|
||||||
import daftarInformasiPublik from "./data/ppid/daftar-informasi-publik-desa-darmasaba/daftarInformasi.json"
|
import daftarInformasiPublik from "./data/ppid/daftar-informasi-publik-desa-darmasaba/daftarInformasi.json";
|
||||||
import pilihanRatingResponden from "./data/ppid/ikm/pilihan-rating-responden/rating-responden.json";
|
import pilihanRatingResponden from "./data/ppid/ikm/pilihan-rating-responden/rating-responden.json";
|
||||||
import umurResponden from "./data/ppid/ikm/umur-responden/umur-responden.json";
|
import umurResponden from "./data/ppid/ikm/umur-responden/umur-responden.json";
|
||||||
import categoryPengumuman from "./data/category-pengumuman.json";
|
import categoryPengumuman from "./data/category-pengumuman.json";
|
||||||
@@ -54,63 +55,90 @@ import programUnggulan from "./data/pendidikan/program-pendidikan-anak/program-u
|
|||||||
import tujuanProgram from "./data/pendidikan/program-pendidikan-anak/tujuan-program.json";
|
import tujuanProgram from "./data/pendidikan/program-pendidikan-anak/tujuan-program.json";
|
||||||
import roles from "./data/user/roles.json";
|
import roles from "./data/user/roles.json";
|
||||||
import users from "./data/user/users.json";
|
import users from "./data/user/users.json";
|
||||||
|
import fileStorage from "./data/file-storage.json";
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
// =========== USER & ROLE ===========
|
// =========== USER & ROLE ===========
|
||||||
// In your seed.ts
|
// In your seed.ts
|
||||||
// =========== ROLES ===========
|
// =========== ROLES ===========
|
||||||
console.log("🔄 Seeding roles...");
|
console.log("🔄 Seeding roles...");
|
||||||
for (const r of roles) {
|
for (const r of roles) {
|
||||||
await prisma.role.upsert({
|
await prisma.role.upsert({
|
||||||
where: { id: r.id },
|
where: { id: r.id },
|
||||||
update: {
|
update: {
|
||||||
name: r.name,
|
name: r.name,
|
||||||
description: r.description,
|
description: r.description,
|
||||||
permissions: r.permissions,
|
permissions: r.permissions,
|
||||||
isActive: r.isActive,
|
isActive: r.isActive,
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
id: r.id,
|
id: r.id,
|
||||||
name: r.name,
|
name: r.name,
|
||||||
description: r.description,
|
description: r.description,
|
||||||
permissions: r.permissions,
|
permissions: r.permissions,
|
||||||
isActive: r.isActive,
|
isActive: r.isActive,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
|
||||||
console.log("✅ Roles seeded");
|
|
||||||
|
|
||||||
// =========== USERS ===========
|
|
||||||
console.log("🔄 Seeding users...");
|
|
||||||
for (const u of users) {
|
|
||||||
// First verify the role exists
|
|
||||||
const roleExists = await prisma.role.findUnique({
|
|
||||||
where: { id: u.roleId }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!roleExists) {
|
|
||||||
console.error(`❌ Role with id ${u.roleId} not found for user ${u.nama}`);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
console.log("✅ Roles seeded");
|
||||||
|
|
||||||
await prisma.user.upsert({
|
// =========== USERS ===========
|
||||||
where: { id: u.id },
|
console.log("🔄 Seeding users...");
|
||||||
update: {
|
for (const u of users) {
|
||||||
username: u.nama,
|
// First verify the role exists
|
||||||
nomor: u.nomor,
|
const roleExists = await prisma.role.findUnique({
|
||||||
roleId: u.roleId,
|
where: { id: u.roleId },
|
||||||
isActive: u.isActive,
|
});
|
||||||
},
|
|
||||||
create: {
|
if (!roleExists) {
|
||||||
id: u.id,
|
console.error(`❌ Role with id ${u.roleId} not found for user ${u.nama}`);
|
||||||
username: u.nama,
|
continue;
|
||||||
nomor: u.nomor,
|
}
|
||||||
roleId: u.roleId,
|
|
||||||
isActive: u.isActive,
|
await prisma.user.upsert({
|
||||||
},
|
where: { id: u.id },
|
||||||
});
|
update: {
|
||||||
}
|
username: u.nama,
|
||||||
console.log("✅ Users seeded");
|
nomor: u.nomor,
|
||||||
|
roleId: u.roleId,
|
||||||
|
isActive: u.isActive,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: u.id,
|
||||||
|
username: u.nama,
|
||||||
|
nomor: u.nomor,
|
||||||
|
roleId: u.roleId,
|
||||||
|
isActive: u.isActive,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("✅ Users seeded");
|
||||||
|
|
||||||
|
// =========== FILE STORAGE ===========
|
||||||
|
console.log("🔄 Seeding file storage...");
|
||||||
|
for (const f of fileStorage) {
|
||||||
|
await prisma.fileStorage.upsert({
|
||||||
|
where: { id: f.id },
|
||||||
|
update: {
|
||||||
|
name: f.name,
|
||||||
|
realName: f.realName,
|
||||||
|
path: f.path,
|
||||||
|
mimeType: f.mimeType,
|
||||||
|
link: f.link,
|
||||||
|
category: f.category,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: f.id,
|
||||||
|
name: f.name,
|
||||||
|
realName: f.realName,
|
||||||
|
path: f.path,
|
||||||
|
mimeType: f.mimeType,
|
||||||
|
link: f.link,
|
||||||
|
category: f.category,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("✅ File storage seeded");
|
||||||
// =========== LANDING PAGE ===========
|
// =========== LANDING PAGE ===========
|
||||||
// =========== SUBMENU PROFILE ===========
|
// =========== SUBMENU PROFILE ===========
|
||||||
// =========== PROFILE PEJABAT DESA ===========
|
// =========== PROFILE PEJABAT DESA ===========
|
||||||
@@ -120,11 +148,13 @@ console.log("✅ Users seeded");
|
|||||||
update: {
|
update: {
|
||||||
name: p.name,
|
name: p.name,
|
||||||
position: p.position,
|
position: p.position,
|
||||||
|
imageId: p.imageId,
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
id: p.id,
|
id: p.id,
|
||||||
name: p.name,
|
name: p.name,
|
||||||
position: p.position,
|
position: p.position,
|
||||||
|
imageId: p.imageId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -134,18 +164,35 @@ console.log("✅ Users seeded");
|
|||||||
|
|
||||||
// =========== PROGRAM INOVASI ===========
|
// =========== PROGRAM INOVASI ===========
|
||||||
for (const p of programInovasi) {
|
for (const p of programInovasi) {
|
||||||
|
let imageId: string | null = null;
|
||||||
|
|
||||||
|
if (p.imageId) {
|
||||||
|
const imageExists = await prisma.fileStorage.findUnique({
|
||||||
|
where: { id: p.imageId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (imageExists) {
|
||||||
|
imageId = p.imageId;
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
`⚠️ imageId ${p.imageId} tidak ditemukan untuk ProgramInovasi ${p.name}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
await prisma.programInovasi.upsert({
|
await prisma.programInovasi.upsert({
|
||||||
where: { id: p.id },
|
where: { id: p.id },
|
||||||
update: {
|
update: {
|
||||||
name: p.name,
|
name: p.name,
|
||||||
description: p.description,
|
description: p.description,
|
||||||
link: p.link,
|
link: p.link,
|
||||||
|
imageId: p.imageId,
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
id: p.id,
|
id: p.id,
|
||||||
name: p.name,
|
name: p.name,
|
||||||
description: p.description,
|
description: p.description,
|
||||||
link: p.link,
|
link: p.link,
|
||||||
|
imageId: p.imageId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -158,11 +205,13 @@ console.log("✅ Users seeded");
|
|||||||
update: {
|
update: {
|
||||||
name: p.name,
|
name: p.name,
|
||||||
iconUrl: p.iconUrl,
|
iconUrl: p.iconUrl,
|
||||||
|
imageId: p.imageId,
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
id: p.id,
|
id: p.id,
|
||||||
name: p.name,
|
name: p.name,
|
||||||
iconUrl: p.iconUrl,
|
iconUrl: p.iconUrl,
|
||||||
|
imageId: p.imageId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -308,11 +357,8 @@ console.log("✅ Users seeded");
|
|||||||
|
|
||||||
// =========== SDGSDesa ===========
|
// =========== SDGSDesa ===========
|
||||||
for (const l of sdgsDesa) {
|
for (const l of sdgsDesa) {
|
||||||
await prisma.sDGSDesa.upsert({
|
await prisma.sdgsDesa.upsert({
|
||||||
where: {
|
where: { id: l.id },
|
||||||
name: l.name,
|
|
||||||
jumlah: l.jumlah,
|
|
||||||
},
|
|
||||||
update: {
|
update: {
|
||||||
name: l.name,
|
name: l.name,
|
||||||
jumlah: l.jumlah,
|
jumlah: l.jumlah,
|
||||||
@@ -330,8 +376,7 @@ console.log("✅ Users seeded");
|
|||||||
for (const l of apbdes) {
|
for (const l of apbdes) {
|
||||||
await prisma.aPBDes.upsert({
|
await prisma.aPBDes.upsert({
|
||||||
where: {
|
where: {
|
||||||
name: l.name,
|
id: l.id,
|
||||||
jumlah: l.jumlah,
|
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
name: l.name,
|
name: l.name,
|
||||||
@@ -558,29 +603,29 @@ console.log("✅ Users seeded");
|
|||||||
}
|
}
|
||||||
console.log("dasar hukum PPID success ...");
|
console.log("dasar hukum PPID success ...");
|
||||||
|
|
||||||
// =========== SUBMENU DAFTAR INFORMASI PUBLIK PPID ===========
|
// =========== SUBMENU DAFTAR INFORMASI PUBLIK PPID ===========
|
||||||
for (const v of daftarInformasiPublik) {
|
for (const v of daftarInformasiPublik) {
|
||||||
// Convert string date to Date object
|
// Convert string date to Date object
|
||||||
const tanggal = new Date(v.tanggal);
|
const tanggal = new Date(v.tanggal);
|
||||||
|
|
||||||
await prisma.daftarInformasiPublik.upsert({
|
await prisma.daftarInformasiPublik.upsert({
|
||||||
where: {
|
where: {
|
||||||
id: v.id,
|
id: v.id,
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
jenisInformasi: v.jenisInformasi,
|
jenisInformasi: v.jenisInformasi,
|
||||||
deskripsi: v.deskripsi,
|
deskripsi: v.deskripsi,
|
||||||
tanggal: tanggal,
|
tanggal: tanggal,
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
id: v.id,
|
id: v.id,
|
||||||
jenisInformasi: v.jenisInformasi,
|
jenisInformasi: v.jenisInformasi,
|
||||||
deskripsi: v.deskripsi,
|
deskripsi: v.deskripsi,
|
||||||
tanggal: tanggal,
|
tanggal: tanggal,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log("daftar informasi publik PPID success ...");
|
console.log("daftar informasi publik PPID success ...");
|
||||||
|
|
||||||
for (const l of pelayananPerizinanBerusaha) {
|
for (const l of pelayananPerizinanBerusaha) {
|
||||||
await prisma.pelayananPerizinanBerusaha.upsert({
|
await prisma.pelayananPerizinanBerusaha.upsert({
|
||||||
@@ -849,12 +894,9 @@ console.log("✅ Users seeded");
|
|||||||
console.log("hubungan organisasi success ...");
|
console.log("hubungan organisasi success ...");
|
||||||
|
|
||||||
for (const d of detailDataPengangguran) {
|
for (const d of detailDataPengangguran) {
|
||||||
// Convert the year to a Date object (using January 1st of the year as the date)
|
|
||||||
const yearAsDate = new Date(d.year, 0, 1);
|
|
||||||
|
|
||||||
await prisma.detailDataPengangguran.upsert({
|
await prisma.detailDataPengangguran.upsert({
|
||||||
where: {
|
where: {
|
||||||
month_year: { month: d.month, year: yearAsDate },
|
month_year: { month: d.month, year: d.year },
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
totalUnemployment: d.totalUnemployment,
|
totalUnemployment: d.totalUnemployment,
|
||||||
@@ -864,7 +906,7 @@ console.log("✅ Users seeded");
|
|||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
month: d.month,
|
month: d.month,
|
||||||
year: yearAsDate,
|
year: d.year,
|
||||||
totalUnemployment: d.totalUnemployment,
|
totalUnemployment: d.totalUnemployment,
|
||||||
educatedUnemployment: d.educatedUnemployment,
|
educatedUnemployment: d.educatedUnemployment,
|
||||||
uneducatedUnemployment: d.uneducatedUnemployment,
|
uneducatedUnemployment: d.uneducatedUnemployment,
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
BIN
public/chatbot-removebg-preview.png
Normal file
|
After Width: | Height: | Size: 303 KiB |
|
Before Width: | Height: | Size: 275 KiB After Width: | Height: | Size: 266 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 275 KiB |
@@ -23,6 +23,7 @@ export default function SpashScreen() {
|
|||||||
<Paper p={"md"} miw={320}>
|
<Paper p={"md"} miw={320}>
|
||||||
<Flex>
|
<Flex>
|
||||||
<Image
|
<Image
|
||||||
|
loading="lazy"
|
||||||
src={images["darmasaba-icon"]}
|
src={images["darmasaba-icon"]}
|
||||||
alt="darmasaba"
|
alt="darmasaba"
|
||||||
w={100}
|
w={100}
|
||||||
|
|||||||
83
src/app/admin/(dashboard)/_com/iconMap.tsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import {
|
||||||
|
IconLeaf,
|
||||||
|
IconTrophy,
|
||||||
|
IconTent,
|
||||||
|
IconChartLine,
|
||||||
|
IconRecycle,
|
||||||
|
IconTruck,
|
||||||
|
IconScale,
|
||||||
|
IconClipboard,
|
||||||
|
IconTrash,
|
||||||
|
IconHomeEco,
|
||||||
|
IconChristmasTreeFilled,
|
||||||
|
IconTrendingUp,
|
||||||
|
IconShieldFilled,
|
||||||
|
IconHome,
|
||||||
|
IconTree,
|
||||||
|
IconDroplet,
|
||||||
|
IconCash,
|
||||||
|
IconSchool,
|
||||||
|
IconShoppingCart,
|
||||||
|
IconHospital,
|
||||||
|
} from '@tabler/icons-react'
|
||||||
|
|
||||||
|
export type IconKey =
|
||||||
|
| 'ekowisata'
|
||||||
|
| 'kompetisi'
|
||||||
|
| 'wisata'
|
||||||
|
| 'ekonomi'
|
||||||
|
| 'sampah'
|
||||||
|
| 'truck'
|
||||||
|
| 'scale'
|
||||||
|
| 'clipboard'
|
||||||
|
| 'trash'
|
||||||
|
| 'lingkunganSehat'
|
||||||
|
| 'sumberOksigen'
|
||||||
|
| 'ekonomiBerkelanjutan'
|
||||||
|
| 'mencegahBencana'
|
||||||
|
| 'rumah'
|
||||||
|
| 'pohon'
|
||||||
|
| 'air'
|
||||||
|
| 'bantuan'
|
||||||
|
| 'pelatihan'
|
||||||
|
| 'subsidi'
|
||||||
|
| 'layananKesehatan'
|
||||||
|
|
||||||
|
const iconMap: Record<IconKey, React.FC<any>> = {
|
||||||
|
ekowisata: IconLeaf,
|
||||||
|
kompetisi: IconTrophy,
|
||||||
|
wisata: IconTent,
|
||||||
|
ekonomi: IconChartLine,
|
||||||
|
sampah: IconRecycle,
|
||||||
|
truck: IconTruck,
|
||||||
|
scale: IconScale,
|
||||||
|
clipboard: IconClipboard,
|
||||||
|
trash: IconTrash,
|
||||||
|
lingkunganSehat: IconHomeEco,
|
||||||
|
sumberOksigen: IconChristmasTreeFilled,
|
||||||
|
ekonomiBerkelanjutan: IconTrendingUp,
|
||||||
|
mencegahBencana: IconShieldFilled,
|
||||||
|
rumah: IconHome,
|
||||||
|
pohon: IconTree,
|
||||||
|
air: IconDroplet,
|
||||||
|
bantuan: IconCash,
|
||||||
|
pelatihan: IconSchool,
|
||||||
|
subsidi: IconShoppingCart,
|
||||||
|
layananKesehatan: IconHospital,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
name: IconKey
|
||||||
|
size?: number
|
||||||
|
color?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IconMapper: React.FC<Props> = ({ name, size = 24, color }) => {
|
||||||
|
const IconComponent = iconMap[name]
|
||||||
|
if (!IconComponent) return null
|
||||||
|
return <IconComponent size={size} color={color} />
|
||||||
|
}
|
||||||
@@ -3,16 +3,20 @@
|
|||||||
|
|
||||||
import { Box, rem, Select } from '@mantine/core';
|
import { Box, rem, Select } from '@mantine/core';
|
||||||
import {
|
import {
|
||||||
|
IconCash,
|
||||||
IconChartLine,
|
IconChartLine,
|
||||||
IconChristmasTreeFilled,
|
IconChristmasTreeFilled,
|
||||||
IconClipboardTextFilled,
|
IconClipboardTextFilled,
|
||||||
IconDroplet,
|
IconDroplet,
|
||||||
IconHome,
|
IconHome,
|
||||||
IconHomeEco,
|
IconHomeEco,
|
||||||
|
IconHospital,
|
||||||
IconLeaf,
|
IconLeaf,
|
||||||
IconRecycle,
|
IconRecycle,
|
||||||
IconScale,
|
IconScale,
|
||||||
|
IconSchool,
|
||||||
IconShieldFilled,
|
IconShieldFilled,
|
||||||
|
IconShoppingCart,
|
||||||
IconTent,
|
IconTent,
|
||||||
IconTrashFilled,
|
IconTrashFilled,
|
||||||
IconTree,
|
IconTree,
|
||||||
@@ -32,13 +36,17 @@ const iconMap = {
|
|||||||
scale: { label: 'Scale', icon: IconScale },
|
scale: { label: 'Scale', icon: IconScale },
|
||||||
clipboard: { label: 'Clipboard', icon: IconClipboardTextFilled },
|
clipboard: { label: 'Clipboard', icon: IconClipboardTextFilled },
|
||||||
trash: { label: 'Trash', icon: IconTrashFilled },
|
trash: { label: 'Trash', icon: IconTrashFilled },
|
||||||
lingkunganSehat: {label: 'Lingkungan Sehat', icon: IconHomeEco},
|
lingkunganSehat: { label: 'Lingkungan Sehat', icon: IconHomeEco },
|
||||||
sumberOksigen: {label: 'Sumber Oksigen', icon: IconChristmasTreeFilled},
|
sumberOksigen: { label: 'Sumber Oksigen', icon: IconChristmasTreeFilled },
|
||||||
ekonomiBerkelanjutan: {label: 'Ekonomi Berkelanjutan', icon: IconTrendingUp},
|
ekonomiBerkelanjutan: { label: 'Ekonomi Berkelanjutan', icon: IconTrendingUp },
|
||||||
mencegahBencana: {label: 'Mencegah Bencana', icon: IconShieldFilled},
|
mencegahBencana: { label: 'Mencegah Bencana', icon: IconShieldFilled },
|
||||||
rumah: {label: 'Rumah', icon: IconHome},
|
rumah: { label: 'Rumah', icon: IconHome },
|
||||||
pohon: {label: 'Pohon', icon: IconTree},
|
pohon: { label: 'Pohon', icon: IconTree },
|
||||||
air: {label: 'Air', icon: IconDroplet}
|
air: { label: 'Air', icon: IconDroplet },
|
||||||
|
bantuan: { label: 'Bantuan', icon: IconCash },
|
||||||
|
pelatihan: { label: 'Pelatihan', icon: IconSchool },
|
||||||
|
subsidi: { label: 'Subsidi', icon: IconShoppingCart },
|
||||||
|
layananKesehatan: { label: 'Layanan Kesehatan', icon: IconHospital },
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,16 +2,20 @@
|
|||||||
|
|
||||||
import { Box, rem, Select } from '@mantine/core';
|
import { Box, rem, Select } from '@mantine/core';
|
||||||
import {
|
import {
|
||||||
|
IconCash,
|
||||||
IconChartLine,
|
IconChartLine,
|
||||||
IconChristmasTreeFilled,
|
IconChristmasTreeFilled,
|
||||||
IconClipboardTextFilled,
|
IconClipboardTextFilled,
|
||||||
IconDroplet,
|
IconDroplet,
|
||||||
IconHome,
|
IconHome,
|
||||||
IconHomeEco,
|
IconHomeEco,
|
||||||
|
IconHospital,
|
||||||
IconLeaf,
|
IconLeaf,
|
||||||
IconRecycle,
|
IconRecycle,
|
||||||
IconScale,
|
IconScale,
|
||||||
|
IconSchool,
|
||||||
IconShieldFilled,
|
IconShieldFilled,
|
||||||
|
IconShoppingCart,
|
||||||
IconTent,
|
IconTent,
|
||||||
IconTrashFilled,
|
IconTrashFilled,
|
||||||
IconTree,
|
IconTree,
|
||||||
@@ -36,7 +40,11 @@ const iconMap = {
|
|||||||
mencegahBencana: {label: 'Mencegah Bencana', icon: IconShieldFilled},
|
mencegahBencana: {label: 'Mencegah Bencana', icon: IconShieldFilled},
|
||||||
rumah: {label: 'Rumah', icon: IconHome},
|
rumah: {label: 'Rumah', icon: IconHome},
|
||||||
pohon: {label: 'Pohon', icon: IconTree},
|
pohon: {label: 'Pohon', icon: IconTree},
|
||||||
air: {label: 'Air', icon: IconDroplet}
|
air: {label: 'Air', icon: IconDroplet},
|
||||||
|
bantuan: {label: 'Bantuan', icon: IconCash},
|
||||||
|
pelatihan: {label: 'Pelatihan', icon: IconSchool},
|
||||||
|
subsidi: {label: 'Subsidi', icon: IconShoppingCart},
|
||||||
|
layananKesehatan: {label: 'Layanan Kesehatan', icon: IconHospital},
|
||||||
};
|
};
|
||||||
|
|
||||||
type IconKey = keyof typeof iconMap;
|
type IconKey = keyof typeof iconMap;
|
||||||
|
|||||||
@@ -7,9 +7,13 @@ import { z } from "zod";
|
|||||||
|
|
||||||
const templateApbDesa = z.object({
|
const templateApbDesa = z.object({
|
||||||
tahun: z.number().min(4, "Tahun minimal 4 karakter"),
|
tahun: z.number().min(4, "Tahun minimal 4 karakter"),
|
||||||
pembiayaanIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 pembiayaan"),
|
pembiayaanIds: z
|
||||||
|
.array(z.string().uuid())
|
||||||
|
.nonempty("Pilih minimal 1 pembiayaan"),
|
||||||
belanjaIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 belanja"),
|
belanjaIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 belanja"),
|
||||||
pendapatanIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 pendapatan"),
|
pendapatanIds: z
|
||||||
|
.array(z.string().uuid())
|
||||||
|
.nonempty("Pilih minimal 1 pendapatan"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const ApbDesaDefaultForm = {
|
const ApbDesaDefaultForm = {
|
||||||
@@ -55,31 +59,45 @@ const ApbDesa = proxy({
|
|||||||
},
|
},
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as
|
||||||
| Prisma.ApbDesaGetPayload<{
|
| Prisma.ApbDesaGetPayload<{
|
||||||
include: {
|
include: {
|
||||||
pendapatan: true;
|
pendapatan: true;
|
||||||
belanja: true;
|
belanja: true;
|
||||||
pembiayaan: true;
|
pembiayaan: true;
|
||||||
};
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
loading: false,
|
loading: false,
|
||||||
async load() {
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
ApbDesa.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
ApbDesa.findMany.page = page;
|
||||||
|
ApbDesa.findMany.search = search;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.loading = true;
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.apbdesa[
|
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.apbdesa[
|
||||||
"find-many"
|
"find-many"
|
||||||
].get();
|
].get({ query });
|
||||||
if (res.status === 200) {
|
|
||||||
this.data = res.data?.data ?? [];
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
ApbDesa.findMany.data = res.data.data ?? [];
|
||||||
|
ApbDesa.findMany.totalPages =
|
||||||
|
res.data.totalPages ?? 1;
|
||||||
} else {
|
} else {
|
||||||
toast.error(res.data?.message || "Gagal mengambil APB Desa");
|
ApbDesa.findMany.data = [];
|
||||||
|
ApbDesa.findMany.totalPages = 1;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.error("Find many error:", error);
|
console.error("Gagal fetch APB Desa paginated:", err);
|
||||||
toast.error("Gagal mengambil APB Desa");
|
ApbDesa.findMany.data = [];
|
||||||
|
ApbDesa.findMany.totalPages = 1;
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
ApbDesa.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -264,34 +282,32 @@ const pendapatan = proxy({
|
|||||||
data: null as any[] | null,
|
data: null as any[] | null,
|
||||||
page: 1,
|
page: 1,
|
||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
total: 0,
|
|
||||||
loading: false,
|
loading: false,
|
||||||
load: async (page = 1, limit = 10) => {
|
search: "",
|
||||||
// Change to arrow function
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
pendapatan.findMany.loading = true; // Use the full path to access the property
|
pendapatan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
pendapatan.findMany.page = page;
|
pendapatan.findMany.page = page;
|
||||||
|
pendapatan.findMany.search = search;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res =
|
const query: any = { page, limit };
|
||||||
await ApiFetch.api.ekonomi.pendapatanaslidesa.pendapatanasli[
|
if (search) query.search = search;
|
||||||
"find-many"
|
|
||||||
].get({
|
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.pendapatanasli[
|
||||||
query: { page, limit },
|
"find-many"
|
||||||
});
|
].get({ query });
|
||||||
|
|
||||||
if (res.status === 200 && res.data?.success) {
|
if (res.status === 200 && res.data?.success) {
|
||||||
pendapatan.findMany.data = res.data.data || [];
|
pendapatan.findMany.data = res.data.data ?? [];
|
||||||
pendapatan.findMany.total = res.data.total || 0;
|
pendapatan.findMany.totalPages =
|
||||||
pendapatan.findMany.totalPages = res.data.totalPages || 1;
|
res.data.totalPages ?? 1;
|
||||||
} else {
|
} else {
|
||||||
console.error("Failed to load pendapatan:", res.data?.message);
|
|
||||||
pendapatan.findMany.data = [];
|
pendapatan.findMany.data = [];
|
||||||
pendapatan.findMany.total = 0;
|
|
||||||
pendapatan.findMany.totalPages = 1;
|
pendapatan.findMany.totalPages = 1;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.error("Error loading pendapatan:", error);
|
console.error("Gagal fetch pendapatan asli desa paginated:", err);
|
||||||
pendapatan.findMany.data = [];
|
pendapatan.findMany.data = [];
|
||||||
pendapatan.findMany.total = 0;
|
|
||||||
pendapatan.findMany.totalPages = 1;
|
pendapatan.findMany.totalPages = 1;
|
||||||
} finally {
|
} finally {
|
||||||
pendapatan.findMany.loading = false;
|
pendapatan.findMany.loading = false;
|
||||||
@@ -308,12 +324,15 @@ const pendapatan = proxy({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${id}`, {
|
const response = await fetch(
|
||||||
method: "GET",
|
`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${id}`,
|
||||||
headers: {
|
{
|
||||||
"Content-Type": "application/json",
|
method: "GET",
|
||||||
},
|
headers: {
|
||||||
});
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
@@ -349,16 +368,19 @@ const pendapatan = proxy({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
pendapatan.update.loading = true;
|
pendapatan.update.loading = true;
|
||||||
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${this.id}`, {
|
const response = await fetch(
|
||||||
method: "PUT",
|
`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${this.id}`,
|
||||||
headers: {
|
{
|
||||||
"Content-Type": "application/json",
|
method: "PUT",
|
||||||
},
|
headers: {
|
||||||
body: JSON.stringify({
|
"Content-Type": "application/json",
|
||||||
name: this.form.name,
|
},
|
||||||
value: this.form.value,
|
body: JSON.stringify({
|
||||||
}),
|
name: this.form.name,
|
||||||
});
|
value: this.form.value,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -495,23 +517,37 @@ const belanja = proxy({
|
|||||||
name: string;
|
name: string;
|
||||||
value: number;
|
value: number;
|
||||||
}>,
|
}>,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
loading: false,
|
loading: false,
|
||||||
async load() {
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
belanja.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
belanja.findMany.page = page;
|
||||||
|
belanja.findMany.search = search;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.loading = true;
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.belanja[
|
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.belanja[
|
||||||
"find-many"
|
"find-many"
|
||||||
].get();
|
].get({ query });
|
||||||
if (res.status === 200) {
|
|
||||||
this.data = res.data?.data ?? [];
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
belanja.findMany.data = res.data.data ?? [];
|
||||||
|
belanja.findMany.totalPages =
|
||||||
|
res.data.totalPages ?? 1;
|
||||||
} else {
|
} else {
|
||||||
toast.error(res.data?.message || "Gagal mengambil Belanja");
|
belanja.findMany.data = [];
|
||||||
|
belanja.findMany.totalPages = 1;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.error("Find many error:", error);
|
console.error("Gagal fetch Belanja paginated:", err);
|
||||||
toast.error("Gagal mengambil Belanja");
|
belanja.findMany.data = [];
|
||||||
|
belanja.findMany.totalPages = 1;
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
belanja.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -525,12 +561,15 @@ const belanja = proxy({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/belanja/${id}`, {
|
const response = await fetch(
|
||||||
method: "GET",
|
`/api/ekonomi/pendapatanaslidesa/belanja/${id}`,
|
||||||
headers: {
|
{
|
||||||
"Content-Type": "application/json",
|
method: "GET",
|
||||||
},
|
headers: {
|
||||||
});
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
@@ -566,16 +605,19 @@ const belanja = proxy({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
belanja.update.loading = true;
|
belanja.update.loading = true;
|
||||||
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/belanja/${this.id}`, {
|
const response = await fetch(
|
||||||
method: "PUT",
|
`/api/ekonomi/pendapatanaslidesa/belanja/${this.id}`,
|
||||||
headers: {
|
{
|
||||||
"Content-Type": "application/json",
|
method: "PUT",
|
||||||
},
|
headers: {
|
||||||
body: JSON.stringify({
|
"Content-Type": "application/json",
|
||||||
name: this.form.name,
|
},
|
||||||
value: this.form.value,
|
body: JSON.stringify({
|
||||||
}),
|
name: this.form.name,
|
||||||
});
|
value: this.form.value,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -710,23 +752,37 @@ const pembiayaan = proxy({
|
|||||||
name: string;
|
name: string;
|
||||||
value: number;
|
value: number;
|
||||||
}>,
|
}>,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
loading: false,
|
loading: false,
|
||||||
async load() {
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
pembiayaan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
pembiayaan.findMany.page = page;
|
||||||
|
pembiayaan.findMany.search = search;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.loading = true;
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.pembiayaan[
|
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.pembiayaan[
|
||||||
"find-many"
|
"find-many"
|
||||||
].get();
|
].get({ query });
|
||||||
if (res.status === 200) {
|
|
||||||
this.data = res.data?.data ?? [];
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
pembiayaan.findMany.data = res.data.data ?? [];
|
||||||
|
pembiayaan.findMany.totalPages =
|
||||||
|
res.data.totalPages ?? 1;
|
||||||
} else {
|
} else {
|
||||||
toast.error(res.data?.message || "Gagal mengambil Pembiayaan");
|
pembiayaan.findMany.data = [];
|
||||||
|
pembiayaan.findMany.totalPages = 1;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.error("Find many error:", error);
|
console.error("Gagal fetch Pembiayaan paginated:", err);
|
||||||
toast.error("Gagal mengambil Pembiayaan");
|
pembiayaan.findMany.data = [];
|
||||||
|
pembiayaan.findMany.totalPages = 1;
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
pembiayaan.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -740,12 +796,15 @@ const pembiayaan = proxy({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pembiayaan/${id}`, {
|
const response = await fetch(
|
||||||
method: "GET",
|
`/api/ekonomi/pendapatanaslidesa/pembiayaan/${id}`,
|
||||||
headers: {
|
{
|
||||||
"Content-Type": "application/json",
|
method: "GET",
|
||||||
},
|
headers: {
|
||||||
});
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
@@ -781,16 +840,19 @@ const pembiayaan = proxy({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
pembiayaan.update.loading = true;
|
pembiayaan.update.loading = true;
|
||||||
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pembiayaan/${this.id}`, {
|
const response = await fetch(
|
||||||
method: "PUT",
|
`/api/ekonomi/pendapatanaslidesa/pembiayaan/${this.id}`,
|
||||||
headers: {
|
{
|
||||||
"Content-Type": "application/json",
|
method: "PUT",
|
||||||
},
|
headers: {
|
||||||
body: JSON.stringify({
|
"Content-Type": "application/json",
|
||||||
name: this.form.name,
|
},
|
||||||
value: this.form.value,
|
body: JSON.stringify({
|
||||||
}),
|
name: this.form.name,
|
||||||
});
|
value: this.form.value,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -71,12 +72,37 @@ const demografiPekerjaan = proxy({
|
|||||||
omit: { isActive: true };
|
omit: { isActive: true };
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.ekonomi.demografipekerjaan[
|
totalPages: 1,
|
||||||
"find-many"
|
loading: false,
|
||||||
].get();
|
search: "",
|
||||||
if (res.status === 200) {
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
demografiPekerjaan.findMany.data = res.data?.data ?? [];
|
demografiPekerjaan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
demografiPekerjaan.findMany.page = page;
|
||||||
|
demografiPekerjaan.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.ekonomi.demografipekerjaan[
|
||||||
|
"find-many"
|
||||||
|
].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
demografiPekerjaan.findMany.data = res.data.data ?? [];
|
||||||
|
demografiPekerjaan.findMany.totalPages =
|
||||||
|
res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
demografiPekerjaan.findMany.data = [];
|
||||||
|
demografiPekerjaan.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch demografi pekerjaan paginated:", err);
|
||||||
|
demografiPekerjaan.findMany.data = [];
|
||||||
|
demografiPekerjaan.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
demografiPekerjaan.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -194,4 +220,4 @@ const demografiPekerjaan = proxy({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
export default demografiPekerjaan
|
export default demografiPekerjaan;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -69,16 +70,37 @@ const jumlahPendudukMiskin = proxy({
|
|||||||
select: { id: true; year: true; totalPoorPopulation: true };
|
select: { id: true; year: true; totalPoorPopulation: true };
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
loading: false,
|
page: 1,
|
||||||
async load() {
|
totalPages: 1,
|
||||||
const res = await ApiFetch.api.ekonomi.jumlahpendudukmiskin[
|
loading: false,
|
||||||
"find-many"
|
search: "",
|
||||||
].get();
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
if (res.status === 200) {
|
jumlahPendudukMiskin.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
jumlahPendudukMiskin.findMany.data = res.data?.data ?? [];
|
jumlahPendudukMiskin.findMany.page = page;
|
||||||
}
|
jumlahPendudukMiskin.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.ekonomi.jumlahpendudukmiskin["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
jumlahPendudukMiskin.findMany.data = res.data.data ?? [];
|
||||||
|
jumlahPendudukMiskin.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
jumlahPendudukMiskin.findMany.data = [];
|
||||||
|
jumlahPendudukMiskin.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch jumlah penduduk miskin paginated:", err);
|
||||||
|
jumlahPendudukMiskin.findMany.data = [];
|
||||||
|
jumlahPendudukMiskin.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
jumlahPendudukMiskin.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
findUnique: {
|
findUnique: {
|
||||||
data: null as Prisma.GrafikJumlahPendudukMiskinGetPayload<{
|
data: null as Prisma.GrafikJumlahPendudukMiskinGetPayload<{
|
||||||
select: { id: true; year: true; totalPoorPopulation: true };
|
select: { id: true; year: true; totalPoorPopulation: true };
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ const templateJumlahPengngguran = z.object({
|
|||||||
uneducatedUnemployment: z
|
uneducatedUnemployment: z
|
||||||
.number()
|
.number()
|
||||||
.min(1, "Pengangguran tidak pendidikan harus diisi"),
|
.min(1, "Pengangguran tidak pendidikan harus diisi"),
|
||||||
percentageChange: z.number().min(0, "Persentase perubahan harus diisi"),
|
percentageChange: z.number({ invalid_type_error: "Persentase perubahan harus angka" }),
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type JumlahPengangguran = {
|
type JumlahPengangguran = {
|
||||||
@@ -29,7 +30,7 @@ type JumlahPengangguran = {
|
|||||||
|
|
||||||
const jumlahPengangguranForm: JumlahPengangguran = {
|
const jumlahPengangguranForm: JumlahPengangguran = {
|
||||||
month: "",
|
month: "",
|
||||||
year: 0,
|
year: new Date().getFullYear(), // Default to current year
|
||||||
totalUnemployment: 0,
|
totalUnemployment: 0,
|
||||||
educatedUnemployment: 0,
|
educatedUnemployment: 0,
|
||||||
uneducatedUnemployment: 0,
|
uneducatedUnemployment: 0,
|
||||||
@@ -60,13 +61,21 @@ const jumlahPengangguran = proxy({
|
|||||||
form: jumlahPengangguranForm,
|
form: jumlahPengangguranForm,
|
||||||
loading: false,
|
loading: false,
|
||||||
async create() {
|
async create() {
|
||||||
const cek = templateJumlahPengngguran.safeParse(
|
// Ensure all number fields are actual numbers
|
||||||
jumlahPengangguran.create.form
|
const formData = {
|
||||||
);
|
...jumlahPengangguran.create.form,
|
||||||
|
year: Number(jumlahPengangguran.create.form.year) || new Date().getFullYear(),
|
||||||
|
totalUnemployment: Number(jumlahPengangguran.create.form.totalUnemployment) || 0,
|
||||||
|
educatedUnemployment: Number(jumlahPengangguran.create.form.educatedUnemployment) || 0,
|
||||||
|
uneducatedUnemployment: Number(jumlahPengangguran.create.form.uneducatedUnemployment) || 0,
|
||||||
|
percentageChange: Number(jumlahPengangguran.create.form.percentageChange) || 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const cek = templateJumlahPengngguran.safeParse(formData);
|
||||||
if (!cek.success) {
|
if (!cek.success) {
|
||||||
const err = `[${cek.error.issues
|
const err = `[${cek.error.issues
|
||||||
.map((v) => `${v.path.join(".")}`)
|
.map((v) => `${v.path.join(".")} (${v.message})`)
|
||||||
.join("\n")}] required`;
|
.join("\n")}]`;
|
||||||
toast.error(err);
|
toast.error(err);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -78,7 +87,7 @@ const jumlahPengangguran = proxy({
|
|||||||
].post(jumlahPengangguran.create.form);
|
].post(jumlahPengangguran.create.form);
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
const id = res.data?.data?.id;
|
const id = res.data?.id;
|
||||||
if (id) {
|
if (id) {
|
||||||
toast.success("Success create");
|
toast.success("Success create");
|
||||||
jumlahPengangguran.create.form = { ...jumlahPengangguranForm };
|
jumlahPengangguran.create.form = { ...jumlahPengangguranForm };
|
||||||
@@ -103,16 +112,40 @@ const jumlahPengangguran = proxy({
|
|||||||
omit: { isActive: true };
|
omit: { isActive: true };
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res =
|
totalPages: 1,
|
||||||
await ApiFetch.api.ekonomi.jumlahpengangguran.detaildatapengangguran[
|
loading: false,
|
||||||
"find-many"
|
search: "",
|
||||||
].get();
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
if (res.status === 200) {
|
jumlahPengangguran.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
jumlahPengangguran.findMany.data = res.data?.data ?? [];
|
jumlahPengangguran.findMany.page = page;
|
||||||
}
|
jumlahPengangguran.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.ekonomi.jumlahpengangguran.detaildatapengangguran[
|
||||||
|
"find-many"
|
||||||
|
].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
jumlahPengangguran.findMany.data = res.data.data ?? [];
|
||||||
|
jumlahPengangguran.findMany.totalPages =
|
||||||
|
res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
jumlahPengangguran.findMany.data = [];
|
||||||
|
jumlahPengangguran.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch jumlah pengangguran paginated:", err);
|
||||||
|
jumlahPengangguran.findMany.data = [];
|
||||||
|
jumlahPengangguran.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
jumlahPengangguran.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
|
|
||||||
findUnique: {
|
findUnique: {
|
||||||
data: null as Prisma.DetailDataPengangguranGetPayload<{
|
data: null as Prisma.DetailDataPengangguranGetPayload<{
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { z } from "zod";
|
|||||||
const templateForm = z.object({
|
const templateForm = z.object({
|
||||||
nama: z.string().min(1, "Nama minimal 1 karakter"),
|
nama: z.string().min(1, "Nama minimal 1 karakter"),
|
||||||
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
|
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
|
||||||
ikonUrl: z.string().optional(),
|
icon: z.string().min(1, "Icon minimal 1 karakter"),
|
||||||
statistik: z.object({
|
statistik: z.object({
|
||||||
tahun: z.string().min(1, "Tahun minimal 1 karakter"),
|
tahun: z.string().min(1, "Tahun minimal 1 karakter"),
|
||||||
jumlah: z.string().min(1, "Jumlah minimal 1 karakter"),
|
jumlah: z.string().min(1, "Jumlah minimal 1 karakter"),
|
||||||
@@ -18,7 +18,7 @@ const templateForm = z.object({
|
|||||||
const defaultForm = {
|
const defaultForm = {
|
||||||
nama: "",
|
nama: "",
|
||||||
deskripsi: "",
|
deskripsi: "",
|
||||||
ikonUrl: "",
|
icon: "",
|
||||||
statistik: {
|
statistik: {
|
||||||
tahun: "",
|
tahun: "",
|
||||||
jumlah: "",
|
jumlah: "",
|
||||||
@@ -148,7 +148,7 @@ const programKemiskinanState = proxy({
|
|||||||
this.form = {
|
this.form = {
|
||||||
nama: data.nama,
|
nama: data.nama,
|
||||||
deskripsi: data.deskripsi,
|
deskripsi: data.deskripsi,
|
||||||
ikonUrl: data.ikonUrl || "",
|
icon: data.icon,
|
||||||
statistik: {
|
statistik: {
|
||||||
tahun: data.statistik.tahun,
|
tahun: data.statistik.tahun,
|
||||||
jumlah: data.statistik.jumlah,
|
jumlah: data.statistik.jumlah,
|
||||||
@@ -189,7 +189,7 @@ const programKemiskinanState = proxy({
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
nama: this.form.nama,
|
nama: this.form.nama,
|
||||||
deskripsi: this.form.deskripsi,
|
deskripsi: this.form.deskripsi,
|
||||||
ikonUrl: this.form.ikonUrl,
|
icon: this.form.icon,
|
||||||
statistik: {
|
statistik: {
|
||||||
tahun: this.form.statistik.tahun,
|
tahun: this.form.statistik.tahun,
|
||||||
jumlah: this.form.statistik.jumlah,
|
jumlah: this.form.statistik.jumlah,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -76,13 +77,37 @@ const grafikSektorUnggulan = proxy({
|
|||||||
};
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
loading: false,
|
loading: false,
|
||||||
async load() {
|
search: "",
|
||||||
const res = await ApiFetch.api.ekonomi.sektourunggulandesa[
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
"find-many"
|
grafikSektorUnggulan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
].get();
|
grafikSektorUnggulan.findMany.page = page;
|
||||||
if (res.status === 200) {
|
grafikSektorUnggulan.findMany.search = search;
|
||||||
grafikSektorUnggulan.findMany.data = res.data?.data ?? [];
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.ekonomi.sektourunggulandesa[
|
||||||
|
"find-many"
|
||||||
|
].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
grafikSektorUnggulan.findMany.data = res.data.data ?? [];
|
||||||
|
grafikSektorUnggulan.findMany.totalPages =
|
||||||
|
res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
grafikSektorUnggulan.findMany.data = [];
|
||||||
|
grafikSektorUnggulan.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch sektor unggulan desa paginated:", err);
|
||||||
|
grafikSektorUnggulan.findMany.data = [];
|
||||||
|
grafikSektorUnggulan.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
grafikSektorUnggulan.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -161,18 +161,34 @@ const posisiOrganisasi = proxy({
|
|||||||
deskripsi: string | null;
|
deskripsi: string | null;
|
||||||
hierarki: number;
|
hierarki: number;
|
||||||
}>,
|
}>,
|
||||||
async load() {
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
loading: false,
|
||||||
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
posisiOrganisasi.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
posisiOrganisasi.findMany.page = page;
|
||||||
|
posisiOrganisasi.findMany.search = search;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
|
const query: any = { page, limit };
|
||||||
"posisi-organisasi"
|
if (search) query.search = search;
|
||||||
]["find-many"].get();
|
|
||||||
if (res.status === 200) {
|
const res = await ApiFetch.api.ekonomi["struktur-organisasi"]["posisi-organisasi"]["find-many"].get({ query });
|
||||||
// The API now returns the id field, so we can use it directly
|
|
||||||
this.data = res.data?.data ?? [];
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
posisiOrganisasi.findMany.data = res.data.data ?? [];
|
||||||
|
posisiOrganisasi.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
posisiOrganisasi.findMany.data = [];
|
||||||
|
posisiOrganisasi.findMany.totalPages = 1;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.error("Find many error:", error);
|
console.error("Gagal fetch posisi organisasi paginated:", err);
|
||||||
this.data = [];
|
posisiOrganisasi.findMany.data = [];
|
||||||
|
posisiOrganisasi.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
posisiOrganisasi.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -75,16 +76,37 @@ const grafikBerdasarkanUsiaKerjaNganggur = proxy({
|
|||||||
omit: { isActive: true };
|
omit: { isActive: true };
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
loading: false,
|
page: 1,
|
||||||
async load() {
|
totalPages: 1,
|
||||||
const res = await ApiFetch.api.ekonomi.grafikusiakerjayangmenganggur[
|
loading: false,
|
||||||
"find-many"
|
search: "",
|
||||||
].get();
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
if (res.status === 200) {
|
grafikBerdasarkanUsiaKerjaNganggur.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.data = res.data?.data ?? [];
|
grafikBerdasarkanUsiaKerjaNganggur.findMany.page = page;
|
||||||
}
|
grafikBerdasarkanUsiaKerjaNganggur.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.ekonomi.grafikusiakerjayangmenganggur["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
grafikBerdasarkanUsiaKerjaNganggur.findMany.data = res.data.data ?? [];
|
||||||
|
grafikBerdasarkanUsiaKerjaNganggur.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
grafikBerdasarkanUsiaKerjaNganggur.findMany.data = [];
|
||||||
|
grafikBerdasarkanUsiaKerjaNganggur.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch grafik berdasarkan usia kerja yang menganggur paginated:", err);
|
||||||
|
grafikBerdasarkanUsiaKerjaNganggur.findMany.data = [];
|
||||||
|
grafikBerdasarkanUsiaKerjaNganggur.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
grafikBerdasarkanUsiaKerjaNganggur.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
findUnique: {
|
findUnique: {
|
||||||
data: null as Prisma.GrafikMenganggurBerdasarkanUsiaGetPayload<{
|
data: null as Prisma.GrafikMenganggurBerdasarkanUsiaGetPayload<{
|
||||||
omit: { isActive: true };
|
omit: { isActive: true };
|
||||||
@@ -259,15 +281,36 @@ const grafikBerdasarkanPendidikan = proxy({
|
|||||||
omit: { isActive: true };
|
omit: { isActive: true };
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
loading: false,
|
page: 1,
|
||||||
async load() {
|
totalPages: 1,
|
||||||
const res = await ApiFetch.api.ekonomi.grafikmenganggurberdasarkanpendidikan[
|
loading: false,
|
||||||
"find-many"
|
search: "",
|
||||||
].get();
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
if (res.status === 200) {
|
grafikBerdasarkanPendidikan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
grafikBerdasarkanPendidikan.findMany.data = res.data?.data ?? [];
|
grafikBerdasarkanPendidikan.findMany.page = page;
|
||||||
}
|
grafikBerdasarkanPendidikan.findMany.search = search;
|
||||||
},
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.ekonomi.grafikmenganggurberdasarkanpendidikan["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
grafikBerdasarkanPendidikan.findMany.data = res.data.data ?? [];
|
||||||
|
grafikBerdasarkanPendidikan.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
grafikBerdasarkanPendidikan.findMany.data = [];
|
||||||
|
grafikBerdasarkanPendidikan.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch grafik berdasarkan pendidikan paginated:", err);
|
||||||
|
grafikBerdasarkanPendidikan.findMany.data = [];
|
||||||
|
grafikBerdasarkanPendidikan.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
grafikBerdasarkanPendidikan.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
findUnique: {
|
findUnique: {
|
||||||
data: null as Prisma.GrafikMenganggurBerdasarkanPendidikanGetPayload<{
|
data: null as Prisma.GrafikMenganggurBerdasarkanPendidikanGetPayload<{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -7,25 +8,13 @@ import { z } from "zod";
|
|||||||
const templateForm = z.object({
|
const templateForm = z.object({
|
||||||
nama: z.string().min(1, "Nama minimal 1 karakter"),
|
nama: z.string().min(1, "Nama minimal 1 karakter"),
|
||||||
imageId: z.string().nonempty(),
|
imageId: z.string().nonempty(),
|
||||||
kontakItems: z.array(
|
kategoriId: z.array(z.string()).min(1, "Minimal pilih satu kategori"),
|
||||||
z.object({
|
|
||||||
nama: z.string().min(1, "Nama minimal 1 karakter"),
|
|
||||||
nomorTelepon: z.string().min(1, "Nomor Telepon minimal 1 karakter"),
|
|
||||||
imageId: z.string().nonempty(),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultForm = {
|
const defaultForm = {
|
||||||
nama: "",
|
nama: "",
|
||||||
imageId: "",
|
imageId: "",
|
||||||
kontakItems: [
|
kategoriId: [] as string[],
|
||||||
{
|
|
||||||
nama: "",
|
|
||||||
nomorTelepon: "",
|
|
||||||
imageId: "",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const kontakDaruratKeamananState = proxy({
|
const kontakDaruratKeamananState = proxy({
|
||||||
@@ -61,20 +50,50 @@ const kontakDaruratKeamananState = proxy({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
findMany: {
|
findMany: {
|
||||||
data: null as
|
data: null as Array<
|
||||||
| Prisma.KontakDaruratKeamananGetPayload<{
|
Prisma.KontakDaruratKeamananGetPayload<{
|
||||||
include: {
|
include: {
|
||||||
kontakItems: true;
|
kategori: true;
|
||||||
image: true;
|
image: true;
|
||||||
|
kontakItems: {
|
||||||
|
include: {
|
||||||
|
kontakItem: true;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}>[]
|
};
|
||||||
| null,
|
}>
|
||||||
async load() {
|
> | null,
|
||||||
const res = await ApiFetch.api.keamanan.kontakdaruratkeamanan[
|
page: 1,
|
||||||
"find-many"
|
totalPages: 1,
|
||||||
].get();
|
loading: false,
|
||||||
if (res.status === 200) {
|
search: "",
|
||||||
kontakDaruratKeamananState.findMany.data = res.data?.data ?? [];
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
kontakDaruratKeamananState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
kontakDaruratKeamananState.findMany.page = page;
|
||||||
|
kontakDaruratKeamananState.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.keamanan.kontakdaruratkeamanan[
|
||||||
|
"findMany"
|
||||||
|
].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
kontakDaruratKeamananState.findMany.data = res.data.data ?? [];
|
||||||
|
kontakDaruratKeamananState.findMany.totalPages =
|
||||||
|
res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
kontakDaruratKeamananState.findMany.data = [];
|
||||||
|
kontakDaruratKeamananState.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch kontak darurat paginated:", err);
|
||||||
|
kontakDaruratKeamananState.findMany.data = [];
|
||||||
|
kontakDaruratKeamananState.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
kontakDaruratKeamananState.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -83,10 +102,15 @@ const kontakDaruratKeamananState = proxy({
|
|||||||
include: {
|
include: {
|
||||||
kontakItems: {
|
kontakItems: {
|
||||||
include: {
|
include: {
|
||||||
image: true;
|
kontakItem: {
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
image: true;
|
image: true;
|
||||||
|
kategori: true;
|
||||||
};
|
};
|
||||||
}> | null,
|
}> | null,
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -168,14 +192,8 @@ const kontakDaruratKeamananState = proxy({
|
|||||||
this.id = data.id;
|
this.id = data.id;
|
||||||
this.form = {
|
this.form = {
|
||||||
nama: data.nama,
|
nama: data.nama,
|
||||||
imageId: data.imageId,
|
imageId: data.imageId || '',
|
||||||
kontakItems: [
|
kategoriId: data.kontakItems?.map((item: any) => item.kontakItemId) || []
|
||||||
{
|
|
||||||
nama: data.kontakItems.nama,
|
|
||||||
nomorTelepon: data.kontakItems.nomorTelepon,
|
|
||||||
imageId: data.kontakItems.imageId,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
return data;
|
return data;
|
||||||
} else {
|
} else {
|
||||||
@@ -213,13 +231,7 @@ const kontakDaruratKeamananState = proxy({
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
nama: this.form.nama,
|
nama: this.form.nama,
|
||||||
imageId: this.form.imageId,
|
imageId: this.form.imageId,
|
||||||
kontakItems: [
|
kategoriId: this.form.kategoriId,
|
||||||
{
|
|
||||||
nama: this.form.kontakItems[0].nama,
|
|
||||||
nomorTelepon: this.form.kontakItems[0].nomorTelepon,
|
|
||||||
imageId: this.form.kontakItems[0].imageId,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -256,4 +268,257 @@ const kontakDaruratKeamananState = proxy({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default kontakDaruratKeamananState;
|
const templateFormItem = z.object({
|
||||||
|
nama: z.string().min(1, "Nama minimal 1 karakter"),
|
||||||
|
nomorTelepon: z.string().min(1, "Nomor Telepon minimal 1 karakter"),
|
||||||
|
imageId: z.string().nonempty(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultFormItem = {
|
||||||
|
nama: "",
|
||||||
|
nomorTelepon: "",
|
||||||
|
imageId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const kontakDaruratItem = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultFormItem },
|
||||||
|
loading: false,
|
||||||
|
async create() {
|
||||||
|
const cek = templateFormItem.safeParse(
|
||||||
|
kontakDaruratItem.create.form
|
||||||
|
);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
kontakDaruratItem.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.keamanan.kontakitem[
|
||||||
|
"create"
|
||||||
|
].post(kontakDaruratItem.create.form);
|
||||||
|
if (res.status === 200) {
|
||||||
|
kontakDaruratItem.findMany.load();
|
||||||
|
return toast.success("success create");
|
||||||
|
}
|
||||||
|
console.log(res);
|
||||||
|
return toast.error("failed create");
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
} finally {
|
||||||
|
kontakDaruratItem.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMany: {
|
||||||
|
data: null as Array<
|
||||||
|
Prisma.KontakItemGetPayload<{
|
||||||
|
include: {
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
|
}>
|
||||||
|
> | null,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
loading: false,
|
||||||
|
search: "",
|
||||||
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
kontakDaruratItem.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
kontakDaruratItem.findMany.page = page;
|
||||||
|
kontakDaruratItem.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.keamanan.kontakitem[
|
||||||
|
"find-many"
|
||||||
|
].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
kontakDaruratItem.findMany.data = res.data.data ?? [];
|
||||||
|
kontakDaruratItem.findMany.totalPages =
|
||||||
|
res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
kontakDaruratItem.findMany.data = [];
|
||||||
|
kontakDaruratItem.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch kontak darurat paginated:", err);
|
||||||
|
kontakDaruratItem.findMany.data = [];
|
||||||
|
kontakDaruratItem.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
kontakDaruratItem.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.KontakItemGetPayload<{
|
||||||
|
include: {
|
||||||
|
kategori: true;
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
|
}> | null,
|
||||||
|
loading: false,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/keamanan/kontakitem/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
kontakDaruratItem.findUnique.data = data.data ?? null;
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch data", res.status, res.statusText);
|
||||||
|
kontakDaruratItem.findUnique.data = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching data:", error);
|
||||||
|
kontakDaruratItem.findUnique.data = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
try {
|
||||||
|
kontakDaruratItem.delete.loading = true;
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/keamanan/kontakitem/del/${id}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(result.message || "Kontak item berhasil dihapus");
|
||||||
|
await kontakDaruratItem.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus kontak item");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus kontak item");
|
||||||
|
} finally {
|
||||||
|
kontakDaruratItem.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultFormItem },
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
async load(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
toast.warn("ID tidak valid");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/keamanan/kontakitem/${id}`,
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
if (result?.success) {
|
||||||
|
const data = result.data;
|
||||||
|
this.id = data.id;
|
||||||
|
this.form = {
|
||||||
|
nama: data.nama,
|
||||||
|
nomorTelepon: data.nomorTelepon,
|
||||||
|
imageId: data.imageId,
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
throw new Error(result?.message || "Gagal memuat data");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading kontak darurat:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal memuat data"
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async update() {
|
||||||
|
const cek = templateFormItem.safeParse(
|
||||||
|
kontakDaruratItem.update.form
|
||||||
|
);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
kontakDaruratItem.update.loading = true;
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/keamanan/kontakitem/${this.id}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
nama: this.form.nama,
|
||||||
|
nomorTelepon: this.form.nomorTelepon,
|
||||||
|
imageId: this.form.imageId,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(
|
||||||
|
errorData.message || `HTTP error! status: ${response.status}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
toast.success("Berhasil update kontak item");
|
||||||
|
await kontakDaruratItem.findMany.load(); // refresh list
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal mengupdate kontak item");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating kontak item:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Gagal mengupdate kontak item"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
kontakDaruratItem.update.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
kontakDaruratItem.update.id = "";
|
||||||
|
kontakDaruratItem.update.form = { ...defaultFormItem };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const kontakDarurat = proxy({
|
||||||
|
kontakDaruratKeamananState,
|
||||||
|
kontakDaruratItem,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default kontakDarurat;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -97,13 +98,37 @@ const laporanPublikState = proxy({
|
|||||||
include: { penanganan: true };
|
include: { penanganan: true };
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.keamanan.laporanpublik["find-many"].get();
|
totalPages: 1,
|
||||||
if (res.status === 200) {
|
loading: false,
|
||||||
laporanPublikState.findMany.data = res.data?.data ?? [];
|
search: "",
|
||||||
}
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
|
laporanPublikState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
laporanPublikState.findMany.page = page;
|
||||||
|
laporanPublikState.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.keamanan.laporanpublik["find-many"].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
laporanPublikState.findMany.data = res.data.data ?? [];
|
||||||
|
laporanPublikState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
laporanPublikState.findMany.data = [];
|
||||||
|
laporanPublikState.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch laporan publik paginated:", err);
|
||||||
|
laporanPublikState.findMany.data = [];
|
||||||
|
laporanPublikState.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
laporanPublikState.findMany.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
findUnique: {
|
findUnique: {
|
||||||
data: null as Prisma.LaporanPublikGetPayload<{
|
data: null as Prisma.LaporanPublikGetPayload<{
|
||||||
include: { penanganan: true };
|
include: { penanganan: true };
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -88,12 +89,37 @@ const pencegahanKriminalitasState = proxy({
|
|||||||
};
|
};
|
||||||
}>[]
|
}>[]
|
||||||
| null,
|
| null,
|
||||||
async load() {
|
page: 1,
|
||||||
const res = await ApiFetch.api.keamanan.pencegahankriminalitas[
|
totalPages: 1,
|
||||||
"find-many"
|
loading: false,
|
||||||
].get();
|
search: "",
|
||||||
if (res.status === 200) {
|
load: async (page = 1, limit = 10, search = "") => {
|
||||||
pencegahanKriminalitasState.findMany.data = res.data?.data ?? [];
|
pencegahanKriminalitasState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||||
|
pencegahanKriminalitasState.findMany.page = page;
|
||||||
|
pencegahanKriminalitasState.findMany.search = search;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query: any = { page, limit };
|
||||||
|
if (search) query.search = search;
|
||||||
|
|
||||||
|
const res = await ApiFetch.api.keamanan.pencegahankriminalitas[
|
||||||
|
"find-many"
|
||||||
|
].get({ query });
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data?.success) {
|
||||||
|
pencegahanKriminalitasState.findMany.data = res.data.data ?? [];
|
||||||
|
pencegahanKriminalitasState.findMany.totalPages =
|
||||||
|
res.data.totalPages ?? 1;
|
||||||
|
} else {
|
||||||
|
pencegahanKriminalitasState.findMany.data = [];
|
||||||
|
pencegahanKriminalitasState.findMany.totalPages = 1;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Gagal fetch pencegahan kriminalitas paginated:", err);
|
||||||
|
pencegahanKriminalitasState.findMany.data = [];
|
||||||
|
pencegahanKriminalitasState.findMany.totalPages = 1;
|
||||||
|
} finally {
|
||||||
|
pencegahanKriminalitasState.findMany.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -23,7 +23,12 @@ type ProgramInovasiForm = Prisma.ProgramInovasiGetPayload<{
|
|||||||
|
|
||||||
const programInovasi = proxy({
|
const programInovasi = proxy({
|
||||||
create: {
|
create: {
|
||||||
form: {} as ProgramInovasiForm,
|
form: {
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
imageId: "",
|
||||||
|
link: ""
|
||||||
|
} as ProgramInovasiForm,
|
||||||
loading: false,
|
loading: false,
|
||||||
async create() {
|
async create() {
|
||||||
// Ensure all required fields are non-null
|
// Ensure all required fields are non-null
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ const sdgsDesa = proxy({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
findUnique: {
|
findUnique: {
|
||||||
data: null as Prisma.SDGSDesaGetPayload<{
|
data: null as Prisma.SdgsDesaGetPayload<{
|
||||||
include: {
|
include: {
|
||||||
image: true;
|
image: true;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { IconFileText, IconBuildingStore, IconSparkles, IconUsers } from '@tabler/icons-react';
|
import { IconFileText, IconBuildingStore, IconSparkles, IconUsers } from '@tabler/icons-react';
|
||||||
@@ -62,43 +62,50 @@ function LayoutTabsLayanan({ children }: { children: React.ReactNode }) {
|
|||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
<Title order={3} fw={700} style={{ color: "#1A1B1E" }}>Layanan</Title>
|
<Title order={3} fw={700} style={{ color: "#1A1B1E" }}>Layanan</Title>
|
||||||
<Tabs
|
<Tabs
|
||||||
color={colors['blue-button']}
|
color={colors["blue-button"]}
|
||||||
variant='pills'
|
variant="pills"
|
||||||
value={activeTab}
|
value={activeTab}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
radius="lg"
|
radius="lg"
|
||||||
keepMounted={false}
|
keepMounted={false}
|
||||||
>
|
>
|
||||||
<TabsList
|
{/* ✅ Scroll horizontal wrapper */}
|
||||||
p="sm"
|
<ScrollArea type="auto" offsetScrollbars>
|
||||||
style={{
|
<TabsList
|
||||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
p="sm"
|
||||||
borderRadius: "1rem",
|
style={{
|
||||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||||
}}
|
borderRadius: "1rem",
|
||||||
>
|
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||||
{tabs.map((tab, i) => (
|
display: "flex",
|
||||||
<Tooltip
|
flexWrap: "nowrap",
|
||||||
key={i}
|
gap: "0.5rem",
|
||||||
label={tab.tooltip}
|
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||||
position="bottom"
|
}}
|
||||||
withArrow
|
>
|
||||||
transitionProps={{ transition: 'pop', duration: 200 }}
|
{tabs.map((tab, i) => (
|
||||||
>
|
<Tooltip
|
||||||
<TabsTab
|
key={i}
|
||||||
value={tab.value}
|
label={tab.tooltip}
|
||||||
leftSection={tab.icon}
|
position="bottom"
|
||||||
style={{
|
withArrow
|
||||||
fontWeight: 600,
|
transitionProps={{ transition: 'pop', duration: 200 }}
|
||||||
fontSize: "0.9rem",
|
|
||||||
transition: "all 0.2s ease",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{tab.label}
|
<TabsTab
|
||||||
</TabsTab>
|
value={tab.value}
|
||||||
</Tooltip>
|
leftSection={tab.icon}
|
||||||
))}
|
style={{
|
||||||
</TabsList>
|
fontWeight: 600,
|
||||||
|
fontSize: "0.9rem",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</TabsTab>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
{tabs.map((tab, i) => (
|
{tabs.map((tab, i) => (
|
||||||
<TabsPanel
|
<TabsPanel
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { IconNews, IconCategory } from '@tabler/icons-react';
|
import { IconNews, IconCategory } from '@tabler/icons-react';
|
||||||
@@ -49,43 +49,50 @@ function LayoutTabsBerita({ children }: { children: React.ReactNode }) {
|
|||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
<Title order={3} fw={700} style={{ color: "#1A1B1E" }}>Berita Desa</Title>
|
<Title order={3} fw={700} style={{ color: "#1A1B1E" }}>Berita Desa</Title>
|
||||||
<Tabs
|
<Tabs
|
||||||
color={colors['blue-button']}
|
color={colors["blue-button"]}
|
||||||
variant="pills"
|
variant="pills"
|
||||||
value={activeTab}
|
value={activeTab}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
radius="lg"
|
radius="lg"
|
||||||
keepMounted={false}
|
keepMounted={false}
|
||||||
>
|
>
|
||||||
<TabsList
|
{/* ✅ Scroll horizontal wrapper */}
|
||||||
p="sm"
|
<ScrollArea type="auto" offsetScrollbars>
|
||||||
style={{
|
<TabsList
|
||||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
p="sm"
|
||||||
borderRadius: "1rem",
|
style={{
|
||||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||||
}}
|
borderRadius: "1rem",
|
||||||
>
|
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||||
{tabs.map((tab, i) => (
|
display: "flex",
|
||||||
<Tooltip
|
flexWrap: "nowrap",
|
||||||
key={i}
|
gap: "0.5rem",
|
||||||
label={tab.tooltip}
|
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||||
position="bottom"
|
}}
|
||||||
withArrow
|
>
|
||||||
transitionProps={{ transition: 'pop', duration: 200 }}
|
{tabs.map((tab, i) => (
|
||||||
>
|
<Tooltip
|
||||||
<TabsTab
|
key={i}
|
||||||
value={tab.value}
|
label={tab.tooltip}
|
||||||
leftSection={tab.icon}
|
position="bottom"
|
||||||
style={{
|
withArrow
|
||||||
fontWeight: 600,
|
transitionProps={{ transition: 'pop', duration: 200 }}
|
||||||
fontSize: "0.9rem",
|
|
||||||
transition: "all 0.2s ease",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{tab.label}
|
<TabsTab
|
||||||
</TabsTab>
|
value={tab.value}
|
||||||
</Tooltip>
|
leftSection={tab.icon}
|
||||||
))}
|
style={{
|
||||||
</TabsList>
|
fontWeight: 600,
|
||||||
|
fontSize: "0.9rem",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</TabsTab>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
{tabs.map((tab, i) => (
|
{tabs.map((tab, i) => (
|
||||||
<TabsPanel
|
<TabsPanel
|
||||||
|
|||||||
@@ -96,9 +96,11 @@ function ListBerita({ search }: { search: string }) {
|
|||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd style={{ width: '30%' }}>
|
<TableTd style={{ width: '30%' }}>
|
||||||
<Text fw={500} truncate="end" lineClamp={1}>
|
<Box w={200}>
|
||||||
{item.judul}
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
</Text>
|
{item.judul}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '20%' }}>
|
<TableTd style={{ width: '20%' }}>
|
||||||
<Text fz="sm" c="dimmed">
|
<Text fz="sm" c="dimmed">
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export default function ListImage() {
|
|||||||
radius="md"
|
radius="md"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
stateFileStorage
|
stateFileStorage
|
||||||
.del({ name: v.name })
|
.del({ id: v.id })
|
||||||
.finally(() => toast("Foto berhasil dihapus"));
|
.finally(() => toast("Foto berhasil dihapus"));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -143,14 +143,15 @@ export default function ListImage() {
|
|||||||
<Flex justify="center">
|
<Flex justify="center">
|
||||||
<Pagination
|
<Pagination
|
||||||
total={total}
|
total={total}
|
||||||
|
value={stateFileStorage.page} // Changed from page to value
|
||||||
size="md"
|
size="md"
|
||||||
radius="md"
|
radius="md"
|
||||||
withEdges
|
withEdges
|
||||||
onChange={(page) => {
|
onChange={(page) => {
|
||||||
stateFileStorage.page = page;
|
stateFileStorage.load({ page });
|
||||||
stateFileStorage.load();
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { IconPhoto, IconVideo } from '@tabler/icons-react';
|
import { IconPhoto, IconVideo } from '@tabler/icons-react';
|
||||||
@@ -48,43 +48,50 @@ function LayoutTabsGallery({ children }: { children: React.ReactNode }) {
|
|||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
<Title order={3} fw={700} style={{ color: "#1A1B1E" }}>Gallery</Title>
|
<Title order={3} fw={700} style={{ color: "#1A1B1E" }}>Gallery</Title>
|
||||||
<Tabs
|
<Tabs
|
||||||
color={colors['blue-button']}
|
color={colors["blue-button"]}
|
||||||
variant='pills'
|
variant="pills"
|
||||||
value={activeTab}
|
value={activeTab}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
radius="lg"
|
radius="lg"
|
||||||
keepMounted={false}
|
keepMounted={false}
|
||||||
>
|
>
|
||||||
<TabsList
|
{/* ✅ Scroll horizontal wrapper */}
|
||||||
p="sm"
|
<ScrollArea type="auto" offsetScrollbars>
|
||||||
style={{
|
<TabsList
|
||||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
p="sm"
|
||||||
borderRadius: "1rem",
|
style={{
|
||||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||||
}}
|
borderRadius: "1rem",
|
||||||
>
|
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||||
{tabs.map((tab, i) => (
|
display: "flex",
|
||||||
<Tooltip
|
flexWrap: "nowrap",
|
||||||
key={i}
|
gap: "0.5rem",
|
||||||
label={tab.tooltip}
|
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||||
position="bottom"
|
}}
|
||||||
withArrow
|
>
|
||||||
transitionProps={{ transition: 'pop', duration: 200 }}
|
{tabs.map((tab, i) => (
|
||||||
>
|
<Tooltip
|
||||||
<TabsTab
|
key={i}
|
||||||
value={tab.value}
|
label={tab.tooltip}
|
||||||
leftSection={tab.icon}
|
position="bottom"
|
||||||
style={{
|
withArrow
|
||||||
fontWeight: 600,
|
transitionProps={{ transition: 'pop', duration: 200 }}
|
||||||
fontSize: "0.9rem",
|
|
||||||
transition: "all 0.2s ease",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{tab.label}
|
<TabsTab
|
||||||
</TabsTab>
|
value={tab.value}
|
||||||
</Tooltip>
|
leftSection={tab.icon}
|
||||||
))}
|
style={{
|
||||||
</TabsList>
|
fontWeight: 600,
|
||||||
|
fontSize: "0.9rem",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</TabsTab>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
{tabs.map((tab, i) => (
|
{tabs.map((tab, i) => (
|
||||||
<TabsPanel
|
<TabsPanel
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { IconListDetails, IconCategory } from '@tabler/icons-react';
|
import { IconListDetails, IconCategory } from '@tabler/icons-react';
|
||||||
@@ -48,37 +48,44 @@ function LayoutTabsLayanan({ children }: { children: React.ReactNode }) {
|
|||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
<Title order={3} fw={700} style={{ color: "#1A1B1E" }}>Pengumuman</Title>
|
<Title order={3} fw={700} style={{ color: "#1A1B1E" }}>Pengumuman</Title>
|
||||||
<Tabs
|
<Tabs
|
||||||
color={colors['blue-button']}
|
color={colors["blue-button"]}
|
||||||
variant='pills'
|
variant="pills"
|
||||||
value={activeTab}
|
value={activeTab}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
radius="lg"
|
radius="lg"
|
||||||
keepMounted={false}
|
keepMounted={false}
|
||||||
>
|
>
|
||||||
<TabsList
|
{/* ✅ Scroll horizontal wrapper */}
|
||||||
p="sm"
|
<ScrollArea type="auto" offsetScrollbars>
|
||||||
style={{
|
<TabsList
|
||||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
p="sm"
|
||||||
borderRadius: "1rem",
|
style={{
|
||||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||||
}}
|
borderRadius: "1rem",
|
||||||
>
|
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||||
{tabs.map((tab, i) => (
|
display: "flex",
|
||||||
<Tooltip key={i} label={tab.tooltip} position="bottom" withArrow transitionProps={{ transition: 'pop', duration: 200 }}>
|
flexWrap: "nowrap",
|
||||||
<TabsTab
|
gap: "0.5rem",
|
||||||
value={tab.value}
|
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||||
leftSection={tab.icon}
|
}}
|
||||||
style={{
|
>
|
||||||
fontWeight: 600,
|
{tabs.map((tab, i) => (
|
||||||
fontSize: "0.9rem",
|
<Tooltip key={i} label={tab.tooltip} position="bottom" withArrow transitionProps={{ transition: 'pop', duration: 200 }}>
|
||||||
transition: "all 0.2s ease",
|
<TabsTab
|
||||||
}}
|
value={tab.value}
|
||||||
>
|
leftSection={tab.icon}
|
||||||
{tab.label}
|
style={{
|
||||||
</TabsTab>
|
fontWeight: 600,
|
||||||
</Tooltip>
|
fontSize: "0.9rem",
|
||||||
))}
|
transition: "all 0.2s ease",
|
||||||
</TabsList>
|
}}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</TabsTab>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
{tabs.map((tab, i) => (
|
{tabs.map((tab, i) => (
|
||||||
<TabsPanel
|
<TabsPanel
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconCategory, IconListCheck } from '@tabler/icons-react';
|
import { IconCategory, IconListCheck } from '@tabler/icons-react';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
@@ -48,37 +48,44 @@ function LayoutTabsPotensi({ children }: { children: React.ReactNode }) {
|
|||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
<Title order={3} fw={700} style={{ color: "#1A1B1E" }}>Potensi</Title>
|
<Title order={3} fw={700} style={{ color: "#1A1B1E" }}>Potensi</Title>
|
||||||
<Tabs
|
<Tabs
|
||||||
color={colors['blue-button']}
|
color={colors["blue-button"]}
|
||||||
variant='pills'
|
variant="pills"
|
||||||
value={activeTab}
|
value={activeTab}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
radius="lg"
|
radius="lg"
|
||||||
keepMounted={false}
|
keepMounted={false}
|
||||||
>
|
>
|
||||||
<TabsList
|
{/* ✅ Scroll horizontal wrapper */}
|
||||||
p="sm"
|
<ScrollArea type="auto" offsetScrollbars>
|
||||||
style={{
|
<TabsList
|
||||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
p="sm"
|
||||||
borderRadius: "1rem",
|
style={{
|
||||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||||
}}
|
borderRadius: "1rem",
|
||||||
>
|
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||||
{tabs.map((tab, i) => (
|
display: "flex",
|
||||||
<Tooltip key={i} label={tab.tooltip} position="bottom" withArrow transitionProps={{ transition: 'pop', duration: 200 }}>
|
flexWrap: "nowrap",
|
||||||
<TabsTab
|
gap: "0.5rem",
|
||||||
value={tab.value}
|
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||||
leftSection={tab.icon}
|
}}
|
||||||
style={{
|
>
|
||||||
fontWeight: 600,
|
{tabs.map((tab, i) => (
|
||||||
fontSize: "0.9rem",
|
<Tooltip key={i} label={tab.tooltip} position="bottom" withArrow transitionProps={{ transition: 'pop', duration: 200 }}>
|
||||||
transition: "all 0.2s ease",
|
<TabsTab
|
||||||
}}
|
value={tab.value}
|
||||||
>
|
leftSection={tab.icon}
|
||||||
{tab.label}
|
style={{
|
||||||
</TabsTab>
|
fontWeight: 600,
|
||||||
</Tooltip>
|
fontSize: "0.9rem",
|
||||||
))}
|
transition: "all 0.2s ease",
|
||||||
</TabsList>
|
}}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</TabsTab>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
{tabs.map((tab, i) => (
|
{tabs.map((tab, i) => (
|
||||||
<TabsPanel
|
<TabsPanel
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
import { ScrollArea, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { IconUser, IconUsers, IconCalendar } from '@tabler/icons-react';
|
import { IconUser, IconUsers, IconCalendar } from '@tabler/icons-react';
|
||||||
@@ -55,56 +55,63 @@ function LayoutTabsDetail({ children }: { children: React.ReactNode }) {
|
|||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
<Title order={3} fw={700} style={{ color: "#1A1B1E" }}>Profile Desa</Title>
|
<Title order={3} fw={700} style={{ color: "#1A1B1E" }}>Profile Desa</Title>
|
||||||
<Tabs
|
<Tabs
|
||||||
color={colors['blue-button']}
|
color={colors["blue-button"]}
|
||||||
variant='pills'
|
variant="pills"
|
||||||
value={activeTab}
|
value={activeTab}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
radius="lg"
|
radius="lg"
|
||||||
keepMounted={false}
|
keepMounted={false}
|
||||||
>
|
>
|
||||||
<TabsList
|
{/* ✅ Scroll horizontal wrapper */}
|
||||||
p="sm"
|
<ScrollArea type="auto" offsetScrollbars>
|
||||||
style={{
|
<TabsList
|
||||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
p="sm"
|
||||||
borderRadius: "1rem",
|
|
||||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{tabs.map((tab, i) => (
|
|
||||||
<Tooltip key={i} label={tab.tooltip} position="bottom" withArrow transitionProps={{ transition: 'pop', duration: 200 }}>
|
|
||||||
<TabsTab
|
|
||||||
value={tab.value}
|
|
||||||
leftSection={tab.icon}
|
|
||||||
style={{
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: "0.9rem",
|
|
||||||
transition: "all 0.2s ease",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{tab.label}
|
|
||||||
</TabsTab>
|
|
||||||
</Tooltip>
|
|
||||||
))}
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
{tabs.map((tab, i) => (
|
|
||||||
<TabsPanel
|
|
||||||
key={i}
|
|
||||||
value={tab.value}
|
|
||||||
style={{
|
style={{
|
||||||
padding: "1.5rem",
|
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||||
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
|
||||||
borderRadius: "1rem",
|
borderRadius: "1rem",
|
||||||
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "nowrap",
|
||||||
|
gap: "0.5rem",
|
||||||
|
paddingInline: "0.5rem", // ✅ biar nggak nempel ke tepi
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Konten dummy, bisa diganti sesuai routing */}
|
{tabs.map((tab, i) => (
|
||||||
<>{children}</>
|
<Tooltip key={i} label={tab.tooltip} position="bottom" withArrow transitionProps={{ transition: 'pop', duration: 200 }}>
|
||||||
</TabsPanel>
|
<TabsTab
|
||||||
))}
|
value={tab.value}
|
||||||
</Tabs>
|
leftSection={tab.icon}
|
||||||
</Stack>
|
style={{
|
||||||
);
|
fontWeight: 600,
|
||||||
|
fontSize: "0.9rem",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</TabsTab>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<TabsPanel
|
||||||
|
key={i}
|
||||||
|
value={tab.value}
|
||||||
|
style={{
|
||||||
|
padding: "1.5rem",
|
||||||
|
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Konten dummy, bisa diganti sesuai routing */}
|
||||||
|
<>{children}</>
|
||||||
|
</TabsPanel>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LayoutTabsDetail;
|
export default LayoutTabsDetail;
|
||||||
|
|||||||
@@ -1,71 +1,148 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
import {
|
||||||
|
Stack,
|
||||||
|
Tabs,
|
||||||
|
TabsList,
|
||||||
|
TabsPanel,
|
||||||
|
TabsTab,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
ScrollArea,
|
||||||
|
} from '@mantine/core';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
IconFileAnalytics,
|
||||||
|
IconCoins,
|
||||||
|
IconShoppingCart,
|
||||||
|
IconWallet,
|
||||||
|
} from '@tabler/icons-react';
|
||||||
|
|
||||||
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const pathname = usePathname()
|
const pathname = usePathname();
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
label: "APB Desa",
|
label: "APB Desa",
|
||||||
value: "apbdesa",
|
value: "apbdesa",
|
||||||
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa"
|
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa",
|
||||||
|
icon: <IconFileAnalytics size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Lihat ringkasan Anggaran Pendapatan dan Belanja Desa",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Pendapatan",
|
label: "Pendapatan",
|
||||||
value: "pendapatan",
|
value: "pendapatan",
|
||||||
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan"
|
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan",
|
||||||
|
icon: <IconCoins size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Kelola data pendapatan desa",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Belanja",
|
label: "Belanja",
|
||||||
value: "belanja",
|
value: "belanja",
|
||||||
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja"
|
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja",
|
||||||
|
icon: <IconShoppingCart size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Atur data belanja desa",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Pembiayaan",
|
label: "Pembiayaan",
|
||||||
value: "pembiayaan",
|
value: "pembiayaan",
|
||||||
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan"
|
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan",
|
||||||
|
icon: <IconWallet size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Kelola data pembiayaan desa",
|
||||||
},
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
|
||||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
const currentTab = tabs.find((tab) => tab.href === pathname);
|
||||||
|
const [activeTab, setActiveTab] = useState<string | null>(
|
||||||
|
currentTab?.value || tabs[0].value
|
||||||
|
);
|
||||||
|
|
||||||
const handleTabChange = (value: string | null) => {
|
const handleTabChange = (value: string | null) => {
|
||||||
const tab = tabs.find(t => t.value === value)
|
const tab = tabs.find((t) => t.value === value);
|
||||||
if (tab) {
|
if (tab) {
|
||||||
router.push(tab.href)
|
router.push(tab.href);
|
||||||
}
|
}
|
||||||
setActiveTab(value)
|
setActiveTab(value);
|
||||||
}
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const match = tabs.find(tab => tab.href === pathname)
|
const match = tabs.find((tab) => tab.href === pathname);
|
||||||
if (match) {
|
if (match) {
|
||||||
setActiveTab(match.value)
|
setActiveTab(match.value);
|
||||||
}
|
}
|
||||||
}, [pathname])
|
}, [pathname]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack gap="lg">
|
||||||
<Title order={3}>Pendapatan Asli Desa</Title>
|
<Title order={2} fw={700} style={{ color: "#1A1B1E" }}>
|
||||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
Pendapatan Asli Desa
|
||||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
</Title>
|
||||||
{tabs.map((e, i) => (
|
|
||||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
<Tabs
|
||||||
))}
|
color={colors['blue-button']}
|
||||||
</TabsList>
|
variant="pills"
|
||||||
{tabs.map((e, i) => (
|
value={activeTab}
|
||||||
<TabsPanel key={i} value={e.value}>
|
onChange={handleTabChange}
|
||||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
radius="lg"
|
||||||
<></>
|
keepMounted={false}
|
||||||
|
>
|
||||||
|
{/* ✅ Scroll horizontal wrapper */}
|
||||||
|
<ScrollArea type="auto" offsetScrollbars>
|
||||||
|
<TabsList
|
||||||
|
p="sm"
|
||||||
|
style={{
|
||||||
|
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "nowrap",
|
||||||
|
gap: "0.5rem",
|
||||||
|
paddingInline: "0.5rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<Tooltip
|
||||||
|
key={i}
|
||||||
|
label={tab.tooltip}
|
||||||
|
position="bottom"
|
||||||
|
withArrow
|
||||||
|
transitionProps={{ transition: "pop", duration: 200 }}
|
||||||
|
>
|
||||||
|
<TabsTab
|
||||||
|
value={tab.value}
|
||||||
|
leftSection={tab.icon}
|
||||||
|
style={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "0.9rem",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</TabsTab>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<TabsPanel
|
||||||
|
key={i}
|
||||||
|
value={tab.value}
|
||||||
|
style={{
|
||||||
|
padding: "1.5rem",
|
||||||
|
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
</TabsPanel>
|
</TabsPanel>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
{children}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,19 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, MultiSelect, Paper, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
MultiSelect,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Group,
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
@@ -23,7 +35,7 @@ function EditAPBDesa() {
|
|||||||
pembiayaanIds: apbState.update.form.pembiayaanIds || [],
|
pembiayaanIds: apbState.update.form.pembiayaanIds || [],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load APB desa by id saat pertama kali
|
// Load APB desa by id
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadAPBdesa = async () => {
|
const loadAPBdesa = async () => {
|
||||||
const id = params?.id as string;
|
const id = params?.id as string;
|
||||||
@@ -46,12 +58,10 @@ function EditAPBDesa() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
loadAPBdesa();
|
loadAPBdesa();
|
||||||
}, [params?.id]); // ✅ hapus beritaState dari dependency
|
}, [params?.id]);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Update global state with form data
|
|
||||||
apbState.update.form = {
|
apbState.update.form = {
|
||||||
...apbState.update.form,
|
...apbState.update.form,
|
||||||
tahun: Number(formData.tahun),
|
tahun: Number(formData.tahun),
|
||||||
@@ -70,65 +80,95 @@ function EditAPBDesa() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors["blue-button"]} size={30} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button
|
||||||
</Box>
|
variant="subtle"
|
||||||
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
|
onClick={() => router.back()}
|
||||||
<Stack gap={"xs"}>
|
p="xs"
|
||||||
<Title order={3}>Edit APB Desa</Title>
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit APB Desa
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Form Card */}
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
{/* Tahun */}
|
||||||
<TextInput
|
<TextInput
|
||||||
type='number'
|
type="number"
|
||||||
value={formData.tahun}
|
value={formData.tahun}
|
||||||
onChange={(val) => {
|
onChange={(e) =>
|
||||||
setFormData({ ...formData, tahun: val.target.value });
|
setFormData({ ...formData, tahun: e.target.value })
|
||||||
}}
|
}
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Tahun</Text>}
|
label={<Text fz="sm" fw="bold">Tahun</Text>}
|
||||||
placeholder="masukkan tahun"
|
placeholder="Masukkan tahun anggaran"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Selects */}
|
||||||
<SelectPendapatan
|
<SelectPendapatan
|
||||||
selectedIds={formData.pendapatanIds}
|
selectedIds={formData.pendapatanIds}
|
||||||
onSelectionChange={(ids) => {
|
onSelectionChange={(ids) =>
|
||||||
setFormData({ ...formData, pendapatanIds: ids });
|
setFormData({ ...formData, pendapatanIds: ids })
|
||||||
}}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectBelanja
|
<SelectBelanja
|
||||||
selectedIds={formData.belanjaIds}
|
selectedIds={formData.belanjaIds}
|
||||||
onSelectionChange={(ids) => {
|
onSelectionChange={(ids) =>
|
||||||
setFormData({ ...formData, belanjaIds: ids });
|
setFormData({ ...formData, belanjaIds: ids })
|
||||||
}}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectPembiayaan
|
<SelectPembiayaan
|
||||||
selectedIds={formData.pembiayaanIds}
|
selectedIds={formData.pembiayaanIds}
|
||||||
onSelectionChange={(ids) => {
|
onSelectionChange={(ids) =>
|
||||||
setFormData({ ...formData, pembiayaanIds: ids });
|
setFormData({ ...formData, pembiayaanIds: ids })
|
||||||
}}
|
}
|
||||||
/>
|
/>
|
||||||
<Button onClick={handleSubmit}>Simpan</Button>
|
|
||||||
|
{/* Save Button */}
|
||||||
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/* --- Sub Components --- */
|
||||||
|
|
||||||
/* Select Pendapatan */
|
function SelectPendapatan({ selectedIds, onSelectionChange }: { selectedIds: string[]; onSelectionChange: (ids: string[]) => void }) {
|
||||||
interface SelectPendapatanProps {
|
|
||||||
selectedIds: string[];
|
|
||||||
onSelectionChange: (ids: string[]) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectPendapatan({
|
|
||||||
selectedIds = [],
|
|
||||||
onSelectionChange,
|
|
||||||
}: SelectPendapatanProps) {
|
|
||||||
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan);
|
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
pendapatanState.findMany.load().then(() => {
|
pendapatanState.findMany.load();
|
||||||
console.log("Pendapatan berhasil dimuat:", pendapatanState.findMany.data);
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!pendapatanState.findMany.data) {
|
if (!pendapatanState.findMany.data) {
|
||||||
@@ -137,10 +177,10 @@ function EditAPBDesa() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Pendapatan</Text>}
|
label={<Text fz="sm" fw="bold">Pendapatan</Text>}
|
||||||
data={pendapatanState.findMany.data.map(p => ({
|
data={pendapatanState.findMany.data.map((p: any) => ({
|
||||||
value: p.id,
|
value: p.id,
|
||||||
label: p.name
|
label: p.name,
|
||||||
}))}
|
}))}
|
||||||
value={selectedIds}
|
value={selectedIds}
|
||||||
onChange={onSelectionChange}
|
onChange={onSelectionChange}
|
||||||
@@ -152,22 +192,11 @@ function EditAPBDesa() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Select Belanja */
|
function SelectBelanja({ selectedIds, onSelectionChange }: { selectedIds: string[]; onSelectionChange: (ids: string[]) => void }) {
|
||||||
interface SelectBelanjaProps {
|
|
||||||
selectedIds: string[];
|
|
||||||
onSelectionChange: (ids: string[]) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectBelanja({
|
|
||||||
selectedIds = [],
|
|
||||||
onSelectionChange,
|
|
||||||
}: SelectBelanjaProps) {
|
|
||||||
const belanjaState = useProxy(PendapatanAsliDesa.belanja);
|
const belanjaState = useProxy(PendapatanAsliDesa.belanja);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
belanjaState.findMany.load().then(() => {
|
belanjaState.findMany.load();
|
||||||
console.log("Belanja berhasil dimuat:", belanjaState.findMany.data);
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!belanjaState.findMany.data) {
|
if (!belanjaState.findMany.data) {
|
||||||
@@ -176,10 +205,10 @@ function EditAPBDesa() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Belanja</Text>}
|
label={<Text fz="sm" fw="bold">Belanja</Text>}
|
||||||
data={belanjaState.findMany.data.map(b => ({
|
data={belanjaState.findMany.data.map((b: any) => ({
|
||||||
value: b.id,
|
value: b.id,
|
||||||
label: b.name
|
label: b.name,
|
||||||
}))}
|
}))}
|
||||||
value={selectedIds}
|
value={selectedIds}
|
||||||
onChange={onSelectionChange}
|
onChange={onSelectionChange}
|
||||||
@@ -191,22 +220,11 @@ function EditAPBDesa() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Select Pembiayaan */
|
function SelectPembiayaan({ selectedIds, onSelectionChange }: { selectedIds: string[]; onSelectionChange: (ids: string[]) => void }) {
|
||||||
interface SelectPembiayaanProps {
|
|
||||||
selectedIds: string[];
|
|
||||||
onSelectionChange: (ids: string[]) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectPembiayaan({
|
|
||||||
selectedIds = [],
|
|
||||||
onSelectionChange,
|
|
||||||
}: SelectPembiayaanProps) {
|
|
||||||
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan);
|
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
pembiayaanState.findMany.load().then(() => {
|
pembiayaanState.findMany.load();
|
||||||
console.log("Pembiayaan berhasil dimuat:", pembiayaanState.findMany.data);
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!pembiayaanState.findMany.data) {
|
if (!pembiayaanState.findMany.data) {
|
||||||
@@ -215,10 +233,10 @@ function EditAPBDesa() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Pembiayaan</Text>}
|
label={<Text fz="sm" fw="bold">Pembiayaan</Text>}
|
||||||
data={pembiayaanState.findMany.data.map(b => ({
|
data={pembiayaanState.findMany.data.map((p: any) => ({
|
||||||
value: b.id,
|
value: p.id,
|
||||||
label: b.name
|
label: p.name,
|
||||||
}))}
|
}))}
|
||||||
value={selectedIds}
|
value={selectedIds}
|
||||||
onChange={onSelectionChange}
|
onChange={onSelectionChange}
|
||||||
|
|||||||
@@ -2,148 +2,204 @@
|
|||||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function DetailAPBDesa() {
|
function DetailAPBDesa() {
|
||||||
const apbState = useProxy(PendapatanAsliDesa.ApbDesa)
|
const apbState = useProxy(PendapatanAsliDesa.ApbDesa);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
console.log("PARAM ID:", params?.id)
|
apbState.findUnique.load(params?.id as string);
|
||||||
apbState.findUnique.load(params?.id as string)
|
}, []);
|
||||||
}, [])
|
|
||||||
|
|
||||||
const formatRupiah = (value: number) => {
|
const formatRupiah = (value: number) =>
|
||||||
return new Intl.NumberFormat('id-ID', {
|
new Intl.NumberFormat('id-ID', {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
currency: 'IDR',
|
currency: 'IDR',
|
||||||
minimumFractionDigits: 0,
|
minimumFractionDigits: 0,
|
||||||
}).format(value);
|
}).format(value);
|
||||||
};
|
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
apbState.delete.byId(selectedId)
|
apbState.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa")
|
router.push(
|
||||||
|
'/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!apbState.findUnique.data) {
|
if (!apbState.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={40} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = apbState.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Box mb={10}>
|
<Button
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
variant="subtle"
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
onClick={() => router.back()}
|
||||||
</Button>
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
</Box>
|
mb={15}
|
||||||
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
>
|
||||||
<Stack>
|
Kembali
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail APB Desa</Text>
|
</Button>
|
||||||
{apbState.findUnique.data ? (
|
|
||||||
<Paper key={apbState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
withBorder
|
||||||
<Box>
|
w={{ base: '100%', md: '70%', lg: '50%' }}
|
||||||
<Text fw={"bold"} fz={"lg"}>Tahun</Text>
|
bg={colors['white-1']}
|
||||||
<Text fz={"lg"}>{apbState.findUnique.data?.tahun}</Text>
|
p="lg"
|
||||||
</Box>
|
radius="md"
|
||||||
<Box>
|
shadow="sm"
|
||||||
<Stack gap={"xs"}>
|
>
|
||||||
<Text fw={"bold"} fz={"lg"}>Detail Pembiayaan</Text>
|
<Stack gap="md">
|
||||||
{(apbState.findUnique.data?.pembiayaan || []).map((item) => (
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
<Text fz={"lg"} key={item.id}>
|
Detail APB Desa
|
||||||
{item.name}: {formatRupiah(Number(item.value))}
|
</Text>
|
||||||
</Text>
|
|
||||||
))}
|
<Paper bg={colors['BG-trans']} p="md" radius="md" shadow="xs">
|
||||||
<Text fz={"lg"} fw={"bold"}>
|
<Stack gap="sm">
|
||||||
Total: {formatRupiah((apbState.findUnique.data?.pembiayaan || [])
|
<Box>
|
||||||
.reduce((sum, item) => sum + Number(item.value), 0))}
|
<Text fz="lg" fw="bold">
|
||||||
|
Tahun
|
||||||
|
</Text>
|
||||||
|
<Text fz="md" c="dimmed">
|
||||||
|
{data.tahun}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Text fw="bold" fz="lg">
|
||||||
|
Detail Pembiayaan
|
||||||
|
</Text>
|
||||||
|
{(data?.pembiayaan || []).map((item) => (
|
||||||
|
<Text fz="md" c="dimmed" key={item.id}>
|
||||||
|
{item.name}: {formatRupiah(Number(item.value))}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
))}
|
||||||
</Box>
|
<Text fz="md" fw="bold">
|
||||||
<Box>
|
Total:{' '}
|
||||||
<Stack gap={"xs"}>
|
{formatRupiah(
|
||||||
<Text fw={"bold"} fz={"lg"}>Detail Belanja</Text>
|
(data?.pembiayaan || []).reduce(
|
||||||
{(apbState.findUnique.data?.belanja || []).map((item) => (
|
(sum, item) => sum + Number(item.value),
|
||||||
<Text fz={"lg"} key={item.id}>
|
0
|
||||||
{item.name}: {formatRupiah(Number(item.value))}
|
)
|
||||||
</Text>
|
)}
|
||||||
))}
|
</Text>
|
||||||
<Text fz={"lg"} fw={"bold"}>
|
</Stack>
|
||||||
Total: {formatRupiah((apbState.findUnique.data?.belanja || [])
|
</Box>
|
||||||
.reduce((sum, item) => sum + Number(item.value), 0))}
|
|
||||||
|
<Box>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Text fw="bold" fz="lg">
|
||||||
|
Detail Belanja
|
||||||
|
</Text>
|
||||||
|
{(data?.belanja || []).map((item) => (
|
||||||
|
<Text fz="md" c="dimmed" key={item.id}>
|
||||||
|
{item.name}: {formatRupiah(Number(item.value))}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
))}
|
||||||
</Box>
|
<Text fz="md" fw="bold">
|
||||||
<Box>
|
Total:{' '}
|
||||||
<Stack gap={"xs"}>
|
{formatRupiah(
|
||||||
<Text fw={"bold"} fz={"lg"}>Detail Pendapatan</Text>
|
(data?.belanja || []).reduce(
|
||||||
{(apbState.findUnique.data?.pendapatan || []).map((item) => (
|
(sum, item) => sum + Number(item.value),
|
||||||
<Text fz={"lg"} key={item.id}>
|
0
|
||||||
{item.name}: {formatRupiah(Number(item.value))}
|
)
|
||||||
</Text>
|
)}
|
||||||
))}
|
</Text>
|
||||||
<Text fz={"lg"} fw={"bold"}>
|
</Stack>
|
||||||
Total: {formatRupiah((apbState.findUnique.data?.pendapatan || [])
|
</Box>
|
||||||
.reduce((sum, item) => sum + Number(item.value), 0))}
|
|
||||||
|
<Box>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Text fw="bold" fz="lg">
|
||||||
|
Detail Pendapatan
|
||||||
|
</Text>
|
||||||
|
{(data?.pendapatan || []).map((item) => (
|
||||||
|
<Text fz="md" c="dimmed" key={item.id}>
|
||||||
|
{item.name}: {formatRupiah(Number(item.value))}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
))}
|
||||||
</Box>
|
<Text fz="md" fw="bold">
|
||||||
<Flex gap={"xs"} mt={10}>
|
Total:{' '}
|
||||||
|
{formatRupiah(
|
||||||
|
(data?.pendapatan || []).reduce(
|
||||||
|
(sum, item) => sum + Number(item.value),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Group gap="sm" mt={10}>
|
||||||
|
<Tooltip label="Hapus APB Desa" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
|
color="red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (apbState.findUnique.data) {
|
setSelectedId(data.id);
|
||||||
setSelectedId(apbState.findUnique.data.id);
|
setModalHapus(true);
|
||||||
setModalHapus(true);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
disabled={apbState.delete.loading || !apbState.findUnique.data}
|
variant="light"
|
||||||
color={"red"}
|
radius="md"
|
||||||
|
size="md"
|
||||||
>
|
>
|
||||||
<IconX size={20} />
|
<IconTrash size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip label="Edit APB Desa" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
color="green"
|
||||||
if (apbState.findUnique.data) {
|
onClick={() =>
|
||||||
router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/${apbState.findUnique.data.id}/edit`);
|
router.push(
|
||||||
}
|
`/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/${data.id}/edit`
|
||||||
}}
|
)
|
||||||
disabled={!apbState.findUnique.data}
|
}
|
||||||
color={"green"}
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
>
|
>
|
||||||
<IconEdit size={20} />
|
<IconEdit size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Tooltip>
|
||||||
</Stack>
|
</Group>
|
||||||
</Paper>
|
</Stack>
|
||||||
) : null}
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleHapus}
|
onConfirm={handleHapus}
|
||||||
text='Apakah anda yakin ingin menghapus APB Desa ini?'
|
text="Apakah Anda yakin ingin menghapus APB Desa ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,15 +1,28 @@
|
|||||||
'use client'
|
'use client';
|
||||||
|
|
||||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, MultiSelect, Paper, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
MultiSelect,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function CreateAPBDesa() {
|
function CreateAPBDesa() {
|
||||||
const apbDesaState = useProxy(PendapatanAsliDesa.ApbDesa)
|
const apbDesaState = useProxy(PendapatanAsliDesa.ApbDesa);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
apbDesaState.create.form = {
|
apbDesaState.create.form = {
|
||||||
@@ -17,74 +30,101 @@ function CreateAPBDesa() {
|
|||||||
pendapatanIds: [],
|
pendapatanIds: [],
|
||||||
belanjaIds: [],
|
belanjaIds: [],
|
||||||
pembiayaanIds: [],
|
pembiayaanIds: [],
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
await apbDesaState.create.submit()
|
await apbDesaState.create.submit();
|
||||||
resetForm()
|
resetForm();
|
||||||
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa")
|
router.push('/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Box>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
<Paper bg={colors["white-1"]} p={"md"} w={{ base: "100%", md: "50%" }}>
|
</Button>
|
||||||
<Stack gap={"xs"}>
|
</Tooltip>
|
||||||
<Title order={3}>Create APB Desa</Title>
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah APB Desa
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Form */}
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
type='number'
|
type="number"
|
||||||
value={apbDesaState.create.form.tahun}
|
value={apbDesaState.create.form.tahun}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
apbDesaState.create.form.tahun = Number(val.target.value);
|
apbDesaState.create.form.tahun = Number(val.target.value);
|
||||||
}}
|
}}
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Tahun</Text>}
|
label={<Text fz="sm" fw="bold">Tahun</Text>}
|
||||||
placeholder="masukkan tahun"
|
placeholder="Masukkan tahun anggaran"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectPendapatan
|
<SelectPendapatan
|
||||||
selectedIds={apbDesaState.create.form.pendapatanIds}
|
selectedIds={apbDesaState.create.form.pendapatanIds}
|
||||||
onSelectionChange={(ids) => {
|
onSelectionChange={(ids) => {
|
||||||
apbDesaState.create.form.pendapatanIds = ids;
|
apbDesaState.create.form.pendapatanIds = ids;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectBelanja
|
<SelectBelanja
|
||||||
selectedIds={apbDesaState.create.form.belanjaIds}
|
selectedIds={apbDesaState.create.form.belanjaIds}
|
||||||
onSelectionChange={(ids) => {
|
onSelectionChange={(ids) => {
|
||||||
apbDesaState.create.form.belanjaIds = ids;
|
apbDesaState.create.form.belanjaIds = ids;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectPembiayaan
|
<SelectPembiayaan
|
||||||
selectedIds={apbDesaState.create.form.pembiayaanIds}
|
selectedIds={apbDesaState.create.form.pembiayaanIds}
|
||||||
onSelectionChange={(ids) => {
|
onSelectionChange={(ids) => {
|
||||||
apbDesaState.create.form.pembiayaanIds = ids;
|
apbDesaState.create.form.pembiayaanIds = ids;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
|
||||||
|
{/* Action */}
|
||||||
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
/* Select Pendapatan */
|
/* ---------- Select Pendapatan ---------- */
|
||||||
interface SelectPendapatanProps {
|
interface SelectPendapatanProps {
|
||||||
selectedIds: string[];
|
selectedIds: string[];
|
||||||
onSelectionChange: (ids: string[]) => void;
|
onSelectionChange: (ids: string[]) => void;
|
||||||
}
|
}
|
||||||
|
function SelectPendapatan({ selectedIds = [], onSelectionChange }: SelectPendapatanProps) {
|
||||||
function SelectPendapatan({
|
|
||||||
selectedIds = [],
|
|
||||||
onSelectionChange,
|
|
||||||
}: SelectPendapatanProps) {
|
|
||||||
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan);
|
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
pendapatanState.findMany.load().then(() => {
|
pendapatanState.findMany.load();
|
||||||
console.log("Pendapatan berhasil dimuat:", pendapatanState.findMany.data);
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!pendapatanState.findMany.data) {
|
if (!pendapatanState.findMany.data) {
|
||||||
@@ -93,10 +133,10 @@ function CreateAPBDesa() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Pendapatan</Text>}
|
label={<Text fz="sm" fw="bold">Pendapatan</Text>}
|
||||||
data={pendapatanState.findMany.data.map(p => ({
|
data={pendapatanState.findMany.data.map((p) => ({
|
||||||
value: p.id,
|
value: p.id,
|
||||||
label: p.name
|
label: p.name,
|
||||||
}))}
|
}))}
|
||||||
value={selectedIds}
|
value={selectedIds}
|
||||||
onChange={onSelectionChange}
|
onChange={onSelectionChange}
|
||||||
@@ -108,22 +148,16 @@ function CreateAPBDesa() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Select Belanja */
|
/* ---------- Select Belanja ---------- */
|
||||||
interface SelectBelanjaProps {
|
interface SelectBelanjaProps {
|
||||||
selectedIds: string[];
|
selectedIds: string[];
|
||||||
onSelectionChange: (ids: string[]) => void;
|
onSelectionChange: (ids: string[]) => void;
|
||||||
}
|
}
|
||||||
|
function SelectBelanja({ selectedIds = [], onSelectionChange }: SelectBelanjaProps) {
|
||||||
function SelectBelanja({
|
|
||||||
selectedIds = [],
|
|
||||||
onSelectionChange,
|
|
||||||
}: SelectBelanjaProps) {
|
|
||||||
const belanjaState = useProxy(PendapatanAsliDesa.belanja);
|
const belanjaState = useProxy(PendapatanAsliDesa.belanja);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
belanjaState.findMany.load().then(() => {
|
belanjaState.findMany.load();
|
||||||
console.log("Belanja berhasil dimuat:", belanjaState.findMany.data);
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!belanjaState.findMany.data) {
|
if (!belanjaState.findMany.data) {
|
||||||
@@ -132,10 +166,10 @@ function CreateAPBDesa() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Belanja</Text>}
|
label={<Text fz="sm" fw="bold">Belanja</Text>}
|
||||||
data={belanjaState.findMany.data.map(b => ({
|
data={belanjaState.findMany.data.map((b) => ({
|
||||||
value: b.id,
|
value: b.id,
|
||||||
label: b.name
|
label: b.name,
|
||||||
}))}
|
}))}
|
||||||
value={selectedIds}
|
value={selectedIds}
|
||||||
onChange={onSelectionChange}
|
onChange={onSelectionChange}
|
||||||
@@ -147,22 +181,16 @@ function CreateAPBDesa() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Select Pembiayaan */
|
/* ---------- Select Pembiayaan ---------- */
|
||||||
interface SelectPembiayaanProps {
|
interface SelectPembiayaanProps {
|
||||||
selectedIds: string[];
|
selectedIds: string[];
|
||||||
onSelectionChange: (ids: string[]) => void;
|
onSelectionChange: (ids: string[]) => void;
|
||||||
}
|
}
|
||||||
|
function SelectPembiayaan({ selectedIds = [], onSelectionChange }: SelectPembiayaanProps) {
|
||||||
function SelectPembiayaan({
|
|
||||||
selectedIds = [],
|
|
||||||
onSelectionChange,
|
|
||||||
}: SelectPembiayaanProps) {
|
|
||||||
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan);
|
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
pembiayaanState.findMany.load().then(() => {
|
pembiayaanState.findMany.load();
|
||||||
console.log("Pembiayaan berhasil dimuat:", pembiayaanState.findMany.data);
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!pembiayaanState.findMany.data) {
|
if (!pembiayaanState.findMany.data) {
|
||||||
@@ -171,10 +199,10 @@ function CreateAPBDesa() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Pembiayaan</Text>}
|
label={<Text fz="sm" fw="bold">Pembiayaan</Text>}
|
||||||
data={pembiayaanState.findMany.data.map(b => ({
|
data={pembiayaanState.findMany.data.map((b) => ({
|
||||||
value: b.id,
|
value: b.id,
|
||||||
label: b.name
|
label: b.name,
|
||||||
}))}
|
}))}
|
||||||
value={selectedIds}
|
value={selectedIds}
|
||||||
onChange={onSelectionChange}
|
onChange={onSelectionChange}
|
||||||
|
|||||||
@@ -1,23 +1,38 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Pagination,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import PendapatanAsliDesa from '../../../_state/ekonomi/PADesa';
|
import PendapatanAsliDesa from '../../../_state/ekonomi/PADesa';
|
||||||
|
|
||||||
|
|
||||||
function APBDesa() {
|
function APBDesa() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='APB Desa'
|
title="APB Desa"
|
||||||
placeholder='pencarian'
|
placeholder="Cari tahun atau nominal..."
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -28,77 +43,147 @@ function APBDesa() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ListAPBDesa({ search }: { search: string }) {
|
function ListAPBDesa({ search }: { search: string }) {
|
||||||
const apbDesaState = useProxy(PendapatanAsliDesa.ApbDesa)
|
const apbDesaState = useProxy(PendapatanAsliDesa.ApbDesa);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const formatRupiah = (value: number) => {
|
const {
|
||||||
return new Intl.NumberFormat('id-ID', {
|
data,
|
||||||
style: 'currency',
|
page,
|
||||||
currency: 'IDR',
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = apbDesaState.findMany;
|
||||||
|
|
||||||
|
const formatRupiah = (value: number) =>
|
||||||
|
new Intl.NumberFormat("id-ID", {
|
||||||
|
style: "currency",
|
||||||
|
currency: "IDR",
|
||||||
minimumFractionDigits: 0,
|
minimumFractionDigits: 0,
|
||||||
}).format(value);
|
}).format(value);
|
||||||
};
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
apbDesaState.findMany.load();
|
load(page, 10, search);
|
||||||
}, [])
|
}, [page, search]);
|
||||||
|
|
||||||
const filteredData = (apbDesaState.findMany.data || []).filter(item => {
|
const filteredData = data || [];
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.tahun.toString().toLowerCase().includes(keyword) ||
|
|
||||||
item.pembiayaan.map((item) => item.value.toString()).includes(keyword) ||
|
|
||||||
item.belanja.map((item) => item.value.toString()).includes(keyword) ||
|
|
||||||
item.pendapatan.map((item) => item.value.toString()).includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!apbDesaState.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper withBorder bg={colors["white-1"]} p="lg" shadow="md" radius="md">
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List APB Desa'
|
<Text fw={600} fz="lg">
|
||||||
href='/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/create'
|
List APB Desa
|
||||||
/>
|
</Text>
|
||||||
<Table striped withTableBorder withRowBorders>
|
<Tooltip label="Tambah APB Desa" withArrow>
|
||||||
<TableThead>
|
<Button
|
||||||
<TableTr>
|
leftSection={<IconPlus size={18} />}
|
||||||
<TableTh>Tahun</TableTh>
|
color="blue"
|
||||||
<TableTh>Pembiayaan</TableTh>
|
variant="light"
|
||||||
<TableTh>Belanja</TableTh>
|
onClick={() =>
|
||||||
<TableTh>Pendapatan</TableTh>
|
router.push(
|
||||||
<TableTh>Detail</TableTh>
|
"/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/create"
|
||||||
</TableTr>
|
)
|
||||||
</TableThead>
|
}
|
||||||
<TableTbody>
|
>
|
||||||
{filteredData.map((item) => (
|
Tambah Baru
|
||||||
<TableTr key={item.id}>
|
</Button>
|
||||||
<TableTd>{item.tahun}</TableTd>
|
</Tooltip>
|
||||||
<TableTd>{formatRupiah(item.pembiayaan.reduce((sum, item) => sum + Number(item.value), 0))}</TableTd>
|
</Group>
|
||||||
<TableTd>{formatRupiah(item.belanja.reduce((sum, item) => sum + Number(item.value), 0))}</TableTd>
|
<Box style={{ overflowX: "auto" }}>
|
||||||
<TableTd>{formatRupiah(item.pendapatan.reduce((sum, item) => sum + Number(item.value), 0))}</TableTd>
|
<Table highlightOnHover>
|
||||||
<TableTd>
|
<TableThead>
|
||||||
<Button
|
<TableTr>
|
||||||
bg={"green"}
|
<TableTh style={{ width: "15%" }}>Tahun</TableTh>
|
||||||
onClick={() =>
|
<TableTh style={{ width: "25%" }}>Pembiayaan</TableTh>
|
||||||
router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/${item.id}`)
|
<TableTh style={{ width: "25%" }}>Belanja</TableTh>
|
||||||
}
|
<TableTh style={{ width: "25%" }}>Pendapatan</TableTh>
|
||||||
>
|
<TableTh style={{ width: "10%" }}>Aksi</TableTh>
|
||||||
<IconDeviceImacCog size={25} />
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
</TableThead>
|
||||||
</TableTbody>
|
<TableTbody>
|
||||||
</Table>
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>{item.tahun}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
{formatRupiah(
|
||||||
|
item.pembiayaan.reduce(
|
||||||
|
(sum, val) => sum + Number(val.value),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
{formatRupiah(
|
||||||
|
item.belanja.reduce(
|
||||||
|
(sum, val) => sum + Number(val.value),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
{formatRupiah(
|
||||||
|
item.pendapatan.reduce(
|
||||||
|
(sum, val) => sum + Number(val.value),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Tooltip label="Lihat Detail" withArrow>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconDeviceImacCog size={20} />
|
||||||
|
<Text ml={5}>Detail</Text>
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={5}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">
|
||||||
|
Tidak ada data APB Desa yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10);
|
||||||
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,16 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -72,40 +81,71 @@ function EditBelanja() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button
|
||||||
</Box>
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Jenis Belanja
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
{/* Card */}
|
||||||
<Stack gap={"xs"}>
|
<Paper
|
||||||
<Title order={4}>Edit Jenis Pendapatan</Title>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<TextInput
|
bg={colors['white-1']}
|
||||||
value={formData.name}
|
p="lg"
|
||||||
onChange={(val) => {
|
radius="md"
|
||||||
setFormData({ ...formData, name: val.target.value });
|
shadow="sm"
|
||||||
}}
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Pendapatan</Text>}
|
>
|
||||||
placeholder='Masukkan nama Jenis Pendapatan'
|
<Stack gap="md">
|
||||||
/>
|
<TextInput
|
||||||
<TextInput
|
label="Nama Jenis Belanja"
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nilai</Text>}
|
placeholder="Masukkan nama jenis belanja"
|
||||||
placeholder='Masukkan nilai'
|
value={formData.name}
|
||||||
value={formatRupiah(formData.value)}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
onChange={(val) => {
|
required
|
||||||
const raw = val.currentTarget.value;
|
/>
|
||||||
const cleanValue = unformatRupiah(raw);
|
|
||||||
setFormData({ ...formData, value: cleanValue });
|
<TextInput
|
||||||
}}
|
label="Nilai"
|
||||||
/>
|
placeholder="Masukkan nilai"
|
||||||
<Group>
|
value={formatRupiah(formData.value)}
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
onChange={(e) => {
|
||||||
</Group>
|
const raw = e.currentTarget.value;
|
||||||
</Stack>
|
const cleanValue = unformatRupiah(raw);
|
||||||
</Paper>
|
setFormData({ ...formData, value: cleanValue });
|
||||||
</Box>
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,30 @@
|
|||||||
'use client'
|
'use client';
|
||||||
|
|
||||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
function CreateBelanja() {
|
function CreateBelanja() {
|
||||||
const belanjaState = useProxy(PendapatanAsliDesa.belanja)
|
const belanjaState = useProxy(PendapatanAsliDesa.belanja);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
const formatRupiah = (value: number | string) => {
|
const formatRupiah = (value: number | string) => {
|
||||||
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
|
const number =
|
||||||
|
typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
|
||||||
return new Intl.NumberFormat('id-ID', {
|
return new Intl.NumberFormat('id-ID', {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
currency: 'IDR',
|
currency: 'IDR',
|
||||||
@@ -25,48 +38,83 @@ function CreateBelanja() {
|
|||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
belanjaState.create.form = {
|
belanjaState.create.form = {
|
||||||
name: "",
|
name: '',
|
||||||
value: 0,
|
value: 0,
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
await belanjaState.create.submit();
|
if (!belanjaState.create.form.name || !belanjaState.create.form.value) {
|
||||||
resetForm()
|
return toast.warn('Lengkapi semua field terlebih dahulu');
|
||||||
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja")
|
}
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
await belanjaState.create.submit();
|
||||||
<Stack gap={"xs"}>
|
resetForm();
|
||||||
<Title order={4}>Create Jenis Belanja</Title>
|
router.push('/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
|
{/* Header dengan back button */}
|
||||||
|
<Group mb="md">
|
||||||
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Jenis Belanja
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Card Form */}
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label={<Text fw="bold" fz="sm">Nama Jenis Belanja</Text>}
|
||||||
|
placeholder="Masukkan nama jenis belanja"
|
||||||
value={belanjaState.create.form.name}
|
value={belanjaState.create.form.name}
|
||||||
onChange={(val) => {
|
onChange={(e) => (belanjaState.create.form.name = e.target.value)}
|
||||||
belanjaState.create.form.name = val.target.value;
|
required
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Belanja</Text>}
|
|
||||||
placeholder='Masukkan nama jenis belanja'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
type='text'
|
type="text"
|
||||||
|
label={<Text fw="bold" fz="sm">Nilai</Text>}
|
||||||
|
placeholder="Masukkan nilai belanja"
|
||||||
value={formatRupiah(belanjaState.create.form.value)}
|
value={formatRupiah(belanjaState.create.form.value)}
|
||||||
onChange={(val) => {
|
onChange={(e) => {
|
||||||
const raw = val.currentTarget.value;
|
const raw = e.currentTarget.value;
|
||||||
const cleanValue = unformatRupiah(raw);
|
belanjaState.create.form.value = unformatRupiah(raw);
|
||||||
belanjaState.create.form.value = cleanValue;
|
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nilai</Text>}
|
required
|
||||||
placeholder='Masukkan nilai'
|
|
||||||
/>
|
/>
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,24 +1,41 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../../_com/header';
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
import PendapatanAsliDesa from '../../../_state/ekonomi/PADesa';
|
import PendapatanAsliDesa from '../../../_state/ekonomi/PADesa';
|
||||||
import HeaderSearch from '../../../_com/header';
|
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
|
|
||||||
|
|
||||||
function Belanja() {
|
function Belanja() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Belanja'
|
title="Belanja"
|
||||||
placeholder='pencarian'
|
placeholder="Cari belanja berdasarkan nama atau nilai..."
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -29,108 +46,175 @@ function Belanja() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ListBelanja({ search }: { search: string }) {
|
function ListBelanja({ search }: { search: string }) {
|
||||||
const belanjaState = useProxy(PendapatanAsliDesa.belanja)
|
const belanjaState = useProxy(PendapatanAsliDesa.belanja);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
|
||||||
const formatRupiah = (value: number) => {
|
const {
|
||||||
return new Intl.NumberFormat('id-ID', {
|
data,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
} = belanjaState.findMany;
|
||||||
|
|
||||||
|
const formatRupiah = (value: number) =>
|
||||||
|
new Intl.NumberFormat('id-ID', {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
currency: 'IDR',
|
currency: 'IDR',
|
||||||
minimumFractionDigits: 0,
|
minimumFractionDigits: 0,
|
||||||
}).format(value);
|
}).format(value);
|
||||||
};
|
|
||||||
|
|
||||||
const totalBelanja = belanjaState.findMany.data.reduce((sum, item) => sum + item.value, 0);
|
const totalBelanja = data?.reduce((sum, item) => sum + item.value, 0) || 0;
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
belanjaState.delete.byId(selectedId)
|
belanjaState.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
belanjaState.findMany.load()
|
load(page, 10, search);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
belanjaState.findMany.load();
|
load(page, 10, search);
|
||||||
}, [])
|
}, [page, search]);
|
||||||
|
|
||||||
const filteredData = (belanjaState.findMany.data || []).filter(item => {
|
const filteredData = data || [];
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
|
||||||
item.value.toString().toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!belanjaState.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Belanja'
|
<Title order={4}>Daftar Belanja</Title>
|
||||||
href='/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja/create'
|
<Tooltip label="Tambah Belanja" withArrow>
|
||||||
/>
|
<Button
|
||||||
<Table striped withTableBorder withRowBorders>
|
leftSection={<IconPlus size={18} />}
|
||||||
<TableThead>
|
color="blue"
|
||||||
<TableTr>
|
variant="light"
|
||||||
<TableTh>Nama</TableTh>
|
onClick={() =>
|
||||||
<TableTh>Nilai</TableTh>
|
router.push('/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja/create')
|
||||||
<TableTh>Persentase</TableTh>
|
}
|
||||||
<TableTh>Edit</TableTh>
|
>
|
||||||
<TableTh>Delete</TableTh>
|
Tambah Baru
|
||||||
</TableTr>
|
</Button>
|
||||||
</TableThead>
|
</Tooltip>
|
||||||
<TableTbody>
|
</Group>
|
||||||
{filteredData.map((item) => (
|
|
||||||
<TableTr key={item.id}>
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
<TableTd>{item.name}</TableTd>
|
<Table highlightOnHover striped withTableBorder withRowBorders>
|
||||||
<TableTd>{formatRupiah(item.value)}</TableTd>
|
<TableThead>
|
||||||
<TableTd>{((item.value / totalBelanja) * 100).toFixed(0)}%</TableTd>
|
<TableTr>
|
||||||
<TableTd>
|
<TableTh>Nama</TableTh>
|
||||||
<Button color='green' onClick={() => router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja/${item.id}`)}>
|
<TableTh>Nilai</TableTh>
|
||||||
<IconEdit size={20} />
|
<TableTh>Persentase</TableTh>
|
||||||
</Button>
|
<TableTh>Aksi</TableTh>
|
||||||
</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Button
|
|
||||||
color='red'
|
|
||||||
disabled={belanjaState.delete.loading}
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedId(item.id)
|
|
||||||
setModalHapus(true)
|
|
||||||
}}>
|
|
||||||
<IconTrash size={20} />
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
</TableThead>
|
||||||
<TableTr>
|
<TableTbody>
|
||||||
<TableTd colSpan={4}>
|
{filteredData.length > 0 ? (
|
||||||
<Text fw={'bold'}>Total</Text>
|
<>
|
||||||
</TableTd>
|
{filteredData.map((item) => (
|
||||||
<TableTd>
|
<TableTr key={item.id}>
|
||||||
{formatRupiah(belanjaState.findMany.data.reduce((total, item) => total + item.value, 0))}
|
<TableTd>
|
||||||
</TableTd>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
</TableTr>
|
{item.name}
|
||||||
</TableTbody>
|
</Text>
|
||||||
</Table>
|
</TableTd>
|
||||||
|
<TableTd>{formatRupiah(item.value)}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
{totalBelanja > 0
|
||||||
|
? ((item.value / totalBelanja) * 100).toFixed(0) + '%'
|
||||||
|
: '0%'}
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Group gap="xs">
|
||||||
|
<Tooltip label="Edit" withArrow>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconEdit size={16} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Hapus" withArrow>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
disabled={belanjaState.delete.loading}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconTrash size={16} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={2}>
|
||||||
|
<Text fw="bold">Total</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd colSpan={2}>
|
||||||
|
<Text fw="bold">{formatRupiah(totalBelanja)}</Text>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={4}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text c="dimmed">Tidak ada data belanja yang cocok</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10, search);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleDelete}
|
onConfirm={handleDelete}
|
||||||
text='Apakah anda yakin ingin menghapus belanja ini?'
|
text="Apakah anda yakin ingin menghapus belanja ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,16 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -20,7 +29,7 @@ function EditPembiayaan() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const formatRupiah = (value: number | string) => {
|
const formatRupiah = (value: number | string) => {
|
||||||
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
|
const number = typeof value === 'number' ? value : Number(value.toString().replace(/\D/g, ''));
|
||||||
return new Intl.NumberFormat('id-ID', {
|
return new Intl.NumberFormat('id-ID', {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
currency: 'IDR',
|
currency: 'IDR',
|
||||||
@@ -46,8 +55,8 @@ function EditPembiayaan() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading pembiayaan:", error);
|
console.error('Error loading pembiayaan:', error);
|
||||||
toast.error("Gagal memuat data pembiayaan");
|
toast.error('Gagal memuat data pembiayaan');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,52 +69,83 @@ function EditPembiayaan() {
|
|||||||
...pembiayaanState.update.form,
|
...pembiayaanState.update.form,
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
value: Number(formData.value),
|
value: Number(formData.value),
|
||||||
}
|
};
|
||||||
|
|
||||||
await pembiayaanState.update.update();
|
await pembiayaanState.update.update();
|
||||||
toast.success("Jenis Pembiayaan berhasil diperbarui!");
|
toast.success('Jenis Pembiayaan berhasil diperbarui!');
|
||||||
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan");
|
router.push('/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating jenis pembiayaan:", error);
|
console.error('Error updating jenis pembiayaan:', error);
|
||||||
toast.error("Terjadi kesalahan saat memperbarui jenis pembiayaan");
|
toast.error('Terjadi kesalahan saat memperbarui jenis pembiayaan');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header dengan Back Button */}
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button
|
||||||
</Box>
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Jenis Pembiayaan
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
{/* Card Form */}
|
||||||
<Stack gap={"xs"}>
|
<Paper
|
||||||
<Title order={4}>Edit Jenis Pembiayaan</Title>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<TextInput
|
bg={colors['white-1']}
|
||||||
value={formData.name}
|
p="lg"
|
||||||
onChange={(val) => {
|
radius="md"
|
||||||
setFormData({ ...formData, name: val.target.value });
|
shadow="sm"
|
||||||
}}
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Pembiayaan</Text>}
|
>
|
||||||
placeholder='Masukkan nama Jenis Pembiayaan'
|
<Stack gap="md">
|
||||||
/>
|
<TextInput
|
||||||
<TextInput
|
label="Nama Jenis Pembiayaan"
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nilai</Text>}
|
placeholder="Masukkan nama jenis pembiayaan"
|
||||||
placeholder='Masukkan nilai'
|
value={formData.name}
|
||||||
value={formatRupiah(formData.value)}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
onChange={(val) => {
|
required
|
||||||
const raw = val.currentTarget.value;
|
/>
|
||||||
const cleanValue = unformatRupiah(raw);
|
|
||||||
setFormData({ ...formData, value: cleanValue });
|
<TextInput
|
||||||
}}
|
label="Nilai"
|
||||||
/>
|
placeholder="Masukkan nilai"
|
||||||
<Group>
|
value={formatRupiah(formData.value)}
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
onChange={(e) => {
|
||||||
</Group>
|
const raw = e.currentTarget.value;
|
||||||
</Stack>
|
const cleanValue = unformatRupiah(raw);
|
||||||
</Paper>
|
setFormData({ ...formData, value: cleanValue });
|
||||||
</Box>
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,30 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Title, TextInput, Group, Text } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Title,
|
||||||
|
TextInput,
|
||||||
|
Text,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
function CreatePembiayaan() {
|
function CreatePembiayaan() {
|
||||||
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan)
|
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
const formatRupiah = (value: number | string) => {
|
const formatRupiah = (value: number | string) => {
|
||||||
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
|
const number =
|
||||||
|
typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
|
||||||
return new Intl.NumberFormat('id-ID', {
|
return new Intl.NumberFormat('id-ID', {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
currency: 'IDR',
|
currency: 'IDR',
|
||||||
@@ -26,48 +38,85 @@ function CreatePembiayaan() {
|
|||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
pembiayaanState.create.form = {
|
pembiayaanState.create.form = {
|
||||||
name: "",
|
name: '',
|
||||||
value: 0,
|
value: 0,
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
await pembiayaanState.create.submit();
|
if (!pembiayaanState.create.form.name || !pembiayaanState.create.form.value) {
|
||||||
resetForm()
|
return toast.warn('Nama dan nilai wajib diisi');
|
||||||
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan")
|
}
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
await pembiayaanState.create.submit();
|
||||||
<Stack gap={"xs"}>
|
resetForm();
|
||||||
<Title order={4}>Create Jenis Pembiayaan</Title>
|
router.push('/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
|
{/* Header */}
|
||||||
|
<Group mb="md">
|
||||||
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Jenis Pembiayaan
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Form Card */}
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label={<Text fw="bold" fz="sm">Nama Jenis Pembiayaan</Text>}
|
||||||
|
placeholder="Masukkan nama jenis pembiayaan"
|
||||||
value={pembiayaanState.create.form.name}
|
value={pembiayaanState.create.form.name}
|
||||||
onChange={(val) => {
|
onChange={(e) => {
|
||||||
pembiayaanState.create.form.name = val.target.value;
|
pembiayaanState.create.form.name = e.currentTarget.value;
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Pembiayaan</Text>}
|
required
|
||||||
placeholder='Masukkan nama jenis pembiayaan'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
type='text'
|
type="text"
|
||||||
|
label={<Text fw="bold" fz="sm">Nilai</Text>}
|
||||||
|
placeholder="Masukkan nilai"
|
||||||
value={formatRupiah(pembiayaanState.create.form.value)}
|
value={formatRupiah(pembiayaanState.create.form.value)}
|
||||||
onChange={(val) => {
|
onChange={(e) => {
|
||||||
const raw = val.currentTarget.value;
|
const raw = e.currentTarget.value;
|
||||||
const cleanValue = unformatRupiah(raw);
|
pembiayaanState.create.form.value = unformatRupiah(raw);
|
||||||
pembiayaanState.create.form.value = cleanValue;
|
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nilai</Text>}
|
required
|
||||||
placeholder='Masukkan nilai'
|
|
||||||
/>
|
/>
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,14 +1,31 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Pagination,
|
||||||
|
} from '@mantine/core';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import PendapatanAsliDesa from '../../../_state/ekonomi/PADesa';
|
import PendapatanAsliDesa from '../../../_state/ekonomi/PADesa';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
|
|
||||||
function Pembiayaan() {
|
function Pembiayaan() {
|
||||||
@@ -16,8 +33,8 @@ function Pembiayaan() {
|
|||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Pembiayaan'
|
title="Pembiayaan"
|
||||||
placeholder='pencarian'
|
placeholder="Cari nama pembiayaan..."
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -28,111 +45,171 @@ function Pembiayaan() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ListPembiayaan({ search }: { search: string }) {
|
function ListPembiayaan({ search }: { search: string }) {
|
||||||
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan)
|
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
|
||||||
const formatRupiah = (value: number) => {
|
const {
|
||||||
return new Intl.NumberFormat('id-ID', {
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = pembiayaanState.findMany;
|
||||||
|
|
||||||
|
const formatRupiah = (value: number) =>
|
||||||
|
new Intl.NumberFormat('id-ID', {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
currency: 'IDR',
|
currency: 'IDR',
|
||||||
minimumFractionDigits: 0,
|
minimumFractionDigits: 0,
|
||||||
}).format(value);
|
}).format(value);
|
||||||
};
|
|
||||||
|
|
||||||
const totalPembiayaan = pembiayaanState.findMany.data.reduce((sum, item) => sum + item.value, 0);
|
const totalPembiayaan = (data || []).reduce((sum, item) => sum + item.value, 0);
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
pembiayaanState.delete.byId(selectedId)
|
pembiayaanState.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
pembiayaanState.findMany.load()
|
load(page, 10, search);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
pembiayaanState.findMany.load();
|
load(page, 10, search);
|
||||||
}, [])
|
}, [page, search]);
|
||||||
|
|
||||||
const filteredData = (pembiayaanState.findMany.data || []).filter(item => {
|
if (loading || !data) {
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
|
||||||
item.value.toString().toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!pembiayaanState.findMany.data) {
|
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filteredData = data || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Pembiayaan'
|
<Title order={4}>Daftar Pembiayaan</Title>
|
||||||
href='/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/create'
|
<Tooltip label="Tambah Pembiayaan" withArrow>
|
||||||
/>
|
<Button
|
||||||
<Table striped withTableBorder withRowBorders>
|
leftSection={<IconPlus size={18} />}
|
||||||
<TableThead>
|
color="blue"
|
||||||
<TableTr>
|
variant="light"
|
||||||
<TableTh>Nama</TableTh>
|
onClick={() =>
|
||||||
<TableTh>Nilai</TableTh>
|
router.push('/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/create')
|
||||||
<TableTh>Persentase</TableTh>
|
}
|
||||||
<TableTh>Edit</TableTh>
|
>
|
||||||
<TableTh>Delete</TableTh>
|
Tambah Baru
|
||||||
</TableTr>
|
</Button>
|
||||||
</TableThead>
|
</Tooltip>
|
||||||
<TableTbody>
|
</Group>
|
||||||
{filteredData.map((item) => (
|
|
||||||
<TableTr key={item.id}>
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
<TableTd>{item.name}</TableTd>
|
<Table highlightOnHover striped withTableBorder withRowBorders>
|
||||||
<TableTd>{formatRupiah(item.value)}</TableTd>
|
<TableThead>
|
||||||
<TableTd>{((item.value / totalPembiayaan) * 100).toFixed(0)}%</TableTd>
|
<TableTr>
|
||||||
<TableTd>
|
<TableTh>Nama</TableTh>
|
||||||
<Button color='green' onClick={() => router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/${item.id}`)}>
|
<TableTh>Nilai</TableTh>
|
||||||
<IconEdit size={20} />
|
<TableTh>Persentase</TableTh>
|
||||||
</Button>
|
<TableTh style={{ width: '20%' }}>Aksi</TableTh>
|
||||||
</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Button
|
|
||||||
color='red'
|
|
||||||
disabled={pembiayaanState.delete.loading}
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedId(item.id)
|
|
||||||
setModalHapus(true)
|
|
||||||
}}>
|
|
||||||
<IconTrash size={20} />
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
</TableThead>
|
||||||
<TableTr>
|
<TableTbody>
|
||||||
<TableTd colSpan={4}>
|
{filteredData.length > 0 ? (
|
||||||
<Text fw={'bold'}>Total</Text>
|
<>
|
||||||
</TableTd>
|
{filteredData.map((item) => (
|
||||||
<TableTd>
|
<TableTr key={item.id}>
|
||||||
{formatRupiah(pembiayaanState.findMany.data.reduce((total, item) => total + item.value, 0))}
|
<TableTd>
|
||||||
</TableTd>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
</TableTr>
|
{item.name}
|
||||||
</TableTbody>
|
</Text>
|
||||||
</Table>
|
</TableTd>
|
||||||
|
<TableTd>{formatRupiah(item.value)}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
{totalPembiayaan > 0
|
||||||
|
? ((item.value / totalPembiayaan) * 100).toFixed(0) + '%'
|
||||||
|
: '0%'}
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Group gap="xs">
|
||||||
|
<Button
|
||||||
|
color="green"
|
||||||
|
variant="light"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconEdit size={18} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
variant="light"
|
||||||
|
disabled={pembiayaanState.delete.loading}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconTrash size={18} />
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
{/* Total Row */}
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={2}>
|
||||||
|
<Text fw="bold">Total</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd colSpan={2}>{formatRupiah(totalPembiayaan)}</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={4}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text c="dimmed">Tidak ada data pembiayaan yang cocok</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10, search);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleDelete}
|
onConfirm={handleDelete}
|
||||||
text='Apakah anda yakin ingin menghapus pembiayaan ini?'
|
text="Apakah anda yakin ingin menghapus pembiayaan ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Pembiayaan;
|
export default Pembiayaan;
|
||||||
|
|||||||
@@ -2,7 +2,16 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -20,7 +29,7 @@ function EditPendapatan() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const formatRupiah = (value: number | string) => {
|
const formatRupiah = (value: number | string) => {
|
||||||
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
|
const number = typeof value === 'number' ? value : Number(value.toString().replace(/\D/g, ''));
|
||||||
return new Intl.NumberFormat('id-ID', {
|
return new Intl.NumberFormat('id-ID', {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
currency: 'IDR',
|
currency: 'IDR',
|
||||||
@@ -28,15 +37,13 @@ function EditPendapatan() {
|
|||||||
}).format(number);
|
}).format(number);
|
||||||
};
|
};
|
||||||
|
|
||||||
const unformatRupiah = (value: string) => {
|
const unformatRupiah = (value: string) => Number(value.replace(/\D/g, ''));
|
||||||
return Number(value.replace(/\D/g, ''));
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadPendapatan = async () => {
|
const id = params?.id as string;
|
||||||
const id = params?.id as string;
|
if (!id) return;
|
||||||
if (!id) return;
|
|
||||||
|
|
||||||
|
const loadPendapatan = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await pendapatanState.update.load(id);
|
const data = await pendapatanState.update.load(id);
|
||||||
if (data) {
|
if (data) {
|
||||||
@@ -46,8 +53,8 @@ function EditPendapatan() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading pendapatan:", error);
|
console.error('Error loading pendapatan:', error);
|
||||||
toast.error("Gagal memuat data pendapatan");
|
toast.error('Gagal memuat data pendapatan');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,52 +67,83 @@ function EditPendapatan() {
|
|||||||
...pendapatanState.update.form,
|
...pendapatanState.update.form,
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
value: Number(formData.value),
|
value: Number(formData.value),
|
||||||
}
|
};
|
||||||
|
|
||||||
await pendapatanState.update.update();
|
await pendapatanState.update.update();
|
||||||
toast.success("Jenis Pendapatan berhasil diperbarui!");
|
toast.success('Jenis Pendapatan berhasil diperbarui!');
|
||||||
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan");
|
router.push('/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating jenis pendapatan:", error);
|
console.error('Error updating jenis pendapatan:', error);
|
||||||
toast.error("Terjadi kesalahan saat memperbarui jenis pendapatan");
|
toast.error('Terjadi kesalahan saat memperbarui jenis pendapatan');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header with Back Button */}
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button
|
||||||
</Box>
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Jenis Pendapatan
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
{/* Card Form */}
|
||||||
<Stack gap={"xs"}>
|
<Paper
|
||||||
<Title order={4}>Edit Jenis Pendapatan</Title>
|
w={{ base: '100%', md: '50%' }}
|
||||||
<TextInput
|
bg={colors['white-1']}
|
||||||
value={formData.name}
|
p="lg"
|
||||||
onChange={(val) => {
|
radius="md"
|
||||||
setFormData({ ...formData, name: val.target.value });
|
shadow="sm"
|
||||||
}}
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Pendapatan</Text>}
|
>
|
||||||
placeholder='Masukkan nama Jenis Pendapatan'
|
<Stack gap="md">
|
||||||
/>
|
<TextInput
|
||||||
<TextInput
|
label="Nama Jenis Pendapatan"
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nilai</Text>}
|
placeholder="Masukkan nama jenis pendapatan"
|
||||||
placeholder='Masukkan nilai'
|
value={formData.name}
|
||||||
value={formatRupiah(formData.value)}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
onChange={(val) => {
|
required
|
||||||
const raw = val.currentTarget.value;
|
/>
|
||||||
const cleanValue = unformatRupiah(raw);
|
|
||||||
setFormData({ ...formData, value: cleanValue });
|
<TextInput
|
||||||
}}
|
label="Nilai"
|
||||||
/>
|
placeholder="Masukkan nilai"
|
||||||
<Group>
|
value={formatRupiah(formData.value)}
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
onChange={(e) => {
|
||||||
</Group>
|
const raw = e.currentTarget.value;
|
||||||
</Stack>
|
const cleanValue = unformatRupiah(raw);
|
||||||
</Paper>
|
setFormData({ ...formData, value: cleanValue });
|
||||||
</Box>
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function CreatePendapatan() {
|
function CreatePendapatan() {
|
||||||
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan)
|
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
const formatRupiah = (value: number | string) => {
|
const formatRupiah = (value: number | string) => {
|
||||||
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
|
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
|
||||||
@@ -25,48 +35,90 @@ function CreatePendapatan() {
|
|||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
pendapatanState.create.form = {
|
pendapatanState.create.form = {
|
||||||
name: "",
|
name: '',
|
||||||
value: 0,
|
value: 0,
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
await pendapatanState.create.submit();
|
await pendapatanState.create.submit();
|
||||||
resetForm()
|
resetForm();
|
||||||
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan")
|
router.push('/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan');
|
||||||
}
|
};
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box mb={10}>
|
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
return (
|
||||||
<Stack gap={"xs"}>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Title order={4}>Create Jenis Pendapatan</Title>
|
{/* Header dengan tombol back + judul */}
|
||||||
|
<Group mb="md">
|
||||||
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Jenis Pendapatan
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Card Form */}
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
value={pendapatanState.create.form.name}
|
value={pendapatanState.create.form.name}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
pendapatanState.create.form.name = val.target.value;
|
pendapatanState.create.form.name = val.target.value;
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Pendapatan</Text>}
|
label={
|
||||||
placeholder='Masukkan nama jenis pendapatan'
|
<Text fw="bold" fz="sm">
|
||||||
|
Nama Jenis Pendapatan
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
placeholder="Masukkan nama jenis pendapatan"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
type='text'
|
type="text"
|
||||||
value={formatRupiah(pendapatanState.create.form.value)}
|
value={formatRupiah(pendapatanState.create.form.value)}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
const raw = val.currentTarget.value;
|
const raw = val.currentTarget.value;
|
||||||
const cleanValue = unformatRupiah(raw);
|
const cleanValue = unformatRupiah(raw);
|
||||||
pendapatanState.create.form.value = cleanValue;
|
pendapatanState.create.form.value = cleanValue;
|
||||||
}}
|
}}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nilai</Text>}
|
label={
|
||||||
placeholder='Masukkan nilai'
|
<Text fw="bold" fz="sm">
|
||||||
|
Nilai
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
placeholder="Masukkan nilai"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,16 +1,33 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../../_com/header';
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
import PendapatanAsliDesa from '../../../_state/ekonomi/PADesa';
|
import PendapatanAsliDesa from '../../../_state/ekonomi/PADesa';
|
||||||
import HeaderSearch from '../../../_com/header';
|
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
|
|
||||||
|
|
||||||
function Pendapatan() {
|
function Pendapatan() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
@@ -18,7 +35,7 @@ function Pendapatan() {
|
|||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Pendapatan'
|
title='Pendapatan'
|
||||||
placeholder='pencarian'
|
placeholder='Cari pendapatan...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -29,105 +46,166 @@ function Pendapatan() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ListPendapatan({ search }: { search: string }) {
|
function ListPendapatan({ search }: { search: string }) {
|
||||||
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan)
|
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
|
||||||
const formatRupiah = (value: number) => {
|
const {
|
||||||
return new Intl.NumberFormat('id-ID', {
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = pendapatanState.findMany;
|
||||||
|
|
||||||
|
const formatRupiah = (value: number) =>
|
||||||
|
new Intl.NumberFormat('id-ID', {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
currency: 'IDR',
|
currency: 'IDR',
|
||||||
minimumFractionDigits: 0,
|
minimumFractionDigits: 0,
|
||||||
}).format(value);
|
}).format(value);
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
pendapatanState.delete.byId(selectedId)
|
pendapatanState.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
pendapatanState.findMany.load()
|
load(page, 10, search);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
pendapatanState.findMany.load();
|
load(page, 10, search);
|
||||||
}, [])
|
}, [page, search]);
|
||||||
|
|
||||||
const filteredData = (pendapatanState.findMany.data || []).filter(item => {
|
const filteredData = data || [];
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
|
||||||
item.value.toString().toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!pendapatanState.findMany.data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const totalValue = filteredData.reduce((total, item) => total + item.value, 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Pendapatan'
|
<Title order={4}>Daftar Pendapatan</Title>
|
||||||
href='/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/create'
|
<Tooltip label="Tambah Pendapatan Baru" withArrow>
|
||||||
/>
|
<Button
|
||||||
<Table striped withTableBorder withRowBorders>
|
leftSection={<IconPlus size={18} />}
|
||||||
<TableThead>
|
color="blue"
|
||||||
<TableTr>
|
variant="light"
|
||||||
<TableTh>Nama</TableTh>
|
onClick={() =>
|
||||||
<TableTh>Nilai</TableTh>
|
router.push('/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/create')
|
||||||
<TableTh>Edit</TableTh>
|
}
|
||||||
<TableTh>Delete</TableTh>
|
>
|
||||||
</TableTr>
|
Tambah Baru
|
||||||
</TableThead>
|
</Button>
|
||||||
<TableTbody>
|
</Tooltip>
|
||||||
{filteredData.map((item) => (
|
</Group>
|
||||||
<TableTr key={item.id}>
|
|
||||||
<TableTd>{item.name}</TableTd>
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
<TableTd>{formatRupiah(item.value)}</TableTd>
|
<Table highlightOnHover striped withTableBorder withRowBorders>
|
||||||
<TableTd>
|
<TableThead>
|
||||||
<Button color='green' onClick={() => router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/${item.id}`)}>
|
<TableTr>
|
||||||
<IconEdit size={20} />
|
<TableTh style={{ width: '40%' }}>Nama</TableTh>
|
||||||
</Button>
|
<TableTh style={{ width: '25%' }}>Nilai</TableTh>
|
||||||
</TableTd>
|
<TableTh style={{ width: '15%' }}>Edit</TableTh>
|
||||||
<TableTd>
|
<TableTh style={{ width: '15%' }}>Delete</TableTh>
|
||||||
<Button
|
|
||||||
color='red'
|
|
||||||
disabled={pendapatanState.delete.loading}
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedId(item.id)
|
|
||||||
setModalHapus(true)
|
|
||||||
}}>
|
|
||||||
<IconTrash size={20} />
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
</TableThead>
|
||||||
<TableTr>
|
<TableTbody>
|
||||||
<TableTd colSpan={4}>
|
{filteredData.length > 0 ? (
|
||||||
<Text fw={'bold'}>Total</Text>
|
<>
|
||||||
</TableTd>
|
{filteredData.map((item) => (
|
||||||
<TableTd>
|
<TableTr key={item.id}>
|
||||||
{formatRupiah(pendapatanState.findMany.data.reduce((total, item) => total + item.value, 0))}
|
<TableTd>
|
||||||
</TableTd>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
</TableTr>
|
{item.name}
|
||||||
</TableTbody>
|
</Text>
|
||||||
</Table>
|
</TableTd>
|
||||||
|
<TableTd>{formatRupiah(item.value)}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/${item.id}`)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconEdit size={18} />
|
||||||
|
<Text ml={5}>Edit</Text>
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
disabled={pendapatanState.delete.loading}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconTrash size={18} />
|
||||||
|
<Text ml={5}>Hapus</Text>
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Row total */}
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={1}>
|
||||||
|
<Text fw={'bold'}>Total</Text>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd colSpan={3}>
|
||||||
|
<Text fw={'bold'}>{formatRupiah(totalValue)}</Text>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={4}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">Tidak ada data pendapatan yang cocok</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10, search);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleDelete}
|
onConfirm={handleDelete}
|
||||||
text='Apakah anda yakin ingin menghapus pendapatan ini?'
|
text="Apakah anda yakin ingin menghapus pendapatan ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,17 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client';
|
||||||
|
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
@@ -11,33 +20,33 @@ import { useProxy } from 'valtio/utils';
|
|||||||
import demografiPekerjaan from '../../../_state/ekonomi/demografi-pekerjaan';
|
import demografiPekerjaan from '../../../_state/ekonomi/demografi-pekerjaan';
|
||||||
|
|
||||||
function EditDemografiPekerjaan() {
|
function EditDemografiPekerjaan() {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const params = useParams() as { id: string }
|
const params = useParams() as { id: string };
|
||||||
const stateDemografi = useProxy(demografiPekerjaan)
|
const stateDemografi = useProxy(demografiPekerjaan);
|
||||||
|
|
||||||
const id = params.id
|
const id = params.id;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
stateDemografi.update.id = id;
|
stateDemografi.update.id = id;
|
||||||
stateDemografi.findUnique.load(id)
|
stateDemografi.findUnique
|
||||||
|
.load(id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const data = stateDemografi.findUnique.data;
|
const data = stateDemografi.findUnique.data;
|
||||||
if (data) {
|
if (data) {
|
||||||
stateDemografi.update.form = {
|
stateDemografi.update.form = {
|
||||||
pekerjaan: String(data.pekerjaan || ''),
|
pekerjaan: String(data.pekerjaan || ''),
|
||||||
lakiLaki: Number(data.lakiLaki || 0),
|
lakiLaki: Number(data.lakiLaki || 0),
|
||||||
perempuan: Number(data.perempuan || 0)
|
perempuan: Number(data.perempuan || 0),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
console.error('Error loading data:', error);
|
console.error('Error loading data:', error);
|
||||||
toast.error('Gagal memuat data');
|
toast.error('Gagal memuat data');
|
||||||
});
|
});
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
// Di handleSubmit, ubah menjadi:
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
stateDemografi.update.id = id;
|
stateDemografi.update.id = id;
|
||||||
@@ -51,52 +60,88 @@ function EditDemografiPekerjaan() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Group mb="md">
|
||||||
<IconArrowBack size={20} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button
|
||||||
</Box>
|
variant="subtle"
|
||||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
onClick={() => router.back()}
|
||||||
<Stack>
|
p="xs"
|
||||||
<Title order={3}>Edit Demografi Pekerjaan</Title>
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Demografi Pekerjaan
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Form Card */}
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Pekerjaan"
|
label="Pekerjaan"
|
||||||
placeholder="masukkan pekerjaan"
|
placeholder="Masukkan jenis pekerjaan"
|
||||||
value={stateDemografi.update.form.pekerjaan}
|
value={stateDemografi.update.form.pekerjaan}
|
||||||
onChange={(val) => {
|
onChange={(e) =>
|
||||||
stateDemografi.update.form.pekerjaan = val.currentTarget.value;
|
(stateDemografi.update.form.pekerjaan = e.currentTarget.value)
|
||||||
}}
|
}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Jumlah Pekerja Laki - Laki"
|
label="Jumlah Pekerja Laki-laki"
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="masukkan jumlah pekerja laki - laki"
|
placeholder="Masukkan jumlah pekerja laki-laki"
|
||||||
value={stateDemografi.update.form.lakiLaki}
|
value={stateDemografi.update.form.lakiLaki}
|
||||||
onChange={(val) => {
|
onChange={(e) =>
|
||||||
stateDemografi.update.form.lakiLaki = Number(val.currentTarget.value);
|
(stateDemografi.update.form.lakiLaki = Number(
|
||||||
}}
|
e.currentTarget.value
|
||||||
|
))
|
||||||
|
}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Jumlah Pekerja Perempuan"
|
label="Jumlah Pekerja Perempuan"
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="masukkan jumlah pekerja perempuan"
|
placeholder="Masukkan jumlah pekerja perempuan"
|
||||||
value={stateDemografi.update.form.perempuan}
|
value={stateDemografi.update.form.perempuan}
|
||||||
onChange={(val) => {
|
onChange={(e) =>
|
||||||
stateDemografi.update.form.perempuan = Number(val.currentTarget.value);
|
(stateDemografi.update.form.perempuan = Number(
|
||||||
}}
|
e.currentTarget.value
|
||||||
|
))
|
||||||
|
}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<Button
|
|
||||||
mt={10}
|
<Group justify="flex-end">
|
||||||
bg={colors['blue-button']}
|
<Button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
>
|
radius="md"
|
||||||
Simpan Perubahan
|
size="md"
|
||||||
</Button>
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EditDemografiPekerjaan;
|
export default EditDemografiPekerjaan;
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'use client';
|
||||||
|
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -12,15 +22,15 @@ import demografiPekerjaan from '../../../_state/ekonomi/demografi-pekerjaan';
|
|||||||
function CreateDemografiPekerjaan() {
|
function CreateDemografiPekerjaan() {
|
||||||
const stateDemografi = useProxy(demografiPekerjaan);
|
const stateDemografi = useProxy(demografiPekerjaan);
|
||||||
const [chartData, setChartData] = useState<any[]>([]);
|
const [chartData, setChartData] = useState<any[]>([]);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
stateDemografi.create.form = {
|
stateDemografi.create.form = {
|
||||||
pekerjaan: "",
|
pekerjaan: '',
|
||||||
lakiLaki: 0,
|
lakiLaki: 0,
|
||||||
perempuan: 0,
|
perempuan: 0,
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
const id = await stateDemografi.create.create();
|
const id = await stateDemografi.create.create();
|
||||||
@@ -32,58 +42,85 @@ function CreateDemografiPekerjaan() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push("/admin/ekonomi/demografi-pekerjaan");
|
router.push('/admin/ekonomi/demografi-pekerjaan');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Group mb="md">
|
||||||
<IconArrowBack size={20} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button
|
||||||
</Box>
|
variant="subtle"
|
||||||
<Box>
|
onClick={() => router.back()}
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
p="xs"
|
||||||
<Title order={4}>Tambah Demografi Pekerjaan</Title>
|
radius="md"
|
||||||
<Stack gap={"xs"}>
|
>
|
||||||
<TextInput
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
label="Pekerjaan"
|
</Button>
|
||||||
type="text"
|
</Tooltip>
|
||||||
value={stateDemografi.create.form.pekerjaan}
|
<Title order={4} ml="sm" c="dark">
|
||||||
placeholder="Masukkan pekerjaan"
|
Tambah Demografi Pekerjaan
|
||||||
onChange={(val) => {
|
</Title>
|
||||||
stateDemografi.create.form.pekerjaan = val.currentTarget.value;
|
</Group>
|
||||||
|
|
||||||
|
{/* Form */}
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
label="Pekerjaan"
|
||||||
|
type="text"
|
||||||
|
value={stateDemografi.create.form.pekerjaan}
|
||||||
|
placeholder="Masukkan pekerjaan"
|
||||||
|
onChange={(val) => {
|
||||||
|
stateDemografi.create.form.pekerjaan = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Jumlah Pekerja Laki-Laki"
|
||||||
|
type="number"
|
||||||
|
value={stateDemografi.create.form.lakiLaki}
|
||||||
|
placeholder="Masukkan jumlah pekerja laki-laki"
|
||||||
|
onChange={(val) => {
|
||||||
|
stateDemografi.create.form.lakiLaki = Number(val.currentTarget.value);
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Jumlah Pekerja Perempuan"
|
||||||
|
type="number"
|
||||||
|
value={stateDemografi.create.form.perempuan}
|
||||||
|
placeholder="Masukkan jumlah pekerja perempuan"
|
||||||
|
onChange={(val) => {
|
||||||
|
stateDemografi.create.form.perempuan = Number(val.currentTarget.value);
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
<TextInput
|
Simpan
|
||||||
label="Jumlah Pekerja Laki - Laki"
|
</Button>
|
||||||
type="number"
|
</Group>
|
||||||
value={stateDemografi.create.form.lakiLaki}
|
</Stack>
|
||||||
placeholder="Masukkan jumlah pekerja laki - laki"
|
</Paper>
|
||||||
onChange={(val) => {
|
|
||||||
stateDemografi.create.form.lakiLaki = Number(val.currentTarget.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Jumlah Pekerja Perempuan"
|
|
||||||
type="number"
|
|
||||||
value={stateDemografi.create.form.perempuan}
|
|
||||||
placeholder="Masukkan jumlah pekerja perempuan"
|
|
||||||
onChange={(val) => {
|
|
||||||
stateDemografi.create.form.perempuan = Number(val.currentTarget.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Group>
|
|
||||||
<Button
|
|
||||||
bg={colors['blue-button']}
|
|
||||||
mt={10}
|
|
||||||
onClick={handleSubmit}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,32 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { BarChart } from '@mantine/charts';
|
import { BarChart } from '@mantine/charts';
|
||||||
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Pagination,
|
||||||
|
Flex,
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import JudulList from '../../_com/judulList';
|
|
||||||
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
||||||
import demografiPekerjaan from '../../_state/ekonomi/demografi-pekerjaan';
|
import demografiPekerjaan from '../../_state/ekonomi/demografi-pekerjaan';
|
||||||
|
|
||||||
@@ -18,7 +36,7 @@ function DemografiPekerjaan() {
|
|||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Demografi Pekerjaan'
|
title='Demografi Pekerjaan'
|
||||||
placeholder='pencarian'
|
placeholder='Cari pekerjaan atau jumlah pekerja...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -34,131 +52,193 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
|
|||||||
pekerjaan: string;
|
pekerjaan: string;
|
||||||
lakiLaki: number;
|
lakiLaki: number;
|
||||||
perempuan: number;
|
perempuan: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const stateDemografi = useProxy(demografiPekerjaan)
|
const stateDemografi = useProxy(demografiPekerjaan);
|
||||||
const [chartData, setChartData] = useState<DemografiPekerjaan[]>([]);
|
const [chartData, setChartData] = useState<DemografiPekerjaan[]>([]);
|
||||||
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
|
const [mounted, setMounted] = useState(false);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = stateDemografi.findMany;
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
stateDemografi.delete.byId(selectedId)
|
stateDemografi.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
|
|
||||||
stateDemografi.findMany.load()
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
setMounted(true)
|
setMounted(true);
|
||||||
stateDemografi.findMany.load()
|
load(page, 10, search);
|
||||||
}, [])
|
}, [page, search]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true);
|
if (data) {
|
||||||
if (stateDemografi.findMany.data) {
|
setChartData(
|
||||||
setChartData(stateDemografi.findMany.data.map((item) => ({
|
data.map((item) => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
pekerjaan: item.pekerjaan,
|
pekerjaan: item.pekerjaan,
|
||||||
lakiLaki: Number(item.lakiLaki),
|
lakiLaki: Number(item.lakiLaki),
|
||||||
perempuan: Number(item.perempuan),
|
perempuan: Number(item.perempuan),
|
||||||
})));
|
}))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [stateDemografi.findMany.data]);
|
}, [data]);
|
||||||
|
|
||||||
const filteredData = (stateDemografi.findMany.data || []).filter(item => {
|
const filteredData = data || [];
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
item.pekerjaan.toLowerCase().includes(keyword) ||
|
<Stack py={10}>
|
||||||
item.lakiLaki.toString().toLowerCase().includes(keyword) ||
|
<Skeleton height={600} radius="md" />
|
||||||
item.perempuan.toString().toLowerCase().includes(keyword)
|
</Stack>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Demografi Pekerjaan'
|
<Title order={4}>List Demografi Pekerjaan</Title>
|
||||||
href='/admin/ekonomi/demografi-pekerjaan/create'
|
<Tooltip label="Tambah Data Pekerjaan" withArrow>
|
||||||
/>
|
<Button
|
||||||
<Table striped withTableBorder withRowBorders>
|
leftSection={<IconPlus size={18} />}
|
||||||
<TableThead>
|
color="blue"
|
||||||
<TableTr>
|
variant="light"
|
||||||
<TableTh>Pekerjaan</TableTh>
|
onClick={() => router.push('/admin/ekonomi/demografi-pekerjaan/create')}
|
||||||
<TableTh>Jumlah Pekerja Laki - Laki</TableTh>
|
>
|
||||||
<TableTh>Jumlah Pekerja Perempuan</TableTh>
|
Tambah Baru
|
||||||
<TableTh>Edit</TableTh>
|
</Button>
|
||||||
<TableTh>Delete</TableTh>
|
</Tooltip>
|
||||||
</TableTr>
|
</Group>
|
||||||
</TableThead>
|
|
||||||
<TableTbody>
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
{filteredData.map((item) => (
|
<Table highlightOnHover>
|
||||||
<TableTr key={item.id}>
|
<TableThead>
|
||||||
<TableTd>{item.pekerjaan}</TableTd>
|
<TableTr>
|
||||||
<TableTd>{item.lakiLaki}</TableTd>
|
<TableTh>Pekerjaan</TableTh>
|
||||||
<TableTd>{item.perempuan}</TableTd>
|
<TableTh>Laki - Laki</TableTh>
|
||||||
<TableTd>
|
<TableTh>Perempuan</TableTh>
|
||||||
<Button color='green' onClick={() => router.push(`/admin/ekonomi/demografi-pekerjaan/${item.id}`)}>
|
<TableTh>Edit</TableTh>
|
||||||
<IconEdit size={20} />
|
<TableTh>Hapus</TableTh>
|
||||||
</Button>
|
|
||||||
</TableTd>
|
|
||||||
<TableTd>
|
|
||||||
<Button
|
|
||||||
color='red'
|
|
||||||
disabled={stateDemografi.delete.loading}
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedId(item.id)
|
|
||||||
setModalHapus(true)
|
|
||||||
}}>
|
|
||||||
<IconTrash size={20} />
|
|
||||||
</Button>
|
|
||||||
</TableTd>
|
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
</TableThead>
|
||||||
</TableTbody>
|
<TableTbody>
|
||||||
</Table>
|
{filteredData.length > 0 ? (
|
||||||
|
filteredData.map((item) => (
|
||||||
|
<TableTr key={item.id}>
|
||||||
|
<TableTd>{item.pekerjaan}</TableTd>
|
||||||
|
<TableTd>{item.lakiLaki}</TableTd>
|
||||||
|
<TableTd>{item.perempuan}</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="green"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`/admin/ekonomi/demografi-pekerjaan/${item.id}`)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconEdit size={18} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
disabled={stateDemografi.delete.loading}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconTrash size={18} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={4}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">Tidak ada data demografi pekerjaan yang cocok</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10, search);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
|
||||||
{/* Chart */}
|
{/* Chart */}
|
||||||
{!mounted && !chartData ? (
|
<Box mt={30} style={{ width: '100%', minHeight: 400 }}>
|
||||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
<Paper bg={colors['white-1']} p="md" radius="md" withBorder>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Stack gap={"xs"}>
|
||||||
<Title pb={10} order={3}>Data Kelahiran & Kematian</Title>
|
<Title pb={10} order={4}>
|
||||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
Grafik Demografi Pekerjaan
|
||||||
</Paper>
|
</Title>
|
||||||
</Box>
|
{mounted && chartData.length > 0 ? (
|
||||||
) : (
|
<BarChart
|
||||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
h={450}
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
data={chartData}
|
||||||
<Title pb={10} order={4}>Data Kelahiran & Kematian</Title>
|
dataKey="pekerjaan"
|
||||||
{mounted && chartData.length > 0 && (
|
type="stacked"
|
||||||
<Box w={{ base: '100%', md: '30%' }}>
|
series={[
|
||||||
<BarChart
|
{ name: 'lakiLaki', color: '#5082EE', label: 'Laki - Laki' },
|
||||||
h={450}
|
{ name: 'perempuan', color: '#6EDF9C', label: 'Perempuan' },
|
||||||
data={chartData}
|
]}
|
||||||
dataKey="pekerjaan"
|
/>
|
||||||
type="stacked"
|
) : (
|
||||||
series={[
|
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||||
{ name: 'lakiLaki', color: '#5082EE', label: 'Laki - Laki' },
|
|
||||||
{ name: 'perempuan', color: '#6EDF9C', label: 'Perempuan' },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
</Paper>
|
<Box py={10}>
|
||||||
</Box>
|
<Group justify='center'>
|
||||||
)}
|
<Flex align="center" gap={10}>
|
||||||
|
<Box bg="#5082EE" w={20} h={20} />
|
||||||
|
<Text>Laki - Laki</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex align="center" gap={10}>
|
||||||
|
<Box bg="#6EDF9C" w={20} h={20} />
|
||||||
|
<Text>Perempuan</Text>
|
||||||
|
</Flex>
|
||||||
|
</Group>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleDelete}
|
onConfirm={handleDelete}
|
||||||
text='Apakah anda yakin ingin menghapus demografi pekerjaan ini?'
|
text="Apakah anda yakin ingin menghapus demografi pekerjaan ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,78 +3,116 @@
|
|||||||
|
|
||||||
import jumlahPendudukMiskin from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-penduduk-miskin';
|
import jumlahPendudukMiskin from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-penduduk-miskin';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Paper, Stack, TextInput, Title, Group, Tooltip } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
function EditJumlahPendudukMiskin() {
|
function EditJumlahPendudukMiskin() {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const params = useParams() as { id: string }
|
const params = useParams() as { id: string };
|
||||||
const stateJPM = useProxy(jumlahPendudukMiskin)
|
const stateJPM = useProxy(jumlahPendudukMiskin);
|
||||||
|
|
||||||
const id = params.id
|
const id = params.id;
|
||||||
|
|
||||||
// Load data saat komponen mount
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id) {
|
if (!id) return;
|
||||||
stateJPM.findUnique.load(id).then(() => {
|
|
||||||
const data = stateJPM.findUnique.data
|
const loadData = async () => {
|
||||||
|
try {
|
||||||
|
await stateJPM.findUnique.load(id);
|
||||||
|
const data = stateJPM.findUnique.data;
|
||||||
if (data) {
|
if (data) {
|
||||||
stateJPM.update.form = {
|
stateJPM.update.form = {
|
||||||
year: data.year || 0,
|
year: data.year || 0,
|
||||||
totalPoorPopulation: data.totalPoorPopulation || 0,
|
totalPoorPopulation: data.totalPoorPopulation || 0,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
})
|
} catch (error) {
|
||||||
}
|
console.error('Gagal memuat data:', error);
|
||||||
}, [id])
|
toast.error('Gagal memuat data jumlah penduduk miskin');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadData();
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
// Set the ID before submitting
|
try {
|
||||||
stateJPM.update.id = id;
|
stateJPM.update.id = id;
|
||||||
await stateJPM.update.submit();
|
await stateJPM.update.submit();
|
||||||
router.push('/admin/ekonomi/jumlah-penduduk-miskin')
|
toast.success('Data jumlah penduduk miskin berhasil diperbarui!');
|
||||||
}
|
router.push('/admin/ekonomi/jumlah-penduduk-miskin');
|
||||||
return (
|
} catch (error) {
|
||||||
<Box>
|
console.error('Gagal menyimpan data:', error);
|
||||||
<Box mb={10}>
|
toast.error('Terjadi kesalahan saat menyimpan data');
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
}
|
||||||
<IconArrowBack size={20} />
|
};
|
||||||
</Button>
|
|
||||||
</Box>
|
return (
|
||||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Stack>
|
<Group mb="md">
|
||||||
<Title order={3}>Edit Jumlah Penduduk Miskin</Title>
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Jumlah Penduduk Miskin
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Tahun"
|
label="Tahun"
|
||||||
placeholder="masukkan tahun"
|
placeholder="Masukkan tahun"
|
||||||
|
type="number"
|
||||||
|
required
|
||||||
value={stateJPM.update.form.year}
|
value={stateJPM.update.form.year}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
stateJPM.update.form.year = Number(val.currentTarget.value);
|
stateJPM.update.form.year = Number(val.currentTarget.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Jumlah Penduduk Miskin"
|
label="Jumlah Penduduk Miskin"
|
||||||
|
placeholder="Masukkan jumlah penduduk miskin"
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="masukkan jumlah penduduk miskin"
|
required
|
||||||
value={stateJPM.update.form.totalPoorPopulation}
|
value={stateJPM.update.form.totalPoorPopulation}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
stateJPM.update.form.totalPoorPopulation = Number(val.currentTarget.value);
|
stateJPM.update.form.totalPoorPopulation = Number(val.currentTarget.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
|
||||||
mt={10}
|
<Group justify="right">
|
||||||
bg={colors['blue-button']}
|
<Button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
>
|
radius="md"
|
||||||
Simpan
|
size="md"
|
||||||
</Button>
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EditJumlahPendudukMiskin;
|
export default EditJumlahPendudukMiskin;
|
||||||
|
|||||||
@@ -1,26 +1,25 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'use client';
|
||||||
import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
|
import { Box, Button, Group, Paper, Stack, TextInput, Title, Tooltip } from '@mantine/core';
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Group, Paper, Stack, TextInput, Title } from '@mantine/core';
|
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import colors from '@/con/colors';
|
||||||
import jumlahPendudukMiskin from '../../../_state/ekonomi/jumlah-penduduk-miskin';
|
import jumlahPendudukMiskin from '../../../_state/ekonomi/jumlah-penduduk-miskin';
|
||||||
|
|
||||||
function CreateJumlahPendudukMiskin() {
|
export default function CreateJumlahPendudukMiskin() {
|
||||||
const stateJPM = useProxy(jumlahPendudukMiskin);
|
const stateJPM = useProxy(jumlahPendudukMiskin);
|
||||||
const [chartData, setChartData] = useState<any[]>([]);
|
const [chartData, setChartData] = useState<any[]>([]);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
stateJPM.create.form = {
|
stateJPM.create.form = {
|
||||||
year: 0,
|
year: new Date().getFullYear(),
|
||||||
totalPoorPopulation: 0,
|
totalPoorPopulation: 0,
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
const id = await stateJPM.create.create();
|
const id = await stateJPM.create.create();
|
||||||
@@ -32,51 +31,72 @@ function CreateJumlahPendudukMiskin() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push("/admin/ekonomi/jumlah-penduduk-miskin");
|
router.push('/admin/ekonomi/jumlah-penduduk-miskin');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Group mb="md">
|
||||||
<IconArrowBack size={20} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Box>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
<Box>
|
</Button>
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
</Tooltip>
|
||||||
<Title order={4}>Tambah Grafik Hasil Kepuasan Masyarakat</Title>
|
<Title order={4} ml="sm" c="dark">
|
||||||
<Stack gap={"xs"}>
|
Tambah Jumlah Penduduk Miskin
|
||||||
<TextInput
|
</Title>
|
||||||
label="Tahun"
|
</Group>
|
||||||
type="number"
|
|
||||||
value={stateJPM.create.form.year}
|
{/* Form Paper */}
|
||||||
placeholder="Masukkan tahun"
|
<Paper
|
||||||
onChange={(val) => {
|
w={{ base: '100%', md: '50%' }}
|
||||||
stateJPM.create.form.year = Number(val.currentTarget.value);
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
label="Tahun"
|
||||||
|
type="number"
|
||||||
|
value={stateJPM.create.form.year || ''}
|
||||||
|
placeholder="Masukkan tahun"
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.currentTarget.value;
|
||||||
|
stateJPM.create.form.year = value ? Number(value) : 0;
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label="Jumlah Penduduk Miskin"
|
||||||
|
type="number"
|
||||||
|
value={stateJPM.create.form.totalPoorPopulation}
|
||||||
|
placeholder="Masukkan jumlah penduduk miskin"
|
||||||
|
onChange={(e) => {
|
||||||
|
stateJPM.create.form.totalPoorPopulation = Number(e.currentTarget.value);
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
<TextInput
|
Simpan
|
||||||
label="Jumlah Penduduk Miskin"
|
</Button>
|
||||||
type="number"
|
</Group>
|
||||||
value={stateJPM.create.form.totalPoorPopulation}
|
</Stack>
|
||||||
placeholder="Masukkan jumlah penduduk miskin"
|
</Paper>
|
||||||
onChange={(val) => {
|
|
||||||
stateJPM.create.form.totalPoorPopulation = Number(val.currentTarget.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Group>
|
|
||||||
<Button
|
|
||||||
bg={colors['blue-button']}
|
|
||||||
mt={10}
|
|
||||||
onClick={handleSubmit}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CreateJumlahPendudukMiskin;
|
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import JudulList from '../../_com/judulList';
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect, useMediaQuery } from '@mantine/hooks';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import jumlahPendudukMiskin from '../../_state/ekonomi/jumlah-penduduk-miskin';
|
import jumlahPendudukMiskin from '../../_state/ekonomi/jumlah-penduduk-miskin';
|
||||||
import { Bar, BarChart, Legend, XAxis, YAxis, Tooltip } from 'recharts';
|
import { Bar, BarChart, Legend, XAxis, YAxis, Tooltip as RechartsTooltip } from 'recharts';
|
||||||
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
||||||
|
|
||||||
function JumlahPendudukMiskin() {
|
function JumlahPendudukMiskin() {
|
||||||
@@ -18,7 +17,7 @@ function JumlahPendudukMiskin() {
|
|||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Jumlah Penduduk Miskin'
|
title='Jumlah Penduduk Miskin'
|
||||||
placeholder='pencarian'
|
placeholder='Cari tahun atau jumlah penduduk miskin...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -29,138 +28,158 @@ function JumlahPendudukMiskin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
||||||
type JPMGrafik = {
|
type JPMGrafik = { id: string; year: number; totalPoorPopulation: number }
|
||||||
id: string;
|
|
||||||
year: number;
|
|
||||||
totalPoorPopulation: number;
|
|
||||||
}
|
|
||||||
const stateJPM = useProxy(jumlahPendudukMiskin);
|
const stateJPM = useProxy(jumlahPendudukMiskin);
|
||||||
const [chartData, setChartData] = useState<JPMGrafik[]>([]);
|
const [chartData, setChartData] = useState<JPMGrafik[]>([]);
|
||||||
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
|
const [mounted, setMounted] = useState(false);
|
||||||
const isTablet = useMediaQuery('(max-width: 1024px)')
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const isMobile = useMediaQuery('(max-width: 768px)')
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const handleDelete = () => {
|
const isTablet = useMediaQuery('(max-width:1024px)');
|
||||||
if (selectedId) {
|
const isMobile = useMediaQuery('(max-width:768px)');
|
||||||
stateJPM.delete.byId(selectedId)
|
|
||||||
setModalHapus(false)
|
|
||||||
setSelectedId(null)
|
|
||||||
|
|
||||||
stateJPM.findMany.load()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
totalPages,
|
||||||
|
} = stateJPM.findMany;
|
||||||
|
// Load data
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
setMounted(true)
|
setMounted(true);
|
||||||
stateJPM.findMany.load()
|
load(page, 10, search);
|
||||||
}, [])
|
}, [page, search]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true);
|
|
||||||
if (stateJPM.findMany.data) {
|
if (stateJPM.findMany.data) {
|
||||||
setChartData(stateJPM.findMany.data.map((item) => ({
|
setChartData(stateJPM.findMany.data.map(item => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
year: Number(item.year),
|
year: Number(item.year),
|
||||||
totalPoorPopulation: Number(item.totalPoorPopulation),
|
totalPoorPopulation: Number(item.totalPoorPopulation)
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
}, [stateJPM.findMany.data]);
|
}, [stateJPM.findMany.data]);
|
||||||
|
|
||||||
const filteredData = (stateJPM.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.year.toString().toLowerCase().includes(keyword) ||
|
|
||||||
item.totalPoorPopulation.toString().toLowerCase().includes(keyword)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!stateJPM.findMany.data) {
|
const handleDelete = () => {
|
||||||
|
if (selectedId) {
|
||||||
|
stateJPM.delete.byId(selectedId);
|
||||||
|
setModalHapus(false);
|
||||||
|
setSelectedId(null);
|
||||||
|
stateJPM.findMany.load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Stack gap={'xs'}>
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Group justify="space-between" mb="md">
|
||||||
<JudulList
|
<Title order={4}>Daftar Jumlah Penduduk Miskin</Title>
|
||||||
title='List Jumlah Penduduk Miskin'
|
<Tooltip label="Tambah Data" withArrow>
|
||||||
href='/admin/ekonomi/jumlah-penduduk-miskin/create'
|
<Button
|
||||||
/>
|
leftSection={<IconEdit size={18} />}
|
||||||
<Table striped withTableBorder withRowBorders>
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
onClick={() => router.push('/admin/ekonomi/jumlah-penduduk-miskin/create')}
|
||||||
|
>
|
||||||
|
Tambah Baru
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Tahun</TableTh>
|
<TableTh style={{ width: '25%' }}>Tahun</TableTh>
|
||||||
<TableTh>Jumlah Penduduk Miskin</TableTh>
|
<TableTh style={{ width: '35%' }}>Jumlah Penduduk Miskin</TableTh>
|
||||||
<TableTh>Edit</TableTh>
|
<TableTh style={{ width: '20%' }}>Edit</TableTh>
|
||||||
<TableTh>Delete</TableTh>
|
<TableTh style={{ width: '20%' }}>Delete</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.length > 0 ? filteredData.map(item => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.year}</TableTd>
|
<TableTd>{item.year}</TableTd>
|
||||||
<TableTd>{item.totalPoorPopulation}</TableTd>
|
<TableTd>{item.totalPoorPopulation}</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button color='green' onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-miskin/${item.id}`)}>
|
<Button variant='light' color="green" onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-miskin/${item.id}`)}>
|
||||||
<IconEdit size={20} />
|
<IconEdit size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button variant='light' color="red" disabled={stateJPM.delete.loading} onClick={() => { setSelectedId(item.id); setModalHapus(true) }}>
|
||||||
color='red'
|
|
||||||
disabled={stateJPM.delete.loading}
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedId(item.id)
|
|
||||||
setModalHapus(true)
|
|
||||||
}}>
|
|
||||||
<IconTrash size={20} />
|
<IconTrash size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
)) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={4}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">Tidak ada data yang cocok</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
{/* Chart */}
|
<Center>
|
||||||
{!mounted && !chartData ? (
|
<Pagination
|
||||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
value={page}
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
onChange={(newPage) => {
|
||||||
<Title pb={10} order={3}>Grafik Jumlah Penduduk Miskin</Title>
|
load(newPage, 10);
|
||||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
</Paper>
|
}}
|
||||||
</Box>
|
total={totalPages}
|
||||||
) : (
|
mt="md"
|
||||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
mb="md"
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
color="blue"
|
||||||
<Title pb={10} order={4}>Grafik Jumlah Penduduk Miskin</Title>
|
radius="md"
|
||||||
{mounted && chartData.length > 0 && (
|
/>
|
||||||
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={chartData} >
|
</Center>
|
||||||
<XAxis dataKey="year" />
|
|
||||||
<YAxis />
|
|
||||||
<Tooltip />
|
|
||||||
<Legend />
|
|
||||||
<Bar dataKey="totalPoorPopulation" fill={colors['blue-button']} name="Jumlah Penduduk Miskin" />
|
|
||||||
</BarChart>
|
|
||||||
)}
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
{/* Chart */}
|
||||||
|
<Paper bg={colors['white-1']} p="md" mt="lg" withBorder radius="md">
|
||||||
|
<Stack>
|
||||||
|
<Box mt="lg" style={{ width: '100%', minHeight: 350 }}>
|
||||||
|
<Title order={4} mb="sm">Grafik Jumlah Penduduk Miskin</Title>
|
||||||
|
{mounted && chartData.length > 0 ? (
|
||||||
|
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={chartData}>
|
||||||
|
<XAxis dataKey="year" />
|
||||||
|
<YAxis />
|
||||||
|
<RechartsTooltip />
|
||||||
|
<Legend />
|
||||||
|
<Bar dataKey="totalPoorPopulation" fill={colors['blue-button']} name="Jumlah Penduduk Miskin" />
|
||||||
|
</BarChart>
|
||||||
|
) : (
|
||||||
|
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Modal Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleDelete}
|
onConfirm={handleDelete}
|
||||||
text='Apakah anda yakin ingin menghapus grafik jumlah penduduk miskin ini?'
|
text='Apakah anda yakin ingin menghapus data ini?'
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,62 +1,124 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
import {
|
||||||
|
Stack,
|
||||||
|
Tabs,
|
||||||
|
TabsList,
|
||||||
|
TabsPanel,
|
||||||
|
TabsTab,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
ScrollArea,
|
||||||
|
} from '@mantine/core';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { IconUsers, IconSchool } from '@tabler/icons-react';
|
||||||
|
|
||||||
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const pathname = usePathname()
|
const pathname = usePathname();
|
||||||
const tabs = [
|
|
||||||
{
|
|
||||||
label: "Pengangguran Berdasarkan Usia",
|
|
||||||
value: "pengangguranberdasarkanusia",
|
|
||||||
href: "/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Pengangguran Berdasarkan Pendidikan",
|
|
||||||
value: "pengangguranberdasarkanpendidikan",
|
|
||||||
href: "/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan"
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
|
||||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
|
||||||
|
|
||||||
const handleTabChange = (value: string | null) => {
|
const tabs = [
|
||||||
const tab = tabs.find(t => t.value === value)
|
{
|
||||||
if (tab) {
|
label: "Pengangguran Berdasarkan Usia",
|
||||||
router.push(tab.href)
|
value: "pengangguranberdasarkanusia",
|
||||||
}
|
href: "/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia",
|
||||||
setActiveTab(value)
|
icon: <IconUsers size={18} stroke={1.8} />,
|
||||||
}
|
tooltip: "Data pengangguran menurut kelompok usia",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Pengangguran Berdasarkan Pendidikan",
|
||||||
|
value: "pengangguranberdasarkanpendidikan",
|
||||||
|
href: "/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan",
|
||||||
|
icon: <IconSchool size={18} stroke={1.8} />,
|
||||||
|
tooltip: "Data pengangguran menurut tingkat pendidikan",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
const currentTab = tabs.find(tab => tab.href === pathname);
|
||||||
const match = tabs.find(tab => tab.href === pathname)
|
const [activeTab, setActiveTab] = useState<string | null>(currentTab?.value || tabs[0].value);
|
||||||
if (match) {
|
|
||||||
setActiveTab(match.value)
|
|
||||||
}
|
|
||||||
}, [pathname])
|
|
||||||
|
|
||||||
return (
|
const handleTabChange = (value: string | null) => {
|
||||||
<Stack>
|
const tab = tabs.find(t => t.value === value);
|
||||||
<Title order={3}>Jumlah Penduduk Usia Kerja yang Menganggur</Title>
|
if (tab) router.push(tab.href);
|
||||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
setActiveTab(value);
|
||||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
};
|
||||||
{tabs.map((e, i) => (
|
|
||||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
useEffect(() => {
|
||||||
))}
|
const match = tabs.find(tab => tab.href === pathname);
|
||||||
</TabsList>
|
if (match) setActiveTab(match.value);
|
||||||
{tabs.map((e, i) => (
|
}, [pathname]);
|
||||||
<TabsPanel key={i} value={e.value}>
|
|
||||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
return (
|
||||||
<></>
|
<Stack gap="lg">
|
||||||
</TabsPanel>
|
<Title order={2} fw={700} style={{ color: "#1A1B1E" }}>
|
||||||
))}
|
Jumlah Penduduk Usia Kerja yang Menganggur
|
||||||
</Tabs>
|
</Title>
|
||||||
|
|
||||||
|
<Tabs
|
||||||
|
color={colors['blue-button']}
|
||||||
|
variant="pills"
|
||||||
|
value={activeTab}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
radius="lg"
|
||||||
|
keepMounted={false}
|
||||||
|
>
|
||||||
|
<ScrollArea type="auto" offsetScrollbars>
|
||||||
|
<TabsList
|
||||||
|
p="sm"
|
||||||
|
style={{
|
||||||
|
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "nowrap",
|
||||||
|
gap: "0.5rem",
|
||||||
|
paddingInline: "0.5rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<Tooltip
|
||||||
|
key={i}
|
||||||
|
label={tab.tooltip}
|
||||||
|
position="bottom"
|
||||||
|
withArrow
|
||||||
|
transitionProps={{ transition: "pop", duration: 200 }}
|
||||||
|
>
|
||||||
|
<TabsTab
|
||||||
|
value={tab.value}
|
||||||
|
leftSection={tab.icon}
|
||||||
|
style={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "0.9rem",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</TabsTab>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
|
{tabs.map((tab, i) => (
|
||||||
|
<TabsPanel
|
||||||
|
key={i}
|
||||||
|
value={tab.value}
|
||||||
|
style={{
|
||||||
|
padding: "1.5rem",
|
||||||
|
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
||||||
|
borderRadius: "1rem",
|
||||||
|
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</Stack>
|
</TabsPanel>
|
||||||
);
|
))}
|
||||||
|
</Tabs>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LayoutTabs;
|
export default LayoutTabs;
|
||||||
@@ -1,108 +1,117 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur';
|
import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur';
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Paper, Stack, TextInput, Title, Tooltip } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function EditGrafikBerdasarkanPendidikan() {
|
function EditGrafikBerdasarkanPendidikan() {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const params = useParams() as { id: string }
|
const params = useParams() as { id: string };
|
||||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan)
|
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan);
|
||||||
const id = params.id
|
const id = params.id;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(id){
|
if (id) {
|
||||||
stategrafik.findUnique.load(id).then(() => {
|
stategrafik.findUnique.load(id).then(() => {
|
||||||
const data = stategrafik.findUnique.data
|
const data = stategrafik.findUnique.data;
|
||||||
if(data){
|
if (data) {
|
||||||
stategrafik.update.form = {
|
stategrafik.update.form = {
|
||||||
SD: data.SD || '',
|
SD: data.SD || '',
|
||||||
SMP: data.SMP || '',
|
SMP: data.SMP || '',
|
||||||
SMA: data.SMA || '',
|
SMA: data.SMA || '',
|
||||||
D3: data.D3 || '',
|
D3: data.D3 || '',
|
||||||
S1: data.S1 || '',
|
S1: data.S1 || '',
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}, [id])
|
}, [id]);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
stategrafik.update.id = id;
|
stategrafik.update.id = id;
|
||||||
await stategrafik.update.submit();
|
await stategrafik.update.submit();
|
||||||
router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan')
|
router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
<IconArrowBack size={20} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Grafik Pengangguran Berdasarkan Pendidikan
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
label="SD"
|
||||||
|
type="number"
|
||||||
|
placeholder="Masukkan jumlah"
|
||||||
|
value={stategrafik.update.form.SD}
|
||||||
|
onChange={(val) => (stategrafik.update.form.SD = val.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="SMP"
|
||||||
|
type="number"
|
||||||
|
placeholder="Masukkan jumlah"
|
||||||
|
value={stategrafik.update.form.SMP}
|
||||||
|
onChange={(val) => (stategrafik.update.form.SMP = val.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="SMA"
|
||||||
|
type="number"
|
||||||
|
placeholder="Masukkan jumlah"
|
||||||
|
value={stategrafik.update.form.SMA}
|
||||||
|
onChange={(val) => (stategrafik.update.form.SMA = val.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="D3"
|
||||||
|
type="number"
|
||||||
|
placeholder="Masukkan jumlah"
|
||||||
|
value={stategrafik.update.form.D3}
|
||||||
|
onChange={(val) => (stategrafik.update.form.D3 = val.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="S1"
|
||||||
|
type="number"
|
||||||
|
placeholder="Masukkan jumlah"
|
||||||
|
value={stategrafik.update.form.S1}
|
||||||
|
onChange={(val) => (stategrafik.update.form.S1 = val.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Title order={3}>Edit Grafik Pengangguran Berdasarkan Pendidikan</Title>
|
|
||||||
<TextInput
|
|
||||||
label="SD"
|
|
||||||
type='number'
|
|
||||||
placeholder="masukkan jumlah"
|
|
||||||
value={stategrafik.update.form.SD}
|
|
||||||
onChange={(val) => {
|
|
||||||
stategrafik.update.form.SD = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="SMP"
|
|
||||||
type="number"
|
|
||||||
placeholder="masukkan jumlah"
|
|
||||||
value={stategrafik.update.form.SMP}
|
|
||||||
onChange={(val) => {
|
|
||||||
stategrafik.update.form.SMP = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="SMA"
|
|
||||||
type="number"
|
|
||||||
placeholder="masukkan jumlah"
|
|
||||||
value={stategrafik.update.form.SMA}
|
|
||||||
onChange={(val) => {
|
|
||||||
stategrafik.update.form.SMA = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="D3"
|
|
||||||
type="number"
|
|
||||||
placeholder="masukkan jumlah"
|
|
||||||
value={stategrafik.update.form.D3}
|
|
||||||
onChange={(val) => {
|
|
||||||
stategrafik.update.form.D3 = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="S1"
|
|
||||||
type="number"
|
|
||||||
placeholder="masukkan jumlah"
|
|
||||||
value={stategrafik.update.form.S1}
|
|
||||||
onChange={(val) => {
|
|
||||||
stategrafik.update.form.S1 = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
mt={10}
|
|
||||||
bg={colors['blue-button']}
|
|
||||||
onClick={handleSubmit}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,30 @@
|
|||||||
'use client'
|
'use client';
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import grafikBerdasarkanJenisKelamin from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin';
|
import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, Title, TextInput } from '@mantine/core';
|
import { Box, Button, Paper, Stack, Title, TextInput, Group, Tooltip } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur';
|
|
||||||
|
|
||||||
|
|
||||||
function CreateGrafikBerdasarkanPendidikan() {
|
function CreateGrafikBerdasarkanPendidikan() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan)
|
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan);
|
||||||
const [donutData, setDonutData] = useState<any[]>([]);
|
const [donutData, setDonutData] = useState<any[]>([]);
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
stategrafik.create.form = {
|
stategrafik.create.form = {
|
||||||
...stategrafik.create.form,
|
...stategrafik.create.form,
|
||||||
SD: "",
|
SD: '',
|
||||||
SMP: "",
|
SMP: '',
|
||||||
SMA: "",
|
SMA: '',
|
||||||
D3: "",
|
D3: '',
|
||||||
S1: "",
|
S1: '',
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
const id = await stategrafik.create.create();
|
const id = await stategrafik.create.create();
|
||||||
@@ -38,73 +36,91 @@ function CreateGrafikBerdasarkanPendidikan() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push("/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan")
|
router.push(
|
||||||
}
|
'/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
<IconArrowBack size={20} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Box>
|
</Button>
|
||||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
</Tooltip>
|
||||||
<Stack>
|
<Title order={4} ml="sm" c="dark">
|
||||||
<Title order={3}>Create Grafik Pengangguran Berdasarkan Pendidikan</Title>
|
Tambah Data Pengangguran Berdasarkan Pendidikan
|
||||||
<TextInput
|
</Title>
|
||||||
label="SD"
|
</Group>
|
||||||
type='number'
|
|
||||||
placeholder="masukkan jumlah"
|
<Paper
|
||||||
value={stategrafik.create.form.SD}
|
w={{ base: '100%', md: '50%' }}
|
||||||
onChange={(val) => {
|
bg={colors['white-1']}
|
||||||
stategrafik.create.form.SD = val.currentTarget.value;
|
p="lg"
|
||||||
}}
|
radius="md"
|
||||||
/>
|
shadow="sm"
|
||||||
<TextInput
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
label="SMP"
|
>
|
||||||
type="number"
|
<Stack gap="md">
|
||||||
placeholder="masukkan jumlah"
|
<TextInput
|
||||||
value={stategrafik.create.form.SMP}
|
label="SD"
|
||||||
onChange={(val) => {
|
type="number"
|
||||||
stategrafik.create.form.SMP = val.currentTarget.value;
|
placeholder="Masukkan jumlah"
|
||||||
}}
|
value={stategrafik.create.form.SD}
|
||||||
/>
|
onChange={(val) => (stategrafik.create.form.SD = val.currentTarget.value)}
|
||||||
<TextInput
|
required
|
||||||
label="SMA"
|
/>
|
||||||
type="number"
|
<TextInput
|
||||||
placeholder="masukkan jumlah"
|
label="SMP"
|
||||||
value={stategrafik.create.form.SMA}
|
type="number"
|
||||||
onChange={(val) => {
|
placeholder="Masukkan jumlah"
|
||||||
stategrafik.create.form.SMA = val.currentTarget.value;
|
value={stategrafik.create.form.SMP}
|
||||||
}}
|
onChange={(val) => (stategrafik.create.form.SMP = val.currentTarget.value)}
|
||||||
/>
|
required
|
||||||
<TextInput
|
/>
|
||||||
label="D3"
|
<TextInput
|
||||||
type="number"
|
label="SMA"
|
||||||
placeholder="masukkan jumlah"
|
type="number"
|
||||||
value={stategrafik.create.form.D3}
|
placeholder="Masukkan jumlah"
|
||||||
onChange={(val) => {
|
value={stategrafik.create.form.SMA}
|
||||||
stategrafik.create.form.D3 = val.currentTarget.value;
|
onChange={(val) => (stategrafik.create.form.SMA = val.currentTarget.value)}
|
||||||
}}
|
required
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="S1"
|
label="D3"
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="masukkan jumlah"
|
placeholder="Masukkan jumlah"
|
||||||
value={stategrafik.create.form.S1}
|
value={stategrafik.create.form.D3}
|
||||||
onChange={(val) => {
|
onChange={(val) => (stategrafik.create.form.D3 = val.currentTarget.value)}
|
||||||
stategrafik.create.form.S1 = val.currentTarget.value;
|
required
|
||||||
}}
|
/>
|
||||||
/>
|
<TextInput
|
||||||
<Button
|
label="S1"
|
||||||
mt={10}
|
type="number"
|
||||||
bg={colors['blue-button']}
|
placeholder="Masukkan jumlah"
|
||||||
onClick={handleSubmit}
|
value={stategrafik.create.form.S1}
|
||||||
>
|
onChange={(val) => (stategrafik.create.form.S1 = val.currentTarget.value)}
|
||||||
Submit
|
required
|
||||||
</Button>
|
/>
|
||||||
</Stack>
|
|
||||||
</Paper>
|
<Group justify="right" mt="md">
|
||||||
</Box>
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,59 +1,62 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
import { Box, Button, Center, Flex, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Cell, Pie, PieChart } from 'recharts';
|
import { Cell, Pie, PieChart } from 'recharts';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';
|
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';
|
||||||
|
|
||||||
function GrafikBerdasarkanPendidikan() {
|
function GrafikBerdasarkanPendidikan() {
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("");
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Detail Data Pengangguran Berdasarkan Pendidikan'
|
title='Detail Data Pengangguran Berdasarkan Pendidikan'
|
||||||
placeholder='pencarian'
|
placeholder='Cari data pendidikan...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ListGrafikBerdasarkanPendidikan search={search}/>
|
<ListGrafikBerdasarkanPendidikan search={search} />
|
||||||
</Stack>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListGrafikBerdasarkanPendidikan({search}: {search: string}) {
|
function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
||||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan)
|
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan);
|
||||||
const [donutData, setDonutData] = useState<any[]>([]);
|
const [donutData, setDonutData] = useState<any[]>([]);
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
await stategrafik.delete.byId(selectedId)
|
await stategrafik.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
|
stategrafik.findMany.load();
|
||||||
stategrafik.findMany.load()
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = stategrafik.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
stategrafik.findMany.load()
|
load(page, 10, search);
|
||||||
}, []);
|
}, [page, search]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (stategrafik.findMany.data) {
|
if (stategrafik.findMany.data) {
|
||||||
@@ -70,36 +73,37 @@ function ListGrafikBerdasarkanPendidikan({search}: {search: string}) {
|
|||||||
{ name: 'S1', value: S1, color: '#1018A8FF', key: 'S1' },
|
{ name: 'S1', value: S1, color: '#1018A8FF', key: 'S1' },
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}, [stategrafik.findMany.data])
|
}, [stategrafik.findMany.data]);
|
||||||
|
|
||||||
const filteredData = (stategrafik.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
item.SD.toString().toLowerCase().includes(keyword) ||
|
<Stack py={10}>
|
||||||
item.SMP.toString().toLowerCase().includes(keyword) ||
|
<Skeleton height={500} radius="md" />
|
||||||
item.SMA.toString().toLowerCase().includes(keyword) ||
|
</Stack>
|
||||||
item.D3.toString().toLowerCase().includes(keyword) ||
|
|
||||||
item.S1.toString().toLowerCase().includes(keyword)
|
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
if (!stategrafik.findMany.data) {
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Skeleton h={500} />
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Stack gap={"xs"}>
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<Paper bg={colors['white-1']} p={"md"}>
|
{/* Header */}
|
||||||
<JudulList
|
<Flex justify="space-between" align="center" mb="md">
|
||||||
title='List Grafik Pengangguran Berdasarkan Pendidikan'
|
<Title order={4}>List Pengangguran Berdasarkan Usia Kerja</Title>
|
||||||
href='/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan/create'
|
<Tooltip label="Tambah Data" withArrow>
|
||||||
/>
|
<Button
|
||||||
<Table striped withTableBorder withRowBorders>
|
leftSection={<IconPlus size={18} />}
|
||||||
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
onClick={() => router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/create')}
|
||||||
|
>
|
||||||
|
Tambah Baru
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>SD</TableTh>
|
<TableTh>SD</TableTh>
|
||||||
@@ -114,8 +118,10 @@ function ListGrafikBerdasarkanPendidikan({search}: {search: string}) {
|
|||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.length === 0 ? (
|
{filteredData.length === 0 ? (
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={6}>
|
<TableTd colSpan={7}>
|
||||||
<Text ta='center' c='dimmed'>Belum ada data grafik responden</Text>
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">Belum ada data grafik responden</Text>
|
||||||
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
) : (
|
) : (
|
||||||
@@ -127,82 +133,87 @@ function ListGrafikBerdasarkanPendidikan({search}: {search: string}) {
|
|||||||
<TableTd>{item.D3}</TableTd>
|
<TableTd>{item.D3}</TableTd>
|
||||||
<TableTd>{item.S1}</TableTd>
|
<TableTd>{item.S1}</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button color='green' onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan/${item.id}`)}>
|
<Tooltip label="Edit Data" withArrow>
|
||||||
<IconEdit size={20} />
|
<Button color="green" variant="light" onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan/${item.id}`)}>
|
||||||
</Button>
|
<IconEdit size={18} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Tooltip label="Hapus Data" withArrow>
|
||||||
color='red'
|
<Button
|
||||||
disabled={stategrafik.delete.loading}
|
color="red"
|
||||||
onClick={() => {
|
variant="light"
|
||||||
setSelectedId(item.id)
|
disabled={stategrafik.delete.loading}
|
||||||
setModalHapus(true)
|
onClick={() => {
|
||||||
}}>
|
setSelectedId(item.id);
|
||||||
<IconTrash size={20} />
|
setModalHapus(true);
|
||||||
</Button>
|
}}>
|
||||||
|
<IconTrash size={18} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
|
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
{/* Chart */}
|
<Center>
|
||||||
<Box>
|
<Pagination
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
value={page}
|
||||||
<Stack>
|
onChange={(newPage) => {
|
||||||
<Title pb={10} order={3}>Grafik Pengangguran Berdasarkan Pendidikan</Title>
|
load(newPage, 10);
|
||||||
{mounted && donutData.length > 0 ? (<Box style={{ width: '100%', height: 'auto', minHeight: 200 }}>
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
<PieChart
|
}}
|
||||||
width={800} height={300}
|
total={totalPages}
|
||||||
data={donutData}
|
mt="md"
|
||||||
>
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
|
||||||
|
{/* Chart */}
|
||||||
|
<Box mt="md">
|
||||||
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
|
<Stack>
|
||||||
|
<Title order={3} pb={10}>Grafik Pengangguran Berdasarkan Pendidikan</Title>
|
||||||
|
{mounted && donutData.length > 0 ? (
|
||||||
|
<Box style={{ width: '100%', minHeight: 250 }}>
|
||||||
|
<PieChart width={800} height={300} data={donutData}>
|
||||||
<Pie
|
<Pie
|
||||||
dataKey="value"
|
dataKey="value"
|
||||||
nameKey="name"
|
nameKey="name"
|
||||||
data={donutData}
|
data={donutData}
|
||||||
cx={500}
|
cx={400}
|
||||||
cy={150}
|
cy={150}
|
||||||
innerRadius={60}
|
innerRadius={60}
|
||||||
outerRadius={115}
|
outerRadius={115}
|
||||||
label={true}
|
label
|
||||||
>
|
>
|
||||||
{donutData.map((entry, index) => (
|
{donutData.map((entry, index) => (
|
||||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||||
))}
|
))}
|
||||||
</Pie>
|
</Pie>
|
||||||
</PieChart>
|
</PieChart>
|
||||||
<Flex gap={"md"} align={"center"}>
|
<Stack gap="xs" mt="sm">
|
||||||
<Box bg={colors['blue-button']} w={20} h={20} />
|
{donutData.map((entry) => (
|
||||||
<Text>SD : {donutData.find((entry) => entry.name === 'SD')?.value}</Text>
|
<Flex key={entry.key} gap="sm" align="center">
|
||||||
</Flex>
|
<Box w={20} h={20} bg={entry.color} />
|
||||||
<Flex gap={"md"} align={"center"}>
|
<Text>{entry.name} : {entry.value}</Text>
|
||||||
<Box bg={'#10A85AFF'} w={20} h={20} />
|
</Flex>
|
||||||
<Text>SMP : {donutData.find((entry) => entry.name === 'SMP')?.value}</Text>
|
))}
|
||||||
</Flex>
|
</Stack>
|
||||||
<Flex gap={"md"} align={"center"}>
|
|
||||||
<Box bg={'#C07B13FF'} w={20} h={20} />
|
|
||||||
<Text>SMA : {donutData.find((entry) => entry.name === 'SMA')?.value}</Text>
|
|
||||||
</Flex>
|
|
||||||
<Flex gap={"md"} align={"center"}>
|
|
||||||
<Box bg={'#1094A8FF'} w={20} h={20} />
|
|
||||||
<Text>D3 : {donutData.find((entry) => entry.name === 'D3')?.value}</Text>
|
|
||||||
</Flex>
|
|
||||||
<Flex gap={"md"} align={"center"}>
|
|
||||||
<Box bg={'#A83610FF'} w={20} h={20} />
|
|
||||||
<Text>S1 : {donutData.find((entry) => entry.name === 'S1')?.value}</Text>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
<Text color="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
|
|||||||
@@ -1,98 +1,130 @@
|
|||||||
'use client'
|
|
||||||
import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur';
|
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
'use client';
|
||||||
|
import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Paper, Stack, TextInput, Title, Group, Tooltip } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
function EditGrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
function EditGrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const params = useParams() as { id: string }
|
const params = useParams() as { id: string };
|
||||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur)
|
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur);
|
||||||
const id = params.id
|
const id = params.id;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(id){
|
if (id) {
|
||||||
stategrafik.findUnique.load(id).then(() => {
|
stategrafik.findUnique.load(id).then(() => {
|
||||||
const data = stategrafik.findUnique.data
|
const data = stategrafik.findUnique.data;
|
||||||
if(data){
|
if (data) {
|
||||||
stategrafik.update.form = {
|
stategrafik.update.form = {
|
||||||
usia18_25: data.usia18_25 || '',
|
usia18_25: data.usia18_25 || '',
|
||||||
usia26_35: data.usia26_35 || '',
|
usia26_35: data.usia26_35 || '',
|
||||||
usia36_45: data.usia36_45 || '',
|
usia36_45: data.usia36_45 || '',
|
||||||
usia46_keatas: data.usia46_keatas || '',
|
usia46_keatas: data.usia46_keatas || '',
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}, [id])
|
}, [id]);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
stategrafik.update.id = id;
|
try {
|
||||||
await stategrafik.update.submit();
|
stategrafik.update.id = id;
|
||||||
router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia')
|
await stategrafik.update.submit();
|
||||||
}
|
toast.success('Data grafik berhasil diperbarui!');
|
||||||
|
router.push(
|
||||||
|
'/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia'
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
toast.error('Terjadi kesalahan saat memperbarui data grafik');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
<IconArrowBack size={20} />
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Button>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Grafik Pengangguran Berdasarkan Usia Kerja
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
label="Usia 18 - 25"
|
||||||
|
type="number"
|
||||||
|
placeholder="Masukkan jumlah"
|
||||||
|
value={stategrafik.update.form.usia18_25}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafik.update.form.usia18_25 = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Usia 26 - 35"
|
||||||
|
type="number"
|
||||||
|
placeholder="Masukkan jumlah"
|
||||||
|
value={stategrafik.update.form.usia26_35}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafik.update.form.usia26_35 = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Usia 36 - 45"
|
||||||
|
type="number"
|
||||||
|
placeholder="Masukkan jumlah"
|
||||||
|
value={stategrafik.update.form.usia36_45}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafik.update.form.usia36_45 = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Usia 46 +"
|
||||||
|
type="number"
|
||||||
|
placeholder="Masukkan jumlah"
|
||||||
|
value={stategrafik.update.form.usia46_keatas}
|
||||||
|
onChange={(val) => {
|
||||||
|
stategrafik.update.form.usia46_keatas = val.currentTarget.value;
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
|
||||||
<Stack>
|
|
||||||
<Title order={3}>Edit Grafik Pengangguran Berdasarkan Usia Kerja</Title>
|
|
||||||
<TextInput
|
|
||||||
label="Usia 18 - 25"
|
|
||||||
type='number'
|
|
||||||
placeholder="masukkan jumlah"
|
|
||||||
value={stategrafik.update.form.usia18_25}
|
|
||||||
onChange={(val) => {
|
|
||||||
stategrafik.update.form.usia18_25 = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Usia 26 - 35"
|
|
||||||
type="number"
|
|
||||||
placeholder="masukkan jumlah"
|
|
||||||
value={stategrafik.update.form.usia26_35}
|
|
||||||
onChange={(val) => {
|
|
||||||
stategrafik.update.form.usia26_35 = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Usia 36 - 45"
|
|
||||||
type="number"
|
|
||||||
placeholder="masukkan jumlah"
|
|
||||||
value={stategrafik.update.form.usia36_45}
|
|
||||||
onChange={(val) => {
|
|
||||||
stategrafik.update.form.usia36_45 = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Usia 46 +"
|
|
||||||
type="number"
|
|
||||||
placeholder="masukkan jumlah"
|
|
||||||
value={stategrafik.update.form.usia46_keatas}
|
|
||||||
onChange={(val) => {
|
|
||||||
stategrafik.update.form.usia46_keatas = val.currentTarget.value;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
mt={10}
|
|
||||||
bg={colors['blue-button']}
|
|
||||||
onClick={handleSubmit}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,29 @@
|
|||||||
'use client'
|
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import React from 'react';
|
'use client';
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import grafikBerdasarkanJenisKelamin from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import colors from '@/con/colors';
|
|
||||||
import { Box, Button, Paper, Stack, Title, TextInput } from '@mantine/core';
|
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
|
||||||
import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur';
|
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur';
|
||||||
|
import colors from '@/con/colors';
|
||||||
|
import { Box, Button, Paper, Stack, Title, TextInput, Group, Tooltip } from '@mantine/core';
|
||||||
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
|
|
||||||
function CreateGrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
function CreateGrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur)
|
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur);
|
||||||
const [donutData, setDonutData] = useState<any[]>([]);
|
const [donutData, setDonutData] = useState<any[]>([]);
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
stategrafik.create.form = {
|
stategrafik.create.form = {
|
||||||
...stategrafik.create.form,
|
...stategrafik.create.form,
|
||||||
usia18_25: "",
|
usia18_25: '',
|
||||||
usia26_35: "",
|
usia26_35: '',
|
||||||
usia36_45: "",
|
usia36_45: '',
|
||||||
usia46_keatas: "",
|
usia46_keatas: '',
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
const id = await stategrafik.create.create();
|
const id = await stategrafik.create.create();
|
||||||
@@ -37,64 +35,84 @@ function CreateGrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push("/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia")
|
router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Group mb="md">
|
||||||
<IconArrowBack size={20} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Box>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
</Button>
|
||||||
<Stack>
|
</Tooltip>
|
||||||
<Title order={3}>Create Grafik Pengangguran Berdasarkan Usia Kerja</Title>
|
<Title order={4} ml="sm" c="dark">
|
||||||
<TextInput
|
Tambah Data Pengangguran Berdasarkan Usia
|
||||||
label="Usia 18 - 25"
|
</Title>
|
||||||
type='number'
|
</Group>
|
||||||
placeholder="masukkan jumlah"
|
|
||||||
value={stategrafik.create.form.usia18_25}
|
{/* Form Paper */}
|
||||||
onChange={(val) => {
|
<Paper
|
||||||
stategrafik.create.form.usia18_25 = val.currentTarget.value;
|
w={{ base: '100%', md: '50%' }}
|
||||||
}}
|
bg={colors['white-1']}
|
||||||
/>
|
p="lg"
|
||||||
<TextInput
|
radius="md"
|
||||||
label="Usia 26 - 35"
|
shadow="sm"
|
||||||
type="number"
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
placeholder="masukkan jumlah"
|
>
|
||||||
value={stategrafik.create.form.usia26_35}
|
<Stack gap="md">
|
||||||
onChange={(val) => {
|
<TextInput
|
||||||
stategrafik.create.form.usia26_35 = val.currentTarget.value;
|
label="Usia 18 - 25"
|
||||||
}}
|
type="number"
|
||||||
/>
|
placeholder="Masukkan jumlah"
|
||||||
<TextInput
|
value={stategrafik.create.form.usia18_25}
|
||||||
label="Usia 36 - 45"
|
onChange={(val) => (stategrafik.create.form.usia18_25 = val.currentTarget.value)}
|
||||||
type="number"
|
required
|
||||||
placeholder="masukkan jumlah"
|
/>
|
||||||
value={stategrafik.create.form.usia36_45}
|
<TextInput
|
||||||
onChange={(val) => {
|
label="Usia 26 - 35"
|
||||||
stategrafik.create.form.usia36_45 = val.currentTarget.value;
|
type="number"
|
||||||
}}
|
placeholder="Masukkan jumlah"
|
||||||
/>
|
value={stategrafik.create.form.usia26_35}
|
||||||
<TextInput
|
onChange={(val) => (stategrafik.create.form.usia26_35 = val.currentTarget.value)}
|
||||||
label="Usia 46 +"
|
required
|
||||||
type="number"
|
/>
|
||||||
placeholder="masukkan jumlah"
|
<TextInput
|
||||||
value={stategrafik.create.form.usia46_keatas}
|
label="Usia 36 - 45"
|
||||||
onChange={(val) => {
|
type="number"
|
||||||
stategrafik.create.form.usia46_keatas = val.currentTarget.value;
|
placeholder="Masukkan jumlah"
|
||||||
}}
|
value={stategrafik.create.form.usia36_45}
|
||||||
/>
|
onChange={(val) => (stategrafik.create.form.usia36_45 = val.currentTarget.value)}
|
||||||
<Button
|
required
|
||||||
mt={10}
|
/>
|
||||||
bg={colors['blue-button']}
|
<TextInput
|
||||||
onClick={handleSubmit}
|
label="Usia 46 +"
|
||||||
>
|
type="number"
|
||||||
Submit
|
placeholder="Masukkan jumlah"
|
||||||
</Button>
|
value={stategrafik.create.form.usia46_keatas}
|
||||||
</Stack>
|
onChange={(val) => (stategrafik.create.form.usia46_keatas = val.currentTarget.value)}
|
||||||
</Paper>
|
required
|
||||||
</Box>
|
/>
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,60 +1,62 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
import { Box, Button, Center, Flex, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Cell, Pie, PieChart } from 'recharts';
|
import { Cell, Pie, PieChart } from 'recharts';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import JudulList from '../../../_com/judulList';
|
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';
|
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';
|
||||||
|
|
||||||
|
|
||||||
function GrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
function GrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState('');
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Detail Data Pengangguran'
|
title='Detail Data Pengangguran'
|
||||||
placeholder='pencarian'
|
placeholder='Cari usia...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ListGrafikBerdasarkanUsiaKerjaYangMenganggur search={search} />
|
<ListGrafikBerdasarkanUsiaKerjaYangMenganggur search={search} />
|
||||||
</Stack>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({search}: {search: string}) {
|
function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: string }) {
|
||||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur)
|
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur);
|
||||||
const [donutData, setDonutData] = useState<any[]>([]);
|
const [donutData, setDonutData] = useState<any[]>([]);
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
await stategrafik.delete.byId(selectedId)
|
await stategrafik.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
|
stategrafik.findMany.load();
|
||||||
stategrafik.findMany.load()
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = stategrafik.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
stategrafik.findMany.load()
|
load(page, 10, search)
|
||||||
}, []);
|
}, [page, search]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (stategrafik.findMany.data) {
|
if (stategrafik.findMany.data) {
|
||||||
@@ -69,139 +71,149 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({search}: {search: string}
|
|||||||
{ name: 'usia46_keatas', value: totalUsia46_keatas, color: '#1094A8FF', key: 'usia46_keatas' },
|
{ name: 'usia46_keatas', value: totalUsia46_keatas, color: '#1094A8FF', key: 'usia46_keatas' },
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}, [stategrafik.findMany.data])
|
}, [stategrafik.findMany.data]);
|
||||||
|
|
||||||
const filteredData = (stategrafik.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
item.usia18_25.toString().toLowerCase().includes(keyword) ||
|
<Stack py={10}>
|
||||||
item.usia26_35.toString().toLowerCase().includes(keyword) ||
|
<Skeleton height={500} radius="md" />
|
||||||
item.usia36_45.toString().toLowerCase().includes(keyword) ||
|
</Stack>
|
||||||
item.usia46_keatas.toString().toLowerCase().includes(keyword)
|
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
if (!stategrafik.findMany.data) {
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Skeleton h={500} />
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Stack gap={"xs"}>
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<Paper bg={colors['white-1']} p={"md"}>
|
<Stack>
|
||||||
<JudulList
|
{/* Header */}
|
||||||
title='List Pengangguran Berdasarkan Usia Kerja'
|
<Flex justify="space-between" align="center" mb="md">
|
||||||
href='/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/create'
|
<Title order={4}>List Pengangguran Berdasarkan Usia Kerja</Title>
|
||||||
/>
|
<Tooltip label="Tambah Data" withArrow>
|
||||||
<Table striped withTableBorder withRowBorders>
|
<Button
|
||||||
<TableThead>
|
leftSection={<IconPlus size={18} />}
|
||||||
<TableTr>
|
color="blue"
|
||||||
<TableTh>Usia 18-25</TableTh>
|
variant="light"
|
||||||
<TableTh>Usia 26-35</TableTh>
|
onClick={() => router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/create')}
|
||||||
<TableTh>Usia 36-45</TableTh>
|
>
|
||||||
<TableTh>Usia 46 +</TableTh>
|
Tambah Baru
|
||||||
<TableTh>Edit</TableTh>
|
</Button>
|
||||||
<TableTh>Delete</TableTh>
|
</Tooltip>
|
||||||
</TableTr>
|
</Flex>
|
||||||
</TableThead>
|
|
||||||
<TableTbody>
|
{/* Table */}
|
||||||
{filteredData.length === 0 ? (
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={6}>
|
<TableTh>Usia 18-25</TableTh>
|
||||||
<Text ta='center' c='dimmed'>Belum ada data grafik responden</Text>
|
<TableTh>Usia 26-35</TableTh>
|
||||||
</TableTd>
|
<TableTh>Usia 36-45</TableTh>
|
||||||
|
<TableTh>Usia 46 +</TableTh>
|
||||||
|
<TableTh>Edit</TableTh>
|
||||||
|
<TableTh>Delete</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
) : (
|
</TableThead>
|
||||||
filteredData.map((item) => (
|
<TableTbody>
|
||||||
<TableTr key={item.id}>
|
{filteredData.length > 0 ? (
|
||||||
<TableTd>{item.usia18_25}</TableTd>
|
filteredData.map(item => (
|
||||||
<TableTd>{item.usia26_35}</TableTd>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.usia36_45}</TableTd>
|
<TableTd>{item.usia18_25}</TableTd>
|
||||||
<TableTd>{item.usia46_keatas}</TableTd>
|
<TableTd>{item.usia26_35}</TableTd>
|
||||||
<TableTd>
|
<TableTd>{item.usia36_45}</TableTd>
|
||||||
<Button color='green' onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/${item.id}`)}>
|
<TableTd>{item.usia46_keatas}</TableTd>
|
||||||
<IconEdit size={20} />
|
<TableTd>
|
||||||
</Button>
|
<Button color="green" onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/${item.id}`)}>
|
||||||
</TableTd>
|
<IconEdit size={20} />
|
||||||
<TableTd>
|
</Button>
|
||||||
<Button
|
</TableTd>
|
||||||
color='red'
|
<TableTd>
|
||||||
disabled={stategrafik.delete.loading}
|
<Button color="red" disabled={stategrafik.delete.loading} onClick={() => { setSelectedId(item.id); setModalHapus(true); }}>
|
||||||
onClick={() => {
|
<IconTrash size={20} />
|
||||||
setSelectedId(item.id)
|
</Button>
|
||||||
setModalHapus(true)
|
</TableTd>
|
||||||
}}>
|
</TableTr>
|
||||||
<IconTrash size={20} />
|
))
|
||||||
</Button>
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={6}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">Belum ada data grafik responden</Text>
|
||||||
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))
|
)}
|
||||||
)}
|
</TableTbody>
|
||||||
</TableTbody>
|
</Table>
|
||||||
|
</Box>
|
||||||
|
|
||||||
</Table>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Chart */}
|
<Center>
|
||||||
<Box>
|
<Pagination
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
value={page}
|
||||||
<Stack>
|
onChange={(newPage) => {
|
||||||
<Title pb={10} order={3}>Grafik Pengangguran Berdasarkan Usia Kerja</Title>
|
load(newPage, 10);
|
||||||
{mounted && donutData.length > 0 ? (<Box style={{ width: '100%', height: 'auto', minHeight: 200 }}>
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
<PieChart
|
}}
|
||||||
width={800} height={300}
|
total={totalPages}
|
||||||
data={donutData}
|
mt="md"
|
||||||
>
|
mb="md"
|
||||||
<Pie
|
color="blue"
|
||||||
dataKey="value"
|
radius="md"
|
||||||
nameKey="name"
|
/>
|
||||||
data={donutData}
|
</Center>
|
||||||
cx={500}
|
|
||||||
cy={150}
|
{/* Chart */}
|
||||||
innerRadius={60}
|
<Paper bg={colors['white-1']} p="md" mt="lg" withBorder radius="md">
|
||||||
outerRadius={115}
|
<Stack>
|
||||||
label={true}
|
<Title order={3} pb={10}>Grafik Pengangguran Berdasarkan Usia Kerja</Title>
|
||||||
>
|
{mounted && donutData.length > 0 ? (
|
||||||
{donutData.map((entry, index) => (
|
<Box style={{ width: '100%', height: 'auto', minHeight: 200 }}>
|
||||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
<PieChart width={800} height={300} data={donutData}>
|
||||||
))}
|
<Pie dataKey="value" nameKey="name" data={donutData} cx={400} cy={150} innerRadius={60} outerRadius={115} label>
|
||||||
</Pie>
|
{donutData.map((entry, index) => (
|
||||||
</PieChart>
|
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||||
|
))}
|
||||||
|
</Pie>
|
||||||
|
</PieChart>
|
||||||
|
<Stack mt="sm" gap="xs">
|
||||||
<Flex gap={"md"} align={"center"}>
|
<Flex gap={"md"} align={"center"}>
|
||||||
<Box bg={colors['blue-button']} w={20} h={20} />
|
<Box bg={colors['blue-button']} w={20} h={20} />
|
||||||
<Text>Usia 18-25 : {donutData.find((entry) => entry.name === 'usia18_25')?.value}</Text>
|
<Text>Usia 18-25 : {donutData.find((entry) => entry.name === 'usia18_25')?.value}</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex gap={"md"} align={"center"}>
|
<Flex gap={"md"} align={"center"}>
|
||||||
<Box bg={'#10A85AFF'} w={20} h={20} />
|
<Box bg={'#10A85AFF'} w={20} h={20} />
|
||||||
<Text>Usia 26-35 : {donutData.find((entry) => entry.name === 'usia26_35')?.value}</Text>
|
<Text>Usia 26-35 : {donutData.find((entry) => entry.name === 'usia26_35')?.value}
|
||||||
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex gap={"md"} align={"center"}>
|
<Flex gap={"md"} align={"center"}>
|
||||||
<Box bg={'#C07B13FF'} w={20} h={20} />
|
<Box bg={'#C07B13FF'} w={20} h={20} />
|
||||||
<Text>Usia 36-45 : {donutData.find((entry) => entry.name === 'usia36_45')?.value}</Text>
|
<Text>Usia 36-45 : {donutData.find((entry) => entry.name === 'usia36_45')?.value}
|
||||||
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex gap={"md"} align={"center"}>
|
<Flex gap={"md"} align={"center"}>
|
||||||
<Box bg={'#1094A8FF'} w={20} h={20} />
|
<Box bg={'#1094A8FF'} w={20} h={20} />
|
||||||
<Text>Usia 46 + : {donutData.find((entry) => entry.name === 'usia46_keatas')?.value}</Text>
|
<Text>Usia 46 + : {donutData.find((entry) => entry.name === 'usia46_keatas')?.value}
|
||||||
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Stack>
|
||||||
) : (
|
</Box>
|
||||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
) : (
|
||||||
)}
|
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||||
</Stack>
|
)}
|
||||||
</Paper>
|
</Stack>
|
||||||
</Box>
|
</Paper>
|
||||||
</Stack>
|
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleDelete}
|
onConfirm={handleDelete}
|
||||||
text='Apakah anda yakin ingin menghapus grafik pengangguran berdasarkan usia kerja ini?'
|
text="Apakah anda yakin ingin menghapus grafik pengangguran berdasarkan usia kerja ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client';
|
||||||
import jumlahPengangguranState from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran';
|
import jumlahPengangguranState from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title, Select, NumberInput } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -10,52 +10,54 @@ import { toast } from 'react-toastify';
|
|||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function EditDetailDataPengangguran() {
|
function EditDetailDataPengangguran() {
|
||||||
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran)
|
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
month: stateDetail.update.form.month,
|
month: '',
|
||||||
year: stateDetail.update.form.year,
|
year: new Date().getFullYear(),
|
||||||
totalUnemployment: stateDetail.update.form.totalUnemployment,
|
educatedUnemployment: 0,
|
||||||
educatedUnemployment: stateDetail.update.form.educatedUnemployment,
|
uneducatedUnemployment: 0,
|
||||||
uneducatedUnemployment: stateDetail.update.form.uneducatedUnemployment,
|
totalUnemployment: 0,
|
||||||
percentageChange: stateDetail.update.form.percentageChange || 0, // Ensure it's always a number
|
percentageChange: 0,
|
||||||
})
|
});
|
||||||
|
|
||||||
|
// Hitung total & perubahan otomatis
|
||||||
const calculateTotalAndChange = async () => {
|
const calculateTotalAndChange = async () => {
|
||||||
const total = formData.educatedUnemployment + formData.uneducatedUnemployment;
|
const total = formData.educatedUnemployment + formData.uneducatedUnemployment;
|
||||||
|
|
||||||
// Ambil data bulan sebelumnya
|
|
||||||
const monthOrder = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'];
|
|
||||||
const currentIndex = monthOrder.findIndex(
|
|
||||||
(m) => m.toLowerCase() === formData.month.toLowerCase()
|
|
||||||
);
|
|
||||||
|
|
||||||
let percentageChange = 0;
|
let percentageChange = 0;
|
||||||
if (currentIndex > 0) {
|
const monthOrder = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'];
|
||||||
const prevMonth = monthOrder[currentIndex - 1];
|
const currentMonthIndex = monthOrder.indexOf(formData.month);
|
||||||
const prev = await stateDetail.findByMonthYear.load({
|
|
||||||
month: prevMonth,
|
|
||||||
year: formData.year,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (prev?.totalUnemployment) {
|
if (currentMonthIndex !== -1) {
|
||||||
percentageChange = Number(
|
let prevMonthIndex = currentMonthIndex - 1;
|
||||||
(((total - prev.totalUnemployment) / prev.totalUnemployment) * 100).toFixed(1)
|
let prevYear = formData.year;
|
||||||
);
|
|
||||||
|
if (prevMonthIndex < 0) {
|
||||||
|
prevMonthIndex = 11;
|
||||||
|
prevYear--;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevMonth = monthOrder[prevMonthIndex];
|
||||||
|
const prevData = await stateDetail.findByMonthYear.load({ month: prevMonth, year: prevYear });
|
||||||
|
|
||||||
|
if (prevData && prevData.totalUnemployment > 0) {
|
||||||
|
const change = ((total - prevData.totalUnemployment) / prevData.totalUnemployment) * 100;
|
||||||
|
percentageChange = parseFloat(change.toFixed(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
totalUnemployment: total,
|
|
||||||
percentageChange,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { total, percentageChange };
|
return { total, percentageChange };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateFormData = async (updates: Partial<typeof formData>) => {
|
||||||
|
const newData = { ...formData, ...updates };
|
||||||
|
const { total, percentageChange } = await calculateTotalAndChange();
|
||||||
|
setFormData({ ...newData, totalUnemployment: total, percentageChange });
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadDetail = async () => {
|
const loadDetail = async () => {
|
||||||
const id = params?.id as string;
|
const id = params?.id as string;
|
||||||
@@ -66,8 +68,11 @@ function EditDetailDataPengangguran() {
|
|||||||
const data = stateDetail.findUnique.data;
|
const data = stateDetail.findUnique.data;
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
// Convert year from Date to number
|
|
||||||
const yearValue = data.year instanceof Date ? data.year.getFullYear() : data.year;
|
// Convert year from Date to number if needed
|
||||||
|
const yearValue = data.year && typeof data.year === 'object' && 'getFullYear' in data.year
|
||||||
|
? (data.year as Date).getFullYear()
|
||||||
|
: Number(data.year);
|
||||||
|
|
||||||
// Set the ID for update
|
// Set the ID for update
|
||||||
stateDetail.update.id = id;
|
stateDetail.update.id = id;
|
||||||
@@ -98,86 +103,72 @@ function EditDetailDataPengangguran() {
|
|||||||
loadDetail();
|
loadDetail();
|
||||||
}, [params?.id]);
|
}, [params?.id]);
|
||||||
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
const { total, percentageChange } = await calculateTotalAndChange();
|
const { total, percentageChange } = await calculateTotalAndChange();
|
||||||
try {
|
try {
|
||||||
stateDetail.update.form = {
|
stateDetail.update.form = { ...formData, totalUnemployment: total, percentageChange };
|
||||||
...formData,
|
|
||||||
totalUnemployment: total,
|
|
||||||
percentageChange,
|
|
||||||
};
|
|
||||||
|
|
||||||
const success = await stateDetail.update.submit();
|
const success = await stateDetail.update.submit();
|
||||||
if (success) {
|
if (success) {
|
||||||
toast.success("Detail data pengangguran berhasil diperbarui!");
|
toast.success('Detail data pengangguran berhasil diperbarui!');
|
||||||
router.push("/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran");
|
router.push('/admin/ekonomi/jumlah-pengangguran');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating:", error);
|
console.error('Error updating:', error);
|
||||||
toast.error("Terjadi kesalahan saat memperbarui data");
|
toast.error('Terjadi kesalahan saat memperbarui data');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
<Group mb="md">
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
<Title order={4} ml="sm">
|
||||||
|
Edit Detail Data Pengangguran
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p="lg" radius="md" shadow="sm" style={{ border: '1px solid #e0e0e0' }}>
|
||||||
<Title order={4}>Edit Detail Data Pengangguran</Title>
|
<Stack gap="md">
|
||||||
<Stack gap="xs">
|
<Select
|
||||||
<TextInput
|
|
||||||
label="Bulan"
|
label="Bulan"
|
||||||
|
data={['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des']}
|
||||||
value={formData.month}
|
value={formData.month}
|
||||||
placeholder="Contoh: Jan, Feb, Mar"
|
onChange={(val) => updateFormData({ month: val || '' })}
|
||||||
onChange={(val) => (setFormData({
|
|
||||||
...formData,
|
|
||||||
month: val.currentTarget.value
|
|
||||||
}))}
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<NumberInput
|
||||||
label="Tahun"
|
label="Tahun"
|
||||||
type="number"
|
|
||||||
value={formData.year}
|
value={formData.year}
|
||||||
onChange={(val) => (setFormData({
|
onChange={(val) => updateFormData({ year: Number(val) })}
|
||||||
...formData,
|
|
||||||
year: Number(val.currentTarget.value)
|
|
||||||
}))}
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Pengangguran Terdidik"
|
label="Pengangguran Terdidik"
|
||||||
type="number"
|
type="number"
|
||||||
value={formData.educatedUnemployment}
|
value={formData.educatedUnemployment}
|
||||||
onChange={(val) => (setFormData({
|
onChange={(val) => updateFormData({ educatedUnemployment: Number(val.currentTarget.value) || 0 })}
|
||||||
...formData,
|
|
||||||
educatedUnemployment: Number(val.currentTarget.value)
|
|
||||||
}))}
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Pengangguran Tidak Terdidik"
|
label="Pengangguran Tidak Terdidik"
|
||||||
type="number"
|
type="number"
|
||||||
value={formData.uneducatedUnemployment}
|
value={formData.uneducatedUnemployment}
|
||||||
onChange={(val) => (setFormData({
|
onChange={(val) => updateFormData({ uneducatedUnemployment: Number(val.currentTarget.value) || 0 })}
|
||||||
...formData,
|
|
||||||
uneducatedUnemployment: Number(val.currentTarget.value)
|
|
||||||
}))}
|
|
||||||
/>
|
/>
|
||||||
<Text fz="sm" fw={500}>
|
<Text fz="sm" fw={500}>Total Otomatis: {formData.totalUnemployment}</Text>
|
||||||
Total Otomatis: {formData.totalUnemployment}
|
<Text fz="sm" fw={500}>Perubahan Otomatis: {formData.percentageChange !== null ? `${formData.percentageChange}%` : '-'}</Text>
|
||||||
</Text>
|
|
||||||
<Text fz="sm" fw={500}>
|
<Group justify="right">
|
||||||
Perubahan Otomatis:{" "}
|
<Button
|
||||||
{formData.percentageChange !== null
|
onClick={handleSubmit}
|
||||||
? `${formData.percentageChange}%`
|
radius="md"
|
||||||
: '-'}
|
size="md"
|
||||||
</Text>
|
style={{
|
||||||
<Group>
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
<Button bg={colors['blue-button']} mt={10} onClick={handleSubmit}>
|
color: '#fff',
|
||||||
Submit
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -187,3 +178,4 @@ function EditDetailDataPengangguran() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default EditDetailDataPengangguran;
|
export default EditDetailDataPengangguran;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||||
import jumlahPengangguranState from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran';
|
import jumlahPengangguranState from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Flex, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
@@ -11,86 +11,125 @@ import { useProxy } from 'valtio/utils';
|
|||||||
|
|
||||||
function DetailJumlahPengangguran() {
|
function DetailJumlahPengangguran() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran)
|
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
stateDetail.findUnique.load(params?.id as string)
|
stateDetail.findUnique.load(params?.id as string);
|
||||||
}, [params?.id])
|
}, [params?.id]);
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
stateDetail.delete.byId(selectedId)
|
stateDetail.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
router.push("/admin/ekonomi/jumlah-pengangguran")
|
router.push("/admin/ekonomi/jumlah-pengangguran");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!stateDetail.findUnique.data) {
|
if (!stateDetail.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Box>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = stateDetail.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Box mb={10}>
|
{/* Tombol Kembali */}
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
<Button
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
variant="subtle"
|
||||||
</Button>
|
onClick={() => router.back()}
|
||||||
</Box>
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
mb={15}
|
||||||
<Stack>
|
>
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Data Pengangguran</Text>
|
Kembali
|
||||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
</Button>
|
||||||
<Stack gap={"xs"}>
|
|
||||||
|
{/* Paper Detail */}
|
||||||
|
<Paper
|
||||||
|
withBorder
|
||||||
|
w={{ base: "100%", md: "60%" }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
|
Detail Data Pengangguran
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
|
<Stack gap="sm">
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"}>Pengangguran Terdidik</Text>
|
<Text fw="bold">Pengangguran Terdidik</Text>
|
||||||
<Text>{stateDetail.findUnique.data?.educatedUnemployment}</Text>
|
<Text c="dimmed">{data.educatedUnemployment || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"}>Pengangguran Tidak Terdidik</Text>
|
<Text fw="bold">Pengangguran Tidak Terdidik</Text>
|
||||||
<Text>{stateDetail.findUnique.data?.uneducatedUnemployment}</Text>
|
<Text c="dimmed">{data.uneducatedUnemployment || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"}>Perubahan</Text>
|
<Text fw="bold">Perubahan</Text>
|
||||||
<Text>{stateDetail.findUnique.data?.percentageChange}</Text>
|
<Text c="dimmed">
|
||||||
|
{data.percentageChange !== null && data.percentageChange !== undefined
|
||||||
|
? `${data.percentageChange}%`
|
||||||
|
: 'Tidak ada data perubahan'}
|
||||||
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"}>Tahun</Text>
|
<Text fw="bold">Tahun</Text>
|
||||||
<Text>{stateDetail.findUnique.data?.year ? new Date(stateDetail.findUnique.data.year).getFullYear() : ''}</Text>
|
<Text c="dimmed">{data.year || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"}>Bulan</Text>
|
<Text fw="bold">Bulan</Text>
|
||||||
<Text>{stateDetail.findUnique.data?.month}</Text>
|
<Text c="dimmed">{data.month || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"}>Total Pengangguran</Text>
|
<Text fw="bold">Total Pengangguran</Text>
|
||||||
<Text>{stateDetail.findUnique.data?.totalUnemployment}</Text>
|
<Text c="dimmed">{data.totalUnemployment || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
|
||||||
<Flex gap={"xs"}>
|
{/* Tombol Edit & Hapus */}
|
||||||
|
<Flex gap="sm">
|
||||||
|
<Tooltip label="Hapus Data Pengangguran" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (stateDetail.findUnique.data) {
|
setSelectedId(data.id);
|
||||||
setSelectedId(stateDetail.findUnique.data.id);
|
|
||||||
setModalHapus(true);
|
setModalHapus(true);
|
||||||
}
|
}}
|
||||||
}}
|
color="red"
|
||||||
disabled={stateDetail.delete.loading || !stateDetail.findUnique.data}
|
variant="light"
|
||||||
color={"red"}>
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
<IconX size={20} />
|
<IconX size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/${stateDetail.findUnique.data?.id}/edit`)} color="green">
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip label="Edit Data Pengangguran" withArrow position="top">
|
||||||
|
<Button
|
||||||
|
onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/${data.id}/edit`)}
|
||||||
|
color="green"
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
<IconEdit size={20} />
|
<IconEdit size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Tooltip>
|
||||||
</Box>
|
</Flex>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -101,7 +140,7 @@ function DetailJumlahPengangguran() {
|
|||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleHapus}
|
onConfirm={handleHapus}
|
||||||
text="Apakah anda yakin ingin menghapus data ini?"
|
text="Apakah Anda yakin ingin menghapus data ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,27 +3,43 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import jumlahPengangguranState from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran';
|
import jumlahPengangguranState from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
NumberInput,
|
||||||
|
Title,
|
||||||
|
Select,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
function CreateJumlahPengangguran() {
|
function CreateJumlahPengangguran() {
|
||||||
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran)
|
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran);
|
||||||
const [chartData, setChartData] = useState<any[]>([]);
|
const [chartData, setChartData] = useState<any[]>([]);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const monthOptions = [
|
||||||
|
'Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun',
|
||||||
|
'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'
|
||||||
|
];
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
stateDetail.create.form = {
|
stateDetail.create.form = {
|
||||||
month: "",
|
month: monthOptions[new Date().getMonth()], // default bulan sekarang
|
||||||
year: 0,
|
year: new Date().getFullYear(), // default tahun sekarang
|
||||||
totalUnemployment: 0,
|
totalUnemployment: 0,
|
||||||
educatedUnemployment: 0,
|
educatedUnemployment: 0,
|
||||||
uneducatedUnemployment: 0,
|
uneducatedUnemployment: 0,
|
||||||
percentageChange: 0,
|
percentageChange: 0,
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const calculateTotalAndChange = async () => {
|
const calculateTotalAndChange = async () => {
|
||||||
const total =
|
const total =
|
||||||
@@ -32,11 +48,8 @@ function CreateJumlahPengangguran() {
|
|||||||
|
|
||||||
stateDetail.create.form.totalUnemployment = total;
|
stateDetail.create.form.totalUnemployment = total;
|
||||||
|
|
||||||
// Ambil data bulan sebelumnya
|
// hitung perubahan dibanding bulan sebelumnya
|
||||||
const monthOrder = [
|
const monthOrder = monthOptions;
|
||||||
'Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun',
|
|
||||||
'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'
|
|
||||||
];
|
|
||||||
const currentIndex = monthOrder.findIndex(
|
const currentIndex = monthOrder.findIndex(
|
||||||
(m) => m.toLowerCase() === stateDetail.create.form.month.toLowerCase()
|
(m) => m.toLowerCase() === stateDetail.create.form.month.toLowerCase()
|
||||||
);
|
);
|
||||||
@@ -68,66 +81,117 @@ function CreateJumlahPengangguran() {
|
|||||||
setChartData([stateDetail.findUnique.data]);
|
setChartData([stateDetail.findUnique.data]);
|
||||||
}
|
}
|
||||||
resetForm();
|
resetForm();
|
||||||
router.push('/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran');
|
router.push('/admin/ekonomi/jumlah-pengangguran');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header */}
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25}/>
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button
|
||||||
</Box>
|
variant="subtle"
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
onClick={() => router.back()}
|
||||||
<Title order={4}>Tambah Detail Data Pengangguran</Title>
|
p="xs"
|
||||||
<Stack gap="xs">
|
radius="md"
|
||||||
<TextInput
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Data Pengangguran
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{/* Form Card */}
|
||||||
|
<Paper
|
||||||
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Select
|
||||||
label="Bulan"
|
label="Bulan"
|
||||||
|
placeholder="Pilih bulan"
|
||||||
|
data={monthOptions}
|
||||||
value={stateDetail.create.form.month}
|
value={stateDetail.create.form.month}
|
||||||
placeholder="Contoh: Jan, Feb, Mar"
|
onChange={(value) => {
|
||||||
onChange={(e) => (stateDetail.create.form.month = e.currentTarget.value)}
|
stateDetail.create.form.month = value || '';
|
||||||
|
calculateTotalAndChange();
|
||||||
|
}}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<TextInput
|
|
||||||
|
<NumberInput
|
||||||
label="Tahun"
|
label="Tahun"
|
||||||
type="date"
|
|
||||||
value={stateDetail.create.form.year}
|
value={stateDetail.create.form.year}
|
||||||
onChange={(e) =>
|
onChange={(value) => {
|
||||||
(stateDetail.create.form.year = Number(e.currentTarget.value))
|
stateDetail.create.form.year = Number(value) || new Date().getFullYear();
|
||||||
}
|
calculateTotalAndChange();
|
||||||
|
}}
|
||||||
|
min={2000}
|
||||||
|
max={2100}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<TextInput
|
|
||||||
|
<NumberInput
|
||||||
label="Pengangguran Terdidik"
|
label="Pengangguran Terdidik"
|
||||||
type="number"
|
|
||||||
value={stateDetail.create.form.educatedUnemployment}
|
value={stateDetail.create.form.educatedUnemployment}
|
||||||
onChange={(e) => {
|
onChange={(value) => {
|
||||||
stateDetail.create.form.educatedUnemployment = Number(
|
stateDetail.create.form.educatedUnemployment = Number(value) || 0;
|
||||||
e.currentTarget.value,
|
calculateTotalAndChange();
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
|
min={0}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<TextInput
|
|
||||||
|
<NumberInput
|
||||||
label="Pengangguran Tidak Terdidik"
|
label="Pengangguran Tidak Terdidik"
|
||||||
type="number"
|
|
||||||
value={stateDetail.create.form.uneducatedUnemployment}
|
value={stateDetail.create.form.uneducatedUnemployment}
|
||||||
onChange={(e) => {
|
onChange={(value) => {
|
||||||
stateDetail.create.form.uneducatedUnemployment = Number(
|
stateDetail.create.form.uneducatedUnemployment = Number(value) || 0;
|
||||||
e.currentTarget.value,
|
calculateTotalAndChange();
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
|
min={0}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<Text fz="sm" fw={500}>
|
|
||||||
Total Otomatis: {stateDetail.create.form.totalUnemployment}
|
<Box>
|
||||||
</Text>
|
<Text fz="sm" fw={500} mb={4}>
|
||||||
<Text fz="sm" fw={500}>
|
Total Otomatis:
|
||||||
Perubahan Otomatis:{" "}
|
</Text>
|
||||||
{stateDetail.create.form.percentageChange !== null
|
<Text fz="sm" c="dimmed">
|
||||||
? `${stateDetail.create.form.percentageChange}%`
|
{stateDetail.create.form.totalUnemployment.toLocaleString()}
|
||||||
: '-'}
|
</Text>
|
||||||
</Text>
|
</Box>
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} mt={10} onClick={handleSubmit}>
|
<Box>
|
||||||
Submit
|
<Text fz="sm" fw={500} mb={4}>
|
||||||
|
Perubahan Otomatis:
|
||||||
|
</Text>
|
||||||
|
<Text fz="sm" c="dimmed">
|
||||||
|
{stateDetail.create.form.percentageChange.toFixed(1)}%
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Action Button */}
|
||||||
|
<Group justify="right" mt="md">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
disabled={!stateDetail.create.form.month || !stateDetail.create.form.year}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,150 +1,181 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
import {
|
||||||
|
Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack,
|
||||||
|
Table, TableTbody, TableTd, TableTh, TableThead, TableTr,
|
||||||
|
Text, Title, Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
import { BarChart } from '@mantine/charts';
|
import { BarChart } from '@mantine/charts';
|
||||||
|
|
||||||
import HeaderSearch from '../../_com/header';
|
import HeaderSearch from '../../_com/header';
|
||||||
import JudulList from '../../_com/judulList';
|
|
||||||
import jumlahPengangguranState from '../../_state/ekonomi/jumlah-pengangguran';
|
import jumlahPengangguranState from '../../_state/ekonomi/jumlah-pengangguran';
|
||||||
|
|
||||||
function DetailDataPengangguran() {
|
function DetailDataPengangguran() {
|
||||||
const [search, setSearch] = useState("")
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Stack gap={"xs"}>
|
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Detail Data Pengangguran'
|
title='Detail Data Pengangguran'
|
||||||
placeholder='pencarian'
|
placeholder='Cari bulan atau tahun...'
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<ListDetailDataPengangguran search={search} />
|
<ListDetailDataPengangguran search={search} />
|
||||||
</Stack>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListDetailDataPengangguran({search}: {search: string}) {
|
function ListDetailDataPengangguran({ search }: { search: string }) {
|
||||||
|
const [chartData, setChartData] = useState<any[]>([]);
|
||||||
type DetailDataPengangguran = {
|
const [mounted, setMounted] = useState(false);
|
||||||
id: string;
|
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran);
|
||||||
month: string;
|
|
||||||
year: number;
|
|
||||||
educatedUnemployment: number;
|
|
||||||
uneducatedUnemployment: number;
|
|
||||||
percentageChange: number;
|
|
||||||
totalUnemployment: number;
|
|
||||||
}
|
|
||||||
const [chartData, setChartData] = useState<DetailDataPengangguran[]>([]);
|
|
||||||
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
|
|
||||||
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran)
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
loading,
|
||||||
|
load,
|
||||||
|
} = stateDetail.findMany;
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
setMounted(true)
|
setMounted(true);
|
||||||
stateDetail.findMany.load()
|
load(page, 10, search);
|
||||||
}, [])
|
}, [page, search]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true);
|
if (data) {
|
||||||
if (stateDetail.findMany.data) {
|
setChartData(
|
||||||
setChartData(stateDetail.findMany.data.map((item) => ({
|
data.map((item) => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
month: item.month,
|
month: item.month,
|
||||||
year: item.year instanceof Date ? item.year.getFullYear() : Number(item.year),
|
year: Number(item.year),
|
||||||
educatedUnemployment: Number(item.educatedUnemployment),
|
educatedUnemployment: Number(item.educatedUnemployment),
|
||||||
uneducatedUnemployment: Number(item.uneducatedUnemployment),
|
uneducatedUnemployment: Number(item.uneducatedUnemployment),
|
||||||
percentageChange: Number(item.percentageChange),
|
percentageChange: Number(item.percentageChange),
|
||||||
totalUnemployment: Number(item.totalUnemployment),
|
totalUnemployment: Number(item.totalUnemployment),
|
||||||
})));
|
}))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [stateDetail.findMany.data]);
|
}, [data]);
|
||||||
|
|
||||||
const filteredData = (stateDetail.findMany.data || []).filter(item => {
|
const filteredData = data || []
|
||||||
const keyword = search.toLowerCase();
|
|
||||||
|
// Loading state
|
||||||
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
item.month.toLowerCase().includes(keyword) ||
|
<Stack py="md">
|
||||||
item.year.toString().toLowerCase().includes(keyword)
|
<Skeleton h={500} radius="md" />
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
if (!stateDetail.findMany.data) {
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Skeleton h={500} />
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Stack py="md" gap="lg">
|
||||||
<Stack gap={"md"}>
|
{/* Table Section */}
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Detail Data Pengangguran'
|
<Title order={4}>Daftar Detail Data Pengangguran</Title>
|
||||||
href='/admin/ekonomi/jumlah-pengangguran/create'
|
<Tooltip label="Tambah Data Baru" withArrow>
|
||||||
/>
|
<Button
|
||||||
<Table striped withTableBorder withRowBorders>
|
leftSection={<IconPlus size={18} />}
|
||||||
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
onClick={() => router.push('/admin/ekonomi/jumlah-pengangguran/create')}
|
||||||
|
>
|
||||||
|
Tambah Baru
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
|
<Table highlightOnHover striped withTableBorder withRowBorders>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Bulan</TableTh>
|
<TableTh style={{ width: '25%' }}>Bulan</TableTh>
|
||||||
<TableTh>Terdidik</TableTh>
|
<TableTh style={{ width: '20%' }}>Terdidik</TableTh>
|
||||||
<TableTh>Tidak Terdidik</TableTh>
|
<TableTh style={{ width: '20%' }}>Tidak Terdidik</TableTh>
|
||||||
<TableTh>Detail</TableTh>
|
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.length > 0 ? (
|
||||||
<TableTr key={item.id}>
|
filteredData.map((item) => (
|
||||||
<TableTd>{item.month}</TableTd>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.educatedUnemployment}</TableTd>
|
<TableTd>{item.month} {item.year}</TableTd>
|
||||||
<TableTd>{item.uneducatedUnemployment}</TableTd>
|
<TableTd>{item.educatedUnemployment}</TableTd>
|
||||||
<TableTd>
|
<TableTd>{item.uneducatedUnemployment}</TableTd>
|
||||||
<Button onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/${item.id}`)}>
|
<TableTd>
|
||||||
<IconDeviceImac size={20} />
|
<Button
|
||||||
</Button>
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/${item.id}`)}
|
||||||
|
>
|
||||||
|
<IconDeviceImac size={20} />
|
||||||
|
<Text ml={5}>Detail</Text>
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={4}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text c="dimmed">Tidak ada data yang cocok</Text>
|
||||||
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
)}
|
||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
<Center>
|
||||||
|
<Pagination
|
||||||
|
value={page}
|
||||||
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
total={totalPages}
|
||||||
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
|
||||||
{/* Chart */}
|
{/* Chart Section */}
|
||||||
{!mounted && !chartData ? (
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
<Title order={4} mb="md">
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
Data Pengangguran Terdidik & Tidak Terdidik
|
||||||
<Title pb={10} order={3}>Data Pengangguran Terdidik dan Tidak Terdidik</Title>
|
</Title>
|
||||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
{mounted && chartData.length > 0 ? (
|
||||||
</Paper>
|
<Box w={{ base: '100%', md: '70%' }}>
|
||||||
|
<BarChart
|
||||||
|
h={450}
|
||||||
|
data={chartData}
|
||||||
|
dataKey="month"
|
||||||
|
series={[
|
||||||
|
{ name: 'educatedUnemployment', color: 'red.6', label: 'Terdidik' },
|
||||||
|
{ name: 'uneducatedUnemployment', color: 'orange.6', label: 'Tidak Terdidik' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Box style={{ width: '100%', minWidth: 300, height: 550, minHeight: 300 }}>
|
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
|
||||||
<Title pb={10} order={4}>Data Pengangguran Terdidik dan Tidak Terdidik</Title>
|
|
||||||
{mounted && chartData.length > 0 && (
|
|
||||||
<Box w={{ base: '100%', md: '70%' }}>
|
|
||||||
<BarChart
|
|
||||||
h={450}
|
|
||||||
data={chartData}
|
|
||||||
dataKey="month"
|
|
||||||
series={[
|
|
||||||
{ name: 'educatedUnemployment', color: 'red.6', label: 'Terdidik' },
|
|
||||||
{ name: 'uneducatedUnemployment', color: 'orange.6', label: 'Tidak Terdidik' },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Paper>
|
||||||
</Box>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,25 @@
|
|||||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||||
import lowonganKerjaState from '@/app/admin/(dashboard)/_state/ekonomi/lowongan-kerja';
|
import lowonganKerjaState from '@/app/admin/(dashboard)/_state/ekonomi/lowongan-kerja';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
|
||||||
function EditLowonganKerja() {
|
function EditLowonganKerja() {
|
||||||
const lowonganState = useProxy(lowonganKerjaState)
|
const lowonganState = useProxy(lowonganKerjaState);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
|
||||||
@@ -24,7 +33,7 @@ function EditLowonganKerja() {
|
|||||||
gaji: lowonganKerjaState.update.form.gaji,
|
gaji: lowonganKerjaState.update.form.gaji,
|
||||||
deskripsi: lowonganKerjaState.update.form.deskripsi,
|
deskripsi: lowonganKerjaState.update.form.deskripsi,
|
||||||
kualifikasi: lowonganKerjaState.update.form.kualifikasi,
|
kualifikasi: lowonganKerjaState.update.form.kualifikasi,
|
||||||
})
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadLowongan = async () => {
|
const loadLowongan = async () => {
|
||||||
@@ -32,7 +41,7 @@ function EditLowonganKerja() {
|
|||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await lowonganState.update.load(id); // akses langsung, bukan dari proxy
|
const data = await lowonganState.update.load(id);
|
||||||
if (data) {
|
if (data) {
|
||||||
setFormData({
|
setFormData({
|
||||||
posisi: data.posisi || '',
|
posisi: data.posisi || '',
|
||||||
@@ -51,20 +60,17 @@ function EditLowonganKerja() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
loadLowongan();
|
loadLowongan();
|
||||||
}, [params?.id])
|
}, [params?.id]);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
// Set the ID for the update
|
|
||||||
lowonganState.update.id = params?.id as string;
|
lowonganState.update.id = params?.id as string;
|
||||||
|
|
||||||
// Update the form state
|
|
||||||
lowonganState.update.form = {
|
lowonganState.update.form = {
|
||||||
...lowonganState.update.form,
|
...lowonganState.update.form,
|
||||||
...formData
|
...formData,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Call the update function
|
|
||||||
await lowonganState.update.update();
|
await lowonganState.update.update();
|
||||||
toast.success("Lowongan kerja berhasil diperbarui!");
|
toast.success("Lowongan kerja berhasil diperbarui!");
|
||||||
router.push("/admin/ekonomi/lowongan-kerja-lokal");
|
router.push("/admin/ekonomi/lowongan-kerja-lokal");
|
||||||
@@ -72,79 +78,105 @@ function EditLowonganKerja() {
|
|||||||
console.error("Error updating lowongan kerja:", error);
|
console.error("Error updating lowongan kerja:", error);
|
||||||
toast.error("Terjadi kesalahan saat memperbarui lowongan kerja");
|
toast.error("Terjadi kesalahan saat memperbarui lowongan kerja");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header dengan tombol back */}
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
</Box>
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Edit Lowongan Kerja Lokal
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
{/* Card Form */}
|
||||||
<Stack gap={"xs"}>
|
<Paper
|
||||||
<Title order={4}>Edit Lowongan Kerja Lokal</Title>
|
w={{ base: '100%', md: '60%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Posisi"
|
||||||
|
placeholder="Masukkan posisi"
|
||||||
value={formData.posisi}
|
value={formData.posisi}
|
||||||
onChange={(val) => {
|
onChange={(e) => setFormData({ ...formData, posisi: e.target.value })}
|
||||||
setFormData(prev => ({ ...prev, posisi: val.target.value }));
|
required
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Posisi</Text>}
|
|
||||||
placeholder='Masukkan posisi'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Nama Perusahaan"
|
||||||
|
placeholder="Masukkan nama perusahaan"
|
||||||
value={formData.namaPerusahaan}
|
value={formData.namaPerusahaan}
|
||||||
onChange={(val) => {
|
onChange={(e) => setFormData({ ...formData, namaPerusahaan: e.target.value })}
|
||||||
setFormData(prev => ({ ...prev, namaPerusahaan: val.target.value }));
|
required
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Perusahaan</Text>}
|
|
||||||
placeholder='Masukkan nama perusahaan'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Lokasi"
|
||||||
|
placeholder="Masukkan lokasi"
|
||||||
value={formData.lokasi}
|
value={formData.lokasi}
|
||||||
onChange={(val) => {
|
onChange={(e) => setFormData({ ...formData, lokasi: e.target.value })}
|
||||||
setFormData(prev => ({ ...prev, lokasi: val.target.value }));
|
required
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Lokasi</Text>}
|
|
||||||
placeholder='Masukkan lokasi'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Tipe Pekerjaan"
|
||||||
|
placeholder="Masukkan tipe pekerjaan"
|
||||||
value={formData.tipePekerjaan}
|
value={formData.tipePekerjaan}
|
||||||
onChange={(val) => {
|
onChange={(e) => setFormData({ ...formData, tipePekerjaan: e.target.value })}
|
||||||
setFormData(prev => ({ ...prev, tipePekerjaan: val.target.value }));
|
required
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Tipe Pekerjaan</Text>}
|
|
||||||
placeholder='Masukkan tipe pekerjaan'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
label="Gaji (per bulan)"
|
||||||
|
placeholder="Masukkan gaji"
|
||||||
value={formData.gaji}
|
value={formData.gaji}
|
||||||
onChange={(val) => {
|
onChange={(e) => setFormData({ ...formData, gaji: e.target.value })}
|
||||||
setFormData(prev => ({ ...prev, gaji: val.target.value }));
|
required
|
||||||
}}
|
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Gaji selama 1 bulan</Text>}
|
|
||||||
placeholder='Masukkan gaji'
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Lowongan Kerja</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Deskripsi Lowongan Kerja
|
||||||
|
</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.deskripsi}
|
value={formData.deskripsi}
|
||||||
onChange={(val) => {
|
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
||||||
setFormData(prev => ({ ...prev, deskripsi: val }));
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"} fz={"sm"}>Kualifikasi Lowongan Kerja</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Kualifikasi Lowongan Kerja
|
||||||
|
</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
value={formData.kualifikasi}
|
value={formData.kualifikasi}
|
||||||
onChange={(val) => {
|
onChange={(val) => setFormData({ ...formData, kualifikasi: val })}
|
||||||
setFormData(prev => ({ ...prev, kualifikasi: val }));
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Group>
|
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -1,125 +1,145 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { useProxy } from 'valtio/utils';
|
|
||||||
|
|
||||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
import lowonganKerjaState from '../../../_state/ekonomi/lowongan-kerja';
|
import lowonganKerjaState from '../../../_state/ekonomi/lowongan-kerja';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function DetailLowonganKerjaLokal() {
|
function DetailLowonganKerjaLokal() {
|
||||||
const lowonganState = useProxy(lowonganKerjaState)
|
const lowonganState = useProxy(lowonganKerjaState);
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
lowonganState.findUnique.load(params?.id as string)
|
lowonganState.findUnique.load(params?.id as string);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const handleHapus = () => {
|
const handleHapus = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
lowonganState.delete.byId(selectedId)
|
lowonganState.delete.byId(selectedId);
|
||||||
setModalHapus(false)
|
setModalHapus(false);
|
||||||
setSelectedId(null)
|
setSelectedId(null);
|
||||||
router.push("/admin/ekonomi/lowongan-kerja-lokal")
|
router.push("/admin/ekonomi/lowongan-kerja-lokal");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!lowonganState.findUnique.data) {
|
if (!lowonganState.findUnique.data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={40} />
|
<Skeleton height={500} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = lowonganState.findUnique.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box py={10}>
|
||||||
<Box mb={10}>
|
<Button
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
variant="subtle"
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
onClick={() => router.back()}
|
||||||
</Button>
|
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||||
</Box>
|
mb={15}
|
||||||
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
>
|
||||||
<Stack>
|
Kembali
|
||||||
<Text fz={"xl"} fw={"bold"}>Detail Lowongan Kerja Lokal</Text>
|
</Button>
|
||||||
{lowonganState.findUnique.data ? (
|
|
||||||
<Paper key={lowonganState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
<Paper
|
||||||
<Stack gap={"xs"}>
|
withBorder
|
||||||
<Box>
|
w={{ base: "100%", md: "70%" }}
|
||||||
<Text fw={"bold"} fz={"lg"}>Bekerja Sebagai</Text>
|
bg={colors['white-1']}
|
||||||
<Text fz={"lg"}>{lowonganState.findUnique.data?.posisi}</Text>
|
p="lg"
|
||||||
</Box>
|
radius="md"
|
||||||
<Box>
|
shadow="sm"
|
||||||
<Text fw={"bold"} fz={"lg"}>Nama Usaha</Text>
|
>
|
||||||
<Text fz={"lg"}>{lowonganState.findUnique.data?.namaPerusahaan}</Text>
|
<Stack gap="md">
|
||||||
</Box>
|
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
|
||||||
<Box>
|
Detail Lowongan Kerja Lokal
|
||||||
<Text fw={"bold"} fz={"lg"}>Lokasi</Text>
|
</Text>
|
||||||
<Text fz={"lg"}>{lowonganState.findUnique.data?.lokasi}</Text>
|
|
||||||
</Box>
|
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||||
<Box>
|
<Stack gap="sm">
|
||||||
<Text fw={"bold"} fz={"lg"}>Tipe Pekerjaan</Text>
|
<Box>
|
||||||
<Text fz={"lg"}>{lowonganState.findUnique.data?.tipePekerjaan}</Text>
|
<Text fz="lg" fw="bold">Bekerja Sebagai</Text>
|
||||||
</Box>
|
<Text fz="md" c="dimmed">{data.posisi || '-'}</Text>
|
||||||
<Box>
|
</Box>
|
||||||
<Text fw={"bold"} fz={"lg"}>Gaji</Text>
|
|
||||||
<Text fz={"lg"}>{lowonganState.findUnique.data?.gaji}</Text>
|
<Box>
|
||||||
</Box>
|
<Text fz="lg" fw="bold">Nama Usaha</Text>
|
||||||
<Box>
|
<Text fz="md" c="dimmed">{data.namaPerusahaan || '-'}</Text>
|
||||||
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
|
</Box>
|
||||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: lowonganState.findUnique.data?.deskripsi }} />
|
|
||||||
</Box>
|
<Box>
|
||||||
<Box>
|
<Text fz="lg" fw="bold">Lokasi</Text>
|
||||||
<Text fw={"bold"} fz={"lg"}>Kualifikasi</Text>
|
<Text fz="md" c="dimmed">{data.lokasi || '-'}</Text>
|
||||||
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: lowonganState.findUnique.data?.kualifikasi }} />
|
</Box>
|
||||||
</Box>
|
|
||||||
<Flex gap={"xs"} mt={10}>
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Tipe Pekerjaan</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.tipePekerjaan || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Gaji</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.gaji || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||||
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Kualifikasi</Text>
|
||||||
|
<Text fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.kualifikasi || '-' }} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Group gap="sm" mt="sm">
|
||||||
|
<Tooltip label="Hapus Lowongan" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
|
color="red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (lowonganState.findUnique.data) {
|
setSelectedId(data.id);
|
||||||
setSelectedId(lowonganState.findUnique.data.id);
|
setModalHapus(true);
|
||||||
setModalHapus(true);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
disabled={lowonganState.delete.loading || !lowonganState.findUnique.data}
|
variant="light"
|
||||||
color={"red"}
|
radius="md"
|
||||||
|
size="md"
|
||||||
>
|
>
|
||||||
<IconX size={20} />
|
<IconTrash size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip label="Edit Lowongan" withArrow position="top">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
color="green"
|
||||||
if (lowonganState.findUnique.data) {
|
onClick={() => router.push(`/admin/ekonomi/lowongan-kerja-lokal/${data.id}/edit`)}
|
||||||
router.push(`/admin/ekonomi/lowongan-kerja-lokal/${lowonganState.findUnique.data.id}/edit`);
|
variant="light"
|
||||||
}
|
radius="md"
|
||||||
}}
|
size="md"
|
||||||
disabled={!lowonganState.findUnique.data}
|
|
||||||
color={"green"}
|
|
||||||
>
|
>
|
||||||
<IconEdit size={20} />
|
<IconEdit size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Tooltip>
|
||||||
</Stack>
|
</Group>
|
||||||
</Paper>
|
</Stack>
|
||||||
) : null}
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleHapus}
|
onConfirm={handleHapus}
|
||||||
text='Apakah anda yakin ingin menghapus lowongan kerja ini?'
|
text="Apakah Anda yakin ingin menghapus lowongan kerja ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,88 +1,123 @@
|
|||||||
'use client'
|
'use client';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import CreateEditor from '../../../_com/createEditor';
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
import lowonganKerjaState from '../../../_state/ekonomi/lowongan-kerja';
|
import lowonganKerjaState from '../../../_state/ekonomi/lowongan-kerja';
|
||||||
|
|
||||||
|
|
||||||
function CreateLowonganKerja() {
|
function CreateLowonganKerja() {
|
||||||
const lowonganState = useProxy(lowonganKerjaState)
|
const lowonganState = useProxy(lowonganKerjaState);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
lowonganState.create.form = {
|
lowonganState.create.form = {
|
||||||
posisi: "",
|
posisi: '',
|
||||||
namaPerusahaan: "",
|
namaPerusahaan: '',
|
||||||
lokasi: "",
|
lokasi: '',
|
||||||
tipePekerjaan: "",
|
tipePekerjaan: '',
|
||||||
gaji: "",
|
gaji: '',
|
||||||
deskripsi: "",
|
deskripsi: '',
|
||||||
kualifikasi: "",
|
kualifikasi: '',
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
await lowonganState.create.create()
|
await lowonganState.create.create();
|
||||||
resetForm()
|
resetForm();
|
||||||
router.push("/admin/ekonomi/lowongan-kerja-lokal")
|
router.push('/admin/ekonomi/lowongan-kerja-lokal');
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Box mb={10}>
|
{/* Header dengan tombol kembali */}
|
||||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
<Group mb="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={25}/>
|
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
|
||||||
</Button>
|
<Button
|
||||||
</Box>
|
variant="subtle"
|
||||||
|
onClick={() => router.back()}
|
||||||
|
p="xs"
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Title order={4} ml="sm" c="dark">
|
||||||
|
Tambah Lowongan Kerja Lokal
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
|
{/* Card Form */}
|
||||||
<Stack gap={"xs"}>
|
<Paper
|
||||||
<Title order={4}>Create Lowongan Kerja Lokal</Title>
|
w={{ base: '100%', md: '50%' }}
|
||||||
|
bg={colors['white-1']}
|
||||||
|
p="lg"
|
||||||
|
radius="md"
|
||||||
|
shadow="sm"
|
||||||
|
style={{ border: '1px solid #e0e0e0' }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
value={lowonganState.create.form.posisi}
|
value={lowonganState.create.form.posisi}
|
||||||
onChange={(val) => {
|
onChange={(val) =>
|
||||||
lowonganState.create.form.posisi = val.target.value;
|
(lowonganState.create.form.posisi = val.target.value)
|
||||||
}}
|
}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Posisi</Text>}
|
label="Posisi"
|
||||||
placeholder='Masukkan posisi'
|
placeholder="Masukkan posisi"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={lowonganState.create.form.namaPerusahaan}
|
value={lowonganState.create.form.namaPerusahaan}
|
||||||
onChange={(val) => {
|
onChange={(val) =>
|
||||||
lowonganState.create.form.namaPerusahaan = val.target.value;
|
(lowonganState.create.form.namaPerusahaan = val.target.value)
|
||||||
}}
|
}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Perusahaan</Text>}
|
label="Nama Perusahaan"
|
||||||
placeholder='Masukkan nama perusahaan'
|
placeholder="Masukkan nama perusahaan"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={lowonganState.create.form.lokasi}
|
value={lowonganState.create.form.lokasi}
|
||||||
onChange={(val) => {
|
onChange={(val) =>
|
||||||
lowonganState.create.form.lokasi = val.target.value;
|
(lowonganState.create.form.lokasi = val.target.value)
|
||||||
}}
|
}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Lokasi</Text>}
|
label="Lokasi"
|
||||||
placeholder='Masukkan lokasi'
|
placeholder="Masukkan lokasi"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={lowonganState.create.form.tipePekerjaan}
|
value={lowonganState.create.form.tipePekerjaan}
|
||||||
onChange={(val) => {
|
onChange={(val) =>
|
||||||
lowonganState.create.form.tipePekerjaan = val.target.value;
|
(lowonganState.create.form.tipePekerjaan = val.target.value)
|
||||||
}}
|
}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Tipe Pekerjaan</Text>}
|
label="Tipe Pekerjaan"
|
||||||
placeholder='Masukkan tipe pekerjaan'
|
placeholder="Masukkan tipe pekerjaan"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={lowonganState.create.form.gaji}
|
value={lowonganState.create.form.gaji}
|
||||||
onChange={(val) => {
|
onChange={(val) =>
|
||||||
lowonganState.create.form.gaji = val.target.value;
|
(lowonganState.create.form.gaji = val.target.value)
|
||||||
}}
|
}
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Gaji selama 1 bulan</Text>}
|
label="Gaji (per bulan)"
|
||||||
placeholder='Masukkan gaji'
|
placeholder="Masukkan gaji"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Lowongan Kerja</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Deskripsi Lowongan Kerja
|
||||||
|
</Text>
|
||||||
<CreateEditor
|
<CreateEditor
|
||||||
value={lowonganState.create.form.deskripsi}
|
value={lowonganState.create.form.deskripsi}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
@@ -90,8 +125,11 @@ function CreateLowonganKerja() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fw={"bold"} fz={"sm"}>Kualifikasi Lowongan Kerja</Text>
|
<Text fw="bold" fz="sm" mb={6}>
|
||||||
|
Kualifikasi Lowongan Kerja
|
||||||
|
</Text>
|
||||||
<CreateEditor
|
<CreateEditor
|
||||||
value={lowonganState.create.form.kualifikasi}
|
value={lowonganState.create.form.kualifikasi}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
@@ -99,12 +137,25 @@ function CreateLowonganKerja() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Group>
|
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
{/* Tombol Simpan */}
|
||||||
|
<Group justify="right">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,40 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
import {
|
||||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
Box,
|
||||||
import HeaderSearch from '../../_com/header';
|
Button,
|
||||||
import JudulList from '../../_com/judulList';
|
Center,
|
||||||
import { useRouter } from 'next/navigation';
|
Group,
|
||||||
import lowonganKerjaState from '../../_state/ekonomi/lowongan-kerja';
|
Pagination,
|
||||||
import { useProxy } from 'valtio/utils';
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
|
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import HeaderSearch from '../../_com/header';
|
||||||
|
import lowonganKerjaState from '../../_state/ekonomi/lowongan-kerja';
|
||||||
|
|
||||||
function LowonganKerjaLokal() {
|
function LowonganKerjaLokal() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Lowongan Kerja Lokal'
|
title="Lowongan Kerja Lokal"
|
||||||
placeholder='pencarian'
|
placeholder="Cari pekerjaan atau perusahaan..."
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -27,74 +45,117 @@ function LowonganKerjaLokal() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ListLowonganKerjaLokal({ search }: { search: string }) {
|
function ListLowonganKerjaLokal({ search }: { search: string }) {
|
||||||
const lowonganState = useProxy(lowonganKerjaState)
|
const stateLowongan = useProxy(lowonganKerjaState);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const {
|
const { data, page, totalPages, loading, load } = stateLowongan.findMany;
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
} = lowonganState.findMany
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
load(page, 10, search)
|
load(page, 10, search);
|
||||||
}, [page, search])
|
}, [page, search]);
|
||||||
|
|
||||||
const filteredData = data || []
|
const filteredData = data || [];
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
<Stack py={10}>
|
<Stack py={10}>
|
||||||
<Skeleton h={500} />
|
<Skeleton height={600} radius="md" />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<JudulList
|
<Group justify="space-between" mb="md">
|
||||||
title='List Lowongan Kerja Lokal'
|
<Title order={4}>Daftar Lowongan Kerja Lokal</Title>
|
||||||
href='/admin/ekonomi/lowongan-kerja-lokal/create'
|
<Tooltip label="Tambah Lowongan Kerja" withArrow>
|
||||||
/>
|
<Button
|
||||||
<Table striped withTableBorder withRowBorders>
|
leftSection={<IconPlus size={18} />}
|
||||||
<TableThead>
|
color="blue"
|
||||||
<TableTr>
|
variant="light"
|
||||||
<TableTh>Pekerjaan</TableTh>
|
onClick={() =>
|
||||||
<TableTh>Nama Perusahaan</TableTh>
|
router.push('/admin/ekonomi/lowongan-kerja-lokal/create')
|
||||||
<TableTh>Lokasi</TableTh>
|
}
|
||||||
<TableTh>Detail</TableTh>
|
>
|
||||||
|
Tambah Baru
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
|
<Table highlightOnHover>
|
||||||
|
<TableThead>
|
||||||
|
<TableTr>
|
||||||
|
<TableTh style={{ width: '25%' }}>Pekerjaan</TableTh>
|
||||||
|
<TableTh style={{ width: '25%' }}>Nama Perusahaan</TableTh>
|
||||||
|
<TableTh style={{ width: '20%' }}>Lokasi</TableTh>
|
||||||
|
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.map((item) => (
|
{filteredData.length > 0 ? (
|
||||||
<TableTr key={item.id}>
|
filteredData.map((item) => (
|
||||||
<TableTd>
|
<TableTr key={item.id}>
|
||||||
<Text fz={"md"}>{item.posisi}</Text>
|
<TableTd style={{ width: '25%' }}>
|
||||||
</TableTd>
|
<Text fw={500} truncate="end" lineClamp={1}>
|
||||||
<TableTd>
|
{item.posisi}
|
||||||
<Text fz={"md"}>{item.namaPerusahaan}</Text>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd style={{ width: '25%' }}>
|
||||||
<Text fz={"md"}>{item.lokasi}</Text>
|
<Text truncate fz="sm" c="dimmed">
|
||||||
</TableTd>
|
{item.namaPerusahaan}
|
||||||
<TableTd>
|
</Text>
|
||||||
<Button onClick={() => router.push(`/admin/ekonomi/lowongan-kerja-lokal/${item.id}`)}>
|
</TableTd>
|
||||||
<IconDeviceImac size={20} />
|
<TableTd style={{ width: '20%' }}>
|
||||||
</Button>
|
<Text truncate fz="sm" c="dimmed">
|
||||||
</TableTd>
|
{item.lokasi}
|
||||||
</TableTr>
|
</Text>
|
||||||
))}
|
</TableTd>
|
||||||
</TableTbody>
|
<TableTd style={{ width: '15%' }}>
|
||||||
</Table>
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/ekonomi/lowongan-kerja-lokal/${item.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconDeviceImac size={20} />
|
||||||
|
<Text ml={5}>Detail</Text>
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableTr>
|
||||||
|
<TableTd colSpan={4}>
|
||||||
|
<Center py={20}>
|
||||||
|
<Text color="dimmed">
|
||||||
|
Tidak ada data lowongan kerja yang cocok
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
)}
|
||||||
|
</TableTbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => load(newPage)}
|
onChange={(newPage) => {
|
||||||
|
load(newPage, 10);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
my="md"
|
mt="md"
|
||||||
|
mb="md"
|
||||||
|
color="blue"
|
||||||
|
radius="md"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||