Compare commits

...

7 Commits

Author SHA1 Message Date
3348cbe8e3 fix: pelayanan surat
Deskripsi:
- pelayanan surat

No Issues
2026-01-14 17:41:27 +08:00
727984a076 fix: update data pengajuan surat
Deskripsi:
- loading saat melakukan pencarian
- disable select dan input date saat status selesai

No Issues
2026-01-14 15:58:51 +08:00
a7a0ad7e37 Merge pull request 'amalia/13-jan-26' (#109) from amalia/13-jan-26 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/jenna-mcp/pulls/109
2026-01-13 17:18:01 +08:00
9fed41cbe8 fix: testing surat 2026-01-13 17:10:59 +08:00
fc387fe8e6 fix: menu setting
deskiripsi:
- navigate
- list length

No Issues
2026-01-13 15:38:29 +08:00
80df579499 qc: nomer 2 dan 4
Deskripsi:
- button back
- breadcrumb
- active menu

No Issues
2026-01-13 15:10:08 +08:00
5bbbc15c27 Merge pull request 'fix: qc' (#108) from amalia/12-jan-26 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/jenna-mcp/pulls/108
2026-01-12 17:47:04 +08:00
7 changed files with 111 additions and 75 deletions

View File

@@ -207,7 +207,7 @@ export default function DesaSetting({
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{list?.map((v: any) => (
{list.length > 0 && list?.map((v: any) => (
<Table.Tr key={v.id}>
<Table.Td>{v.name}</Table.Td>
<Table.Td>

View File

@@ -4,19 +4,17 @@ import {
Button,
Divider,
Flex,
Grid,
Group,
Input,
List,
Modal,
Stack,
Table,
Text,
Title,
Tooltip,
Tooltip
} from "@mantine/core";
import { useDisclosure, useShallowEffect } from "@mantine/hooks";
import { IconEdit, IconEye, IconPlus, IconTrash } from "@tabler/icons-react";
import { IconEye, IconTrash } from "@tabler/icons-react";
import type { JsonValue } from "generated/prisma/runtime/library";
import { useState } from "react";
import useSWR from "swr";
@@ -625,7 +623,7 @@ export default function KategoriPelayananSurat({
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{list?.map((v: any) => (
{list.length > 0 && list?.map((v: any) => (
<Table.Tr key={v.id}>
<Table.Td>{v.name}</Table.Td>
<Table.Td>

View File

@@ -1,5 +1,5 @@
import apiFetch from "@/lib/apiFetch";
import { Flex, Loader, Modal, Text } from "@mantine/core";
import { Flex, Modal, Progress, Stack, Text } from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks";
import html2canvas from "html2canvas";
import jsPDF from "jspdf";
@@ -35,7 +35,7 @@ export default function ModalSurat({
fontSize: "14px",
fontFamily: "Times New Roman",
};
const [uploading, setUploading] = useState<"Menyiapkan" | "Mengupload" | "Selesai">("Menyiapkan")
const [uploading, setUploading] = useState<{ text: "Menyiapkan" | "Mengupload" | "Selesai", value: number }>({ text: "Menyiapkan", value: 10 })
const hiddenRef = useRef<any>(null);
const { data, mutate, isLoading } = useSWR("surat", () =>
apiFetch.api.surat.detail.get({
@@ -52,7 +52,7 @@ export default function ModalSurat({
const uploadPdf = async () => {
try {
if (data && data.data && data.data.surat && (data.data.surat.file == "" || data.data.surat.file == null)) {
setUploading("Mengupload");
setUploading({ text: "Mengupload", value: 75 });
const element = hiddenRef.current;
const canvas = await html2canvas(element, {
scale: 2,
@@ -95,7 +95,7 @@ export default function ModalSurat({
filename: resImg.data?.filename!,
});
setUploading("Selesai");
setUploading({ text: "Selesai", value: 100 });
setTimeout(() => {
onClose(resUpdate.data?.link);
}, 1000)
@@ -136,14 +136,24 @@ export default function ModalSurat({
},
}}
title={
<Flex justify="space-between" align="center" w="100%">
<div style={{ fontSize: 18, fontWeight: 600 }}>Preview Surat</div>
<>
<Flex justify="space-between" align="center" w="100%">
<div style={{ fontSize: 18, fontWeight: 600 }}>Preview Surat</div>
<Flex gap={8} align="center">
{/* <Flex gap={8} align="center">
<Loader color="blue" size="xs" />
<Text size="sm">{uploading}</Text>
<Text size="sm">{uploading.text}</Text>
</Flex> */}
</Flex>
</Flex>
<Stack
align="stretch"
justify="center"
gap="xs"
>
<Text size="sm" ta="center">{uploading.text} - Harap menunggu sampai selesai</Text>
<Progress radius="md" value={uploading.value} animated size="lg" />
</Stack>
</>
}
>
<div ref={hiddenRef} style={A4Style}>

View File

@@ -317,57 +317,60 @@ function SearchData() {
}
return (
<FormSection
title="Cari Pengajuan Surat"
info="Masukkan nomor pengajuan dan nomor telepon yang digunakan saat pengajuan surat."
>
<Grid>
<Grid.Col span={6}>
<TextInput
label={
<FieldLabel
label="Nomor Pengajuan"
hint="Nomor pengajuan surat"
/>
}
placeholder="PS-2025-000123"
onChange={(e) => {
setSearchPengajuan(e.target.value);
}}
/>
</Grid.Col>
<>
<FullScreenLoading visible={submitLoading} text="Mencari Data" />
<FormSection
title="Cari Pengajuan Surat"
info="Masukkan nomor pengajuan dan nomor telepon yang digunakan saat pengajuan surat."
>
<Grid>
<Grid.Col span={6}>
<TextInput
label={
<FieldLabel
label="Nomor Pengajuan"
hint="Nomor pengajuan surat"
/>
}
placeholder="PS-2025-000123"
onChange={(e) => {
setSearchPengajuan(e.target.value);
}}
/>
</Grid.Col>
<Grid.Col span={6}>
<TextInput
label={
<FieldLabel
label="Nomor Telephone"
hint="Nomor telephone yang dapat dihubungi / terhubung dengan whatsapp"
/>
}
placeholder="08123456789"
type="number"
onChange={(e) => {
setSearchPengajuanPhone(e.target.value);
}}
/>
</Grid.Col>
<Grid.Col span={6}>
<TextInput
label={
<FieldLabel
label="Nomor Telephone"
hint="Nomor telephone yang dapat dihubungi / terhubung dengan whatsapp"
/>
}
placeholder="08123456789"
type="number"
onChange={(e) => {
setSearchPengajuanPhone(e.target.value);
}}
/>
</Grid.Col>
<Grid.Col span={12}>
<Button
fullWidth
variant="light"
color="blue"
onClick={() => {
handleSearch();
}}
loading={submitLoading}
>
Cari Pengajuan
</Button>
</Grid.Col>
</Grid>
</FormSection>
<Grid.Col span={12}>
<Button
fullWidth
variant="light"
color="blue"
onClick={() => {
handleSearch();
}}
loading={submitLoading}
>
Cari Pengajuan
</Button>
</Grid.Col>
</Grid>
</FormSection>
</>
);
}
@@ -387,6 +390,7 @@ function DataUpdate({
const [dataSyaratDokumen, setDataSyaratDokumen] = useState<DataItem[]>([]);
const [dataPengajuan, setDataPengajuan] = useState<DataPengajuan | {}>({});
const [status, setStatus] = useState("");
const [loadingFetchData, setLoadingFetchData] = useState(false);
const [formSurat, setFormSurat] = useState<FormUpdateSurat>({
dataPelengkap: [],
syaratDokumen: [],
@@ -394,6 +398,7 @@ function DataUpdate({
async function fetchData() {
try {
setLoadingFetchData(true);
const res = await apiFetch.api.pelayanan["detail-data"].post({
nomerPengajuan: noPengajuan,
});
@@ -421,6 +426,8 @@ function DataUpdate({
}
} catch (error) {
console.error("Error fetching data:", error);
} finally {
setLoadingFetchData(false);
}
}
@@ -600,7 +607,7 @@ function DataUpdate({
return (
<>
<FullScreenLoading visible={submitLoading} />
<FullScreenLoading visible={submitLoading || loadingFetchData} />
<Modal
opened={opened}
onClose={close}
@@ -667,6 +674,7 @@ function DataUpdate({
<Grid.Col span={6} key={index}>
{item.type == "enum" ? (
<Select
disabled={status != "ditolak" && status != "antrian"}
allowDeselect={false}
label={<FieldLabel label={item.name} hint={item.desc} />}
data={item.options ?? []}
@@ -686,6 +694,7 @@ function DataUpdate({
/>
) : item.type == "date" ? (
<DateInput
disabled={status != "ditolak" && status != "antrian"}
locale="id"
valueFormat="DD MMMM YYYY"
label={<FieldLabel label={item.name} hint={item.desc} />}
@@ -712,7 +721,7 @@ function DataUpdate({
(n: any) => n.key === item.key,
)?.value,
)
: parseTanggalID(item.value)
: parseTanggalID(item.value)
}
/>
) : (

View File

@@ -73,7 +73,7 @@ export default function DashboardLayout() {
<AppShell
padding="lg"
navbar={{
width: 260,
width: 300,
breakpoint: "sm",
collapsed: { mobile: !opened, desktop: !opened },
}}
@@ -215,8 +215,6 @@ function NavigationDashboard() {
const location = useLocation();
const [permissions, setPermissions] = useState<JsonValue[]>([]);
console.log(location)
useEffect(() => {
async function fetchPermissions() {
const { data } = await apiFetch.api.user.find.get();
@@ -292,14 +290,19 @@ function NavigationDashboard() {
.map((item) => (
<NavLink
key={item.path}
active={isActive(item.path as keyof typeof clientRoute)}
active={isActive(item.path as keyof typeof clientRoute) ||
(location.pathname == "/scr/dashboard/pelayanan-surat/detail-pelayanan" && item.path == "/scr/dashboard/pelayanan-surat/list-pelayanan") ||
(location.pathname == "/scr/dashboard/pengaduan/detail" && item.path == "/scr/dashboard/pengaduan/list") ||
(location.pathname == "/scr/dashboard/warga/detail-warga" && item.path == "/scr/dashboard/warga/list-warga")}
leftSection={item.icon}
label={
<Flex align="center" gap={6}>
<Text fw={500}>{item.label}</Text>
{(isActive(item.path as keyof typeof clientRoute) ||
{(
isActive(item.path as keyof typeof clientRoute) ||
(location.pathname == "/scr/dashboard/pelayanan-surat/detail-pelayanan" && item.path == "/scr/dashboard/pelayanan-surat/list-pelayanan") ||
(location.pathname == "/scr/dashboard/pengaduan/detail" && item.path == "/scr/dashboard/pengaduan/list")
(location.pathname == "/scr/dashboard/pengaduan/detail" && item.path == "/scr/dashboard/pengaduan/list") ||
(location.pathname == "/scr/dashboard/warga/detail-warga" && item.path == "/scr/dashboard/warga/list-warga")
)
&& (
<Badge
@@ -319,7 +322,10 @@ function NavigationDashboard() {
navigate(clientRoutes[item.path as keyof typeof clientRoute])
}
style={{
backgroundColor: isActive(item.path as keyof typeof clientRoute)
backgroundColor: isActive(item.path as keyof typeof clientRoute) ||
(location.pathname == "/scr/dashboard/pelayanan-surat/detail-pelayanan" && item.path == "/scr/dashboard/pelayanan-surat/list-pelayanan") ||
(location.pathname == "/scr/dashboard/pengaduan/detail" && item.path == "/scr/dashboard/pengaduan/list") ||
(location.pathname == "/scr/dashboard/warga/detail-warga" && item.path == "/scr/dashboard/warga/list-warga")
? "rgba(0,255,200,0.1)"
: "transparent",
borderRadius: "8px",

View File

@@ -1,4 +1,5 @@
import BreadCrumbs from "@/components/BreadCrumbs";
import FullScreenLoading from "@/components/FullScreenLoading";
import ModalFile from "@/components/ModalFile";
import ModalSurat from "@/components/ModalSurat";
import notification from "@/components/notificationGlobal";
@@ -125,6 +126,7 @@ function DetailDataPengajuan({
const [editValue, setEditValue] = useState({ id: "", jenis: "", val: "", option: null as any, type: "", key: "" })
const [openEdit, setOpenEdit] = useState(false)
const [loadingUpdate, setLoadingUpdate] = useState(false)
const [loadingFS, setLoadingFS] = useState({ value: false, text: "" })
useEffect(() => {
async function fetchHost() {
@@ -143,6 +145,7 @@ function DetailDataPengajuan({
async function sendWA({ status, linkSurat, linkUpdate }: { status: string, linkSurat: string, linkUpdate: string }) {
try {
setLoadingFS({ value: true, text: "Sending message to warga" })
const resWA = await apiFetch.api["send-wa"]["pengajuan-surat"].post({
noPengajuan: data?.noPengajuan ?? "",
jenisSurat: data?.category ?? "",
@@ -181,10 +184,14 @@ function DetailDataPengajuan({
type: "error",
});
}
finally {
setLoadingFS({ value: false, text: "" })
}
}
const handleKonfirmasi = async (cat: "terima" | "tolak") => {
try {
setLoadingFS({ value: true, text: "Updating status" })
const statusFix = cat == "tolak"
? "ditolak"
: data.status == "antrian"
@@ -234,6 +241,8 @@ function DetailDataPengajuan({
message: "Failed to update pengajuan surat",
type: "error",
});
} finally {
setLoadingFS({ value: false, text: "" })
}
};
@@ -307,6 +316,8 @@ function DetailDataPengajuan({
return (
<>
<FullScreenLoading visible={loadingFS.value} text={loadingFS.text} />
{/* MODAL EDIT DATA PELENGKAP */}
<Modal
opened={openEdit}
@@ -374,6 +385,7 @@ function DetailDataPengajuan({
open={openedPreviewFile && !_.isEmpty(viewImg.file)}
onClose={() => {
setOpenedPreviewFile(false);
setViewImg({ file: "", folder: "" })
}}
folder={viewImg.folder}
fileName={viewImg.file}

View File

@@ -18,12 +18,13 @@ import {
import type { JsonValue } from "generated/prisma/runtime/library";
import _ from "lodash";
import { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import { useLocation, useNavigate } from "react-router-dom";
export default function DetailSettingPage() {
const { search } = useLocation();
const query = new URLSearchParams(search);
const type = query.get("type");
const navigate = useNavigate();
const [permissions, setPermissions] = useState<JsonValue[]>([]);
const dataMenu = [
{ title: "Dashboard", link: "/scr/dashboard/dashboard-home", active: false },
@@ -114,7 +115,7 @@ export default function DetailSettingPage() {
.map((item) => (
<NavLink
key={item.key}
href={"?type=" + item.path}
onClick={()=>{navigate("?type=" + item.path)}}
label={item.label}
leftSection={item.icon}
active={