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",
"recharts": "^2.15.3",
"swr": "^2.3.2",
"uuid": "^11.1.0",
"valtio": "^2.1.3",
"zod": "^3.24.3"
},

View File

@@ -236,6 +236,7 @@ updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= BERITA ========================================= //
model Berita {
id String @id @default(cuid())
@@ -581,3 +582,18 @@ model DoctorSign {
deletedAt DateTime @default(now())
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 layanan from './data/list-layanan.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 dasarHukumPPID from './data/ppid/dasar-hukum-ppid/dasarhukumPPID.json'
import profileDesa from './data/desa/profile/profile_desa.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 () => {
for (const l of layanan) {
await prisma.layanan.upsert({
@@ -121,18 +126,39 @@ import profilePerbekel from './data/desa/profile/profil_perbekel.json'
}
console.log("cara memperoleh salinan informasi success ...")
const seedProfilePPID = async () => {
const targetDir = path.resolve("public", "assets", "images", "ppid", "profile-ppid")
await mkdir(targetDir, { recursive: true })
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
},
where: { id: c.id },
update: {
name: c.name,
biodata: c.biodata,
riwayat: c.riwayat,
pengalaman: c.pengalaman,
unggulan: c.unggulan,
imageUrl: c.imageUrl
imageUrl: finalImageUrl,
},
create: {
id: c.id,
@@ -141,11 +167,16 @@ import profilePerbekel from './data/desa/profile/profil_perbekel.json'
riwayat: c.riwayat,
pengalaman: c.pengalaman,
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) {
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 { z } from "zod";
// Schema validasi form
/**
* Schema validasi form ProfilePPID menggunakan Zod.
*/
const templateForm = z.object({
name: z.string().min(3, "Nama 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"),
});
// Type ambil dari Prisma
/**
* Tipe data ProfilePPID yang digunakan dalam form dan API, berdasarkan Prisma schema.
*/
type ProfilePPIDForm = Prisma.ProfilePPIDGetPayload<{
select: {
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({
/**
* Bagian untuk ambil data berdasarkan ID
*/
findById: {
data: null as ProfilePPIDForm | null,
loading: false,
/**
* Inisialisasi data kosong ke dalam state.
*/
initialize() {
stateProfilePPID.findById.data = {
id: '',
@@ -42,12 +58,18 @@ const stateProfilePPID = proxy({
imageUrl: ''
} as ProfilePPIDForm;
},
/**
* Mengambil data profil berdasarkan ID.
* @param {string} id - ID dari profile yang ingin diambil.
*/
async load(id: string) {
try {
stateProfilePPID.findById.loading = true;
const res = await ApiFetch.api.ppid.profileppid["find-by-id"].get({
query: { id },
});
if (res.status === 200) {
stateProfilePPID.findById.data = res.data?.data ?? null;
} else {
@@ -62,10 +84,19 @@ const stateProfilePPID = proxy({
},
},
/**
* Bagian untuk update data profile
*/
update: {
loading: false,
/**
* Melakukan validasi dan menyimpan perubahan data profile ke server.
* @param {ProfilePPIDForm} data - Data profil yang akan disimpan.
*/
async save(data: ProfilePPIDForm) {
const cek = templateForm.safeParse(data);
if (!cek.success) {
const errors = cek.error.issues
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
@@ -77,6 +108,7 @@ const stateProfilePPID = proxy({
try {
stateProfilePPID.update.loading = true;
const res = await ApiFetch.api.ppid.profileppid["update"].post(data);
if (res.status === 200) {
toast.success("Berhasil update profile");
await stateProfilePPID.findById.load(data.id);
@@ -91,13 +123,24 @@ const stateProfilePPID = proxy({
}
},
},
/**
* Bagian untuk upload gambar profil
*/
uploadImage: {
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) {
if (!file || !id) {
toast.error("File atau ID harus disertakan");
return;
}
try {
stateProfilePPID.uploadImage.loading = true;
@@ -106,6 +149,7 @@ const stateProfilePPID = proxy({
form.append("id", id);
const res = await ApiFetch.api.ppid.profileppid["edit-img"].post(form);
if (res.status === 200) {
toast.success("Berhasil mengunggah gambar");
await stateProfilePPID.findById.load(id);
@@ -118,8 +162,11 @@ const stateProfilePPID = proxy({
} finally {
stateProfilePPID.uploadImage.loading = false;
}
}
}
},
},
});
/**
* Ekspor state utama ProfilePPID untuk digunakan di komponen lain.
*/
export default stateProfilePPID;

View File

@@ -1,44 +1,31 @@
'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 { Prisma } from '@prisma/client';
import { IconImageInPicture } from '@tabler/icons-react';
import { useProxy } from 'valtio/utils';
import stateDashboardBerita from '../../_state/desa/berita';
import { BeritaEditor } from './_com/BeritaEditor';
import colors from '@/con/colors';
function Page() {
return (
<Stack>
<Box>
<Title order={3}>Berita</Title>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<BeritaCreate />
<BeritaList />
</SimpleGrid>
</Stack>
</Box>
);
}
function BeritaList() {
const beritaState = useProxy(stateDashboardBerita)
useShallowEffect(() => {
beritaState.berita.findMany.load()
}, [])
if (!beritaState.berita.findMany.data) return <Stack>
{Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)}
</Stack>
return <Stack>
<Text>News List</Text>
{beritaState.berita.findMany.data?.map((item) => (
<Text key={item.id}>{item.judul}</Text>
))}
</Stack>
}
function BeritaCreate() {
const beritaState = useProxy(stateDashboardBerita)
return <Stack gap={"md"}>
<Text>Create Some News</Text>
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<SelectCategory onChange={(val) => {
beritaState.berita.create.form.katagoryBeritaId = val.id
}} />
@@ -57,6 +44,32 @@ function BeritaCreate() {
beritaState.berita.create.create()
}} />
</Stack>
</Paper>
</Box>
)
}
function BeritaList() {
const beritaState = useProxy(stateDashboardBerita)
useShallowEffect(() => {
beritaState.berita.findMany.load()
}, [])
if (!beritaState.berita.findMany.data) return <Stack py={10}>
{Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)}
</Stack>
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Text fz={"xl"} fw={"bold"}>List Berita</Text>
{beritaState.berita.findMany.data?.map((item) => (
<Text key={item.id}>{item.judul}</Text>
))}
</Stack>
</Paper>
</Box>
)
}
function SelectCategory({ onChange }: {
@@ -74,7 +87,7 @@ function SelectCategory({ onChange }: {
if (!beritaState.category.findMany.data) return <Skeleton h={40} />
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,
label: item.name
}))} 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 (
<div>
Gallery
</div>
<Box>
<Stack gap={"xs"}>
<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() {
return (
<div>
Layanan
</div>
<Box py={10}>
<Stack >
<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;

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 Penghargaan from './ui/penghargaan/page';
import GambarPerhargaan from './ui/gambar_perhargaan/page';
function Page() {
return (
<div>
<Box py={10}>
<Stack>
<Title order={3}>Penghargaan</Title>
<Tabs color={colors['blue-button']} variant='pills' defaultValue={"Penghargaan"}>
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
<TabsTab value="Penghargaan">
Penghargaan
</div>
</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'
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 { useProxy } from 'valtio/utils';
import stateDesaPengumuman from '../../_state/desa/pengumuman';
import { useShallowEffect } from '@mantine/hooks';
import { Prisma } from '@prisma/client';
import { BeritaEditor } from '../berita/_com/BeritaEditor';
import colors from '@/con/colors';
function Page() {
return (
<Stack>
<Box>
<Title order={3}>Pengumuman</Title>
<SimpleGrid cols={{
base: 1, md: 2
}}>
<PengumumanList />
<PengumumanCreate />
<PengumumanList />
</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() {
const pengumumanState = useProxy(stateDesaPengumuman)
useShallowEffect(() => {
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} />)}
</Stack>
return <Stack>
<Text>Announcement List</Text>
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Text fz={"xl"} fw={"bold"}>List Pengumuman</Text>
{pengumumanState.pengumuman.findMany.data?.map((item) => (
<Text key={item.id}>{item.judul}</Text>
))}
</Stack>;
}
function PengumumanCreate() {
const pengumumanState = useProxy(stateDesaPengumuman)
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>
</Paper>
</Box>
)
}
function SelectCategory({ onChange }: {
@@ -75,7 +88,7 @@ function SelectCategory({ onChange }: {
if (!pengumumanState.category.findMany.data) return <Skeleton h={40} />
return <Group>
{/* {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,
label: item.name
}))} 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 (
<div>
Potensi
</div>
<Box py={10}>
<Stack gap={"xs"}>
<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 { 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() {
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 (
<Box py={10}>
<SimpleGrid cols={{ base: 1, md: 2 }}>
@@ -11,11 +34,21 @@ function SejarahDesa() {
<Stack>
<Title order={3}>Sejarah Desa</Title>
<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>
<Button
mt={10}
bg={colors['blue-button']}
onClick={submit}
>
Submit
</Button>
@@ -23,13 +56,7 @@ function SejarahDesa() {
</Stack>
</Paper>
</Box>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack>
<Title order={3}>List Sejarah Desa</Title>
</Stack>
</Paper>
</Box>
<ListPage />
</SimpleGrid>
</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,14 +1,16 @@
'use client'
import { Box, Text } from '@mantine/core';
import { Box, Paper, Text } from '@mantine/core';
import React from 'react';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import { useProxy } from 'valtio/utils';
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import colors from '@/con/colors';
function DoctorSignUI() {
const doctorSign = useProxy(stateArtikelKesehatan.doctorSign)
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Text fw={"bold"}>Kapan Harus ke Dokter</Text>
<KesehatanEditor
showSubmit={false}
@@ -16,6 +18,7 @@ function DoctorSignUI() {
doctorSign.create.form.content = val
}}
/>
</Paper>
</Box>
);
}

View File

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

View File

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

View File

@@ -1,15 +1,18 @@
'use client'
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';
function MythFactUI() {
const mythFact = useProxy(stateArtikelKesehatan.mythFact)
return (
<Box py={10}>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<TextInput
label={<Text fw={"bold"}>Title Pertolongan Pertama Penyakit</Text>}
placeholder="Masukkan title"
label={<Text fw={"bold"}>Judul Pertolongan Pertama Penyakit</Text>}
placeholder="Masukkan judul"
onChange={(val) => {
mythFact.create.form.title = val.target.value
}}
@@ -28,6 +31,8 @@ function MythFactUI() {
mythFact.create.form.fakta = val.target.value
}}
/>
</Stack>
</Paper>
</Box>
);
}

View File

@@ -1,5 +1,5 @@
'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 SymptomUI from './symptom/page';
import PreventionUI from './prevention/page';
@@ -39,19 +39,25 @@ function ArtikelKesehatan() {
base: 1, md: 2
}}>
<Box >
<Title order={3}>Artikel Kesehatan</Title>
<Stack gap={"xs"}>
<Title order={4}>Artikel Kesehatan</Title>
<IntoductionUI />
<SymptomUI />
<PreventionUI />
<FirstAidUI />
<MythFactUI />
<DoctorSignUI />
<Button mt={10} onClick={submitAllForms}>Submit</Button>
<Group>
<Button bg={colors['blue-button']} mt={10} onClick={submitAllForms}>Submit</Button>
</Group>
</Stack>
</Box>
<Box>
<Title order={3}>List Artikel Kesehatan</Title>
<Stack gap={"xs"}>
<Title order={4}>Data Artikel Kesehatan</Title>
<AllList />
</Stack>
</Box>
</SimpleGrid>
</Stack>
@@ -78,35 +84,49 @@ function AllList() {
) return <Stack>
{Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)}
</Stack>
return <Stack>
<Title order={4}>Intoduction</Title>
return <Stack gap={"xs"}>
{/* Introduction */}
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Pendahuluan</Title>
{listState.introduction.findMany.data?.map((item) => (
<Box key={item.id}>
<Text dangerouslySetInnerHTML={{ __html: item.content }}></Text>
</Box>
))}
</Paper>
{/* Symptom */}
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Gejala Penyakit</Title>
{listState.symptom.findMany.data?.map((item) => (
<Box key={item.id}>
<Title order={4}>{item.title}</Title>
<Text dangerouslySetInnerHTML={{ __html: item.content }}></Text>
</Box>
))}
</Paper>
{/* Prevention */}
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Pencegahan Penyakit</Title>
{listState.prevention.findMany.data?.map((item) => (
<Box key={item.id}>
<Title order={4}>{item.title}</Title>
<Text dangerouslySetInnerHTML={{ __html: item.content }}></Text>
</Box>
))}
</Paper>
{/* First Aid */}
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Pertolongan Pertama</Title>
{listState.firstAid.findMany.data?.map((item) => (
<Box key={item.id}>
<Title order={4}>{item.title}</Title>
<Text dangerouslySetInnerHTML={{ __html: item.content }}></Text>
</Box>
))}
</Paper>
{/* Myth Fact */}
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Mitos vs Fakta</Title>
{listState.mythFact.findMany.data?.map((item) => (
<Box key={item.id}>
<Title order={4}>{item.title}</Title>
@@ -136,13 +156,16 @@ function AllList() {
</Table>
</Box>
))}
</Paper>
{/* Doctor Sign */}
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Kapan Harus Ke Dokter?</Title>
{listState.doctorSign.findMany.data?.map((item) => (
<Box key={item.id}>
<Text dangerouslySetInnerHTML={{ __html: item.content }} />
</Box>
))}
</Paper>
</Stack>
}

View File

@@ -1,18 +1,21 @@
'use client'
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 { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function PreventionUI() {
const preventionState = useProxy(stateArtikelKesehatan.prevention)
return (
<Box py={10}>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<TextInput
mb={10}
label={<Text fw={"bold"}>Title Pencegahan Penyakit</Text>}
placeholder="Masukkan title"
label={<Text fw={"bold"}>Judul Pencegahan Penyakit</Text>}
placeholder="Masukkan judul"
onChange={(val) => {
preventionState.create.form.title = val.target.value
}}
@@ -23,6 +26,8 @@ function PreventionUI() {
preventionState.create.form.content = val
}}
/>
</Stack>
</Paper>
</Box>
);
}

View File

@@ -1,17 +1,20 @@
'use client'
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 { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function SymptomUI() {
const symptomState = useProxy(stateArtikelKesehatan.symptom)
return (
<Box py={10}>
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<TextInput
mb={10}
label={<Text fw={"bold"}>Title Gejala Penyakit</Text>}
placeholder='masukkan title'
label={<Text fw={"bold"}>Judul Gejala Penyakit</Text>}
placeholder='masukkan judul'
onChange={(val) => {
symptomState.create.form.title = val.target.value
}}
@@ -22,6 +25,8 @@ function SymptomUI() {
symptomState.create.form.content = val
}}
/>
</Stack>
</Paper>
</Box>
);
}

View File

@@ -1,11 +1,14 @@
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';
function DokterDanTenagaMedis() {
const dokterdantenagamedisState = useProxy(stateFasilitasKesehatan.dokterdantenagamedis)
return (
<Box>
<Paper bg={colors['white-1']} p={"md"}>
<Stack gap={"xs"}>
<Text fw={"bold"}>Dokter & Tenaga Medis</Text>
<TextInput
label="Nama Dokter"
@@ -29,6 +32,8 @@ function DokterDanTenagaMedis() {
dokterdantenagamedisState.create.form.jadwal = val.target.value
}}
/>
</Stack>
</Paper>
</Box>
);
}

View File

@@ -1,17 +1,20 @@
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 { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function FasilitasPendukung() {
const fasilitaspendukungState = useProxy(stateFasilitasKesehatan.fasilitaspendukung)
return <Box>
<Paper bg={colors['white-1']} p={'md'}>
<Text fw={"bold"}>Fasilitas Pendukung</Text>
<KesehatanEditor
showSubmit={false}
onChange={(val) => {
fasilitaspendukungState.create.form.content = val;
}} />
</Paper>
</Box>
}

View File

@@ -1,12 +1,15 @@
'use client'
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';
function InformasiUmum() {
const infromasiState = useProxy(stateFasilitasKesehatan.informasiumum)
return <Box>
<Paper bg={colors['white-1']} p={'md'}>
<Text fw={"bold"}>Informasi Umum</Text>
<Stack gap={"xs"}>
<TextInput
label="Fasilitas"
placeholder='masukkan nama fasilitas kesehatan'
@@ -29,6 +32,8 @@ function InformasiUmum() {
infromasiState.create.form.jamOperasional = val.target.value
}}
/>
</Stack>
</Paper>
</Box>
}

View File

@@ -1,18 +1,21 @@
'use client'
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 { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function LayananUnggulan() {
const informasiumumState = useProxy(stateFasilitasKesehatan.layananunggulan)
return <Box>
<Paper bg={colors['white-1']} p={'md'}>
<Text fw={"bold"}>Layanan Unggulan</Text>
<KesehatanEditor
showSubmit={false}
onChange={(val) => {
informasiumumState.create.form.content = val;
}} />
</Paper>
</Box>
;
}

View File

@@ -2,7 +2,7 @@
import stateFasilitasKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/fasilitasKesehatan';
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 { useProxy } from 'valtio/utils';
import DokterDanTenagaMedis from './dokterdantenagamedis/page';
@@ -91,7 +91,7 @@ function FasilitasKesehatan() {
}}>
<Box>
<Stack gap={'xs'}>
<Title order={3}>Fasilitas Kesehatan</Title>
<Title order={4}>Tambah Fasilitas Kesehatan</Title>
{/* Informasi Umum */}
<InformasiUmum />
{/* Layanan Unggulan */}
@@ -110,7 +110,7 @@ function FasilitasKesehatan() {
<Box>
<Stack gap={"xs"}>
<Title order={3}>List Fasilitas Kesehatan</Title>
<Title order={4}>Data Fasilitas Kesehatan</Title>
<AllList />
</Stack>
</Box>
@@ -141,6 +141,7 @@ function AllList() {
{Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)}
</Stack>
return <Stack>
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Informasi Umum</Title>
{allListState.informasiumum.findMany.data?.map((item) => (
<Box key={item.id}>
@@ -150,15 +151,19 @@ function AllList() {
<Text>{item.jamOperasional}</Text>
</Box>
))}
</Paper>
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Layanan Unggulan</Title>
{allListState.layananunggulan.findMany.data?.map((item) => (
<Box key={item.id}>
<Text dangerouslySetInnerHTML={{ __html: item.content }} />
</Box>
))}
</Paper>
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Dokter & Tenaga Medis</Title>
<Table
@@ -191,6 +196,9 @@ function AllList() {
))}
</TableTbody>
</Table>
</Paper>
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Fasilitas Pendukung</Title>
{allListState.fasilitaspendukung.findMany.data?.map((item) => (
@@ -198,7 +206,9 @@ function AllList() {
<Text dangerouslySetInnerHTML={{ __html: item.content }} />
</Box>
))}
</Paper>
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Tarif & Layanan</Title>
<Table
suppressHydrationWarning
@@ -227,6 +237,9 @@ function AllList() {
))}
</TableTbody>
</Table>
</Paper>
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Prosedur Pendaftaran</Title>
{allListState.prosedurpendaftaran.findMany.data?.map((item) => (
@@ -234,7 +247,7 @@ function AllList() {
<Text dangerouslySetInnerHTML={{ __html: item.content }} />
</Box>
))}
</Paper>
</Stack>
}

View File

@@ -1,17 +1,20 @@
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 { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function ProsedurPendaftaran() {
const prosedurpendaftaranState = useProxy(stateFasilitasKesehatan.prosedurpendaftaran)
return <Box>
<Paper bg={colors['white-1']} p={'md'}>
<Text fw={"bold"}>Prosedur Pendaftaran</Text>
<KesehatanEditor
showSubmit={false}
onChange={(val) => {
prosedurpendaftaranState.create.form.content = val;
}} />
</Paper>
</Box>
}

View File

@@ -1,11 +1,14 @@
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';
function TarifDanLayanan() {
const tarifdanlayanan = useProxy(stateFasilitasKesehatan.tarifdanlayanan)
return <Box>
<Paper bg={colors['white-1']} p={'md'}>
<Text fw={"bold"}>Tarif & Layanan</Text>
<Stack gap={"xs"}>
<TextInput
label="Tarif"
placeholder='masukkan tarif'
@@ -21,6 +24,8 @@ function TarifDanLayanan() {
tarifdanlayanan.create.form.layanan = val.target.value
}}
/>
</Stack>
</Paper>
</Box>
}

View File

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

View File

@@ -1,12 +1,15 @@
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 { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function DeskripsiKegiatan() {
const deskripsiKegiatanState = useProxy(stateJadwalKegiatan.deskripsiKegiatan)
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Text pt={10} fw={"bold"}>Deskripsi Kegiatan</Text>
<KesehatanEditor
showSubmit={false}
@@ -14,6 +17,8 @@ function DeskripsiKegiatan() {
deskripsiKegiatanState.create.form.deskripsi = val
}}
/>
</Stack>
</Paper>
</Box>
);
}

View File

@@ -1,12 +1,15 @@
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 { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function DokumenYangDiperlukan() {
const dokumenDiperlukan = useProxy(stateJadwalKegiatan.dokumenjadwalkegiatan)
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Text pt={10} fw={"bold"}>Dokumen Yang Diperlukan</Text>
<KesehatanEditor
showSubmit={false}
@@ -14,6 +17,8 @@ function DokumenYangDiperlukan() {
dokumenDiperlukan.create.form.content = val;
}}
/>
</Stack>
</Paper>
</Box>
);
}

View File

@@ -1,12 +1,15 @@
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';
function InformasiKegiatan() {
const informasiKegiatanState = useProxy(stateJadwalKegiatan.informasiKegiatan)
return (
<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
label="Nama Kegiatan"
placeholder="Masukkan nama kegiatan"
@@ -35,6 +38,8 @@ function InformasiKegiatan() {
informasiKegiatanState.create.form.lokasi = val.target.value
}}
/>
</Stack>
</Paper>
</Box>
);
}

View File

@@ -1,12 +1,15 @@
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 { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function LayananTersedia() {
const layananTersediaState = useProxy(stateJadwalKegiatan.layanantersedia)
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Text pt={10} fw={"bold"}>Layanan Yang Tersedia</Text>
<KesehatanEditor
showSubmit={false}
@@ -14,6 +17,8 @@ function LayananTersedia() {
layananTersediaState.create.form.content = val;
}}
/>
</Stack>
</Paper>
</Box>
);
}

View File

@@ -1,6 +1,6 @@
'use client'
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 { useProxy } from 'valtio/utils';
import DeskripsiKegiatan from './deskripsi_kegiatan/page';
@@ -9,6 +9,7 @@ import InformasiKegiatan from './informasi_kegiatan/page';
import LayananTersedia from './layanan_yang_tersedia/page';
import Pendaftaran from './pendaftaran/page';
import SyaratDanKetentuan from './syarat_dan_ketentuan/page';
import colors from '@/con/colors';
function JadwalKegiatan() {
const allState = useProxy(stateJadwalKegiatan)
@@ -54,7 +55,8 @@ function JadwalKegiatan() {
base: 1, md: 2
}}>
<Box>
<Title order={3}>Jadwal Kegiatan</Title>
<Stack gap={"xs"}>
<Title order={4}>Tambah Jadwal Kegiatan</Title>
<InformasiKegiatan />
<DeskripsiKegiatan />
<LayananTersedia />
@@ -64,11 +66,14 @@ function JadwalKegiatan() {
<Button mt={10} onClick={submitAllForms}>
Submit
</Button>
</Stack>
</Box>
<Box>
<Title order={3}>List Jadwal Kegiatan</Title>
<Stack gap={"xs"}>
<Title order={4}>Data Jadwal Kegiatan</Title>
<AllList />
</Stack>
</Box>
</SimpleGrid>
</Stack>
@@ -103,7 +108,9 @@ function AllList() {
);
}
return (
<Stack>
<Stack gap={"xs"}>
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Informasi Kegiatan</Title>
{allList.informasiKegiatan.findMany.data?.map((item) => {
return (
@@ -115,6 +122,9 @@ function AllList() {
</Box>
)
})}
</Paper>
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Deskripsi Kegiatan</Title>
{allList.deskripsiKegiatan.findMany.data?.map((item) => {
@@ -124,7 +134,9 @@ function AllList() {
</Box>
)
})}
</Paper>
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Layanan Yang Tersedia</Title>
{allList.layanantersedia.findMany.data?.map((item) => {
return (
@@ -133,7 +145,9 @@ function AllList() {
</Box>
)
})}
</Paper>
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Syarat dan Ketentuan</Title>
{allList.syaratketentuan.findMany.data?.map((item) => {
return (
@@ -142,7 +156,9 @@ function AllList() {
</Box>
)
})}
</Paper>
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Dokumen Yang Diperlukan</Title>
{allList.dokumenjadwalkegiatan.findMany.data?.map((item) => {
return (
@@ -151,7 +167,9 @@ function AllList() {
</Box>
)
})}
</Paper>
<Paper bg={colors['white-1']} p={'md'}>
<Title order={4}>Pendaftaran</Title>
{allList.pendaftaranjadwal.findMany.data?.map((item) => {
return (
@@ -165,6 +183,7 @@ function AllList() {
</Box>
)
})}
</Paper>
</Stack>
)
}

View File

@@ -1,6 +1,7 @@
'use client'
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';
function Pendaftaran() {
@@ -8,6 +9,8 @@ function Pendaftaran() {
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Text pt={10} fw={"bold"}>Pendaftaran</Text>
<TextInput
label='Nama Balita'
@@ -51,6 +54,8 @@ function Pendaftaran() {
pendaftaran.create.form.catatan = val.target.value
}}
/>
</Stack>
</Paper>
</Box>
);
}

View File

@@ -1,12 +1,15 @@
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 { KesehatanEditor } from '../../../_com/kesehatanEditor';
import colors from '@/con/colors';
function SyaratDanKetentuan() {
const syaratKetentuan = useProxy(stateJadwalKegiatan.syaratketentuan)
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Text pt={10} fw={"bold"}>Syarat dan Ketentuan</Text>
<KesehatanEditor
showSubmit={false}
@@ -14,6 +17,8 @@ function SyaratDanKetentuan() {
syaratKetentuan.create.form.content = val;
}}
/>
</Stack>
</Paper>
</Box>
);
}

View File

@@ -1,7 +1,8 @@
'use client'
/* eslint-disable @typescript-eslint/no-explicit-any */
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 { useEffect, useState } from 'react';
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
@@ -32,9 +33,10 @@ function PersentaseDataKelahiranKematian() {
<Stack py={10}>
{/* Form Input */}
<Box>
<Title order={3}>Persentase Data Kelahiran & Kematian</Title>
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
<Title order={4}>Tambah Persentase Data Kelahiran & Kematian</Title>
<Stack gap={"xs"}>
<TextInput
w={{ base: '100%', md: '50%' }}
label="Tahun"
type="number"
value={persentase.create.form.tahun}
@@ -44,7 +46,6 @@ function PersentaseDataKelahiranKematian() {
}}
/>
<TextInput
w={{ base: '100%', md: '50%' }}
label="Kematian Kasar"
type="number"
value={persentase.create.form.kematianKasar}
@@ -54,7 +55,6 @@ function PersentaseDataKelahiranKematian() {
}}
/>
<TextInput
w={{ base: '100%', md: '50%' }}
label="Kematian Bayi"
type="number"
value={persentase.create.form.kematianBayi}
@@ -64,7 +64,6 @@ function PersentaseDataKelahiranKematian() {
}}
/>
<TextInput
w={{ base: '100%', md: '50%' }}
label="Kelahiran Kasar"
type="number"
value={persentase.create.form.kelahiranKasar}
@@ -73,7 +72,9 @@ function PersentaseDataKelahiranKematian() {
persentase.create.form.kelahiranKasar = val.currentTarget.value;
}}
/>
<Group>
<Button
bg={colors['blue-button']}
mt={10}
onClick={async () => {
await persentase.create.create();
@@ -85,13 +86,25 @@ function PersentaseDataKelahiranKematian() {
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
{/* Chart */}
{!mounted && !chartData ? (
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Title pb={10} order={3}>Data Kelahiran & Kematian</Title>
<Skeleton h={400} />
</Paper>
</Box>
) : (
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Title pb={10} order={4}>Data Kelahiran & Kematian</Title>
{mounted && chartData.length > 0 && (
<BarChart width={isMobile ? 450 : isTablet ? 600 : 900} height={380} data={chartData} >
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={chartData} >
<XAxis dataKey="tahun" />
<YAxis />
<Tooltip />
@@ -101,7 +114,9 @@ function PersentaseDataKelahiranKematian() {
<Bar dataKey="kelahiranKasar" fill="#4dabf7" name="Kelahiran Kasar" />
</BarChart>
)}
</Paper>
</Box>
)}
</Stack>
);
}

View File

@@ -1,5 +1,5 @@
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 FasilitasKesehatan from './_ui/fasilitas_kesehatan/page';
import GrafikHasilKepuasan from './_ui/grafik_hasil_kepuasan/page';
@@ -10,8 +10,9 @@ import PersentaseDataKelahiranKematian from './_ui/persentase_data_kelahiran_kem
function Page() {
return (
<Stack>
<Title order={3}>Data Kesehatan Warga</Title>
<Tabs color={colors['blue-button']} variant='pills' defaultValue={"Persentase Kelahiran & Kematian"}>
<TabsList >
<TabsList bg={colors['BG-trans']} p={'xs'}>
<TabsTab value="Persentase Kelahiran & Kematian">
Persentase Kelahiran & Kematian
</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 { KesehatanEditor } from '../_com/kesehatanEditor';
function Page() {
function Posyandu() {
return (
<div>
Posyandu
</div>
<Box>
<Title order={3}>Kesehatan</Title>
<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 (
<div>
Puskesmas
</div>
<Stack gap={"xs"}>
<Box>
<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}>
<Text fw={"bold"} mb={5}>Preview Gambar:</Text>
<Image
src={item.imageUrl}
src={item.imageUrl ?? '/perbekel.png'}
alt="Profile"
w={200}
/>

View File

@@ -9,6 +9,8 @@ import { useProxy } from 'valtio/utils';
import stateProfilePPID from '../../_state/ppid/profile_ppid/profile_PPID';
import { useShallowEffect } from '@mantine/hooks';
import ProfileList from './listPage';
import { useState } from 'react';
import { toast } from 'react-toastify';
function Page() {
@@ -23,14 +25,16 @@ function Page() {
}
function ProfileCreate() {
const [isLoading, setIsLoading] = useState(false)
const allState = useProxy(stateProfilePPID)
// Initialize data if it doesn't exist
useShallowEffect(() => {
if (!allState.findById.data) {
if (!allState.findById.data && isLoading) {
allState.findById.initialize()
setIsLoading(false)
}
}, [])
}, [isLoading])
const submit = () => {
if (
@@ -41,6 +45,12 @@ function ProfileCreate() {
allState.findById.data?.unggulan
) {
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'
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 { IconChevronLeft, IconChevronRight } from "@tabler/icons-react";
import _ from 'lodash';
@@ -10,75 +23,106 @@ import { navBar } from "./_com/list_PageAdmin";
export default function Layout({ children }: { children: React.ReactNode }) {
const [opened, { toggle }] = useDisclosure();
const [desktopOpened, { toggle: toggleDesktop }] =
useDisclosure(true);
const [desktopOpened, { toggle: toggleDesktop }] = useDisclosure(true);
// Normalisasi semua segmen jadi lowercase
const segments = useSelectedLayoutSegments().map(s => _.lowerCase(s));
const segments = useSelectedLayoutSegments()
return (
<AppShell
suppressHydrationWarning
header={{ height: 60 }}
navbar={{
width: 300, breakpoint: 'sm', collapsed: { mobile: !opened, desktop: !desktopOpened }
width: 300,
breakpoint: 'sm',
collapsed: {
mobile: !opened,
desktop: !desktopOpened,
},
}}
padding={'md'}
>
<AppShellHeader bg={colors["white-trans-1"]}>
<Group px={10} align="center">
{!desktopOpened && <ActionIcon variant="light" onClick={toggleDesktop}><IconChevronRight /></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} />
{!desktopOpened && (
<ActionIcon variant="light" onClick={toggleDesktop}>
<IconChevronRight />
</ActionIcon>
<Text fw={'bold'} c={colors["blue-button"]} fz={'lg'}>Dashboard Admin</Text>
)}
<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>
<Text fw={'bold'} c={colors["blue-button"]} fz={'lg'}>
Dashboard Admin
</Text>
</Group>
</AppShellHeader>
<AppShellNavbar
c={colors["blue-button"]}
component={ScrollArea}
>
<AppShellNavbar c={colors["blue-button"]} component={ScrollArea}>
<AppShell.Section>
{navBar.map((v, k) => {
const isParentActive = segments.includes(_.lowerCase(v.name));
return (
<NavLink
c={_.lowerCase(v.name) == segments[1] ? colors["blue-button"] : "grey"}
key={k}
defaultOpened={_.lowerCase(v.name) == segments[1]}
// onClick={() => setActive(k)}
label={<Text
style={{ fontWeight: _.lowerCase(v.name) == segments[1] ? "bold" : "normal" }}
>{v.name}</Text>}
defaultOpened={isParentActive}
c={isParentActive ? colors["blue-button"] : "grey"}
label={
<Text style={{ fontWeight: isParentActive ? "bold" : "normal" }}>
{v.name}
</Text>
}
>
{v.children.map((child, key) => {
const isChildActive = segments.includes(_.lowerCase(child.name));
return (
<NavLink
c={_.lowerCase(child.name) == _.lowerCase(segments[2]) ? colors["blue-button"] : "grey"}
key={key}
href={child.path}
// active={isClient && Number(child.id) === active}
// onClick={() => setActive(Number(child.id))}
label={<Text
style={{ fontWeight: _.lowerCase(child.name) == _.lowerCase(segments[2]) ? "bold" : "normal" }}
>{child.name}</Text>}
c={isChildActive ? colors["blue-button"] : "grey"}
label={
<Text style={{ fontWeight: isChildActive ? "bold" : "normal" }}>
{child.name}
</Text>
}
/>
)
);
})}
</NavLink>
)
);
})}
</AppShell.Section>
<AppShell.Section py={20}>
<Group justify="end">
<ActionIcon variant="light" onClick={toggleDesktop}><IconChevronLeft /></ActionIcon>
<ActionIcon variant="light" onClick={toggleDesktop}>
<IconChevronLeft />
</ActionIcon>
</Group>
</AppShell.Section>
</AppShellNavbar>
<AppShellMain bg={colors.Bg}>
{children}
</AppShellMain>
<AppShellMain bg={colors.Bg}>{children}</AppShellMain>
</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 profileDesaFindMany from "./find-many";
import lambangDesaUpdate from "./lambangDesa";
import maskotDesaUpdate from "./maskotDesa";
import profilePerbekelUpdate from "./profilePerbekel";
import sejarahDesaUpdate from "./sejarahDesa";
import visimisiDesaUpdate from "./visimisiDesa";
import lambangDesaUpdate from "./lambangDesa/update";
import maskotDesaUpdate from "./maskotDesa/update";
import profilePerbekelUpdate from "../profilePerbekel/update";
import sejarahDesaUpdate from "./sejarah/update";
import visimisiDesaUpdate from "./visimisiDesa/update";
import profileDesaFindById from "./find-by-id";
const ProfileDesa = new Elysia({
prefix: "/profile",
tags: ["Desa/Profile"]
})
.get("/find-many", profileDesaFindMany)
.get("/find-by-id", profileDesaFindById)
.post("/profilePerbekel/update", profilePerbekelUpdate, {
body: t.Object({
id: t.String(),
@@ -27,7 +27,7 @@ const ProfileDesa = new Elysia({
misi: t.String(),
})
})
.post("/sejarahDesa/update", sejarahDesaUpdate, {
.post("/sejarah/update", sejarahDesaUpdate, {
body: t.Object({
id: 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 { staticPlugin } from '@elysiajs/static'
import cors, { HTTPMethod } from "@elysiajs/cors";
import { staticPlugin } from "@elysiajs/static";
import swagger from "@elysiajs/swagger";
import { Elysia, t } from "elysia";
import fs from "fs/promises";
import path from "path";
import Desa from "./_lib/desa";
import getPotensi from "./_lib/get-potensi";
import img from "./_lib/img";
import imgDel from "./_lib/img-del";
import imgs from "./_lib/imgs";
import Kesehatan from "./_lib/kesehatan";
import PPID from "./_lib/ppid";
import uplCsv from "./_lib/upl-csv";
import { uplCsvSingle } from "./_lib/upl-csv-single";
import uplImg from "./_lib/upl-img";
import { uplImgSingle } from "./_lib/upl-img-single";
import Desa from "./_lib/desa";
import Kesehatan from "./_lib/kesehatan";
import PPID from "./_lib/ppid";
import FileStorage from "./_lib/fileStorage";
const ROOT = process.cwd();
@@ -23,7 +24,14 @@ if (!process.env.WIBU_UPLOAD_DIR)
throw new Error("WIBU_UPLOAD_DIR is not defined");
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
fs.mkdir(UPLOAD_DIR, {
@@ -63,15 +71,18 @@ const Utils = new Elysia({
const ApiServer = new Elysia()
.use(swagger({ path: "/api/docs" }))
.use(staticPlugin({
prefix: '/', // biar bisa akses dari root URL
assets: './public'
}))
.use(
staticPlugin({
prefix: "/", // biar bisa akses dari root URL
assets: "./public",
})
)
.use(cors(corsConfig))
.use(PPID)
.use(Kesehatan)
.use(Desa)
.use(Utils)
.use(FileStorage)
.onError(({ code }) => {
if (code === "NOT_FOUND") {
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;
}
}