Fix FileInput dengan Dropzone
This commit is contained in:
@@ -1,28 +1,28 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import EditEditor from "@/app/admin/(dashboard)/_com/editEditor";
|
||||||
|
import stateDashboardBerita from "@/app/admin/(dashboard)/_state/desa/berita";
|
||||||
|
import colors from "@/con/colors";
|
||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Center,
|
Group,
|
||||||
Image,
|
Image,
|
||||||
Paper,
|
Paper,
|
||||||
Select,
|
Select,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
Title,
|
Title
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { IconArrowBack, IconImageInPicture } from "@tabler/icons-react";
|
import { Dropzone } from "@mantine/dropzone";
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from "@tabler/icons-react";
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams, useRouter } from "next/navigation";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { useProxy } from "valtio/utils";
|
import { useProxy } from "valtio/utils";
|
||||||
import EditEditor from "@/app/admin/(dashboard)/_com/editEditor";
|
|
||||||
import colors from "@/con/colors";
|
|
||||||
import ApiFetch from "@/lib/api-fetch";
|
|
||||||
import { FileInput } from "@mantine/core";
|
|
||||||
import stateDashboardBerita from "@/app/admin/(dashboard)/_state/desa/berita";
|
|
||||||
|
|
||||||
|
|
||||||
function EditBerita() {
|
function EditBerita() {
|
||||||
@@ -130,27 +130,62 @@ function EditBerita() {
|
|||||||
placeholder="masukkan deskripsi"
|
placeholder="masukkan deskripsi"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Baru (Opsional)</Text>}
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
value={file}
|
<Box>
|
||||||
onChange={async (e) => {
|
<Dropzone
|
||||||
if (!e) return;
|
onDrop={(files) => {
|
||||||
setFile(e);
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
if (selectedFile) {
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
setFile(selectedFile);
|
||||||
);
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
setPreviewImage(base64);
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
{previewImage ? (
|
<div>
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
<Text size="xl" inline>
|
||||||
) : (
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
</Text>
|
||||||
<IconImageInPicture />
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
</Center>
|
Maksimal 5MB dan harus format gambar
|
||||||
)}
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
|||||||
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Image, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -113,25 +114,62 @@ export default function CreateBerita() {
|
|||||||
placeholder="masukkan deskripsi"
|
placeholder="masukkan deskripsi"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
value={file}
|
<Box>
|
||||||
onChange={async (e) => {
|
<Dropzone
|
||||||
if (!e) return;
|
onDrop={(files) => {
|
||||||
setFile(e);
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
if (selectedFile) {
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
setFile(selectedFile);
|
||||||
);
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
setPreviewImage(base64);
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
{previewImage ? (
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
accept={{ 'image/*': [] }}
|
||||||
) : (
|
>
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
<IconImageInPicture />
|
<Dropzone.Accept>
|
||||||
</Center>
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
)}
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
||||||
<CreateEditor
|
<CreateEditor
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import penghargaanState from '@/app/admin/(dashboard)/_state/desa/penghargaan';
|
import penghargaanState from '@/app/admin/(dashboard)/_state/desa/penghargaan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Paper, Stack, Title, TextInput, FileInput, Center, Text, Image } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import React, { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
@@ -105,26 +106,62 @@ function EditPenghargaan() {
|
|||||||
label={<Text fz={"sm"} fw={"bold"}>Juara</Text>}
|
label={<Text fz={"sm"} fw={"bold"}>Juara</Text>}
|
||||||
placeholder="masukkan juara"
|
placeholder="masukkan juara"
|
||||||
/>
|
/>
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Baru (Opsional)</Text>}
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
value={file}
|
<Box>
|
||||||
onChange={async (e) => {
|
<Dropzone
|
||||||
if (!e) return;
|
onDrop={(files) => {
|
||||||
setFile(e);
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
if (selectedFile) {
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
setFile(selectedFile);
|
||||||
);
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
setPreviewImage(base64);
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
{previewImage ? (
|
<div>
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
<Text size="xl" inline>
|
||||||
) : (
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
</Text>
|
||||||
<IconImageInPicture />
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
</Center>
|
Maksimal 5MB dan harus format gambar
|
||||||
)}
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import penghargaanState from '../../../_state/desa/penghargaan';
|
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
|
||||||
import CreateEditor from '../../../_com/createEditor';
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
|
import penghargaanState from '../../../_state/desa/penghargaan';
|
||||||
|
|
||||||
|
|
||||||
function CreatePenghargaan() {
|
function CreatePenghargaan() {
|
||||||
@@ -85,25 +86,62 @@ function CreatePenghargaan() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Konten</Text>}
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
value={file}
|
<Box>
|
||||||
onChange={async (e) => {
|
<Dropzone
|
||||||
if (!e) return;
|
onDrop={(files) => {
|
||||||
setFile(e);
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
if (selectedFile) {
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
setFile(selectedFile);
|
||||||
);
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
setPreviewImage(base64);
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
{previewImage ? (
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
accept={{ 'image/*': [] }}
|
||||||
) : (
|
>
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
<IconImageInPicture />
|
<Dropzone.Accept>
|
||||||
</Center>
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
)}
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
import stateProfileDesa from '@/app/admin/(dashboard)/_state/desa/profile';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Group, Image, Paper, Stack, Text, Title } from '@mantine/core';
|
import { Box, Button, Center, Group, Image, Paper, Stack, Text, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -44,21 +45,7 @@ function ProfilePerbekel() {
|
|||||||
perbekelState.findUnique.reset(); // opsional: reset juga data lama
|
perbekelState.findUnique.reset(); // opsional: reset juga data lama
|
||||||
};
|
};
|
||||||
}, [params?.id, router]);
|
}, [params?.id, router]);
|
||||||
|
|
||||||
const handleFileChange = (newFile: File | null) => {
|
|
||||||
if (!newFile) {
|
|
||||||
setFile(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setFile(newFile);
|
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (event) => {
|
|
||||||
setPreviewImage(event.target?.result as string);
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(newFile);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (isSubmitting || !perbekelState.edit.form.biodata.trim()) {
|
if (isSubmitting || !perbekelState.edit.form.biodata.trim()) {
|
||||||
@@ -128,27 +115,61 @@ function ProfilePerbekel() {
|
|||||||
value={perbekelState.edit.form.biodata}
|
value={perbekelState.edit.form.biodata}
|
||||||
onChange={(val) => perbekelState.edit.form.biodata = val}
|
onChange={(val) => perbekelState.edit.form.biodata = val}
|
||||||
/>
|
/>
|
||||||
{/* File Upload */}
|
|
||||||
<FileInput
|
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar Baru (Opsional)</Text>}
|
|
||||||
value={file}
|
|
||||||
onChange={handleFileChange}
|
|
||||||
accept="image/*"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Preview Gambar */}
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="sm" fw="bold" mb="xs">Preview Gambar</Text>
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
{previewImage ? (
|
<Box>
|
||||||
<Image alt="Profile preview" src={previewImage} w={200} h={200} fit="cover" radius="md" />
|
<Dropzone
|
||||||
) : (
|
onDrop={(files) => {
|
||||||
<Center w={200} h={200} bg="gray.2">
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
<Stack align="center" gap="xs">
|
if (selectedFile) {
|
||||||
<IconImageInPicture size={48} color="gray" />
|
setFile(selectedFile);
|
||||||
<Text size="sm" c="gray">Tidak ada gambar</Text>
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
</Stack>
|
}
|
||||||
</Center>
|
}}
|
||||||
)}
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
|
|||||||
@@ -116,14 +116,14 @@ function ListDetailDataPengangguran() {
|
|||||||
{!mounted && !chartData ? (
|
{!mounted && !chartData ? (
|
||||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<Title pb={10} order={3}>Data Kelahiran & Kematian</Title>
|
<Title pb={10} order={3}>Data Pengangguran Terdidik dan Tidak Terdidik</Title>
|
||||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Box style={{ width: '100%', minWidth: 300, height: 550, minHeight: 300 }}>
|
<Box style={{ width: '100%', minWidth: 300, height: 550, minHeight: 300 }}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'}>
|
||||||
<Title pb={10} order={4}>Data Kelahiran & Kematian</Title>
|
<Title pb={10} order={4}>Data Pengangguran Terdidik dan Tidak Terdidik</Title>
|
||||||
{mounted && chartData.length > 0 && (
|
{mounted && chartData.length > 0 && (
|
||||||
<Box w={{ base: '100%', md: '70%' }}>
|
<Box w={{ base: '100%', md: '70%' }}>
|
||||||
<BarChart
|
<BarChart
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import desaDigitalState from '@/app/admin/(dashboard)/_state/inovasi/desa-digital';
|
import desaDigitalState from '@/app/admin/(dashboard)/_state/inovasi/desa-digital';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -95,27 +96,61 @@ function EditPenghargaan() {
|
|||||||
label={<Text fz={"sm"} fw={"bold"}>Judul</Text>}
|
label={<Text fz={"sm"} fw={"bold"}>Judul</Text>}
|
||||||
placeholder="masukkan judul"
|
placeholder="masukkan judul"
|
||||||
/>
|
/>
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Baru (Opsional)</Text>}
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
value={file}
|
<Box>
|
||||||
onChange={async (e) => {
|
<Dropzone
|
||||||
if (!e) return;
|
onDrop={(files) => {
|
||||||
setFile(e);
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
if (selectedFile) {
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
setFile(selectedFile);
|
||||||
);
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
setPreviewImage(base64);
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
{previewImage ? (
|
<div>
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
<Text size="xl" inline>
|
||||||
) : (
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
</Text>
|
||||||
<IconImageInPicture />
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
</Center>
|
Maksimal 5MB dan harus format gambar
|
||||||
)}
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import CreateEditor from '../../../_com/createEditor';
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
import desaDigitalState from '../../../_state/inovasi/desa-digital';
|
import desaDigitalState from '../../../_state/inovasi/desa-digital';
|
||||||
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
|
||||||
|
|
||||||
function CreateDesaDigital() {
|
function CreateDesaDigital() {
|
||||||
@@ -49,7 +50,7 @@ function CreateDesaDigital() {
|
|||||||
|
|
||||||
// Submit the form
|
// Submit the form
|
||||||
const success = await stateDesaDigital.create.create()
|
const success = await stateDesaDigital.create.create()
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
resetForm()
|
resetForm()
|
||||||
router.push("/admin/inovasi/desa-digital-smart-village")
|
router.push("/admin/inovasi/desa-digital-smart-village")
|
||||||
@@ -87,25 +88,61 @@ function CreateDesaDigital() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Konten</Text>}
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
value={file}
|
<Box>
|
||||||
onChange={async (e) => {
|
<Dropzone
|
||||||
if (!e) return;
|
onDrop={(files) => {
|
||||||
setFile(e);
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
if (selectedFile) {
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
setFile(selectedFile);
|
||||||
);
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
setPreviewImage(base64);
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
{previewImage ? (
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
accept={{ 'image/*': [] }}
|
||||||
) : (
|
>
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
<IconImageInPicture />
|
<Dropzone.Accept>
|
||||||
</Center>
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
)}
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import infoTeknoState from '@/app/admin/(dashboard)/_state/inovasi/info-tekno';
|
import infoTeknoState from '@/app/admin/(dashboard)/_state/inovasi/info-tekno';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -94,27 +95,61 @@ function EditInfoTeknologiTepatGuna() {
|
|||||||
label={<Text fz={"sm"} fw={"bold"}>Judul</Text>}
|
label={<Text fz={"sm"} fw={"bold"}>Judul</Text>}
|
||||||
placeholder="masukkan judul"
|
placeholder="masukkan judul"
|
||||||
/>
|
/>
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Baru (Opsional)</Text>}
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
value={file}
|
<Box>
|
||||||
onChange={async (e) => {
|
<Dropzone
|
||||||
if (!e) return;
|
onDrop={(files) => {
|
||||||
setFile(e);
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
if (selectedFile) {
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
setFile(selectedFile);
|
||||||
);
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
setPreviewImage(base64);
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
{previewImage ? (
|
<div>
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
<Text size="xl" inline>
|
||||||
) : (
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
</Text>
|
||||||
<IconImageInPicture />
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
</Center>
|
Maksimal 5MB dan harus format gambar
|
||||||
)}
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
<Text fz={"sm"} fw={"bold"}>Deskripsi</Text>
|
||||||
<EditEditor
|
<EditEditor
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import CreateEditor from '../../../_com/createEditor';
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
import infoTeknoState from '../../../_state/inovasi/info-tekno';
|
import infoTeknoState from '../../../_state/inovasi/info-tekno';
|
||||||
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
|
||||||
|
|
||||||
function CreateInfoTeknologiTepatGuna() {
|
function CreateInfoTeknologiTepatGuna() {
|
||||||
@@ -49,7 +50,7 @@ function CreateInfoTeknologiTepatGuna() {
|
|||||||
|
|
||||||
// Submit the form
|
// Submit the form
|
||||||
const success = await stateInfoTekno.create.create()
|
const success = await stateInfoTekno.create.create()
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
resetForm()
|
resetForm()
|
||||||
router.push("/admin/inovasi/info-teknologi-tepat-guna")
|
router.push("/admin/inovasi/info-teknologi-tepat-guna")
|
||||||
@@ -87,25 +88,61 @@ function CreateInfoTeknologiTepatGuna() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar Konten</Text>}
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
value={file}
|
<Box>
|
||||||
onChange={async (e) => {
|
<Dropzone
|
||||||
if (!e) return;
|
onDrop={(files) => {
|
||||||
setFile(e);
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
if (selectedFile) {
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
setFile(selectedFile);
|
||||||
);
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
setPreviewImage(base64);
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
{previewImage ? (
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
accept={{ 'image/*': [] }}
|
||||||
) : (
|
>
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
<IconImageInPicture />
|
<Dropzone.Accept>
|
||||||
</Center>
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
)}
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import infoWabahPenyakit from '@/app/admin/(dashboard)/_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
import infoWabahPenyakit from '@/app/admin/(dashboard)/_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -91,57 +92,91 @@ function EditInfoWabahPenyakit() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Title order={3}>Edit Info Wabah Penyakit</Title>
|
<Title order={3}>Edit Info Wabah Penyakit</Title>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label={<Text fz="sm" fw="bold">Judul</Text>}
|
||||||
placeholder="masukkan judul"
|
placeholder="masukkan judul"
|
||||||
/>
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
value={formData.deskripsiSingkat}
|
|
||||||
onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })}
|
|
||||||
label={<Text fz="sm" fw="bold">Deskripsi Singkat</Text>}
|
|
||||||
placeholder="masukkan deskripsi"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Box>
|
|
||||||
<Text fz="sm" fw="bold">Deskripsi</Text>
|
|
||||||
<EditEditor
|
|
||||||
value={formData.deskripsi}
|
|
||||||
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
|
||||||
|
|
||||||
<FileInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
value={formData.deskripsiSingkat}
|
||||||
value={file}
|
onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })}
|
||||||
onChange={async (e) => {
|
label={<Text fz="sm" fw="bold">Deskripsi Singkat</Text>}
|
||||||
if (!e) return;
|
placeholder="masukkan deskripsi"
|
||||||
setFile(e);
|
/>
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
|
||||||
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
|
||||||
);
|
|
||||||
setPreviewImage(base64);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{previewImage ? (
|
<Box>
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||||
) : (
|
<EditEditor
|
||||||
<Center w={200} h={200} bg="gray">
|
value={formData.deskripsi}
|
||||||
<IconImageInPicture />
|
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
||||||
</Center>
|
/>
|
||||||
)}
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Box>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
<div>
|
||||||
Simpan
|
<Text size="xl" inline>
|
||||||
</Button>
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
</Stack>
|
</Text>
|
||||||
</Paper>
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box >
|
</Box >
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import CreateEditor from '../../../_com/createEditor';
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
import infoWabahPenyakit from '../../../_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
import infoWabahPenyakit from '../../../_state/kesehatan/info-wabah-penyakit/infoWabahPenyakit';
|
||||||
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
|
||||||
function CreateInfoWabahPenyakit() {
|
function CreateInfoWabahPenyakit() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -94,28 +95,62 @@ function CreateInfoWabahPenyakit() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Box>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
<FileInput
|
<div>
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
<Text size="xl" inline>
|
||||||
value={file}
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
onChange={async (e) => {
|
</Text>
|
||||||
if (!e) return;
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
setFile(e);
|
Maksimal 5MB dan harus format gambar
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
</Text>
|
||||||
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
</div>
|
||||||
);
|
</Group>
|
||||||
setPreviewImage(base64);
|
</Dropzone>
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{previewImage ? (
|
{/* Tampilkan preview kalau ada */}
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
{previewImage && (
|
||||||
) : (
|
<Box mt="sm">
|
||||||
<Center w={200} h={200} bg="gray">
|
<Image
|
||||||
<IconImageInPicture />
|
src={previewImage}
|
||||||
</Center>
|
alt="Preview"
|
||||||
)}
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
Simpan
|
Simpan
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import kontakDarurat from '@/app/admin/(dashboard)/_state/kesehatan/kontak-darurat/kontakDarurat';
|
import kontakDarurat from '@/app/admin/(dashboard)/_state/kesehatan/kontak-darurat/kontakDarurat';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -89,49 +90,82 @@ function EditKontakDarurat() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Title order={3}>Edit Kontak Darurat</Title>
|
<Title order={3}>Edit Kontak Darurat</Title>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label={<Text fz="sm" fw="bold">Judul</Text>}
|
||||||
placeholder="masukkan judul"
|
placeholder="masukkan judul"
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
<Text fz="sm" fw="bold">Deskripsi</Text>
|
|
||||||
<EditEditor
|
|
||||||
value={formData.deskripsi}
|
|
||||||
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
<Box>
|
||||||
|
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||||
|
<EditEditor
|
||||||
|
value={formData.deskripsi}
|
||||||
|
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Box>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
<FileInput
|
<div>
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
<Text size="xl" inline>
|
||||||
value={file}
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
onChange={async (e) => {
|
</Text>
|
||||||
if (!e) return;
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
setFile(e);
|
Maksimal 5MB dan harus format gambar
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
</Text>
|
||||||
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
</div>
|
||||||
);
|
</Group>
|
||||||
setPreviewImage(base64);
|
</Dropzone>
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{previewImage ? (
|
{/* Tampilkan preview kalau ada */}
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
{previewImage && (
|
||||||
) : (
|
<Box mt="sm">
|
||||||
<Center w={200} h={200} bg="gray">
|
<Image
|
||||||
<IconImageInPicture />
|
src={previewImage}
|
||||||
</Center>
|
alt="Preview"
|
||||||
)}
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
maxHeight: '200px',
|
||||||
Simpan
|
objectFit: 'contain',
|
||||||
</Button>
|
borderRadius: '8px',
|
||||||
</Stack>
|
border: '1px solid #ddd',
|
||||||
</Paper>
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box >
|
</Box >
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,113 +1,147 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
|
||||||
import kontakDarurat from '../../../_state/kesehatan/kontak-darurat/kontakDarurat';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import { useProxy } from 'valtio/utils';
|
||||||
import CreateEditor from '../../../_com/createEditor';
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
|
import kontakDarurat from '../../../_state/kesehatan/kontak-darurat/kontakDarurat';
|
||||||
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
|
||||||
|
|
||||||
function CreateKontakDarurat() {
|
function CreateKontakDarurat() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const kontakDaruratState = useProxy(kontakDarurat)
|
const kontakDaruratState = useProxy(kontakDarurat)
|
||||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
kontakDaruratState.create.form = {
|
kontakDaruratState.create.form = {
|
||||||
name: "",
|
name: "",
|
||||||
deskripsi: "",
|
deskripsi: "",
|
||||||
imageId: "",
|
imageId: "",
|
||||||
};
|
|
||||||
setPreviewImage(null);
|
|
||||||
setFile(null);
|
|
||||||
};
|
};
|
||||||
|
setPreviewImage(null);
|
||||||
const handleSubmit = async () => {
|
setFile(null);
|
||||||
if (!file) {
|
};
|
||||||
return toast.warn("Pilih file gambar terlebih dahulu");
|
|
||||||
}
|
const handleSubmit = async () => {
|
||||||
|
if (!file) {
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({
|
return toast.warn("Pilih file gambar terlebih dahulu");
|
||||||
file,
|
|
||||||
name: file.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
const uploaded = res.data?.data;
|
|
||||||
if (!uploaded?.id) {
|
|
||||||
return toast.error("Gagal upload gambar");
|
|
||||||
}
|
|
||||||
|
|
||||||
kontakDaruratState.create.form.imageId = uploaded.id;
|
|
||||||
|
|
||||||
await kontakDaruratState.create.create();
|
|
||||||
|
|
||||||
resetForm();
|
|
||||||
router.push("/admin/kesehatan/kontak-darurat")
|
|
||||||
}
|
}
|
||||||
return (
|
|
||||||
<Box>
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
<Box mb={10}>
|
file,
|
||||||
<Button variant="subtle" onClick={() => router.back()}>
|
name: file.name,
|
||||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
});
|
||||||
</Button>
|
|
||||||
</Box>
|
const uploaded = res.data?.data;
|
||||||
|
if (!uploaded?.id) {
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
return toast.error("Gagal upload gambar");
|
||||||
<Stack gap="xs">
|
}
|
||||||
<Title order={3}>Create Kontak Darurat</Title>
|
|
||||||
|
kontakDaruratState.create.form.imageId = uploaded.id;
|
||||||
<TextInput
|
|
||||||
value={kontakDaruratState.create.form.name}
|
await kontakDaruratState.create.create();
|
||||||
onChange={(val) => {
|
|
||||||
kontakDaruratState.create.form.name = val.target.value;
|
resetForm();
|
||||||
}}
|
router.push("/admin/kesehatan/kontak-darurat")
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
}
|
||||||
placeholder="masukkan judul"
|
return (
|
||||||
/>
|
<Box>
|
||||||
|
<Box mb={10}>
|
||||||
<Box>
|
<Button variant="subtle" onClick={() => router.back()}>
|
||||||
<Text fz="sm" fw="bold">Deskripsi</Text>
|
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||||
<CreateEditor
|
</Button>
|
||||||
value={kontakDaruratState.create.form.deskripsi}
|
|
||||||
onChange={(val) => {
|
|
||||||
kontakDaruratState.create.form.deskripsi = val;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<FileInput
|
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
|
||||||
value={file}
|
|
||||||
onChange={async (e) => {
|
|
||||||
if (!e) return;
|
|
||||||
setFile(e);
|
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
|
||||||
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
|
||||||
);
|
|
||||||
setPreviewImage(base64);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{previewImage ? (
|
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
|
||||||
) : (
|
|
||||||
<Center w={200} h={200} bg="gray">
|
|
||||||
<IconImageInPicture />
|
|
||||||
</Center>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
|
||||||
Simpan
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
|
||||||
|
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Title order={3}>Create Kontak Darurat</Title>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
value={kontakDaruratState.create.form.name}
|
||||||
|
onChange={(val) => {
|
||||||
|
kontakDaruratState.create.form.name = val.target.value;
|
||||||
|
}}
|
||||||
|
label={<Text fz="sm" fw="bold">Judul</Text>}
|
||||||
|
placeholder="masukkan judul"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||||
|
<CreateEditor
|
||||||
|
value={kontakDaruratState.create.form.deskripsi}
|
||||||
|
onChange={(val) => {
|
||||||
|
kontakDaruratState.create.form.deskripsi = val;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Box>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CreateKontakDarurat;
|
export default CreateKontakDarurat;
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import penangananDarurat from '@/app/admin/(dashboard)/_state/kesehatan/penanganan-darurat/penangananDarurat';
|
import penangananDarurat from '@/app/admin/(dashboard)/_state/kesehatan/penanganan-darurat/penangananDarurat';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -105,28 +106,61 @@ function EditPenangananDarurat() {
|
|||||||
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Box>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
<FileInput
|
<div>
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
<Text size="xl" inline>
|
||||||
value={file}
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
onChange={async (e) => {
|
</Text>
|
||||||
if (!e) return;
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
setFile(e);
|
Maksimal 5MB dan harus format gambar
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
</Text>
|
||||||
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
</div>
|
||||||
);
|
</Group>
|
||||||
setPreviewImage(base64);
|
</Dropzone>
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{previewImage ? (
|
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
|
||||||
) : (
|
|
||||||
<Center w={200} h={200} bg="gray">
|
|
||||||
<IconImageInPicture />
|
|
||||||
</Center>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
Simpan
|
Simpan
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -79,31 +80,65 @@ function CreatePenangananDarurat() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Box>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
<FileInput
|
<div>
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
<Text size="xl" inline>
|
||||||
value={file}
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
onChange={async (e) => {
|
</Text>
|
||||||
if (!e) return;
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
setFile(e);
|
Maksimal 5MB dan harus format gambar
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
</Text>
|
||||||
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
</div>
|
||||||
);
|
</Group>
|
||||||
setPreviewImage(base64);
|
</Dropzone>
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{previewImage ? (
|
{/* Tampilkan preview kalau ada */}
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
{previewImage && (
|
||||||
) : (
|
<Box mt="sm">
|
||||||
<Center w={200} h={200} bg="gray">
|
<Image
|
||||||
<IconImageInPicture />
|
src={previewImage}
|
||||||
</Center>
|
alt="Preview"
|
||||||
)}
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
</Box>
|
||||||
Simpan
|
</Box>
|
||||||
</Button>
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import posyandustate from '@/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu';
|
import posyandustate from '@/app/admin/(dashboard)/_state/kesehatan/posyandu/posyandu';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -94,25 +95,62 @@ function EditPosyandu() {
|
|||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<Title order={4}>Edit Posyandu</Title>
|
<Title order={4}>Edit Posyandu</Title>
|
||||||
{previewImage ? (
|
<Box>
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
) : (
|
<Box>
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
<Dropzone
|
||||||
<IconImageInPicture />
|
onDrop={(files) => {
|
||||||
</Center>
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
)}
|
if (selectedFile) {
|
||||||
<FileInput
|
setFile(selectedFile);
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
value={file}
|
}
|
||||||
onChange={async (e) => {
|
}}
|
||||||
if (!e) return;
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
setFile(e);
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
accept={{ 'image/*': [] }}
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
>
|
||||||
);
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
setPreviewImage(base64);
|
<Dropzone.Accept>
|
||||||
}}
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
/>
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -65,25 +66,62 @@ function CreatePosyandu() {
|
|||||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<Title order={4}>Create Posyandu</Title>
|
<Title order={4}>Create Posyandu</Title>
|
||||||
{previewImage ? (
|
<Box>
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
) : (
|
<Box>
|
||||||
<Center w={200} h={200} bg={"gray"}>
|
<Dropzone
|
||||||
<IconImageInPicture />
|
onDrop={(files) => {
|
||||||
</Center>
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
)}
|
if (selectedFile) {
|
||||||
<FileInput
|
setFile(selectedFile);
|
||||||
label={<Text fz={"sm"} fw={"bold"}>Upload Gambar</Text>}
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
value={file}
|
}
|
||||||
onChange={async (e) => {
|
}}
|
||||||
if (!e) return;
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
setFile(e);
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
accept={{ 'image/*': [] }}
|
||||||
"data:image/png;base64," + Buffer.from(buf).toString("base64")
|
>
|
||||||
);
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
setPreviewImage(base64);
|
<Dropzone.Accept>
|
||||||
}}
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
/>
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Posyandu</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Nama Posyandu</Text>}
|
||||||
placeholder='Masukkan nama posyandu'
|
placeholder='Masukkan nama posyandu'
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
|||||||
import programKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/program-kesehatan/programKesehatan';
|
import programKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/program-kesehatan/programKesehatan';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -91,57 +92,90 @@ function EditProgramKesehatan() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Title order={3}>Edit Program Kesehatan</Title>
|
<Title order={3}>Edit Program Kesehatan</Title>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
label={<Text fz="sm" fw="bold">Judul</Text>}
|
label={<Text fz="sm" fw="bold">Judul</Text>}
|
||||||
placeholder="masukkan judul"
|
placeholder="masukkan judul"
|
||||||
/>
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
value={formData.deskripsiSingkat}
|
|
||||||
onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })}
|
|
||||||
label={<Text fz="sm" fw="bold">Deskripsi Singkat</Text>}
|
|
||||||
placeholder="masukkan deskripsi"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Box>
|
|
||||||
<Text fz="sm" fw="bold">Deskripsi</Text>
|
|
||||||
<EditEditor
|
|
||||||
value={formData.deskripsi}
|
|
||||||
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
|
||||||
|
|
||||||
<FileInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
value={formData.deskripsiSingkat}
|
||||||
value={file}
|
onChange={(e) => setFormData({ ...formData, deskripsiSingkat: e.target.value })}
|
||||||
onChange={async (e) => {
|
label={<Text fz="sm" fw="bold">Deskripsi Singkat</Text>}
|
||||||
if (!e) return;
|
placeholder="masukkan deskripsi"
|
||||||
setFile(e);
|
/>
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
|
||||||
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
|
||||||
);
|
|
||||||
setPreviewImage(base64);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{previewImage ? (
|
<Box>
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
<Text fz="sm" fw="bold">Deskripsi</Text>
|
||||||
) : (
|
<EditEditor
|
||||||
<Center w={200} h={200} bg="gray">
|
value={formData.deskripsi}
|
||||||
<IconImageInPicture />
|
onChange={(val) => setFormData({ ...formData, deskripsi: val })}
|
||||||
</Center>
|
/>
|
||||||
)}
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Box>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
<div>
|
||||||
Simpan
|
<Text size="xl" inline>
|
||||||
</Button>
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
</Stack>
|
</Text>
|
||||||
</Paper>
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box >
|
</Box >
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import CreateEditor from '../../../_com/createEditor';
|
import CreateEditor from '../../../_com/createEditor';
|
||||||
import programKesehatan from '../../../_state/kesehatan/program-kesehatan/programKesehatan';
|
import programKesehatan from '../../../_state/kesehatan/program-kesehatan/programKesehatan';
|
||||||
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
|
||||||
function CreateProgramKesehatan() {
|
function CreateProgramKesehatan() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -94,28 +95,61 @@ function CreateProgramKesehatan() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
|
<Box>
|
||||||
|
<Dropzone
|
||||||
|
onDrop={(files) => {
|
||||||
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
|
if (selectedFile) {
|
||||||
|
setFile(selectedFile);
|
||||||
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
<FileInput
|
<div>
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
<Text size="xl" inline>
|
||||||
value={file}
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
onChange={async (e) => {
|
</Text>
|
||||||
if (!e) return;
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
setFile(e);
|
Maksimal 5MB dan harus format gambar
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
</Text>
|
||||||
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
</div>
|
||||||
);
|
</Group>
|
||||||
setPreviewImage(base64);
|
</Dropzone>
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{previewImage ? (
|
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
|
||||||
) : (
|
|
||||||
<Center w={200} h={200} bg="gray">
|
|
||||||
<IconImageInPicture />
|
|
||||||
</Center>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
Simpan
|
Simpan
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
import puskesmasState from '@/app/admin/(dashboard)/_state/kesehatan/puskesmas/puskesmas';
|
import puskesmasState from '@/app/admin/(dashboard)/_state/kesehatan/puskesmas/puskesmas';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { ChangeEvent, useEffect, useState } from 'react';
|
import { ChangeEvent, useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -141,22 +142,6 @@ function EditPuskesmas() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFileChange = (selectedFile: File | null) => {
|
|
||||||
if (selectedFile) {
|
|
||||||
setFile(selectedFile);
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
if (e.target?.result) {
|
|
||||||
setPreviewImage(e.target.result as string);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(selectedFile);
|
|
||||||
} else {
|
|
||||||
setFile(null);
|
|
||||||
setPreviewImage(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
@@ -186,7 +171,7 @@ function EditPuskesmas() {
|
|||||||
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
<Paper bg={colors['white-1']} p="md" w={{ base: '100%', md: '50%' }}>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Title order={3}>Edit Puskesmas</Title>
|
<Title order={3}>Edit Puskesmas</Title>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Nama Puskesmas</Text>}
|
label={<Text fz="sm" fw="bold">Nama Puskesmas</Text>}
|
||||||
placeholder="masukkan nama puskesmas"
|
placeholder="masukkan nama puskesmas"
|
||||||
@@ -252,26 +237,66 @@ function EditPuskesmas() {
|
|||||||
onChange={(e) => handleNestedChange('kontak', 'kontakUGD', e.target.value)}
|
onChange={(e) => handleNestedChange('kontak', 'kontakUGD', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FileInput
|
<Box>
|
||||||
placeholder="Pilih gambar"
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
label="Gambar"
|
<Box>
|
||||||
accept="image/*"
|
<Dropzone
|
||||||
leftSection={<IconImageInPicture size={16} />}
|
onDrop={(files) => {
|
||||||
value={file}
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
onChange={handleFileChange}
|
if (selectedFile) {
|
||||||
/>
|
setFile(selectedFile);
|
||||||
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
{previewImage ? (
|
<div>
|
||||||
<Image alt="Preview" src={previewImage} w={200} h={200} />
|
<Text size="xl" inline>
|
||||||
) : (
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
<Center w={200} h={200} bg="gray">
|
</Text>
|
||||||
<IconImageInPicture />
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
</Center>
|
Maksimal 5MB dan harus format gambar
|
||||||
)}
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
<Button
|
{/* Tampilkan preview kalau ada */}
|
||||||
onClick={handleSubmit}
|
{previewImage && (
|
||||||
bg={colors['blue-button']}
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
bg={colors['blue-button']}
|
||||||
loading={statePuskesmas.edit.loading}
|
loading={statePuskesmas.edit.loading}
|
||||||
>
|
>
|
||||||
Simpan Perubahan
|
Simpan Perubahan
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { Box, Button, Center, FileInput, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -38,7 +39,7 @@ function CreatePuskesmas() {
|
|||||||
setFile(null);
|
setFile(null);
|
||||||
setPreviewImage(null);
|
setPreviewImage(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
@@ -144,7 +145,7 @@ function CreatePuskesmas() {
|
|||||||
statePuskesmas.create.form.kontak.facebook = e.target.value;
|
statePuskesmas.create.form.kontak.facebook = e.target.value;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={<Text fz="sm" fw="bold">Kontak UGD</Text>}
|
label={<Text fz="sm" fw="bold">Kontak UGD</Text>}
|
||||||
placeholder="masukkan kontak ugd"
|
placeholder="masukkan kontak ugd"
|
||||||
value={statePuskesmas.create.form.kontak.kontakUGD}
|
value={statePuskesmas.create.form.kontak.kontakUGD}
|
||||||
@@ -153,27 +154,61 @@ function CreatePuskesmas() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FileInput
|
<Box>
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar</Text>}
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
value={file}
|
<Box>
|
||||||
onChange={async (e) => {
|
<Dropzone
|
||||||
if (!e) return;
|
onDrop={(files) => {
|
||||||
setFile(e);
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
const base64 = await e.arrayBuffer().then((buf) =>
|
if (selectedFile) {
|
||||||
'data:image/png;base64,' + Buffer.from(buf).toString('base64')
|
setFile(selectedFile);
|
||||||
);
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
setPreviewImage(base64);
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
{previewImage ? (
|
<div>
|
||||||
<Image alt="" src={previewImage} w={200} h={200} />
|
<Text size="xl" inline>
|
||||||
) : (
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
<Center w={200} h={200} bg="gray">
|
</Text>
|
||||||
<IconImageInPicture />
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
</Center>
|
Maksimal 5MB dan harus format gambar
|
||||||
)}
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
<Button onClick={handleSubmit} bg={colors['blue-button']}>
|
||||||
Simpan Puskesmas
|
Simpan Puskesmas
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ function Page() {
|
|||||||
const [barChartData, setBarChartData] = useState<Array<{ month: string; count: number }>>([]);
|
const [barChartData, setBarChartData] = useState<Array<{ month: string; count: number }>>([]);
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
|
if (!data && !loading) {
|
||||||
|
state.findMany.load();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (data) {
|
if (data) {
|
||||||
// Hitung total berdasarkan jenis kelamin
|
// Hitung total berdasarkan jenis kelamin
|
||||||
const totalLaki = data.filter((item: any) => item.jenisKelamin?.name?.toLowerCase() === 'laki-laki').length;
|
const totalLaki = data.filter((item: any) => item.jenisKelamin?.name?.toLowerCase() === 'laki-laki').length;
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Alert, Box, Button, Center, FileInput, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Alert, Box, Button, Center, Group, Image, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import stateProfilePPID from '../../../_state/ppid/profile_ppid/profile_PPID';
|
import stateProfilePPID from '../../../_state/ppid/profile_ppid/profile_PPID';
|
||||||
|
|
||||||
import ApiFetch from '@/lib/api-fetch';
|
import ApiFetch from '@/lib/api-fetch';
|
||||||
import { IconAlertCircle, IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
import { Dropzone } from '@mantine/dropzone';
|
||||||
|
import { IconAlertCircle, IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import Biodata from './biodata/biodataForm';
|
import Biodata from './biodata/biodataForm';
|
||||||
@@ -58,21 +59,6 @@ function EditProfilePPID() {
|
|||||||
stateProfilePPID.editForm.updateField(field as any, value);
|
stateProfilePPID.editForm.updateField(field as any, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFileChange = (newFile: File | null) => {
|
|
||||||
if (!newFile) {
|
|
||||||
setFile(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setFile(newFile);
|
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (event) => {
|
|
||||||
setPreviewImage(event.target?.result as string);
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(newFile);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (isSubmitting || !stateProfilePPID.editForm.form.name.trim()) {
|
if (isSubmitting || !stateProfilePPID.editForm.form.name.trim()) {
|
||||||
toast.error("Nama wajib diisi");
|
toast.error("Nama wajib diisi");
|
||||||
@@ -183,26 +169,61 @@ function EditProfilePPID() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* File Upload */}
|
{/* File Upload */}
|
||||||
<FileInput
|
|
||||||
label={<Text fz="sm" fw="bold">Upload Gambar Baru (Opsional)</Text>}
|
|
||||||
value={file}
|
|
||||||
onChange={handleFileChange}
|
|
||||||
accept="image/*"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Preview Gambar */}
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="sm" fw="bold" mb="xs">Preview Gambar</Text>
|
<Text fz={"md"} fw={"bold"}>Gambar</Text>
|
||||||
{previewImage ? (
|
<Box>
|
||||||
<Image alt="Profile preview" src={previewImage} w={200} h={200} fit="cover" radius="md" />
|
<Dropzone
|
||||||
) : (
|
onDrop={(files) => {
|
||||||
<Center w={200} h={200} bg="gray.2">
|
const selectedFile = files[0]; // Ambil file pertama
|
||||||
<Stack align="center" gap="xs">
|
if (selectedFile) {
|
||||||
<IconImageInPicture size={48} color="gray" />
|
setFile(selectedFile);
|
||||||
<Text size="sm" c="gray">Tidak ada gambar</Text>
|
setPreviewImage(URL.createObjectURL(selectedFile)); // Buat preview
|
||||||
</Stack>
|
}
|
||||||
</Center>
|
}}
|
||||||
)}
|
onReject={() => toast.error('File tidak valid.')}
|
||||||
|
maxSize={5 * 1024 ** 2} // Maks 5MB
|
||||||
|
accept={{ 'image/*': [] }}
|
||||||
|
>
|
||||||
|
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||||
|
<Dropzone.Accept>
|
||||||
|
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Accept>
|
||||||
|
<Dropzone.Reject>
|
||||||
|
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||||
|
</Dropzone.Reject>
|
||||||
|
<Dropzone.Idle>
|
||||||
|
<IconPhoto size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||||
|
</Dropzone.Idle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xl" inline>
|
||||||
|
Drag gambar ke sini atau klik untuk pilih file
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed" inline mt={7}>
|
||||||
|
Maksimal 5MB dan harus format gambar
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Dropzone>
|
||||||
|
|
||||||
|
{/* Tampilkan preview kalau ada */}
|
||||||
|
{previewImage && (
|
||||||
|
<Box mt="sm">
|
||||||
|
<Image
|
||||||
|
src={previewImage}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '200px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Rich Components */}
|
{/* Rich Components */}
|
||||||
|
|||||||
@@ -93,12 +93,15 @@ function EditPosisiOrganisasiPPID() {
|
|||||||
label={<Text fw={"bold"} fz={"sm"}>Nama Posisi Organisasi</Text>}
|
label={<Text fw={"bold"} fz={"sm"}>Nama Posisi Organisasi</Text>}
|
||||||
placeholder='Masukkan nama posisi organisasi'
|
placeholder='Masukkan nama posisi organisasi'
|
||||||
/>
|
/>
|
||||||
<EditEditor
|
<Box>
|
||||||
value={formData.deskripsi}
|
<Text fz={"md"} fw={"bold"}>Deskripsi</Text>
|
||||||
onChange={(htmlContent) => {
|
<EditEditor
|
||||||
setFormData({ ...formData, deskripsi: htmlContent });
|
value={formData.deskripsi}
|
||||||
}}
|
onChange={(htmlContent) => {
|
||||||
/>
|
setFormData({ ...formData, deskripsi: htmlContent });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={formData.hierarki}
|
value={formData.hierarki}
|
||||||
onChange={(e) => setFormData({ ...formData, hierarki: parseInt(e.target.value) })}
|
onChange={(e) => setFormData({ ...formData, hierarki: parseInt(e.target.value) })}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||||
import stateStrukturPPID from '@/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID';
|
import stateStrukturPPID from '@/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
@@ -46,12 +46,15 @@ function CreatePosisiOrganisasiPPID() {
|
|||||||
value={stateOrganisasi.create.form.nama}
|
value={stateOrganisasi.create.form.nama}
|
||||||
onChange={(e) => (stateOrganisasi.create.form.nama = e.currentTarget.value)}
|
onChange={(e) => (stateOrganisasi.create.form.nama = e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<CreateEditor
|
<Box>
|
||||||
value={stateOrganisasi.create.form.deskripsi}
|
<Text fz={"md"} fw={"bold"}>Deskripsi</Text>
|
||||||
onChange={(htmlContent) => {
|
<CreateEditor
|
||||||
stateOrganisasi.create.form.deskripsi = htmlContent;
|
value={stateOrganisasi.create.form.deskripsi}
|
||||||
}}
|
onChange={(htmlContent) => {
|
||||||
/>
|
stateOrganisasi.create.form.deskripsi = htmlContent;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Hierarki"
|
label="Hierarki"
|
||||||
type="number"
|
type="number"
|
||||||
|
|||||||
Reference in New Issue
Block a user