Senin, 19 May 2025 :

Yang Sudah Di Kerjakan
- Tampilan UI Admin di menu kesehatan

Yang Akan Dikerjakan:
- API Di Menu Desa
- Tampilan UI Admin Di Menu Keamanan
This commit is contained in:
2025-05-19 17:00:43 +08:00
parent f5d68d4982
commit d3a43c72ab
84 changed files with 2634 additions and 800 deletions

BIN
bun.lockb

Binary file not shown.

BIN
foldergambar/desa/name.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 KiB

View File

@@ -67,6 +67,7 @@
"readdirp": "^4.1.1", "readdirp": "^4.1.1",
"recharts": "^2.15.3", "recharts": "^2.15.3",
"swr": "^2.3.2", "swr": "^2.3.2",
"uuid": "^11.1.0",
"valtio": "^2.1.3", "valtio": "^2.1.3",
"zod": "^3.24.3" "zod": "^3.24.3"
}, },

View File

@@ -210,32 +210,33 @@ model GrafikBerdasarkanUmur {
// ========================================= MENU DESA ========================================= // // ========================================= MENU DESA ========================================= //
// ========================================= PROFILE DESA ========================================= // // ========================================= PROFILE DESA ========================================= //
model ProfileDesa { model ProfileDesa {
id String @id @default(cuid()) id String @id @default(cuid())
sejarah String @db.Text sejarah String @db.Text
visi String @db.Text visi String @db.Text
misi String @db.Text misi String @db.Text
lambang String @db.Text lambang String @db.Text
maskot String @db.Text maskot String @db.Text
ProfilPerbekel ProfilPerbekel? @relation(fields: [profilPerbekelId], references: [id]) ProfilPerbekel ProfilPerbekel? @relation(fields: [profilPerbekelId], references: [id])
profilPerbekelId String? profilPerbekelId String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime @default(now()) deletedAt DateTime @default(now())
isActive Boolean @default(true) isActive Boolean @default(true)
} }
model ProfilPerbekel { model ProfilPerbekel {
id String @id @default(cuid()) id String @id @default(cuid())
biodata String @db.Text biodata String @db.Text
pengalaman String @db.Text pengalaman String @db.Text
pengalamanOrganisasi String @db.Text pengalamanOrganisasi String @db.Text
programUnggulan String @db.Text programUnggulan String @db.Text
ProfileDesa ProfileDesa[] ProfileDesa ProfileDesa[]
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime @default(now()) deletedAt DateTime @default(now())
isActive Boolean @default(true) isActive Boolean @default(true)
} }
// ========================================= BERITA ========================================= // // ========================================= BERITA ========================================= //
model Berita { model Berita {
id String @id @default(cuid()) id String @id @default(cuid())
@@ -581,3 +582,18 @@ model DoctorSign {
deletedAt DateTime @default(now()) deletedAt DateTime @default(now())
isActive Boolean @default(true) isActive Boolean @default(true)
} }
// === BARU
model FileStorage {
id String @id @default(cuid())
name String @unique
realName String
path String
mimeType String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime? // nullable agar bisa menandakan belum dihapus
isActive Boolean @default(true)
link String
}

View File

Before

Width:  |  Height:  |  Size: 275 KiB

After

Width:  |  Height:  |  Size: 275 KiB

View File

@@ -6,11 +6,16 @@ import caraMemperolehSalinanInformasi from './data/list-caraMemperolehSalinanInf
import jenisInformasiDiminta from './data/list-jenisInfromasi.json' import jenisInformasiDiminta from './data/list-jenisInfromasi.json'
import layanan from './data/list-layanan.json' import layanan from './data/list-layanan.json'
import potensi from './data/list-potensi.json' import potensi from './data/list-potensi.json'
import profilePPID from './data/ppid/profile-ppid/profilePPid.json'
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 profileDesa from './data/desa/profile/profile_desa.json' import profileDesa from './data/desa/profile/profile_desa.json'
import profilePerbekel from './data/desa/profile/profil_perbekel.json' import profilePerbekel from './data/desa/profile/profil_perbekel.json'
import profilePPID from './data/ppid/profile-ppid/profilePPid.json'
import path from 'path'
import fs from 'fs'
import { mkdir, writeFile } from 'fs/promises'
import { v4 as uuid } from 'uuid'
(async () => { (async () => {
for (const l of layanan) { for (const l of layanan) {
await prisma.layanan.upsert({ await prisma.layanan.upsert({
@@ -121,31 +126,57 @@ import profilePerbekel from './data/desa/profile/profil_perbekel.json'
} }
console.log("cara memperoleh salinan informasi success ...") console.log("cara memperoleh salinan informasi success ...")
for (const c of profilePPID) { const seedProfilePPID = async () => {
await prisma.profilePPID.upsert({ const targetDir = path.resolve("public", "assets", "images", "ppid", "profile-ppid")
where: { await mkdir(targetDir, { recursive: true })
id: c.id
}, for (const c of profilePPID) {
let finalImageUrl = c.imageUrl
// kalau imageUrl diawali dengan "/assets/images", artinya kita harus copy file dari seed-images
if (c.imageUrl.startsWith("/assets/images/")) {
const filename = path.basename(c.imageUrl) // misal "perbekel.png"
const seedImagePath = path.resolve("prisma", "seed-images", filename)
// Buat nama baru yang unik agar tidak bentrok
const targetFilename = `${uuid()}_${filename}`
const targetPath = path.join(targetDir, targetFilename)
// Salin file dari prisma/seed-images ke public/
const buffer = fs.readFileSync(seedImagePath)
await writeFile(targetPath, buffer)
finalImageUrl = `/assets/images/ppid/profile-ppid/${targetFilename}`
}
// Upsert ke database
await prisma.profilePPID.upsert({
where: { id: c.id },
update: { update: {
name: c.name, name: c.name,
biodata: c.biodata, biodata: c.biodata,
riwayat: c.riwayat, riwayat: c.riwayat,
pengalaman: c.pengalaman, pengalaman: c.pengalaman,
unggulan: c.unggulan, unggulan: c.unggulan,
imageUrl: c.imageUrl imageUrl: finalImageUrl,
}, },
create: { create: {
id: c.id, id: c.id,
name: c.name, name: c.name,
biodata: c.biodata, biodata: c.biodata,
riwayat: c.riwayat, riwayat: c.riwayat,
pengalaman: c.pengalaman, pengalaman: c.pengalaman,
unggulan: c.unggulan, unggulan: c.unggulan,
imageUrl: c.imageUrl imageUrl: finalImageUrl,
} },
}) })
} }
console.log("profile PPID success ...")
console.log("✅ profilePPID seeded from JSON with image copying")
}
await seedProfilePPID()
for (const v of visiMisiPPID) { for (const v of visiMisiPPID) {
await prisma.visiMisiPPID.upsert({ await prisma.visiMisiPPID.upsert({

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

View File

@@ -0,0 +1,239 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
/* Sejarah */
const templateFormSejarahForm = z.object({
sejarah: z.string().min(3, "Sejarah minimal 3 karakter"),
})
type SejarahForm = Prisma.ProfileDesaGetPayload<{
select: {
id: true;
sejarah: true;
}
}>
const Sejarah = proxy({
findById: {
data: null as SejarahForm | null,
loading: false,
initialize() {
Sejarah.findById.data = {
id: "",
sejarah: "",
} as SejarahForm;
},
async load(id: string) {
try {
Sejarah.findById.loading = true;
const res = await ApiFetch.api.desa.profile["find-by-id"].get({
query: { id },
});
if (res.status === 200) {
Sejarah.findById.data = {
id: id,
sejarah: res.data?.data?.sejarah ?? ""
};
} else {
toast.error("Gagal mengambil data sejarah");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data sejarah");
} finally {
Sejarah.findById.loading = false;
}
}
},
update: {
loading: false,
async save(data: SejarahForm) {
const cek = templateFormSejarahForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
Sejarah.update.loading = true;
const res = await ApiFetch.api.desa.profile.sejarah["update"].post(data);
if (res.status === 200) {
toast.success("Berhasil update sejarah");
await Sejarah.findById.load(data.id);
} else {
toast.error("Gagal update sejarah");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat update sejarah");
} finally {
Sejarah.update.loading = false;
}
}
}
})
/* Visi Misi Desa */
const templateFormVisiForm = z.object({
visi: z.string().min(3, "Visi minimal 3 karakter"),
misi: z.string().min(3, "Misi minimal 3 karakter")
})
type VisiMisiDesaForm = Prisma.ProfileDesaGetPayload<{
select: {
id: true;
visi: true;
misi: true;
}
}>
const VisiMisiDesa = proxy({
findById: {
data: null as VisiMisiDesaForm | null,
loading: false,
initialize() {
VisiMisiDesa.findById.data = {
id: "",
visi: "",
misi: ""
} as VisiMisiDesaForm;
},
async load(id: string) {
try {
VisiMisiDesa.findById.loading = true;
const res = await ApiFetch.api.desa.profile["find-by-id"].get({
query: { id },
});
if (res.status === 200) {
VisiMisiDesa.findById.data = {
id: id,
visi: res.data?.data?.visi ?? "",
misi: res.data?.data?.misi ?? ""
};
} else {
toast.error("Gagal mengambil data visi misi");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data visi misi");
} finally {
VisiMisiDesa.findById.loading = false;
}
}
},
update: {
loading: false,
async save(data: VisiMisiDesaForm) {
const cek = templateFormVisiForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
VisiMisiDesa.update.loading = true;
const res = await ApiFetch.api.desa.profile.visimisiDesa["update"].post(data);
if (res.status === 200) {
toast.success("Berhasil update visi misi");
await VisiMisiDesa.findById.load(data.id);
} else {
toast.error("Gagal update visi");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat update visi misi");
} finally {
VisiMisiDesa.update.loading = false;
}
}
}
})
/* Lambang Desa */
const templateFormLambangDesaForm = z.object({
lambang: z.string().min(3, "Lambang minimal 3 karakter"),
})
type LambangDesaForm = Prisma.ProfileDesaGetPayload<{
select: {
id: true;
lambang: true;
}
}>
const LambangDesa = proxy({
findById: {
data: null as LambangDesaForm | null,
loading: false,
initialize() {
LambangDesa.findById.data = {
id: "",
lambang: "",
} as LambangDesaForm;
},
async load(id: string) {
try {
LambangDesa.findById.loading = true;
const res = await ApiFetch.api.desa.profile["find-by-id"].get({
query: { id },
});
if (res.status === 200) {
LambangDesa.findById.data = {
id: id,
lambang: res.data?.data?.lambang ?? ""
};
} else {
toast.error("Gagal mengambil data lambang desa");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat mengambil data lambang desa");
} finally {
LambangDesa.findById.loading = false;
}
}
},
update: {
loading: false,
async save(data: LambangDesaForm) {
const cek = templateFormLambangDesaForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
.join(", ");
toast.error(`Form tidak valid: ${errors}`);
return;
}
try {
LambangDesa.update.loading = true;
const res = await ApiFetch.api.desa.profile.lambangDesa["update"].post(data);
if (res.status === 200) {
toast.success("Berhasil update lambang desa");
await LambangDesa.findById.load(data.id);
} else {
toast.error("Gagal update lambang desa");
}
} catch (error) {
console.error((error as Error).message);
toast.error("Terjadi kesalahan saat update lambang desa");
} finally {
LambangDesa.update.loading = false;
}
}
}
});
const stateProfileDesa = {
Sejarah,
VisiMisiDesa,
LambangDesa,
};
export default stateProfileDesa;

View File

@@ -4,7 +4,9 @@ import { toast } from "react-toastify";
import { proxy } from "valtio"; import { proxy } from "valtio";
import { z } from "zod"; import { z } from "zod";
// Schema validasi form /**
* Schema validasi form ProfilePPID menggunakan Zod.
*/
const templateForm = z.object({ const templateForm = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"), name: z.string().min(3, "Nama minimal 3 karakter"),
biodata: z.string().min(3, "Biodata minimal 3 karakter"), biodata: z.string().min(3, "Biodata minimal 3 karakter"),
@@ -13,7 +15,9 @@ const templateForm = z.object({
unggulan: z.string().min(3, "Unggulan minimal 3 karakter"), unggulan: z.string().min(3, "Unggulan minimal 3 karakter"),
}); });
// Type ambil dari Prisma /**
* Tipe data ProfilePPID yang digunakan dalam form dan API, berdasarkan Prisma schema.
*/
type ProfilePPIDForm = Prisma.ProfilePPIDGetPayload<{ type ProfilePPIDForm = Prisma.ProfilePPIDGetPayload<{
select: { select: {
id: true; id: true;
@@ -26,11 +30,23 @@ type ProfilePPIDForm = Prisma.ProfilePPIDGetPayload<{
}; };
}>; }>;
// Proxy utama /**
* State utama ProfilePPID yang mencakup fitur:
* - Ambil data berdasarkan ID
* - Update data
* - Upload gambar
*/
const stateProfilePPID = proxy({ const stateProfilePPID = proxy({
/**
* Bagian untuk ambil data berdasarkan ID
*/
findById: { findById: {
data: null as ProfilePPIDForm | null, data: null as ProfilePPIDForm | null,
loading: false, loading: false,
/**
* Inisialisasi data kosong ke dalam state.
*/
initialize() { initialize() {
stateProfilePPID.findById.data = { stateProfilePPID.findById.data = {
id: '', id: '',
@@ -39,15 +55,21 @@ const stateProfilePPID = proxy({
riwayat: '', riwayat: '',
pengalaman: '', pengalaman: '',
unggulan: '', unggulan: '',
imageUrl:'' imageUrl: ''
} as ProfilePPIDForm; } as ProfilePPIDForm;
}, },
/**
* Mengambil data profil berdasarkan ID.
* @param {string} id - ID dari profile yang ingin diambil.
*/
async load(id: string) { async load(id: string) {
try { try {
stateProfilePPID.findById.loading = true; stateProfilePPID.findById.loading = true;
const res = await ApiFetch.api.ppid.profileppid["find-by-id"].get({ const res = await ApiFetch.api.ppid.profileppid["find-by-id"].get({
query: { id }, query: { id },
}); });
if (res.status === 200) { if (res.status === 200) {
stateProfilePPID.findById.data = res.data?.data ?? null; stateProfilePPID.findById.data = res.data?.data ?? null;
} else { } else {
@@ -62,10 +84,19 @@ const stateProfilePPID = proxy({
}, },
}, },
/**
* Bagian untuk update data profile
*/
update: { update: {
loading: false, loading: false,
/**
* Melakukan validasi dan menyimpan perubahan data profile ke server.
* @param {ProfilePPIDForm} data - Data profil yang akan disimpan.
*/
async save(data: ProfilePPIDForm) { async save(data: ProfilePPIDForm) {
const cek = templateForm.safeParse(data); const cek = templateForm.safeParse(data);
if (!cek.success) { if (!cek.success) {
const errors = cek.error.issues const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`) .map((issue) => `${issue.path.join(".")}: ${issue.message}`)
@@ -77,6 +108,7 @@ const stateProfilePPID = proxy({
try { try {
stateProfilePPID.update.loading = true; stateProfilePPID.update.loading = true;
const res = await ApiFetch.api.ppid.profileppid["update"].post(data); const res = await ApiFetch.api.ppid.profileppid["update"].post(data);
if (res.status === 200) { if (res.status === 200) {
toast.success("Berhasil update profile"); toast.success("Berhasil update profile");
await stateProfilePPID.findById.load(data.id); await stateProfilePPID.findById.load(data.id);
@@ -91,13 +123,24 @@ const stateProfilePPID = proxy({
} }
}, },
}, },
/**
* Bagian untuk upload gambar profil
*/
uploadImage: { uploadImage: {
loading: false, loading: false,
/**
* Mengunggah gambar profil berdasarkan ID.
* @param {File} file - File gambar yang akan diunggah.
* @param {string} id - ID dari profil yang akan diperbarui gambarnya.
*/
async save(file: File, id: string) { async save(file: File, id: string) {
if (!file || !id) { if (!file || !id) {
toast.error("File atau ID harus disertakan"); toast.error("File atau ID harus disertakan");
return; return;
} }
try { try {
stateProfilePPID.uploadImage.loading = true; stateProfilePPID.uploadImage.loading = true;
@@ -106,6 +149,7 @@ const stateProfilePPID = proxy({
form.append("id", id); form.append("id", id);
const res = await ApiFetch.api.ppid.profileppid["edit-img"].post(form); const res = await ApiFetch.api.ppid.profileppid["edit-img"].post(form);
if (res.status === 200) { if (res.status === 200) {
toast.success("Berhasil mengunggah gambar"); toast.success("Berhasil mengunggah gambar");
await stateProfilePPID.findById.load(id); await stateProfilePPID.findById.load(id);
@@ -118,8 +162,11 @@ const stateProfilePPID = proxy({
} finally { } finally {
stateProfilePPID.uploadImage.loading = false; stateProfilePPID.uploadImage.loading = false;
} }
} },
} },
}); });
/**
* Ekspor state utama ProfilePPID untuk digunakan di komponen lain.
*/
export default stateProfilePPID; export default stateProfilePPID;

View File

@@ -1,62 +1,75 @@
'use client' 'use client'
import { Center, Group, Select, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core'; import { Box, Center, Group, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { Prisma } from '@prisma/client'; import { Prisma } from '@prisma/client';
import { IconImageInPicture } from '@tabler/icons-react'; import { IconImageInPicture } from '@tabler/icons-react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import stateDashboardBerita from '../../_state/desa/berita'; import stateDashboardBerita from '../../_state/desa/berita';
import { BeritaEditor } from './_com/BeritaEditor'; import { BeritaEditor } from './_com/BeritaEditor';
import colors from '@/con/colors';
function Page() { function Page() {
return ( return (
<Stack> <Box>
<SimpleGrid cols={{base: 1, md: 2}}> <Title order={3}>Berita</Title>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<BeritaCreate /> <BeritaCreate />
<BeritaList /> <BeritaList />
</SimpleGrid> </SimpleGrid>
</Stack> </Box>
); );
} }
function BeritaCreate() {
const beritaState = useProxy(stateDashboardBerita)
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<SelectCategory onChange={(val) => {
beritaState.berita.create.form.katagoryBeritaId = val.id
}} />
<TextInput onChange={(val) => {
beritaState.berita.create.form.judul = val.target.value
}} label={"Judul"} placeholder='masukkan judul' />
<TextInput onChange={(val) => {
beritaState.berita.create.form.deskripsi = val.target.value
}} label={"Deskripsi"} placeholder='masukkan deskripsi' />
<Center w={200} h={200} bg={"gray"}>
<IconImageInPicture />
</Center>
<BeritaEditor onSubmit={(val) => {
beritaState.berita.create.form.content = val
beritaState.berita.create.create()
}} />
</Stack>
</Paper>
</Box>
)
}
function BeritaList() { function BeritaList() {
const beritaState = useProxy(stateDashboardBerita) const beritaState = useProxy(stateDashboardBerita)
useShallowEffect(() => { useShallowEffect(() => {
beritaState.berita.findMany.load() beritaState.berita.findMany.load()
}, []) }, [])
if (!beritaState.berita.findMany.data) return <Stack> if (!beritaState.berita.findMany.data) return <Stack py={10}>
{Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)} {Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)}
</Stack> </Stack>
return <Stack> return (
<Text>News List</Text> <Box py={10}>
{beritaState.berita.findMany.data?.map((item) => ( <Paper bg={colors['white-1']} p={'md'}>
<Text key={item.id}>{item.judul}</Text> <Stack>
))} <Text fz={"xl"} fw={"bold"}>List Berita</Text>
</Stack> {beritaState.berita.findMany.data?.map((item) => (
} <Text key={item.id}>{item.judul}</Text>
))}
function BeritaCreate() { </Stack>
const beritaState = useProxy(stateDashboardBerita) </Paper>
return <Stack gap={"md"}> </Box>
<Text>Create Some News</Text> )
<SelectCategory onChange={(val) => {
beritaState.berita.create.form.katagoryBeritaId = val.id
}} />
<TextInput onChange={(val) => {
beritaState.berita.create.form.judul = val.target.value
}} label={"Judul"} placeholder='masukkan judul' />
<TextInput onChange={(val) => {
beritaState.berita.create.form.deskripsi = val.target.value
}} label={"Deskripsi"} placeholder='masukkan deskripsi' />
<Center w={200} h={200} bg={"gray"}>
<IconImageInPicture />
</Center>
<BeritaEditor onSubmit={(val) => {
beritaState.berita.create.form.content = val
beritaState.berita.create.create()
}} />
</Stack>
} }
function SelectCategory({ onChange }: { function SelectCategory({ onChange }: {
@@ -74,7 +87,7 @@ function SelectCategory({ onChange }: {
if (!beritaState.category.findMany.data) return <Skeleton h={40} /> if (!beritaState.category.findMany.data) return <Skeleton h={40} />
return <Group> return <Group>
<Select placeholder='pilih katagori' label={"select katagori"} data={beritaState.category.findMany.data.map((item) => ({ <Select placeholder='pilih katagori' label={<Text fz={"sm"} fw={"bold"}>Pilih Kategori</Text>} data={beritaState.category.findMany.data.map((item) => ({
value: item.id, value: item.id,
label: item.name label: item.name
}))} onChange={(v) => { }))} onChange={(v) => {

View File

@@ -1,11 +1,35 @@
import React from 'react'; import colors from '@/con/colors';
import { Box, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { IconPhoto, IconVideo } from '@tabler/icons-react';
import Foto from './ui/foto/page';
import Video from './ui/video/page';
function Page() { function Gallery() {
return ( return (
<div> <Box>
Gallery <Stack gap={"xs"}>
</div> <Title order={3}>Gallery</Title>
<Tabs color={colors['blue-button']} variant="pills" defaultValue="foto">
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
<TabsTab value="foto" leftSection={<IconPhoto size={12} />}>
Foto
</TabsTab>
<TabsTab value="video" leftSection={<IconVideo size={12} />}>
Video
</TabsTab>
</TabsList>
<TabsPanel value="foto">
<Foto/>
</TabsPanel>
<TabsPanel value="video">
<Video/>
</TabsPanel>
</Tabs>
</Stack>
</Box>
); );
} }
export default Page; export default Gallery;

View File

@@ -0,0 +1,17 @@
import colors from '@/con/colors';
import { Box, Paper, Stack, Title } from '@mantine/core';
import React from 'react';
function ListFoto() {
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={3}>List Foto</Title>
</Stack>
</Paper>
</Box>
);
}
export default ListFoto;

View File

@@ -0,0 +1,52 @@
import colors from '@/con/colors';
import { Box, Button, Center, Group, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconUpload } from '@tabler/icons-react';
import { DesaEditor } from '../../../_com/desaEditor';
import ListFoto from './listPage';
function Foto() {
return (
<Box py={10}>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={3}>Foto</Title>
<TextInput
label={<Text fz={"sm"} fw={"bold"}>Tanggal Foto</Text>}
placeholder="2022-01-01"
/>
<TextInput
label={<Text fz={"sm"} fw={"bold"}>Judul Foto</Text>}
placeholder="Judul Foto"
/>
<Text fz={"sm"} fw={"bold"}>Upload Foto</Text>
<Box bg={colors['BG-trans']} p={"md"}>
<Center>
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Center>
</Box>
<Box>
<Text fz={"sm"} fw={"bold"}>Deskripsi Foto</Text>
<DesaEditor
showSubmit={false}
/>
</Box>
<Group>
<Button
mt={10}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
<ListFoto/>
</SimpleGrid>
</Box>
);
}
export default Foto;

View File

@@ -0,0 +1,17 @@
import colors from '@/con/colors';
import { Box, Paper, Stack, Title } from '@mantine/core';
import React from 'react';
function ListVideo() {
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={3}>List Video</Title>
</Stack>
</Paper>
</Box>
);
}
export default ListVideo;

View File

@@ -0,0 +1,52 @@
import colors from '@/con/colors';
import { Box, Button, Center, Group, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
import { IconUpload } from '@tabler/icons-react';
import { DesaEditor } from '../../../_com/desaEditor';
import ListVideo from './listPage';
function Video() {
return (
<Box py={10}>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={3}>Video</Title>
<TextInput
label={<Text fz={"sm"} fw={"bold"}>Tanggal Video</Text>}
placeholder="2022-01-01"
/>
<TextInput
label={<Text fz={"sm"} fw={"bold"}>Judul Video</Text>}
placeholder="Judul Video"
/>
<Text fz={"sm"} fw={"bold"}>Upload Video</Text>
<Box bg={colors['BG-trans']} p={"md"}>
<Center>
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Center>
</Box>
<Box>
<Text fz={"sm"} fw={"bold"}>Deskripsi Video</Text>
<DesaEditor
showSubmit={false}
/>
</Box>
<Group>
<Button
mt={10}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
<ListVideo/>
</SimpleGrid>
</Box>
);
}
export default Video;

View File

@@ -1,11 +1,48 @@
import React from 'react'; import colors from '@/con/colors';
import { Box, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import SuratKeterangan from './ui/surat_keterangan/page';
import PerizinanUsaha from './ui/perizinan_usaha/page';
import TelunjukSaktiDesa from './ui/telunjuk_sakti_desa/page';
import PendudukNonPermanent from './ui/penduduk_non_permanent/page';
function Page() { function Page() {
return ( return (
<div> <Box py={10}>
Layanan <Stack >
</div> <Title order={3}>Layanan</Title>
<Tabs color={colors['blue-button']} variant='pills' defaultValue={"Pelayanan Surat Keterangan"}>
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
<TabsTab value="Pelayanan Surat Keterangan">
Pelayanan Surat Keterangan
</TabsTab>
<TabsTab value="Pelayanan Perizinan Berusaha">
Pelayanan Perizinan Berusaha
</TabsTab>
<TabsTab value="Pelayanan Telunjuk Sakti Desa">
Pelayanan Telunjuk Sakti Desa
</TabsTab>
<TabsTab value="Pelayanan Penduduk Non-Permanent">
Pelayanan Penduduk Non-Permanent
</TabsTab>
</TabsList>
<TabsPanel value="Pelayanan Surat Keterangan">
<SuratKeterangan />
</TabsPanel>
<TabsPanel value="Pelayanan Perizinan Berusaha">
<PerizinanUsaha />
</TabsPanel>
<TabsPanel value="Pelayanan Telunjuk Sakti Desa">
<TelunjukSaktiDesa />
</TabsPanel>
<TabsPanel value="Pelayanan Penduduk Non-Permanent">
<PendudukNonPermanent />
</TabsPanel>
</Tabs>
</Stack>
</Box>
); );
} }
export default Page; export default Page;

View File

@@ -0,0 +1,17 @@
import colors from '@/con/colors';
import { Box, Paper, Stack, Title } from '@mantine/core';
import React from 'react';
function ListPendudukNonPermanent() {
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>List Penduduk Non-Permanent</Title>
</Stack>
</Paper>
</Box>
);
}
export default ListPendudukNonPermanent;

View File

@@ -0,0 +1,35 @@
import colors from '@/con/colors';
import { Box, SimpleGrid, Paper, Stack, Title, Group, Button, Text } from '@mantine/core';
import React from 'react';
import { DesaEditor } from '../../../_com/desaEditor';
import ListPendudukNonPermanent from './listPage';
function PendudukNonPermanent() {
return (
<Box py={10}>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>Penduduk Non-Permanent</Title>
<Text fw={"bold"}>Deskripsi Penduduk Non-Permanent</Text>
<DesaEditor showSubmit={false} />
<Group>
<Button
mt={10}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
<ListPendudukNonPermanent />
</SimpleGrid>
</Box>
);
}
export default PendudukNonPermanent;

View File

@@ -0,0 +1,17 @@
import colors from '@/con/colors';
import { Box, Paper, Stack, Title } from '@mantine/core';
import React from 'react';
function ListPerizinanUsaha() {
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={3}>List Perizinan Usaha</Title>
</Stack>
</Paper>
</Box>
);
}
export default ListPerizinanUsaha;

View File

@@ -0,0 +1,40 @@
import colors from '@/con/colors';
import { Box, Button, Group, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
import React from 'react';
import ListPerizinanUsaha from './listPage';
import { DesaEditor } from '../../../_com/desaEditor';
function PerizinanUsaha() {
return (
<Box py={10}>
<Stack gap={"xs"}>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={3}>Pelayanan Perizinan Usaha</Title>
<Box>
<Text fz={"sm"} fw={"bold"}>Deskripsi Perizinan Usaha</Text>
<DesaEditor
showSubmit={false}
/>
</Box>
<Group>
<Button
mt={10}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
<ListPerizinanUsaha />
</SimpleGrid>
</Stack>
</Box>
);
}
export default PerizinanUsaha;

View File

@@ -0,0 +1,17 @@
import colors from '@/con/colors';
import { Box, Paper, Stack, Title } from '@mantine/core';
import React from 'react';
function ListSuratKeterangan() {
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>List Surat Keterangan</Title>
</Stack>
</Paper>
</Box>
);
}
export default ListSuratKeterangan;

View File

@@ -0,0 +1,48 @@
import colors from '@/con/colors';
import { Box, SimpleGrid, Paper, Stack, Title, Button, Group, TextInput, Text, Center, Flex } from '@mantine/core';
import { IconUpload } from '@tabler/icons-react';
import React from 'react';
import ListSuratKeterangan from './listPage';
function SuratKeterangan() {
return (
<Box py={10}>
<Stack gap={"xs"}>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={3}>Pelayanan Surat Keterangan</Title>
<TextInput
label={<Text fz={"sm"} fw={"bold"}>Nama Surat Keterangan</Text>}
placeholder='masukkan nama surat keterangan'
/>
<Text fz={"sm"} fw={"bold"}>Upload Gambar Surat Keterangan</Text>
<Box bg={colors['BG-trans']} p={"md"}>
<Center>
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Center>
</Box>
<Flex>
<Text fz={"xs"} c={"red"}>*</Text>
<Text fz={"xs"} c={"dimmed"} fw={"bold"}>Upload foto untuk konten surat keterangan</Text>
</Flex>
<Group>
<Button
mt={10}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
<ListSuratKeterangan />
</SimpleGrid>
</Stack>
</Box>
);
}
export default SuratKeterangan;

View File

@@ -0,0 +1,17 @@
import colors from '@/con/colors';
import { Box, Paper, Stack, Title } from '@mantine/core';
import React from 'react';
function ListTelunjukSaktiDesa() {
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>List Telunjuk Sakti Desa</Title>
</Stack>
</Paper>
</Box>
);
}
export default ListTelunjukSaktiDesa;

View File

@@ -0,0 +1,36 @@
import colors from '@/con/colors';
import { Box, SimpleGrid, Paper, Stack, Title, Group, Button, Text } from '@mantine/core';
import React from 'react';
import { DesaEditor } from '../../../_com/desaEditor';
import ListTelunjukSaktiDesa from './listPage';
function TelunjukSaktiDesa() {
return (
<Box py={10}>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>Telunjuk Sakti Desa</Title>
<Box>
<Text fz={"sm"} fw={"bold"}>Deskripsi Telunjuk Sakti Desa</Text>
<DesaEditor showSubmit={false} />
</Box>
<Group>
<Button
mt={10}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
<ListTelunjukSaktiDesa />
</SimpleGrid>
</Box>
);
}
export default TelunjukSaktiDesa;

View File

@@ -1,10 +1,33 @@
import colors from '@/con/colors';
import { Box, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import React from 'react'; import React from 'react';
import Penghargaan from './ui/penghargaan/page';
import GambarPerhargaan from './ui/gambar_perhargaan/page';
function Page() { function Page() {
return ( return (
<div> <Box py={10}>
Penghargaan <Stack>
</div> <Title order={3}>Penghargaan</Title>
<Tabs color={colors['blue-button']} variant='pills' defaultValue={"Penghargaan"}>
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
<TabsTab value="Penghargaan">
Penghargaan
</TabsTab>
<TabsTab value="Gambar Penghargaan">
Gambar Penghargaan
</TabsTab>
</TabsList>
<TabsPanel value="Penghargaan">
<Penghargaan/>
</TabsPanel>
<TabsPanel value="Gambar Penghargaan">
<GambarPerhargaan/>
</TabsPanel>
</Tabs>
</Stack>
</Box>
); );
} }

View File

@@ -0,0 +1,17 @@
import colors from '@/con/colors';
import { Box, Paper, Stack, Title } from '@mantine/core';
import React from 'react';
function ListGambarPenghargaan() {
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>List Gambar Penghargaan</Title>
</Stack>
</Paper>
</Box>
);
}
export default ListGambarPenghargaan;

View File

@@ -0,0 +1,50 @@
import colors from '@/con/colors';
import { Box, Paper, SimpleGrid, Stack, Title, Text, Group, Button, TextInput, Center } from '@mantine/core';
import React from 'react';
import { DesaEditor } from '../../../_com/desaEditor';
import ListGambarPenghargaan from './listPage';
import { IconUpload } from '@tabler/icons-react';
function GambarPerhargaan() {
return (
<Box py={10}>
<Stack gap={"xs"}>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={3}>Tambah Gambar Penghargaan</Title>
<TextInput
label="Judul Gambar Penghargaan"
placeholder='masukkan judul gambar penghargaan'
/>
<Text fw={"bold"}>Deskripsi Gambar Penghargaan</Text>
<DesaEditor showSubmit={false} />
<Box>
<Text fw={"bold"}>Upload Gambar Penghargaan</Text>
<Box bg={colors['BG-trans']} p={"md"}>
<Center>
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Center>
</Box>
</Box>
<Group>
<Button
mt={10}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
<ListGambarPenghargaan />
</SimpleGrid>
</Stack>
</Box>
);
}
export default GambarPerhargaan;

View File

@@ -0,0 +1,17 @@
import colors from '@/con/colors';
import { Box, Paper, Stack, Title } from '@mantine/core';
import React from 'react';
function ListPenghargaan() {
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>List Penghargaan</Title>
</Stack>
</Paper>
</Box>
);
}
export default ListPenghargaan;

View File

@@ -0,0 +1,40 @@
import colors from '@/con/colors';
import { Box, Button, Group, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
import React from 'react';
import { DesaEditor } from '../../../_com/desaEditor';
import ListPenghargaan from './listPage';
function Penghargaan() {
return (
<Box py={10}>
<Stack>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={3}>Penghargaan</Title>
<Box>
<Text fz={"sm"} fw={"bold"}>Deskripsi Penghargaan</Text>
<DesaEditor
showSubmit={false}
/>
</Box>
<Group>
<Button
mt={10}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
<ListPenghargaan/>
</SimpleGrid>
</Stack>
</Box>
);
}
export default Penghargaan;

View File

@@ -1,62 +1,75 @@
'use client' 'use client'
import { Group, Select, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core'; import { Box, Group, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import React from 'react'; import React from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import stateDesaPengumuman from '../../_state/desa/pengumuman'; import stateDesaPengumuman from '../../_state/desa/pengumuman';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { Prisma } from '@prisma/client'; import { Prisma } from '@prisma/client';
import { BeritaEditor } from '../berita/_com/BeritaEditor'; import { BeritaEditor } from '../berita/_com/BeritaEditor';
import colors from '@/con/colors';
function Page() { function Page() {
return ( return (
<Stack> <Box>
<Title order={3}>Pengumuman</Title>
<SimpleGrid cols={{ <SimpleGrid cols={{
base: 1, md: 2 base: 1, md: 2
}}> }}>
<PengumumanList />
<PengumumanCreate /> <PengumumanCreate />
<PengumumanList />
</SimpleGrid> </SimpleGrid>
</Stack> </Box>
); );
} }
function PengumumanCreate() {
const pengumumanState = useProxy(stateDesaPengumuman)
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<SelectCategory onChange={(val) => {
pengumumanState.pengumuman.create.form.categoryPengumumanId = val.id
}} />
<TextInput onChange={(val) => {
pengumumanState.pengumuman.create.form.judul = val.target.value
}} label={<Text fz={"sm"} fw={"bold"}>Judul</Text>} placeholder='masukkan judul' />
<TextInput onChange={(val) => {
pengumumanState.pengumuman.create.form.deskripsi = val.target.value
}} label={<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>} placeholder='masukkan deskripsi' />
<BeritaEditor onSubmit={(val) => {
pengumumanState.pengumuman.create.form.content = val
pengumumanState.pengumuman.create.create()
}} />
</Stack>
</Paper>
</Box>
)
}
function PengumumanList() { function PengumumanList() {
const pengumumanState = useProxy(stateDesaPengumuman) const pengumumanState = useProxy(stateDesaPengumuman)
useShallowEffect(() => { useShallowEffect(() => {
pengumumanState.pengumuman.findMany.load() pengumumanState.pengumuman.findMany.load()
}, []) }, [])
if (!pengumumanState.pengumuman.findMany.data) return <Stack> if (!pengumumanState.pengumuman.findMany.data) return <Stack py={10}>
{Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)} {Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)}
</Stack> </Stack>
return <Stack> return (
<Text>Announcement List</Text> <Box py={10}>
{pengumumanState.pengumuman.findMany.data?.map((item) => ( <Paper bg={colors['white-1']} p={'md'}>
<Text key={item.id}>{item.judul}</Text> <Stack gap={"xs"}>
))} <Text fz={"xl"} fw={"bold"}>List Pengumuman</Text>
</Stack>; {pengumumanState.pengumuman.findMany.data?.map((item) => (
} <Text key={item.id}>{item.judul}</Text>
))}
function PengumumanCreate() { </Stack>
const pengumumanState = useProxy(stateDesaPengumuman) </Paper>
</Box>
)
return <Stack gap={"md"}>
<Text>Create Some Announcement</Text>
<SelectCategory onChange={(val) => {
pengumumanState.pengumuman.create.form.categoryPengumumanId = val.id
}} />
<TextInput onChange={(val) => {
pengumumanState.pengumuman.create.form.judul = val.target.value
}} label={"Judul"} placeholder='masukkan judul' />
<TextInput onChange={(val) => {
pengumumanState.pengumuman.create.form.deskripsi = val.target.value
}} label={"Deskripsi"} placeholder='masukkan deskripsi' />
<BeritaEditor onSubmit={(val) => {
pengumumanState.pengumuman.create.form.content = val
pengumumanState.pengumuman.create.create()
}} />
</Stack>
} }
function SelectCategory({ onChange }: { function SelectCategory({ onChange }: {
@@ -75,7 +88,7 @@ function SelectCategory({ onChange }: {
if (!pengumumanState.category.findMany.data) return <Skeleton h={40} /> if (!pengumumanState.category.findMany.data) return <Skeleton h={40} />
return <Group> return <Group>
{/* {JSON.stringify(pengumumanState.category.findMany.data)} */} {/* {JSON.stringify(pengumumanState.category.findMany.data)} */}
<Select placeholder='pilih kategori' label={"select katagori"} data={pengumumanState.category.findMany.data.map((item) => ({ <Select placeholder='pilih kategori' label={<Text fz={"sm"} fw={"bold"}>Pilih Kategori</Text>} data={pengumumanState.category.findMany.data.map((item) => ({
value: item.id, value: item.id,
label: item.name label: item.name
}))} onChange={(v) => { }))} onChange={(v) => {

View File

@@ -0,0 +1,17 @@
import colors from '@/con/colors';
import { Box, Paper, Stack, Title } from '@mantine/core';
import React from 'react';
function ListPotensi() {
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>List Potensi Desa</Title>
</Stack>
</Paper>
</Box>
);
}
export default ListPotensi;

View File

@@ -1,11 +1,40 @@
import React from 'react'; import colors from '@/con/colors';
import { Box, Button, Group, Paper, SimpleGrid, Stack, TextInput, Title } from '@mantine/core';
import ListPotensi from './listPage';
function Page() { function Potensi() {
return ( return (
<div> <Box py={10}>
Potensi <Stack gap={"xs"}>
</div> <Title order={3}>Potensi Desa</Title>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<TextInput
label="Nama Potensi"
placeholder='masukkan nama potensi'
/>
<TextInput
label="Kategori Potensi"
placeholder='masukkan kategori potensi'
/>
<Group>
<Button
mt={10}
bg={colors['blue-button']}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
<ListPotensi />
</SimpleGrid>
</Stack>
</Box>
); );
} }
export default Page; export default Potensi;

View File

@@ -0,0 +1,17 @@
import colors from '@/con/colors';
import { Box, Paper, Stack, Title } from '@mantine/core';
import React from 'react';
function ListPage() {
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>List Sejarah Desa</Title>
</Stack>
</Paper>
</Box>
);
}
export default ListPage;

View File

@@ -1,8 +1,31 @@
'use client'
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Group, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core'; import { Box, Button, Group, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
import { DesaEditor } from '../../../_com/desaEditor'; import { useProxy } from 'valtio/utils';
import DesaEditorText from '../../../_com/desaEditorText';
import ListPage from './listPage';
import { useShallowEffect } from '@mantine/hooks';
function SejarahDesa() { function SejarahDesa() {
const stateSejarah = useProxy(stateProfileDesa.Sejarah)
useShallowEffect(() => {
if (!stateSejarah.findById.data) {
stateSejarah.findById.initialize()
}
}, [])
const submit = () => {
if (stateSejarah.findById.data?.id && stateSejarah.findById.data.sejarah) {
stateSejarah.update.save({
id: stateSejarah.findById.data.id,
sejarah: stateSejarah.findById.data.sejarah
})
}
}
return ( return (
<Box py={10}> <Box py={10}>
<SimpleGrid cols={{ base: 1, md: 2 }}> <SimpleGrid cols={{ base: 1, md: 2 }}>
@@ -11,11 +34,21 @@ function SejarahDesa() {
<Stack> <Stack>
<Title order={3}>Sejarah Desa</Title> <Title order={3}>Sejarah Desa</Title>
<Text fw={"bold"}>Deskripsi Sejarah Desa</Text> <Text fw={"bold"}>Deskripsi Sejarah Desa</Text>
<DesaEditor showSubmit={false}/> <DesaEditorText
key={stateSejarah.findById.data?.id ?? 'new'}
showSubmit={false}
onChange={(val) => {
if (stateSejarah.findById.data) {
stateSejarah.findById.data.sejarah = val
}
}}
initialContent={stateSejarah.findById.data?.sejarah ?? ""}
/>
<Group> <Group>
<Button <Button
mt={10} mt={10}
bg={colors['blue-button']} bg={colors['blue-button']}
onClick={submit}
> >
Submit Submit
</Button> </Button>
@@ -23,13 +56,7 @@ function SejarahDesa() {
</Stack> </Stack>
</Paper> </Paper>
</Box> </Box>
<Box> <ListPage />
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>List Sejarah Desa</Title>
</Stack>
</Paper>
</Box>
</SimpleGrid> </SimpleGrid>
</Box> </Box>
); );

View File

@@ -0,0 +1,93 @@
'use client'
import colors from '@/con/colors';
import { Button, Stack } from '@mantine/core';
import { Link, RichTextEditor } from '@mantine/tiptap';
import Highlight from '@tiptap/extension-highlight';
import SubScript from '@tiptap/extension-subscript';
import Superscript from '@tiptap/extension-superscript';
import TextAlign from '@tiptap/extension-text-align';
import Underline from '@tiptap/extension-underline';
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
const content =
'<h2 style="text-align: center;">Welcome to Mantine rich text editor</h2><p><code>RichTextEditor</code> component focuses on usability and is designed to be as simple as possible to bring a familiar editing experience to regular users. <code>RichTextEditor</code> is based on <a href="https://tiptap.dev/" rel="noopener noreferrer" target="_blank">Tiptap.dev</a> and supports all of its features:</p><ul><li>General text formatting: <strong>bold</strong>, <em>italic</em>, <u>underline</u>, <s>strike-through</s> </li><li>Headings (h1-h6)</li><li>Sub and super scripts (<sup>&lt;sup /&gt;</sup> and <sub>&lt;sub /&gt;</sub> tags)</li><li>Ordered and bullet lists</li><li>Text align&nbsp;</li><li>And all <a href="https://tiptap.dev/extensions" target="_blank" rel="noopener noreferrer">other extensions</a></li></ul>';
export function KesehatanEditor({showSubmit = true} : {
showSubmit: boolean
}) {
const editor = useEditor({
extensions: [
StarterKit,
Underline,
Link,
Superscript,
SubScript,
Highlight,
TextAlign.configure({ types: ['heading', 'paragraph'] }),
],
immediatelyRender: false,
content,
});
return (
<Stack>
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar sticky stickyOffset={60}>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold />
<RichTextEditor.Italic />
<RichTextEditor.Underline />
<RichTextEditor.Strikethrough />
<RichTextEditor.ClearFormatting />
<RichTextEditor.Highlight />
<RichTextEditor.Code />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.H1 />
<RichTextEditor.H2 />
<RichTextEditor.H3 />
<RichTextEditor.H4 />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Blockquote />
<RichTextEditor.Hr />
<RichTextEditor.BulletList />
<RichTextEditor.OrderedList />
<RichTextEditor.Subscript />
<RichTextEditor.Superscript />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Link />
<RichTextEditor.Unlink />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.AlignLeft />
<RichTextEditor.AlignCenter />
<RichTextEditor.AlignJustify />
<RichTextEditor.AlignRight />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Undo />
<RichTextEditor.Redo />
</RichTextEditor.ControlsGroup>
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
{showSubmit && (
<Button
mt={10}
bg={colors['blue-button']}
>
Submit
</Button>
)}
</Stack>
);
}

View File

@@ -0,0 +1,95 @@
'use client'
import { Button, Stack } from '@mantine/core';
import { Link, RichTextEditor } from '@mantine/tiptap';
import Highlight from '@tiptap/extension-highlight';
import SubScript from '@tiptap/extension-subscript';
import Superscript from '@tiptap/extension-superscript';
import TextAlign from '@tiptap/extension-text-align';
import Underline from '@tiptap/extension-underline';
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
function KesehatanEditorText({ onSubmit, onChange, showSubmit = true, initialContent = '', }: {
onSubmit?: (val: string) => void,
onChange: (val: string) => void,
showSubmit?: boolean,
initialContent?: string }) {
const editor = useEditor({
extensions: [
StarterKit,
Underline,
Link,
Superscript,
SubScript,
Highlight,
TextAlign.configure({ types: ['heading', 'paragraph'] }),
],
immediatelyRender: false,
content: initialContent,
onUpdate : ({editor}) => {
onChange(editor.getHTML())
}
});
return (
<Stack>
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar sticky stickyOffset={60}>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold />
<RichTextEditor.Italic />
<RichTextEditor.Underline />
<RichTextEditor.Strikethrough />
<RichTextEditor.ClearFormatting />
<RichTextEditor.Highlight />
<RichTextEditor.Code />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.H1 />
<RichTextEditor.H2 />
<RichTextEditor.H3 />
<RichTextEditor.H4 />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Blockquote />
<RichTextEditor.Hr />
<RichTextEditor.BulletList />
<RichTextEditor.OrderedList />
<RichTextEditor.Subscript />
<RichTextEditor.Superscript />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Link />
<RichTextEditor.Unlink />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.AlignLeft />
<RichTextEditor.AlignCenter />
<RichTextEditor.AlignJustify />
<RichTextEditor.AlignRight />
</RichTextEditor.ControlsGroup>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Undo />
<RichTextEditor.Redo />
</RichTextEditor.ControlsGroup>
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
{showSubmit && (
<Button onClick={() => {
if (!editor) return
onSubmit?.(editor?.getHTML())
}}>Submit</Button>
)}
</Stack>
);
}
export default KesehatanEditorText;

View File

@@ -1,21 +1,24 @@
'use client' 'use client'
import { Box, Text } from '@mantine/core'; import { Box, Paper, Text } from '@mantine/core';
import React from 'react'; import React from 'react';
import { KesehatanEditor } from '../../../_com/kesehatanEditor'; import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan'; import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import colors from '@/con/colors';
function DoctorSignUI() { function DoctorSignUI() {
const doctorSign = useProxy(stateArtikelKesehatan.doctorSign) const doctorSign = useProxy(stateArtikelKesehatan.doctorSign)
return ( return (
<Box> <Box>
<Text fw={"bold"}>Kapan Harus ke Dokter</Text> <Paper bg={colors['white-1']} p={'md'}>
<KesehatanEditor <Text fw={"bold"}>Kapan Harus ke Dokter</Text>
showSubmit={false} <KesehatanEditor
onChange={(val) => { showSubmit={false}
doctorSign.create.form.content = val onChange={(val) => {
}} doctorSign.create.form.content = val
/> }}
/>
</Paper>
</Box> </Box>
); );
} }

View File

@@ -1,27 +1,32 @@
'use client' 'use client'
import { Box, Text, TextInput } from '@mantine/core'; import { Box, Paper, Stack, Text, TextInput } from '@mantine/core';
import React from 'react'; import React from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan'; import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import { KesehatanEditor } from '../../../_com/kesehatanEditor'; import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function FirstAidUI() { function FirstAidUI() {
const firstAidState = useProxy(stateArtikelKesehatan.firstAid) const firstAidState = useProxy(stateArtikelKesehatan.firstAid)
return ( return (
<Box> <Box>
<TextInput <Paper bg={colors['white-1']} p={'md'}>
label={<Text fw={"bold"}>Title Pertolongan Pertama</Text>} <Stack gap={"xs"}>
placeholder="Masukkan title" <TextInput
onChange={(val) => { label={<Text fw={"bold"}>Judul Pertolongan Pertama</Text>}
firstAidState.create.form.title = val.target.value placeholder="Masukkan judul"
}} onChange={(val) => {
/> firstAidState.create.form.title = val.target.value
<KesehatanEditor }}
showSubmit={false} />
onChange={(val) => { <KesehatanEditor
firstAidState.create.form.content = val showSubmit={false}
}} onChange={(val) => {
/> firstAidState.create.form.content = val
}}
/>
</Stack>
</Paper>
</Box> </Box>
); );
} }

View File

@@ -1,21 +1,26 @@
'use client' 'use client'
import { Box, Text } from '@mantine/core'; import { Box, Paper, Stack, Text } from '@mantine/core';
import React from 'react'; import React from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor'; import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan'; import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import colors from '@/con/colors';
function IntoductionUI() { function IntoductionUI() {
const introduction = useProxy(stateArtikelKesehatan.introduction) const introduction = useProxy(stateArtikelKesehatan.introduction)
return ( return (
<Box py={10}> <Box>
<Text fw={"bold"}>Pendahuluan</Text> <Paper bg={colors['white-1']} p={'md'}>
<KesehatanEditor <Stack gap={"xs"}>
showSubmit={false} <Text fw={"bold"}>Pendahuluan</Text>
onChange={(val) => { <KesehatanEditor
introduction.create.form.content = val; showSubmit={false}
}} onChange={(val) => {
/> introduction.create.form.content = val;
}}
/>
</Stack>
</Paper>
</Box> </Box>
); );
} }

View File

@@ -1,33 +1,38 @@
'use client' 'use client'
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan'; import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import { Box, Text, TextInput } from '@mantine/core'; import colors from '@/con/colors';
import { Box, Paper, Stack, Text, TextInput } from '@mantine/core';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
function MythFactUI() { function MythFactUI() {
const mythFact = useProxy(stateArtikelKesehatan.mythFact) const mythFact = useProxy(stateArtikelKesehatan.mythFact)
return ( return (
<Box py={10}> <Box>
<TextInput <Paper bg={colors['white-1']} p={'md'}>
label={<Text fw={"bold"}>Title Pertolongan Pertama Penyakit</Text>} <Stack gap={"xs"}>
placeholder="Masukkan title" <TextInput
onChange={(val) => { label={<Text fw={"bold"}>Judul Pertolongan Pertama Penyakit</Text>}
mythFact.create.form.title = val.target.value placeholder="Masukkan judul"
}} onChange={(val) => {
/> mythFact.create.form.title = val.target.value
<TextInput }}
label={<Text fw={"bold"}>Mitos</Text>} />
placeholder="Masukkan mitos" <TextInput
onChange={(val) => { label={<Text fw={"bold"}>Mitos</Text>}
mythFact.create.form.mitos = val.target.value placeholder="Masukkan mitos"
}} onChange={(val) => {
/> mythFact.create.form.mitos = val.target.value
<TextInput }}
label={<Text fw={"bold"}>Fakta</Text>} />
placeholder="Masukkan fakta" <TextInput
onChange={(val) => { label={<Text fw={"bold"}>Fakta</Text>}
mythFact.create.form.fakta = val.target.value placeholder="Masukkan fakta"
}} onChange={(val) => {
/> mythFact.create.form.fakta = val.target.value
}}
/>
</Stack>
</Paper>
</Box> </Box>
); );
} }

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import { Box, Button, Center, SimpleGrid, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core'; import { Box, Button, Center, Group, Paper, SimpleGrid, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import IntoductionUI from './introduction/page'; import IntoductionUI from './introduction/page';
import SymptomUI from './symptom/page'; import SymptomUI from './symptom/page';
import PreventionUI from './prevention/page'; import PreventionUI from './prevention/page';
@@ -39,19 +39,25 @@ function ArtikelKesehatan() {
base: 1, md: 2 base: 1, md: 2
}}> }}>
<Box > <Box >
<Title order={3}>Artikel Kesehatan</Title> <Stack gap={"xs"}>
<IntoductionUI /> <Title order={4}>Artikel Kesehatan</Title>
<SymptomUI /> <IntoductionUI />
<PreventionUI /> <SymptomUI />
<FirstAidUI /> <PreventionUI />
<MythFactUI /> <FirstAidUI />
<DoctorSignUI /> <MythFactUI />
<Button mt={10} onClick={submitAllForms}>Submit</Button> <DoctorSignUI />
<Group>
<Button bg={colors['blue-button']} mt={10} onClick={submitAllForms}>Submit</Button>
</Group>
</Stack>
</Box> </Box>
<Box> <Box>
<Title order={3}>List Artikel Kesehatan</Title> <Stack gap={"xs"}>
<AllList /> <Title order={4}>Data Artikel Kesehatan</Title>
<AllList />
</Stack>
</Box> </Box>
</SimpleGrid> </SimpleGrid>
</Stack> </Stack>
@@ -78,71 +84,88 @@ function AllList() {
) return <Stack> ) return <Stack>
{Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)} {Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)}
</Stack> </Stack>
return <Stack> return <Stack gap={"xs"}>
<Title order={4}>Intoduction</Title> {/* Introduction */}
{listState.introduction.findMany.data?.map((item) => ( <Paper bg={colors['white-1']} p={'md'}>
<Box key={item.id}> <Title order={4}>Pendahuluan</Title>
<Text dangerouslySetInnerHTML={{ __html: item.content }}></Text> {listState.introduction.findMany.data?.map((item) => (
</Box> <Box key={item.id}>
))} <Text dangerouslySetInnerHTML={{ __html: item.content }}></Text>
</Box>
))}
</Paper>
{/* Symptom */} {/* Symptom */}
{listState.symptom.findMany.data?.map((item) => ( <Paper bg={colors['white-1']} p={'md'}>
<Box key={item.id}> <Title order={4}>Gejala Penyakit</Title>
<Title order={4}>{item.title}</Title> {listState.symptom.findMany.data?.map((item) => (
<Text dangerouslySetInnerHTML={{ __html: item.content }}></Text> <Box key={item.id}>
</Box> <Title order={4}>{item.title}</Title>
))} <Text dangerouslySetInnerHTML={{ __html: item.content }}></Text>
</Box>
))}
</Paper>
{/* Prevention */} {/* Prevention */}
{listState.prevention.findMany.data?.map((item) => ( <Paper bg={colors['white-1']} p={'md'}>
<Box key={item.id}> <Title order={4}>Pencegahan Penyakit</Title>
<Title order={4}>{item.title}</Title> {listState.prevention.findMany.data?.map((item) => (
<Text dangerouslySetInnerHTML={{ __html: item.content }}></Text> <Box key={item.id}>
</Box> <Title order={4}>{item.title}</Title>
))} <Text dangerouslySetInnerHTML={{ __html: item.content }}></Text>
</Box>
))}
</Paper>
{/* First Aid */} {/* First Aid */}
{listState.firstAid.findMany.data?.map((item) => ( <Paper bg={colors['white-1']} p={'md'}>
<Box key={item.id}> <Title order={4}>Pertolongan Pertama</Title>
<Title order={4}>{item.title}</Title> {listState.firstAid.findMany.data?.map((item) => (
<Text dangerouslySetInnerHTML={{ __html: item.content }}></Text> <Box key={item.id}>
</Box> <Title order={4}>{item.title}</Title>
))} <Text dangerouslySetInnerHTML={{ __html: item.content }}></Text>
</Box>
))}
</Paper>
{/* Myth Fact */} {/* Myth Fact */}
{listState.mythFact.findMany.data?.map((item) => ( <Paper bg={colors['white-1']} p={'md'}>
<Box key={item.id}> <Title order={4}>Mitos vs Fakta</Title>
<Title order={4}>{item.title}</Title> {listState.mythFact.findMany.data?.map((item) => (
<Table <Box key={item.id}>
striped <Title order={4}>{item.title}</Title>
highlightOnHover <Table
withTableBorder striped
withColumnBorders highlightOnHover
bg={colors['white-1']} withTableBorder
> withColumnBorders
<TableThead > bg={colors['white-1']}
<TableTr > >
<TableTh > <TableThead >
<Center>Mitos</Center> <TableTr >
</TableTh> <TableTh >
<TableTh > <Center>Mitos</Center>
<Center>Fakta</Center> </TableTh>
</TableTh> <TableTh >
</TableTr> <Center>Fakta</Center>
</TableThead> </TableTh>
<TableTbody > </TableTr>
<TableTr> </TableThead>
<TableTd ta="center">{item.mitos}</TableTd> <TableTbody >
<TableTd ta="center">{item.fakta}</TableTd> <TableTr>
</TableTr> <TableTd ta="center">{item.mitos}</TableTd>
</TableTbody> <TableTd ta="center">{item.fakta}</TableTd>
</Table> </TableTr>
</Box> </TableTbody>
))} </Table>
</Box>
))}
</Paper>
{/* Doctor Sign */} {/* Doctor Sign */}
<Title order={4}>Kapan Harus Ke Dokter?</Title> <Paper bg={colors['white-1']} p={'md'}>
{listState.doctorSign.findMany.data?.map((item) => ( <Title order={4}>Kapan Harus Ke Dokter?</Title>
<Box key={item.id}> {listState.doctorSign.findMany.data?.map((item) => (
<Text dangerouslySetInnerHTML={{ __html: item.content }}/> <Box key={item.id}>
</Box> <Text dangerouslySetInnerHTML={{ __html: item.content }} />
))} </Box>
))}
</Paper>
</Stack> </Stack>
} }

View File

@@ -1,28 +1,33 @@
'use client' 'use client'
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan'; import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import { Box, Text, TextInput } from '@mantine/core'; import { Box, Paper, Stack, Text, TextInput } from '@mantine/core';
import React from 'react'; import React from 'react';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor'; import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function PreventionUI() { function PreventionUI() {
const preventionState = useProxy(stateArtikelKesehatan.prevention) const preventionState = useProxy(stateArtikelKesehatan.prevention)
return ( return (
<Box py={10}> <Box>
<TextInput <Paper bg={colors['white-1']} p={'md'}>
mb={10} <Stack gap={"xs"}>
label={<Text fw={"bold"}>Title Pencegahan Penyakit</Text>} <TextInput
placeholder="Masukkan title" mb={10}
onChange={(val) => { label={<Text fw={"bold"}>Judul Pencegahan Penyakit</Text>}
preventionState.create.form.title = val.target.value placeholder="Masukkan judul"
}} onChange={(val) => {
/> preventionState.create.form.title = val.target.value
<KesehatanEditor }}
showSubmit={false} />
onChange={(val) => { <KesehatanEditor
preventionState.create.form.content = val showSubmit={false}
}} onChange={(val) => {
/> preventionState.create.form.content = val
}}
/>
</Stack>
</Paper>
</Box> </Box>
); );
} }

View File

@@ -1,27 +1,32 @@
'use client' 'use client'
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan'; import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import { Box, Text, TextInput } from '@mantine/core'; import { Box, Paper, Stack, Text, TextInput } from '@mantine/core';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor'; import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function SymptomUI() { function SymptomUI() {
const symptomState = useProxy(stateArtikelKesehatan.symptom) const symptomState = useProxy(stateArtikelKesehatan.symptom)
return ( return (
<Box py={10}> <Box>
<TextInput <Paper bg={colors['white-1']} p={'md'}>
mb={10} <Stack gap={"xs"}>
label={<Text fw={"bold"}>Title Gejala Penyakit</Text>} <TextInput
placeholder='masukkan title' mb={10}
onChange={(val) => { label={<Text fw={"bold"}>Judul Gejala Penyakit</Text>}
symptomState.create.form.title = val.target.value placeholder='masukkan judul'
}} onChange={(val) => {
/> symptomState.create.form.title = val.target.value
<KesehatanEditor }}
showSubmit={false} />
onChange={(val) => { <KesehatanEditor
symptomState.create.form.content = val showSubmit={false}
}} onChange={(val) => {
/> symptomState.create.form.content = val
}}
/>
</Stack>
</Paper>
</Box> </Box>
); );
} }

View File

@@ -1,12 +1,15 @@
import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan'; import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import { Box, Text, TextInput } from '@mantine/core'; import colors from '@/con/colors';
import { Box, Paper, Stack, Text, TextInput } from '@mantine/core';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
function DokterDanTenagaMedis() { function DokterDanTenagaMedis() {
const dokterdantenagamedisState = useProxy(stateFasilitasKesehatan.dokterdantenagamedis) const dokterdantenagamedisState = useProxy(stateFasilitasKesehatan.dokterdantenagamedis)
return ( return (
<Box> <Box>
<Text fw={"bold"}>Dokter & Tenaga Medis</Text> <Paper bg={colors['white-1']} p={"md"}>
<Stack gap={"xs"}>
<Text fw={"bold"}>Dokter & Tenaga Medis</Text>
<TextInput <TextInput
label="Nama Dokter" label="Nama Dokter"
placeholder='masukkan nama dokter' placeholder='masukkan nama dokter'
@@ -29,6 +32,8 @@ function DokterDanTenagaMedis() {
dokterdantenagamedisState.create.form.jadwal = val.target.value dokterdantenagamedisState.create.form.jadwal = val.target.value
}} }}
/> />
</Stack>
</Paper>
</Box> </Box>
); );
} }

View File

@@ -1,17 +1,20 @@
import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan'; import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import { Box, Text } from '@mantine/core'; import { Box, Paper, Text } from '@mantine/core';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor'; import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function FasilitasPendukung() { function FasilitasPendukung() {
const fasilitaspendukungState = useProxy(stateFasilitasKesehatan.fasilitaspendukung) const fasilitaspendukungState = useProxy(stateFasilitasKesehatan.fasilitaspendukung)
return <Box> return <Box>
<Text fw={"bold"}>Fasilitas Pendukung</Text> <Paper bg={colors['white-1']} p={'md'}>
<KesehatanEditor <Text fw={"bold"}>Fasilitas Pendukung</Text>
<KesehatanEditor
showSubmit={false} showSubmit={false}
onChange={(val) => { onChange={(val) => {
fasilitaspendukungState.create.form.content = val; fasilitaspendukungState.create.form.content = val;
}} /> }} />
</Paper>
</Box> </Box>
} }

View File

@@ -1,35 +1,40 @@
'use client' 'use client'
import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan'; import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import { Box, Text, TextInput } from '@mantine/core'; import colors from '@/con/colors';
import { Box, Paper, Stack, Text, TextInput } from '@mantine/core';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
function InformasiUmum() { function InformasiUmum() {
const infromasiState = useProxy(stateFasilitasKesehatan.informasiumum) const infromasiState = useProxy(stateFasilitasKesehatan.informasiumum)
return<Box> return <Box>
<Text fw={"bold"}>Informasi Umum</Text> <Paper bg={colors['white-1']} p={'md'}>
<TextInput <Text fw={"bold"}>Informasi Umum</Text>
label="Fasilitas" <Stack gap={"xs"}>
placeholder='masukkan nama fasilitas kesehatan' <TextInput
onChange={(val) => { label="Fasilitas"
infromasiState.create.form.fasilitas = val.target.value placeholder='masukkan nama fasilitas kesehatan'
}} onChange={(val) => {
/> infromasiState.create.form.fasilitas = val.target.value
<TextInput }}
label="Alamat" />
placeholder='masukkan alamat' <TextInput
onChange={(val) => { label="Alamat"
infromasiState.create.form.alamat = val.target.value placeholder='masukkan alamat'
}} onChange={(val) => {
/> infromasiState.create.form.alamat = val.target.value
<TextInput }}
mb={10} />
label="Jam Operasional" <TextInput
placeholder='masukkan jam operasional' mb={10}
onChange={(val) => { label="Jam Operasional"
infromasiState.create.form.jamOperasional = val.target.value placeholder='masukkan jam operasional'
}} onChange={(val) => {
/> infromasiState.create.form.jamOperasional = val.target.value
</Box> }}
/>
</Stack>
</Paper>
</Box>
} }
export default InformasiUmum; export default InformasiUmum;

View File

@@ -1,18 +1,21 @@
'use client' 'use client'
import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan'; import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import { Box, Text } from '@mantine/core'; import { Box, Paper, Text } from '@mantine/core';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor'; import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function LayananUnggulan() { function LayananUnggulan() {
const informasiumumState = useProxy(stateFasilitasKesehatan.layananunggulan) const informasiumumState = useProxy(stateFasilitasKesehatan.layananunggulan)
return <Box> return <Box>
<Paper bg={colors['white-1']} p={'md'}>
<Text fw={"bold"}>Layanan Unggulan</Text> <Text fw={"bold"}>Layanan Unggulan</Text>
<KesehatanEditor <KesehatanEditor
showSubmit={false} showSubmit={false}
onChange={(val) => { onChange={(val) => {
informasiumumState.create.form.content = val; informasiumumState.create.form.content = val;
}} /> }} />
</Paper>
</Box> </Box>
; ;
} }

View File

@@ -2,7 +2,7 @@
import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan'; import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Center, SimpleGrid, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core'; import { Box, Button, Center, Paper, SimpleGrid, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import DokterDanTenagaMedis from './dokterdantenagamedis/page'; import DokterDanTenagaMedis from './dokterdantenagamedis/page';
@@ -91,7 +91,7 @@ function FasilitasKesehatan() {
}}> }}>
<Box> <Box>
<Stack gap={'xs'}> <Stack gap={'xs'}>
<Title order={3}>Fasilitas Kesehatan</Title> <Title order={4}>Tambah Fasilitas Kesehatan</Title>
{/* Informasi Umum */} {/* Informasi Umum */}
<InformasiUmum /> <InformasiUmum />
{/* Layanan Unggulan */} {/* Layanan Unggulan */}
@@ -110,7 +110,7 @@ function FasilitasKesehatan() {
<Box> <Box>
<Stack gap={"xs"}> <Stack gap={"xs"}>
<Title order={3}>List Fasilitas Kesehatan</Title> <Title order={4}>Data Fasilitas Kesehatan</Title>
<AllList /> <AllList />
</Stack> </Stack>
</Box> </Box>
@@ -141,100 +141,113 @@ function AllList() {
{Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)} {Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)}
</Stack> </Stack>
return <Stack> return <Stack>
<Title order={4}>Informasi Umum</Title> <Paper bg={colors['white-1']} p={'md'}>
{allListState.informasiumum.findMany.data?.map((item) => ( <Title order={4}>Informasi Umum</Title>
<Box key={item.id}> {allListState.informasiumum.findMany.data?.map((item) => (
<Box key={item.id}>
<Text>{item.fasilitas}</Text> <Text>{item.fasilitas}</Text>
<Text>{item.alamat}</Text> <Text>{item.alamat}</Text>
<Text>{item.jamOperasional}</Text> <Text>{item.jamOperasional}</Text>
</Box> </Box>
))} ))}
</Paper>
<Title order={4}>Layanan Unggulan</Title> <Paper bg={colors['white-1']} p={'md'}>
{allListState.layananunggulan.findMany.data?.map((item) => ( <Title order={4}>Layanan Unggulan</Title>
<Box key={item.id}> {allListState.layananunggulan.findMany.data?.map((item) => (
<Text dangerouslySetInnerHTML={{ __html: item.content }} /> <Box key={item.id}>
</Box> <Text dangerouslySetInnerHTML={{ __html: item.content }} />
))} </Box>
))}
</Paper>
<Title order={4}>Dokter & Tenaga Medis</Title> <Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Dokter & Tenaga Medis</Title>
<Table <Table
striped striped
highlightOnHover highlightOnHover
withTableBorder withTableBorder
withColumnBorders withColumnBorders
bg={colors['white-1']} bg={colors['white-1']}
> >
<TableThead > <TableThead >
<TableTr > <TableTr >
<TableTh > <TableTh >
<Center>Nama</Center> <Center>Nama</Center>
</TableTh> </TableTh>
<TableTh > <TableTh >
<Center>Specialist</Center> <Center>Specialist</Center>
</TableTh> </TableTh>
<TableTh > <TableTh >
<Center>Jadwal</Center> <Center>Jadwal</Center>
</TableTh> </TableTh>
</TableTr>
</TableThead>
<TableTbody >
{allListState.dokterdantenagamedis.findMany.data?.map((item) => (
<TableTr key={item.id}>
<TableTd ta="center">{item.name}</TableTd>
<TableTd ta="center">Specialist {item.specialist}</TableTd>
<TableTd ta="center">{item.jadwal}</TableTd>
</TableTr> </TableTr>
))} </TableThead>
</TableTbody> <TableTbody >
</Table> {allListState.dokterdantenagamedis.findMany.data?.map((item) => (
<TableTr key={item.id}>
<TableTd ta="center">{item.name}</TableTd>
<TableTd ta="center">Specialist {item.specialist}</TableTd>
<TableTd ta="center">{item.jadwal}</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
<Title order={4}>Fasilitas Pendukung</Title> <Paper bg={colors['white-1']} p={'md'}>
{allListState.fasilitaspendukung.findMany.data?.map((item) => (
<Box key={item.id}>
<Text dangerouslySetInnerHTML={{ __html: item.content }} />
</Box>
))}
<Title order={4}>Tarif & Layanan</Title> <Title order={4}>Fasilitas Pendukung</Title>
<Table {allListState.fasilitaspendukung.findMany.data?.map((item) => (
suppressHydrationWarning <Box key={item.id}>
striped <Text dangerouslySetInnerHTML={{ __html: item.content }} />
highlightOnHover </Box>
withTableBorder ))}
withColumnBorders </Paper>
bg={colors['white-1']}
> <Paper bg={colors['white-1']} p={'md'}>
<TableThead> <Title order={4}>Tarif & Layanan</Title>
<TableTr> <Table
<TableTh> suppressHydrationWarning
Layanan striped
</TableTh> highlightOnHover
<TableTh> withTableBorder
Tarif withColumnBorders
</TableTh> bg={colors['white-1']}
</TableTr> >
</TableThead> <TableThead>
<TableTbody> <TableTr>
{allListState.tarifdanlayanan.findMany.data?.map((item) => ( <TableTh>
<TableTr key={item.id}> Layanan
<TableTd>{item.layanan}</TableTd> </TableTh>
<TableTd>Rp.{item.tarif}</TableTd> <TableTh>
Tarif
</TableTh>
</TableTr> </TableTr>
))} </TableThead>
</TableTbody> <TableTbody>
</Table> {allListState.tarifdanlayanan.findMany.data?.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.layanan}</TableTd>
<TableTd>Rp.{item.tarif}</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
<Title order={4}>Prosedur Pendaftaran</Title> <Paper bg={colors['white-1']} p={'md'}>
{allListState.prosedurpendaftaran.findMany.data?.map((item) => (
<Box key={item.id}>
<Text dangerouslySetInnerHTML={{__html: item.content}}/>
</Box>
))}
<Title order={4}>Prosedur Pendaftaran</Title>
{allListState.prosedurpendaftaran.findMany.data?.map((item) => (
<Box key={item.id}>
<Text dangerouslySetInnerHTML={{ __html: item.content }} />
</Box>
))}
</Paper>
</Stack> </Stack>
} }

View File

@@ -1,18 +1,21 @@
import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan'; import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import { Box, Text } from '@mantine/core'; import { Box, Text, Paper } from '@mantine/core';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor'; import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function ProsedurPendaftaran() { function ProsedurPendaftaran() {
const prosedurpendaftaranState = useProxy(stateFasilitasKesehatan.prosedurpendaftaran) const prosedurpendaftaranState = useProxy(stateFasilitasKesehatan.prosedurpendaftaran)
return <Box> return <Box>
<Paper bg={colors['white-1']} p={'md'}>
<Text fw={"bold"}>Prosedur Pendaftaran</Text> <Text fw={"bold"}>Prosedur Pendaftaran</Text>
<KesehatanEditor <KesehatanEditor
showSubmit={false} showSubmit={false}
onChange={(val) => { onChange={(val) => {
prosedurpendaftaranState.create.form.content = val; prosedurpendaftaranState.create.form.content = val;
}} /> }} />
</Box> </Paper>
</Box>
} }

View File

@@ -1,27 +1,32 @@
import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan'; import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
import { Box, Text, TextInput } from '@mantine/core'; import colors from '@/con/colors';
import { Box, Paper, Stack, Text, TextInput } from '@mantine/core';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
function TarifDanLayanan() { function TarifDanLayanan() {
const tarifdanlayanan = useProxy(stateFasilitasKesehatan.tarifdanlayanan) const tarifdanlayanan = useProxy(stateFasilitasKesehatan.tarifdanlayanan)
return <Box> return <Box>
<Text fw={"bold"}>Tarif & Layanan</Text> <Paper bg={colors['white-1']} p={'md'}>
<TextInput <Text fw={"bold"}>Tarif & Layanan</Text>
label="Tarif" <Stack gap={"xs"}>
placeholder='masukkan tarif' <TextInput
onChange={(val) => { label="Tarif"
tarifdanlayanan.create.form.tarif = val.target.value placeholder='masukkan tarif'
}} onChange={(val) => {
/> tarifdanlayanan.create.form.tarif = val.target.value
<TextInput }}
mb={10} />
label="Layanan" <TextInput
placeholder='masukkan layanan' mb={10}
onChange={(val) => { label="Layanan"
tarifdanlayanan.create.form.layanan = val.target.value placeholder='masukkan layanan'
}} onChange={(val) => {
/> tarifdanlayanan.create.form.layanan = val.target.value
</Box> }}
/>
</Stack>
</Paper>
</Box>
} }
export default TarifDanLayanan; export default TarifDanLayanan;

View File

@@ -2,7 +2,7 @@
'use client' 'use client'
import stategrafikKepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan'; import stategrafikKepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Box, Button, Group, Stack, TextInput, Title } from '@mantine/core'; import { Box, Button, Group, Paper, Skeleton, Stack, TextInput, Title } from '@mantine/core';
import { useMediaQuery, useShallowEffect } from '@mantine/hooks'; import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts'; import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
@@ -35,54 +35,65 @@ function GrafikHasilKepuasan() {
return ( return (
<Stack gap={"xs"}> <Stack py={10} gap={"xs"}>
<Title order={3}>Grafik Hasil Kepuasan</Title> <Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
<Box> <Title order={4}>Tambah Grafik Hasil Kepuasan</Title>
<TextInput <Stack gap={"xs"}>
w={{ base: '100%', md: '50%' }} <TextInput
label="Label" label="Label"
placeholder='Masukkan label yang diinginkan' placeholder='Masukkan label yang diinginkan'
value={grafikkepuasan.create.form.label} value={grafikkepuasan.create.form.label}
onChange={(val) => { onChange={(val) => {
grafikkepuasan.create.form.label = val.currentTarget.value grafikkepuasan.create.form.label = val.currentTarget.value
}} }}
/> />
<TextInput <TextInput
w={{ base: '100%', md: '50%' }} label="Jumlah Penderita"
label="Jumlah Penderita" type='number'
type='number' placeholder='Masukkan jumlah penderita'
placeholder='Masukkan jumlah penderita' value={grafikkepuasan.create.form.jumlah}
value={grafikkepuasan.create.form.jumlah} onChange={(val) => {
onChange={(val) => { grafikkepuasan.create.form.jumlah = val.currentTarget.value
grafikkepuasan.create.form.jumlah = val.currentTarget.value }}
}} />
/> <Group>
</Box> <Button bg={colors['blue-button']} mt={10}
<Group> onClick={async () => {
<Button mt={10} await grafikkepuasan.create.create();
onClick={async () => { await grafikkepuasan.findMany.load();
await grafikkepuasan.create.create(); if (grafikkepuasan.findMany.data) {
await grafikkepuasan.findMany.load(); setChartData(grafikkepuasan.findMany.data);
if (grafikkepuasan.findMany.data) { }
setChartData(grafikkepuasan.findMany.data); }}
} >Submit</Button>
}} </Group>
>Submit</Button> </Stack>
</Group> </Paper>
<Box h={400} w={{ base: "100%", md: "80%" }}> {!chartData ? (
<Title py={15} order={3}>Data Kelahiran & Kematian</Title> <Box style={{ width: '100%', minWidth: 300, minHeight: 300 }}>
<BarChart <Paper bg={colors['white-1']} p={'md'}>
width={isMobile ? 450 : isTablet ? 600 : 900} <Title py={15} order={4}>Data Hasil Kepuasan</Title>
height={380} <Skeleton h={400} />
data={chartData} </Paper>
> </Box>
<XAxis dataKey="label" /> ) : (
<YAxis /> <Box style={{ width: '100%', minWidth: 300, minHeight: 300 }}>
<Tooltip /> <Paper bg={colors['white-1']} p={'md'}>
<Legend /> <Title py={15} order={4}>Data Hasil Kepuasan</Title>
<Bar dataKey="jumlah" fill={colors['blue-button']} name="Jumlah" /> <BarChart
</BarChart> width={isMobile ? 250 : isTablet ? 300 : 350}
</Box> height={380}
data={chartData}
>
<XAxis dataKey="label" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="jumlah" fill={colors['blue-button']} name="Jumlah" />
</BarChart>
</Paper>
</Box>
)}
</Stack> </Stack>
); );
} }

View File

@@ -1,19 +1,24 @@
import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan'; import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
import { Box, Text } from '@mantine/core'; import { Box, Paper, Stack, Text } from '@mantine/core';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor'; import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function DeskripsiKegiatan() { function DeskripsiKegiatan() {
const deskripsiKegiatanState = useProxy(stateJadwalKegiatan.deskripsiKegiatan) const deskripsiKegiatanState = useProxy(stateJadwalKegiatan.deskripsiKegiatan)
return ( return (
<Box> <Box>
<Text pt={10} fw={"bold"}>Deskripsi Kegiatan</Text> <Paper bg={colors['white-1']} p={'md'}>
<KesehatanEditor <Stack gap={"xs"}>
showSubmit={false} <Text pt={10} fw={"bold"}>Deskripsi Kegiatan</Text>
onChange={(val) => { <KesehatanEditor
deskripsiKegiatanState.create.form.deskripsi = val showSubmit={false}
}} onChange={(val) => {
/> deskripsiKegiatanState.create.form.deskripsi = val
}}
/>
</Stack>
</Paper>
</Box> </Box>
); );
} }

View File

@@ -1,19 +1,24 @@
import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan'; import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
import { Box, Text } from '@mantine/core'; import { Box, Paper, Stack, Text } from '@mantine/core';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor'; import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function DokumenYangDiperlukan() { function DokumenYangDiperlukan() {
const dokumenDiperlukan = useProxy(stateJadwalKegiatan.dokumenjadwalkegiatan) const dokumenDiperlukan = useProxy(stateJadwalKegiatan.dokumenjadwalkegiatan)
return ( return (
<Box> <Box>
<Text pt={10} fw={"bold"}>Dokumen Yang Diperlukan</Text> <Paper bg={colors['white-1']} p={'md'}>
<KesehatanEditor <Stack gap={"xs"}>
showSubmit={false} <Text pt={10} fw={"bold"}>Dokumen Yang Diperlukan</Text>
onChange={(val)=> { <KesehatanEditor
dokumenDiperlukan.create.form.content = val; showSubmit={false}
}} onChange={(val) => {
/> dokumenDiperlukan.create.form.content = val;
}}
/>
</Stack>
</Paper>
</Box> </Box>
); );
} }

View File

@@ -1,12 +1,15 @@
import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan'; import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
import { Box, Text, TextInput } from '@mantine/core'; import colors from '@/con/colors';
import { Box, Paper, Stack, TextInput, Title } from '@mantine/core';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
function InformasiKegiatan() { function InformasiKegiatan() {
const informasiKegiatanState = useProxy(stateJadwalKegiatan.informasiKegiatan) const informasiKegiatanState = useProxy(stateJadwalKegiatan.informasiKegiatan)
return ( return (
<Box> <Box>
<Text pt={10} fw={"bold"}>Informasi Kegiatan</Text> <Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={4}>Informasi Kegiatan</Title>
<TextInput <TextInput
label="Nama Kegiatan" label="Nama Kegiatan"
placeholder="Masukkan nama kegiatan" placeholder="Masukkan nama kegiatan"
@@ -35,6 +38,8 @@ function InformasiKegiatan() {
informasiKegiatanState.create.form.lokasi = val.target.value informasiKegiatanState.create.form.lokasi = val.target.value
}} }}
/> />
</Stack>
</Paper>
</Box> </Box>
); );
} }

View File

@@ -1,19 +1,24 @@
import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan'; import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
import { Box, Text } from '@mantine/core'; import { Box, Paper, Stack, Text } from '@mantine/core';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor'; import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function LayananTersedia() { function LayananTersedia() {
const layananTersediaState = useProxy(stateJadwalKegiatan.layanantersedia) const layananTersediaState = useProxy(stateJadwalKegiatan.layanantersedia)
return ( return (
<Box> <Box>
<Text pt={10} fw={"bold"}>Layanan Yang Tersedia</Text> <Paper bg={colors['white-1']} p={'md'}>
<KesehatanEditor <Stack gap={"xs"}>
showSubmit={false} <Text pt={10} fw={"bold"}>Layanan Yang Tersedia</Text>
onChange={(val) => { <KesehatanEditor
layananTersediaState.create.form.content = val; showSubmit={false}
}} onChange={(val) => {
/> layananTersediaState.create.form.content = val;
}}
/>
</Stack>
</Paper>
</Box> </Box>
); );
} }

View File

@@ -1,6 +1,6 @@
'use client' 'use client'
import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan'; import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
import { Box, Button, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core'; import { Box, Button, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import DeskripsiKegiatan from './deskripsi_kegiatan/page'; import DeskripsiKegiatan from './deskripsi_kegiatan/page';
@@ -9,6 +9,7 @@ import InformasiKegiatan from './informasi_kegiatan/page';
import LayananTersedia from './layanan_yang_tersedia/page'; import LayananTersedia from './layanan_yang_tersedia/page';
import Pendaftaran from './pendaftaran/page'; import Pendaftaran from './pendaftaran/page';
import SyaratDanKetentuan from './syarat_dan_ketentuan/page'; import SyaratDanKetentuan from './syarat_dan_ketentuan/page';
import colors from '@/con/colors';
function JadwalKegiatan() { function JadwalKegiatan() {
const allState = useProxy(stateJadwalKegiatan) const allState = useProxy(stateJadwalKegiatan)
@@ -54,21 +55,25 @@ function JadwalKegiatan() {
base: 1, md: 2 base: 1, md: 2
}}> }}>
<Box> <Box>
<Title order={3}>Jadwal Kegiatan</Title> <Stack gap={"xs"}>
<InformasiKegiatan /> <Title order={4}>Tambah Jadwal Kegiatan</Title>
<DeskripsiKegiatan /> <InformasiKegiatan />
<LayananTersedia /> <DeskripsiKegiatan />
<SyaratDanKetentuan /> <LayananTersedia />
<DokumenYangDiperlukan /> <SyaratDanKetentuan />
<Pendaftaran /> <DokumenYangDiperlukan />
<Button mt={10} onClick={submitAllForms}> <Pendaftaran />
Submit <Button mt={10} onClick={submitAllForms}>
</Button> Submit
</Button>
</Stack>
</Box> </Box>
<Box> <Box>
<Title order={3}>List Jadwal Kegiatan</Title> <Stack gap={"xs"}>
<AllList /> <Title order={4}>Data Jadwal Kegiatan</Title>
<AllList />
</Stack>
</Box> </Box>
</SimpleGrid> </SimpleGrid>
</Stack> </Stack>
@@ -103,68 +108,82 @@ function AllList() {
); );
} }
return ( return (
<Stack> <Stack gap={"xs"}>
<Title order={4}>Informasi Kegiatan</Title>
{allList.informasiKegiatan.findMany.data?.map((item) => {
return (
<Box key={item.id}>
<Text>{item.name}</Text>
<Text>{item.tanggal}</Text>
<Text>{item.waktu}</Text>
<Text>{item.lokasi}</Text>
</Box>
)
})}
<Title order={4}>Deskripsi Kegiatan</Title> <Paper bg={colors['white-1']} p={'md'}>
{allList.deskripsiKegiatan.findMany.data?.map((item) => { <Title order={4}>Informasi Kegiatan</Title>
return ( {allList.informasiKegiatan.findMany.data?.map((item) => {
<Box key={item.id}> return (
<Text dangerouslySetInnerHTML={{ __html: item.deskripsi }} /> <Box key={item.id}>
</Box> <Text>{item.name}</Text>
) <Text>{item.tanggal}</Text>
})} <Text>{item.waktu}</Text>
<Text>{item.lokasi}</Text>
</Box>
)
})}
</Paper>
<Title order={4}>Layanan Yang Tersedia</Title> <Paper bg={colors['white-1']} p={'md'}>
{allList.layanantersedia.findMany.data?.map((item) => {
return (
<Box key={item.id}>
<Text dangerouslySetInnerHTML={{ __html: item.content }} />
</Box>
)
})}
<Title order={4}>Syarat dan Ketentuan</Title> <Title order={4}>Deskripsi Kegiatan</Title>
{allList.syaratketentuan.findMany.data?.map((item) => { {allList.deskripsiKegiatan.findMany.data?.map((item) => {
return ( return (
<Box key={item.id}> <Box key={item.id}>
<Text dangerouslySetInnerHTML={{ __html: item.content }} /> <Text dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box> </Box>
) )
})} })}
</Paper>
<Title order={4}>Dokumen Yang Diperlukan</Title> <Paper bg={colors['white-1']} p={'md'}>
{allList.dokumenjadwalkegiatan.findMany.data?.map((item) => { <Title order={4}>Layanan Yang Tersedia</Title>
return ( {allList.layanantersedia.findMany.data?.map((item) => {
<Box key={item.id}> return (
<Text dangerouslySetInnerHTML={{ __html: item.content }} /> <Box key={item.id}>
</Box> <Text dangerouslySetInnerHTML={{ __html: item.content }} />
) </Box>
})} )
})}
</Paper>
<Title order={4}>Pendaftaran</Title> <Paper bg={colors['white-1']} p={'md'}>
{allList.pendaftaranjadwal.findMany.data?.map((item) => { <Title order={4}>Syarat dan Ketentuan</Title>
return ( {allList.syaratketentuan.findMany.data?.map((item) => {
<Box key={item.id}> return (
<Text>{item.name}</Text> <Box key={item.id}>
<Text>{item.tanggal}</Text> <Text dangerouslySetInnerHTML={{ __html: item.content }} />
<Text>{item.namaOrangtua}</Text> </Box>
<Text>{item.nomor}</Text> )
<Text>{item.alamat}</Text> })}
<Text>{item.catatan}</Text> </Paper>
</Box>
) <Paper bg={colors['white-1']} p={'md'}>
})} <Title order={4}>Dokumen Yang Diperlukan</Title>
{allList.dokumenjadwalkegiatan.findMany.data?.map((item) => {
return (
<Box key={item.id}>
<Text dangerouslySetInnerHTML={{ __html: item.content }} />
</Box>
)
})}
</Paper>
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Pendaftaran</Title>
{allList.pendaftaranjadwal.findMany.data?.map((item) => {
return (
<Box key={item.id}>
<Text>{item.name}</Text>
<Text>{item.tanggal}</Text>
<Text>{item.namaOrangtua}</Text>
<Text>{item.nomor}</Text>
<Text>{item.alamat}</Text>
<Text>{item.catatan}</Text>
</Box>
)
})}
</Paper>
</Stack> </Stack>
) )
} }

View File

@@ -1,56 +1,61 @@
'use client' 'use client'
import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan'; import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
import { Box, Text, Textarea, TextInput } from '@mantine/core'; import colors from '@/con/colors';
import { Box, Paper, Stack, Text, Textarea, TextInput } from '@mantine/core';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
function Pendaftaran() { function Pendaftaran() {
const pendaftaran = useProxy(stateJadwalKegiatan.pendaftaranjadwal) const pendaftaran = useProxy(stateJadwalKegiatan.pendaftaranjadwal)
return ( return (
<Box> <Box>
<Text pt={10} fw={"bold"}>Pendaftaran</Text> <Paper bg={colors['white-1']} p={'md'}>
<TextInput <Stack gap={"xs"}>
label='Nama Balita' <Text pt={10} fw={"bold"}>Pendaftaran</Text>
placeholder='Masukkan nama balita' <TextInput
onChange={(val) => { label='Nama Balita'
pendaftaran.create.form.name = val.target.value placeholder='Masukkan nama balita'
}} onChange={(val) => {
/> pendaftaran.create.form.name = val.target.value
<TextInput }}
label='Tanggal' />
placeholder='Masukkan tanggal' <TextInput
onChange={(val) => { label='Tanggal'
pendaftaran.create.form.tanggal = val.target.value placeholder='Masukkan tanggal'
}} onChange={(val) => {
/> pendaftaran.create.form.tanggal = val.target.value
<TextInput }}
label='Nama Orang Tua / Wali' />
placeholder='Masukkan nama orang tua / wali' <TextInput
onChange={(val) => { label='Nama Orang Tua / Wali'
pendaftaran.create.form.namaOrangtua = val.target.value placeholder='Masukkan nama orang tua / wali'
}} onChange={(val) => {
/> pendaftaran.create.form.namaOrangtua = val.target.value
<TextInput }}
label='No. Telepon' />
placeholder='Masukkan no. telepon' <TextInput
onChange={(val) => { label='No. Telepon'
pendaftaran.create.form.nomor = val.target.value placeholder='Masukkan no. telepon'
}} onChange={(val) => {
/> pendaftaran.create.form.nomor = val.target.value
<TextInput }}
label='Alamat' />
placeholder='Masukkan alamat' <TextInput
onChange={(val) => { label='Alamat'
pendaftaran.create.form.alamat = val.target.value placeholder='Masukkan alamat'
}} onChange={(val) => {
/> pendaftaran.create.form.alamat = val.target.value
<Textarea }}
label='Catatan Khusus (Opsional)' />
placeholder='Masukkan catatan khusus' <Textarea
onChange={(val) => { label='Catatan Khusus (Opsional)'
pendaftaran.create.form.catatan = val.target.value placeholder='Masukkan catatan khusus'
}} onChange={(val) => {
/> pendaftaran.create.form.catatan = val.target.value
}}
/>
</Stack>
</Paper>
</Box> </Box>
); );
} }

View File

@@ -1,19 +1,24 @@
import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan'; import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan';
import { Box, Text } from '@mantine/core'; import { Box, Paper, Stack, Text } from '@mantine/core';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor'; import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function SyaratDanKetentuan() { function SyaratDanKetentuan() {
const syaratKetentuan = useProxy(stateJadwalKegiatan.syaratketentuan) const syaratKetentuan = useProxy(stateJadwalKegiatan.syaratketentuan)
return ( return (
<Box> <Box>
<Text pt={10} fw={"bold"}>Syarat dan Ketentuan</Text> <Paper bg={colors['white-1']} p={'md'}>
<KesehatanEditor <Stack gap={"xs"}>
showSubmit={false} <Text pt={10} fw={"bold"}>Syarat dan Ketentuan</Text>
onChange={(val) => { <KesehatanEditor
syaratKetentuan.create.form.content = val; showSubmit={false}
}} onChange={(val) => {
/> syaratKetentuan.create.form.content = val;
}}
/>
</Stack>
</Paper>
</Box> </Box>
); );
} }

View File

@@ -1,7 +1,8 @@
'use client' 'use client'
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import statePersentase from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran'; import statePersentase from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
import { Box, Button, Stack, TextInput, Title } from '@mantine/core'; import colors from '@/con/colors';
import { Box, Button, Group, Paper, Skeleton, Stack, TextInput, Title } from '@mantine/core';
import { useMediaQuery, useShallowEffect } from '@mantine/hooks'; import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts'; import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
@@ -32,76 +33,90 @@ function PersentaseDataKelahiranKematian() {
<Stack py={10}> <Stack py={10}>
{/* Form Input */} {/* Form Input */}
<Box> <Box>
<Title order={3}>Persentase Data Kelahiran & Kematian</Title> <Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
<TextInput <Title order={4}>Tambah Persentase Data Kelahiran & Kematian</Title>
w={{ base: '100%', md: '50%' }} <Stack gap={"xs"}>
label="Tahun" <TextInput
type="number" label="Tahun"
value={persentase.create.form.tahun} type="number"
placeholder="Masukkan tahun" value={persentase.create.form.tahun}
onChange={(val) => { placeholder="Masukkan tahun"
persentase.create.form.tahun = val.currentTarget.value; onChange={(val) => {
}} persentase.create.form.tahun = val.currentTarget.value;
/> }}
<TextInput />
w={{ base: '100%', md: '50%' }} <TextInput
label="Kematian Kasar" label="Kematian Kasar"
type="number" type="number"
value={persentase.create.form.kematianKasar} value={persentase.create.form.kematianKasar}
placeholder="Masukkan kematian kasar" placeholder="Masukkan kematian kasar"
onChange={(val) => { onChange={(val) => {
persentase.create.form.kematianKasar = val.currentTarget.value; persentase.create.form.kematianKasar = val.currentTarget.value;
}} }}
/> />
<TextInput <TextInput
w={{ base: '100%', md: '50%' }} label="Kematian Bayi"
label="Kematian Bayi" type="number"
type="number" value={persentase.create.form.kematianBayi}
value={persentase.create.form.kematianBayi} placeholder="Masukkan kematian bayi"
placeholder="Masukkan kematian bayi" onChange={(val) => {
onChange={(val) => { persentase.create.form.kematianBayi = val.currentTarget.value;
persentase.create.form.kematianBayi = val.currentTarget.value; }}
}} />
/> <TextInput
<TextInput label="Kelahiran Kasar"
w={{ base: '100%', md: '50%' }} type="number"
label="Kelahiran Kasar" value={persentase.create.form.kelahiranKasar}
type="number" placeholder="Masukkan kelahiran kasar"
value={persentase.create.form.kelahiranKasar} onChange={(val) => {
placeholder="Masukkan kelahiran kasar" persentase.create.form.kelahiranKasar = val.currentTarget.value;
onChange={(val) => { }}
persentase.create.form.kelahiranKasar = val.currentTarget.value; />
}} <Group>
/> <Button
<Button bg={colors['blue-button']}
mt={10} mt={10}
onClick={async () => { onClick={async () => {
await persentase.create.create(); await persentase.create.create();
await persentase.findMany.load(); await persentase.findMany.load();
if (persentase.findMany.data) { if (persentase.findMany.data) {
setChartData(persentase.findMany.data); setChartData(persentase.findMany.data);
} }
}} }}
> >
Submit Submit
</Button> </Button>
</Group>
</Stack>
</Paper>
</Box> </Box>
{/* Chart */} {/* Chart */}
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}> {!mounted && !chartData ? (
<Title pb={10} order={3}>Data Kelahiran & Kematian</Title> <Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
{mounted && chartData.length > 0 && ( <Paper bg={colors['white-1']} p={'md'}>
<BarChart width={isMobile ? 450 : isTablet ? 600 : 900} height={380} data={chartData} > <Title pb={10} order={3}>Data Kelahiran & Kematian</Title>
<XAxis dataKey="tahun" /> <Skeleton h={400} />
<YAxis /> </Paper>
<Tooltip /> </Box>
<Legend /> ) : (
<Bar dataKey="kematianKasar" fill="#f03e3e" name="Kematian Kasar" /> <Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
<Bar dataKey="kematianBayi" fill="#ff922b" name="Kematian Bayi" /> <Paper bg={colors['white-1']} p={'md'}>
<Bar dataKey="kelahiranKasar" fill="#4dabf7" name="Kelahiran Kasar" /> <Title pb={10} order={4}>Data Kelahiran & Kematian</Title>
</BarChart> {mounted && chartData.length > 0 && (
)} <BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={chartData} >
</Box> <XAxis dataKey="tahun" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="kematianKasar" fill="#f03e3e" name="Kematian Kasar" />
<Bar dataKey="kematianBayi" fill="#ff922b" name="Kematian Bayi" />
<Bar dataKey="kelahiranKasar" fill="#4dabf7" name="Kelahiran Kasar" />
</BarChart>
)}
</Paper>
</Box>
)}
</Stack> </Stack>
); );
} }

View File

@@ -1,5 +1,5 @@
import colors from '@/con/colors'; import colors from '@/con/colors';
import { Stack, Tabs, TabsList, TabsPanel, TabsTab } from '@mantine/core'; import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import ArtikelKesehatan from './_ui/artikel_kesehatan/page'; import ArtikelKesehatan from './_ui/artikel_kesehatan/page';
import FasilitasKesehatan from './_ui/fasilitas_kesehatan/page'; import FasilitasKesehatan from './_ui/fasilitas_kesehatan/page';
import GrafikHasilKepuasan from './_ui/grafik_hasil_kepuasan/page'; import GrafikHasilKepuasan from './_ui/grafik_hasil_kepuasan/page';
@@ -10,8 +10,9 @@ import PersentaseDataKelahiranKematian from './_ui/persentase_data_kelahiran_kem
function Page() { function Page() {
return ( return (
<Stack> <Stack>
<Title order={3}>Data Kesehatan Warga</Title>
<Tabs color={colors['blue-button']} variant='pills' defaultValue={"Persentase Kelahiran & Kematian"}> <Tabs color={colors['blue-button']} variant='pills' defaultValue={"Persentase Kelahiran & Kematian"}>
<TabsList > <TabsList bg={colors['BG-trans']} p={'xs'}>
<TabsTab value="Persentase Kelahiran & Kematian"> <TabsTab value="Persentase Kelahiran & Kematian">
Persentase Kelahiran & Kematian Persentase Kelahiran & Kematian
</TabsTab> </TabsTab>

View File

@@ -1,11 +1,54 @@
import colors from '@/con/colors';
import { Box, Button, Group, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
import React from 'react'; import React from 'react';
import { KesehatanEditor } from '../_com/kesehatanEditor';
function Page() { function Posyandu() {
return ( return (
<div> <Box>
Posyandu <Title order={3}>Kesehatan</Title>
</div> <SimpleGrid py={10} cols={{ base: 1, md: 2 }}>
<Box>
<Paper bg={colors['white-1']} p={"md"}>
<Stack gap={"xs"}>
<Title order={3}>Posyandu</Title>
<TextInput
label={<Text fz={"sm"} fw={"bold"}>Nama Posyandu</Text>}
placeholder='masukkan nama posyandu'
/>
<TextInput
label={<Text fz={"sm"} fw={"bold"}>Nomor Telepon Posyandu</Text>}
placeholder='masukkan nomor telepon posyandu'
/>
<Box>
<Text fz={"sm"} fw={"bold"}>Deskripsi Posyandu</Text>
<KesehatanEditor
showSubmit={false}
/>
</Box>
<Box>
<Text fz={"sm"} fw={"bold"}>Pelayanan Posyandu</Text>
<KesehatanEditor
showSubmit={false}
/>
</Box>
<Group>
<Button bg={colors['blue-button']}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box>
<Box>
<Paper bg={colors['white-1']} p={"md"}>
<Stack>
<Title order={3}>Preview Data Posyandu</Title>
</Stack>
</Paper>
</Box>
</SimpleGrid>
</Box>
); );
} }
export default Page; export default Posyandu;

View File

@@ -1,11 +1,34 @@
import React from 'react'; import colors from '@/con/colors';
import { Box, Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import UpdatePuskesmas from './ui/Edit-Puskesmas/updatePuskesmas';
import CreatePuskesmas from './ui/Tambah-Puskesmas/createPuskesmas';
function Page() { function Puskesmas() {
return ( return (
<div> <Stack gap={"xs"}>
Puskesmas <Box>
</div> <Title order={3}>Puskesmas</Title>
<Tabs defaultValue="create" color={colors['blue-button']} variant='pills'>
<TabsList mb={10} bg={colors['BG-trans']} p={'xs'}>
<TabsTab value="create" >
Tambah Puskesmas
</TabsTab>
<TabsTab value="update" >
Edit Puskesmas
</TabsTab>
</TabsList>
<TabsPanel value="create">
<CreatePuskesmas />
</TabsPanel>
<TabsPanel value="update">
<UpdatePuskesmas />
</TabsPanel>
</Tabs>
</Box>
</Stack>
); );
} }
export default Page; export default Puskesmas;

View File

@@ -0,0 +1,55 @@
import colors from '@/con/colors';
import { Box, Stack, SimpleGrid, Paper, Title, TextInput, Text } from '@mantine/core';
import React from 'react';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
function UpdatePuskesmas() {
return (
<Box>
<Stack gap={"xs"}>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Box>
<Paper bg={colors['white-1']} p={"md"}>
<Stack gap={"xs"}>
<Title order={4}>Edit Puskesmas</Title>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Nama Puskesmas</Text>}
placeholder='Masukkan nama puskesmas'
/>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>No Telp Puskesmas</Text>}
placeholder='Masukkan no telp puskesmas'
/>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>
<KesehatanEditor
showSubmit={false}
/>
</Box>
<Box>
<Text fw={"bold"} fz={"sm"}>Pelayanan Posyandu</Text>
<KesehatanEditor
showSubmit={false}
/>
</Box>
</Stack>
</Paper>
</Box>
<Box>
<Paper bg={colors['white-1']} p={"md"}>
<Stack gap={"xs"}>
<Title order={4}>Preview Data Puskesmas</Title>
<Text fw={"bold"} fz={"sm"}>Nama Puskesmas</Text>
<Text fw={"bold"} fz={"sm"}>No Telp Puskesmas</Text>
<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>
<Text fw={"bold"} fz={"sm"}>Pelayanan Posyandu</Text>
</Stack>
</Paper>
</Box>
</SimpleGrid>
</Stack>
</Box>
);
}
export default UpdatePuskesmas;

View File

@@ -0,0 +1,55 @@
import colors from '@/con/colors';
import { Box, Paper, SimpleGrid, Stack, Text, TextInput, Title } from '@mantine/core';
import React from 'react';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
function CreatePuskesmas() {
return (
<Box>
<Stack gap={"xs"}>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Box>
<Paper bg={colors['white-1']} p={"md"}>
<Stack gap={"xs"}>
<Title order={4}>Tambah Puskesmas</Title>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>Nama Puskesmas</Text>}
placeholder='Masukkan nama puskesmas'
/>
<TextInput
label={<Text fw={"bold"} fz={"sm"}>No Telp Puskesmas</Text>}
placeholder='Masukkan no telp puskesmas'
/>
<Box>
<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>
<KesehatanEditor
showSubmit={false}
/>
</Box>
<Box>
<Text fw={"bold"} fz={"sm"}>Pelayanan Posyandu</Text>
<KesehatanEditor
showSubmit={false}
/>
</Box>
</Stack>
</Paper>
</Box>
<Box>
<Paper bg={colors['white-1']} p={"md"}>
<Stack gap={"xs"}>
<Title order={4}>Preview Data Puskesmas</Title>
<Text fw={"bold"} fz={"sm"}>Nama Puskesmas</Text>
<Text fw={"bold"} fz={"sm"}>No Telp Puskesmas</Text>
<Text fw={"bold"} fz={"sm"}>Deskripsi</Text>
<Text fw={"bold"} fz={"sm"}>Pelayanan Posyandu</Text>
</Stack>
</Paper>
</Box>
</SimpleGrid>
</Stack>
</Box>
);
}
export default CreatePuskesmas;

View File

@@ -29,7 +29,7 @@ function ProfileList() {
<Box mb={20}> <Box mb={20}>
<Text fw={"bold"} mb={5}>Preview Gambar:</Text> <Text fw={"bold"} mb={5}>Preview Gambar:</Text>
<Image <Image
src={item.imageUrl} src={item.imageUrl ?? '/perbekel.png'}
alt="Profile" alt="Profile"
w={200} w={200}
/> />

View File

@@ -9,6 +9,8 @@ import { useProxy } from 'valtio/utils';
import stateProfilePPID from '../../_state/ppid/profile_ppid/profile_PPID'; import stateProfilePPID from '../../_state/ppid/profile_ppid/profile_PPID';
import { useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import ProfileList from './listPage'; import ProfileList from './listPage';
import { useState } from 'react';
import { toast } from 'react-toastify';
function Page() { function Page() {
@@ -23,14 +25,16 @@ function Page() {
} }
function ProfileCreate() { function ProfileCreate() {
const [isLoading, setIsLoading] = useState(false)
const allState = useProxy(stateProfilePPID) const allState = useProxy(stateProfilePPID)
// Initialize data if it doesn't exist // Initialize data if it doesn't exist
useShallowEffect(() => { useShallowEffect(() => {
if (!allState.findById.data) { if (!allState.findById.data && isLoading) {
allState.findById.initialize() allState.findById.initialize()
setIsLoading(false)
} }
}, []) }, [isLoading])
const submit = () => { const submit = () => {
if ( if (
@@ -41,6 +45,12 @@ function ProfileCreate() {
allState.findById.data?.unggulan allState.findById.data?.unggulan
) { ) {
allState.update.save(allState.findById.data) allState.update.save(allState.findById.data)
setIsLoading(true)
toast.success("success")
console.log("[SUBMIT SUCCESS]", JSON.stringify(allState.findById.data, null, 2))
allState.findById.initialize()
} else {
toast.error("error")
} }
} }

View File

@@ -1,6 +1,19 @@
'use client' 'use client'
import colors from "@/con/colors"; import colors from "@/con/colors";
import { ActionIcon, AppShell, AppShellHeader, AppShellMain, AppShellNavbar, Burger, Group, Image, NavLink, ScrollArea, Text } from "@mantine/core"; import {
ActionIcon,
AppShell,
AppShellHeader,
AppShellMain,
AppShellNavbar,
Burger,
Group,
Image,
NavLink,
ScrollArea,
Text
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks"; import { useDisclosure } from "@mantine/hooks";
import { IconChevronLeft, IconChevronRight } from "@tabler/icons-react"; import { IconChevronLeft, IconChevronRight } from "@tabler/icons-react";
import _ from 'lodash'; import _ from 'lodash';
@@ -10,75 +23,106 @@ import { navBar } from "./_com/list_PageAdmin";
export default function Layout({ children }: { children: React.ReactNode }) { export default function Layout({ children }: { children: React.ReactNode }) {
const [opened, { toggle }] = useDisclosure(); const [opened, { toggle }] = useDisclosure();
const [desktopOpened, { toggle: toggleDesktop }] = const [desktopOpened, { toggle: toggleDesktop }] = useDisclosure(true);
useDisclosure(true);
// Normalisasi semua segmen jadi lowercase
const segments = useSelectedLayoutSegments().map(s => _.lowerCase(s));
const segments = useSelectedLayoutSegments()
return ( return (
<AppShell <AppShell
suppressHydrationWarning suppressHydrationWarning
header={{ height: 60 }} header={{ height: 60 }}
navbar={{ navbar={{
width: 300, breakpoint: 'sm', collapsed: { mobile: !opened, desktop: !desktopOpened } width: 300,
breakpoint: 'sm',
collapsed: {
mobile: !opened,
desktop: !desktopOpened,
},
}} }}
padding={'md'} padding={'md'}
> >
<AppShellHeader bg={colors["white-trans-1"]}> <AppShellHeader bg={colors["white-trans-1"]}>
<Group px={10} align="center"> <Group px={10} align="center">
{!desktopOpened && <ActionIcon variant="light" onClick={toggleDesktop}><IconChevronRight /></ActionIcon>} {!desktopOpened && (
<Burger opened={opened} onClick={toggle} hiddenFrom="sm" size={'sm'} /> <ActionIcon variant="light" onClick={toggleDesktop}>
<ActionIcon w={50} h={50} variant="transparent" component={Link} href="/admin"> <IconChevronRight />
<Image py={5} src={'/assets/images/darmasaba-icon.png'} alt="" width={50} height={50} /> </ActionIcon>
)}
<Burger
opened={opened}
onClick={toggle}
hiddenFrom="sm"
size={'sm'}
/>
<ActionIcon
w={50}
h={50}
variant="transparent"
component={Link}
href="/admin"
>
<Image
py={5}
src={'/assets/images/darmasaba-icon.png'}
alt=""
width={50}
height={50}
/>
</ActionIcon> </ActionIcon>
<Text fw={'bold'} c={colors["blue-button"]} fz={'lg'}>Dashboard Admin</Text> <Text fw={'bold'} c={colors["blue-button"]} fz={'lg'}>
Dashboard Admin
</Text>
</Group> </Group>
</AppShellHeader> </AppShellHeader>
<AppShellNavbar
c={colors["blue-button"]} <AppShellNavbar c={colors["blue-button"]} component={ScrollArea}>
component={ScrollArea} <AppShell.Section>
>
<AppShell.Section >
{navBar.map((v, k) => { {navBar.map((v, k) => {
const isParentActive = segments.includes(_.lowerCase(v.name));
return ( return (
<NavLink <NavLink
c={_.lowerCase(v.name) == segments[1] ? colors["blue-button"] : "grey"}
key={k} key={k}
defaultOpened={_.lowerCase(v.name) == segments[1]} defaultOpened={isParentActive}
// onClick={() => setActive(k)} c={isParentActive ? colors["blue-button"] : "grey"}
label={<Text label={
style={{ fontWeight: _.lowerCase(v.name) == segments[1] ? "bold" : "normal" }} <Text style={{ fontWeight: isParentActive ? "bold" : "normal" }}>
>{v.name}</Text>} {v.name}
</Text>
}
> >
{v.children.map((child, key) => { {v.children.map((child, key) => {
const isChildActive = segments.includes(_.lowerCase(child.name));
return ( return (
<NavLink <NavLink
c={_.lowerCase(child.name) == _.lowerCase(segments[2]) ? colors["blue-button"] : "grey"}
key={key} key={key}
href={child.path} href={child.path}
// active={isClient && Number(child.id) === active} c={isChildActive ? colors["blue-button"] : "grey"}
// onClick={() => setActive(Number(child.id))} label={
label={<Text <Text style={{ fontWeight: isChildActive ? "bold" : "normal" }}>
style={{ fontWeight: _.lowerCase(child.name) == _.lowerCase(segments[2]) ? "bold" : "normal" }} {child.name}
>{child.name}</Text>} </Text>
}
/> />
) );
})} })}
</NavLink> </NavLink>
) );
})} })}
</AppShell.Section> </AppShell.Section>
<AppShell.Section py={20}> <AppShell.Section py={20}>
<Group justify="end"> <Group justify="end">
<ActionIcon variant="light" onClick={toggleDesktop}><IconChevronLeft /></ActionIcon> <ActionIcon variant="light" onClick={toggleDesktop}>
<IconChevronLeft />
</ActionIcon>
</Group> </Group>
</AppShell.Section> </AppShell.Section>
</AppShellNavbar> </AppShellNavbar>
<AppShellMain bg={colors.Bg}>
{children} <AppShellMain bg={colors.Bg}>{children}</AppShellMain>
</AppShellMain>
</AppShell> </AppShell>
); );
} }

View File

@@ -0,0 +1,33 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function profileDesaFindById(context: Context) {
try {
const id = context?.params?.slugs?.[0];
// If no ID provided, get the first profile
if (!id) {
const data = await prisma.profileDesa.findFirst();
return {
success: true,
data,
};
}
const data = await prisma.profileDesa.findUniqueOrThrow({
where: { id },
});
return {
success: true,
data,
};
} catch (error) {
console.error("Error fetching profileDesa:", error);
return {
success: false,
message: error instanceof Error ? error.message : "Unknown error",
};
}
}

View File

@@ -1,12 +0,0 @@
import prisma from "@/lib/prisma";
export default async function profileDesaFindMany() {
const res = await prisma.profileDesa.findMany({
include: {
ProfilPerbekel: true,
},
});
return {
data: res,
};
}

View File

@@ -1,16 +1,16 @@
import Elysia, { t } from "elysia"; import Elysia, { t } from "elysia";
import profileDesaFindMany from "./find-many"; import lambangDesaUpdate from "./lambangDesa/update";
import lambangDesaUpdate from "./lambangDesa"; import maskotDesaUpdate from "./maskotDesa/update";
import maskotDesaUpdate from "./maskotDesa"; import profilePerbekelUpdate from "../profilePerbekel/update";
import profilePerbekelUpdate from "./profilePerbekel"; import sejarahDesaUpdate from "./sejarah/update";
import sejarahDesaUpdate from "./sejarahDesa"; import visimisiDesaUpdate from "./visimisiDesa/update";
import visimisiDesaUpdate from "./visimisiDesa"; import profileDesaFindById from "./find-by-id";
const ProfileDesa = new Elysia({ const ProfileDesa = new Elysia({
prefix: "/profile", prefix: "/profile",
tags: ["Desa/Profile"] tags: ["Desa/Profile"]
}) })
.get("/find-many", profileDesaFindMany) .get("/find-by-id", profileDesaFindById)
.post("/profilePerbekel/update", profilePerbekelUpdate, { .post("/profilePerbekel/update", profilePerbekelUpdate, {
body: t.Object({ body: t.Object({
id: t.String(), id: t.String(),
@@ -27,7 +27,7 @@ const ProfileDesa = new Elysia({
misi: t.String(), misi: t.String(),
}) })
}) })
.post("/sejarahDesa/update", sejarahDesaUpdate, { .post("/sejarah/update", sejarahDesaUpdate, {
body: t.Object({ body: t.Object({
id: t.String(), id: t.String(),
sejarah: t.String(), sejarah: t.String(),

View File

@@ -0,0 +1,63 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
import fs from "fs/promises";
import path from "path";
import { nanoid } from "nanoid";
const UPLOAD_DIR = process.env.WIBU_UPLOAD_DIR;
const fileStorageCreate = async (context: Context) => {
const body = (await context.body) as {
name: string;
file: File;
};
const file = body.file;
const name = body.name;
if (!file) {
return {
status: 400,
body: "No file uploaded",
};
}
if (!name) {
return {
status: 400,
body: "No name provided",
};
}
if (!UPLOAD_DIR) {
return {
status: 500,
body: "UPLOAD_DIR is not defined",
};
}
const pathName = "desa/ppid/profile-ppid";
const rootPath = path.join(UPLOAD_DIR, pathName);
await fs.mkdir(rootPath, { recursive: true });
const ext = file.name.split(".").pop();
const newName = nanoid() + "." + ext;
const data = await prisma.fileStorage.create({
data: {
name: newName,
path: rootPath,
mimeType: file.type,
link: `/api/fileStorage/findUnique/${newName}`,
},
});
await fs.writeFile(
path.join(rootPath, newName),
Buffer.from(await file.arrayBuffer())
);
return {
data,
};
};
export default fileStorageCreate;

View File

@@ -0,0 +1,6 @@
import prisma from "@/lib/prisma";
export const fileStorageFindMany = async () => {
const data = await prisma.fileStorage.findMany();
return data;
};

View File

@@ -0,0 +1,34 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
import fs from "fs/promises";
import path from "path";
const fileStorageFindUnique = async (context: Context) => {
const { name } = context.params;
const data = await prisma.fileStorage.findUnique({
where: {
name,
},
});
if (!data) {
context.set.status = "No Content";
return {
status: 404,
message: "File not found",
};
}
console.log(data);
const file = await fs.readFile(path.join(data.path, data.name));
context.set.headers = {
"Content-Type": data.mimeType,
"Content-Length": file.length,
};
return file;
};
export default fileStorageFindUnique;

View File

@@ -0,0 +1,19 @@
import Elysia, { t } from "elysia";
import fileStorageCreate from "./_lib/create";
import fileStorageFindUnique from "./_lib/findUniq";
import { fileStorageFindMany } from "./_lib/findMany";
const FileStorage = new Elysia({
prefix: "/api/fileStorage",
tags: ["FileStorage"],
})
.post("/create", fileStorageCreate, {
body: t.Object({
name: t.String(),
file: t.File(),
}),
})
.get("/findUnique/:name", fileStorageFindUnique)
.get("/findMany", fileStorageFindMany);
export default FileStorage;

View File

@@ -1,21 +1,22 @@
import prisma from "@/lib/prisma"; import prisma from "@/lib/prisma";
import { staticPlugin } from '@elysiajs/static'
import cors, { HTTPMethod } from "@elysiajs/cors"; import cors, { HTTPMethod } from "@elysiajs/cors";
import { staticPlugin } from "@elysiajs/static";
import swagger from "@elysiajs/swagger"; import swagger from "@elysiajs/swagger";
import { Elysia, t } from "elysia"; import { Elysia, t } from "elysia";
import fs from "fs/promises"; import fs from "fs/promises";
import path from "path"; import path from "path";
import Desa from "./_lib/desa";
import getPotensi from "./_lib/get-potensi"; import getPotensi from "./_lib/get-potensi";
import img from "./_lib/img"; import img from "./_lib/img";
import imgDel from "./_lib/img-del"; import imgDel from "./_lib/img-del";
import imgs from "./_lib/imgs"; import imgs from "./_lib/imgs";
import Kesehatan from "./_lib/kesehatan";
import PPID from "./_lib/ppid";
import uplCsv from "./_lib/upl-csv"; import uplCsv from "./_lib/upl-csv";
import { uplCsvSingle } from "./_lib/upl-csv-single"; import { uplCsvSingle } from "./_lib/upl-csv-single";
import uplImg from "./_lib/upl-img"; import uplImg from "./_lib/upl-img";
import { uplImgSingle } from "./_lib/upl-img-single"; import { uplImgSingle } from "./_lib/upl-img-single";
import Desa from "./_lib/desa"; import FileStorage from "./_lib/fileStorage";
import Kesehatan from "./_lib/kesehatan";
import PPID from "./_lib/ppid";
const ROOT = process.cwd(); const ROOT = process.cwd();
@@ -23,7 +24,14 @@ if (!process.env.WIBU_UPLOAD_DIR)
throw new Error("WIBU_UPLOAD_DIR is not defined"); throw new Error("WIBU_UPLOAD_DIR is not defined");
const UPLOAD_DIR = path.join(ROOT, process.env.WIBU_UPLOAD_DIR); const UPLOAD_DIR = path.join(ROOT, process.env.WIBU_UPLOAD_DIR);
const UPLOAD_DIR_IMAGE = path.join(UPLOAD_DIR, "public", "assets", "images", "ppid", "profile-ppid"); const UPLOAD_DIR_IMAGE = path.join(
UPLOAD_DIR,
"public",
"assets",
"images",
"ppid",
"profile-ppid"
);
// create uploads dir // create uploads dir
fs.mkdir(UPLOAD_DIR, { fs.mkdir(UPLOAD_DIR, {
@@ -63,15 +71,18 @@ const Utils = new Elysia({
const ApiServer = new Elysia() const ApiServer = new Elysia()
.use(swagger({ path: "/api/docs" })) .use(swagger({ path: "/api/docs" }))
.use(staticPlugin({ .use(
prefix: '/', // biar bisa akses dari root URL staticPlugin({
assets: './public' prefix: "/", // biar bisa akses dari root URL
})) assets: "./public",
})
)
.use(cors(corsConfig)) .use(cors(corsConfig))
.use(PPID) .use(PPID)
.use(Kesehatan) .use(Kesehatan)
.use(Desa) .use(Desa)
.use(Utils) .use(Utils)
.use(FileStorage)
.onError(({ code }) => { .onError(({ code }) => {
if (code === "NOT_FOUND") { if (code === "NOT_FOUND") {
return { return {

View File

@@ -0,0 +1,67 @@
'use client'
import ApiFetch from '@/lib/api-fetch';
import { Button, Card, Container, FileInput, Flex, Image, SimpleGrid, Stack, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useState } from 'react';
import { toast } from 'react-toastify';
function Page() {
const [gambar, setGambar] = useState<string | null>(null);
const [listFile, setListFile] = useState<string[]>([])
const [fileNya, setFileNya] = useState<File | null>(null)
const loadListFile = async () => {
const { data } = await ApiFetch.api.fileStorage.findMany.get()
setListFile(data?.map((item) => item.link) || [])
}
useShallowEffect(() => {
loadListFile()
}, [])
const submit = async () => {
console.log("kirim gamabar")
const file = fileNya
if (!file) return toast.warn("file dibutuhkan");
const { data } = await ApiFetch.api.fileStorage.create.post({
file: file,
name: file.name
})
console.log(data?.data)
setGambar(data?.data?.link || null)
toast.success("berhasil upload")
loadListFile()
setGambar(null)
}
return (
<Container w={"90%"} >
<Stack>
<Text>Uoload gambar</Text>
<Card withBorder>
<Flex gap={"lg"} >
<FileInput label={"upload gambar"} onChange={async (e) => {
console.log(e?.name)
setGambar(e ? "data:image/png;base64," + Buffer.from(await e.arrayBuffer()).toString("base64") : null)
setFileNya(e)
}} />
<Button onClick={submit}>submit</Button>
</Flex>
{gambar && <Image w={400} src={gambar || null} alt="gambar" />}
</Card>
<SimpleGrid cols={6} p={"lg"}>
{listFile.map((v) => (
<Image key={v} src={v} alt="gambar" />
))}
</SimpleGrid>
</Stack>
</Container>
);
}
export default Page;

8
types/env.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
declare namespace NodeJS {
interface ProcessEnv {
DATABASE_URL?: string;
WIBU_UPLOAD_DIR?: string;
NEXT_PUBLIC_BASE_URL?: string;
}
}