Compare commits
4 Commits
nico/6-aug
...
nico/8-aug
| Author | SHA1 | Date | |
|---|---|---|---|
| b3bf6b0327 | |||
| a65529cb23 | |||
| afc7bced44 | |||
| 0ac9fa1f53 |
@@ -221,81 +221,6 @@ model KategoriPrestasiDesa {
|
||||
PrestasiDesa PrestasiDesa[]
|
||||
}
|
||||
|
||||
//========================================= INDEKS KEPUASAN MASYARAKAT ========================================= //
|
||||
// Entitas Survey
|
||||
model Survey {
|
||||
id String @id @default(cuid())
|
||||
title String // Judul survei
|
||||
totalRespondents Int // Total jumlah responden
|
||||
averageScore Float // Rata-rata skor
|
||||
monthlyStats MonthlyStat[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
// Entitas Statistik Bulanan
|
||||
model MonthlyStat {
|
||||
id String @id @default(cuid())
|
||||
month String // Nama bulan (e.g., "Januari", "Februari")
|
||||
respondentsCount Int // Jumlah responden per bulan
|
||||
surveyId String @unique(map: "monthly_stat_survey_id_month_key")
|
||||
survey Survey @relation(fields: [surveyId], references: [id])
|
||||
AgeStat AgeStat[]
|
||||
ResponseStat ResponseStat[]
|
||||
genderStat genderStat[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
// Entitas Gender
|
||||
|
||||
model genderStat {
|
||||
id String @id @default(cuid())
|
||||
laki Int
|
||||
perempuan Int
|
||||
percentLaki Float
|
||||
percentPerempuan Float
|
||||
total Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
MonthlyStat MonthlyStat? @relation(fields: [monthlyStatId], references: [id])
|
||||
monthlyStatId String?
|
||||
}
|
||||
|
||||
// Entitas Age
|
||||
|
||||
model AgeStat {
|
||||
id String @id @default(cuid())
|
||||
group String // "18-44", "45+" dll
|
||||
count Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
MonthlyStat MonthlyStat? @relation(fields: [monthlyStatId], references: [id])
|
||||
monthlyStatId String?
|
||||
}
|
||||
|
||||
// Entitas Response
|
||||
|
||||
model ResponseStat {
|
||||
id String @id @default(cuid())
|
||||
label String // BAIK / BURUK
|
||||
count Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
MonthlyStat MonthlyStat? @relation(fields: [monthlyStatId], references: [id])
|
||||
monthlyStatId String?
|
||||
}
|
||||
|
||||
//========================================= MENU PPID ========================================= //
|
||||
|
||||
//========================================= STRUKTUR PPID ========================================= //
|
||||
@@ -732,16 +657,16 @@ model PelayananPendudukNonPermanen {
|
||||
|
||||
// ========================================= PENGHARGAAN ========================================= //
|
||||
model Penghargaan {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
juara String
|
||||
deskripsi String @db.Text
|
||||
deskripsi String @db.Text
|
||||
image FileStorage? @relation(fields: [imageId], references: [id])
|
||||
imageId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
// ========================================= MENU KESEHATAN ========================================= //
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { toast } from "react-toastify";
|
||||
@@ -22,19 +23,6 @@ const defaultForm = {
|
||||
kategoriBeritaId: "",
|
||||
};
|
||||
|
||||
// 3. Kategori proxy
|
||||
const category = proxy({
|
||||
findMany: {
|
||||
data: [] as Prisma.KategoriBeritaGetPayload<{ omit: { isActive: true } }>[],
|
||||
async load() {
|
||||
const res = await ApiFetch.api.desa.berita.category["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
category.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 4. Berita proxy
|
||||
const berita = proxy({
|
||||
create: {
|
||||
@@ -71,6 +59,8 @@ const berita = proxy({
|
||||
},
|
||||
},
|
||||
|
||||
// State untuk berita utama (hanya 1)
|
||||
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.BeritaGetPayload<{
|
||||
@@ -83,38 +73,43 @@ const berita = proxy({
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
|
||||
async load(page = 1, limit = 10) {
|
||||
berita.findMany.loading = true;
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "", kategori = "") => {
|
||||
berita.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
berita.findMany.page = page;
|
||||
try {
|
||||
const res = await ApiFetch.api.desa.berita["find-many"].get({
|
||||
query: {
|
||||
page,
|
||||
limit,
|
||||
},
|
||||
});
|
||||
berita.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
if (kategori) query.kategori = kategori;
|
||||
|
||||
const res = await ApiFetch.api.desa.berita["find-many"].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
berita.findMany.data = res.data.data ?? [];
|
||||
berita.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
berita.findMany.data = [];
|
||||
berita.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch berita paginated:", err);
|
||||
berita.findMany.data = [];
|
||||
berita.findMany.totalPages = 1;
|
||||
} finally {
|
||||
berita.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
findUnique: {
|
||||
data: null as
|
||||
| Prisma.BeritaGetPayload<{
|
||||
include: {
|
||||
image: true;
|
||||
kategoriBerita: true;
|
||||
};
|
||||
}> | null,
|
||||
data: null as Prisma.BeritaGetPayload<{
|
||||
include: {
|
||||
image: true;
|
||||
kategoriBerita: true;
|
||||
};
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(`/api/desa/berita/${id}`);
|
||||
@@ -122,11 +117,11 @@ const berita = proxy({
|
||||
const data = await res.json();
|
||||
berita.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error('Failed to fetch berita:', res.statusText);
|
||||
console.error("Failed to fetch berita:", res.statusText);
|
||||
berita.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching berita:', error);
|
||||
console.error("Error fetching berita:", error);
|
||||
berita.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
@@ -140,14 +135,14 @@ const berita = proxy({
|
||||
berita.delete.loading = true;
|
||||
|
||||
const response = await fetch(`/api/desa/berita/delete/${id}`, {
|
||||
method: 'DELETE',
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(result.message || "Berita berhasil dihapus");
|
||||
await berita.findMany.load(); // refresh list
|
||||
@@ -172,21 +167,21 @@ const berita = proxy({
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/desa/berita/${id}`, {
|
||||
method: 'GET',
|
||||
method: "GET",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
|
||||
if (result?.success) {
|
||||
const data = result.data;
|
||||
this.id = data.id;
|
||||
@@ -203,7 +198,9 @@ const berita = proxy({
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading berita:", error);
|
||||
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
@@ -217,14 +214,14 @@ const berita = proxy({
|
||||
toast.error(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
berita.edit.loading = true;
|
||||
|
||||
|
||||
const response = await fetch(`/api/desa/berita/${this.id}`, {
|
||||
method: 'PUT',
|
||||
method: "PUT",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
judul: this.form.judul,
|
||||
@@ -234,14 +231,16 @@ const berita = proxy({
|
||||
imageId: this.form.imageId,
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
|
||||
throw new Error(
|
||||
errorData.message || `HTTP error! status: ${response.status}`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
|
||||
if (result.success) {
|
||||
toast.success("Berhasil update berita");
|
||||
await berita.findMany.load(); // refresh list
|
||||
@@ -251,7 +250,11 @@ const berita = proxy({
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating berita:", error);
|
||||
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat update berita");
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Terjadi kesalahan saat update berita"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
berita.edit.loading = false;
|
||||
@@ -271,21 +274,22 @@ const berita = proxy({
|
||||
};
|
||||
}> | null,
|
||||
loading: false,
|
||||
async load() {
|
||||
// findFirst.load()
|
||||
async load(kategori?: string) {
|
||||
this.loading = true;
|
||||
try {
|
||||
const res = await ApiFetch.api.desa.berita["find-first"].get();
|
||||
const res = await ApiFetch.api.desa.berita["find-first"].get({
|
||||
query: kategori ? { kategori } : {},
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
// Add type assertion to ensure type safety
|
||||
berita.findFirst.data = res.data.data as Prisma.BeritaGetPayload<{
|
||||
include: {
|
||||
image: true;
|
||||
kategoriBerita: true;
|
||||
};
|
||||
}> | null;
|
||||
this.data = res.data.data || null;
|
||||
} else {
|
||||
this.data = null;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch berita terbaru:", err);
|
||||
this.data = null;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
@@ -299,7 +303,7 @@ const berita = proxy({
|
||||
};
|
||||
}>[],
|
||||
loading: false,
|
||||
|
||||
|
||||
async load() {
|
||||
try {
|
||||
this.loading = true;
|
||||
@@ -313,15 +317,227 @@ const berita = proxy({
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
//=============== Kategori Berita ===============
|
||||
|
||||
const templateKategoriBerita = z.object({
|
||||
name: z.string().min(1, "Nama harus diisi"),
|
||||
});
|
||||
|
||||
const defaultKategoriBerita = {
|
||||
name: "",
|
||||
};
|
||||
|
||||
const kategoriBerita = proxy({
|
||||
create: {
|
||||
form: { ...defaultKategoriBerita },
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateKategoriBerita.safeParse(kategoriBerita.create.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
kategoriBerita.create.loading = true;
|
||||
const res = await ApiFetch.api.desa.kategoriberita["create"].post(
|
||||
kategoriBerita.create.form
|
||||
);
|
||||
if (res.status === 200) {
|
||||
kategoriBerita.findMany.load();
|
||||
return toast.success("Data Kategori Berita Berhasil Dibuat");
|
||||
}
|
||||
console.log(res);
|
||||
return toast.error("failed create");
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return toast.error("failed create");
|
||||
} finally {
|
||||
kategoriBerita.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: [] as Prisma.KategoriBeritaGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}>[],
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.desa.kategoriberita["findMany"].get();
|
||||
if (res.status === 200) {
|
||||
kategoriBerita.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.KategoriBeritaGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}> | null,
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(`/api/desa/kategoriberita/${id}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
kategoriBerita.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch data", res.status, res.statusText);
|
||||
kategoriBerita.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
kategoriBerita.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async delete(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
kategoriBerita.delete.loading = true;
|
||||
|
||||
const response = await fetch(`/api/desa/kategoriberita/del/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(
|
||||
result.message || "Data Kategori Berita berhasil dihapus"
|
||||
);
|
||||
await kategoriBerita.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(
|
||||
result?.message || "Gagal menghapus Data Kategori Berita"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus Data Kategori Berita");
|
||||
} finally {
|
||||
kategoriBerita.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...defaultKategoriBerita },
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/desa/kategoriberita/${id}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result?.success) {
|
||||
const data = result.data;
|
||||
this.id = data.id;
|
||||
this.form = {
|
||||
name: data.name,
|
||||
};
|
||||
return data; // Return the loaded data
|
||||
} else {
|
||||
throw new Error(result?.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading kategori berita:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async update() {
|
||||
const cek = templateKategoriBerita.safeParse(kategoriBerita.update.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
kategoriBerita.update.loading = true;
|
||||
|
||||
const response = await fetch(`/api/desa/kategoriberita/${this.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: this.form.name,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
errorData.message || `HTTP error! status: ${response.status}`
|
||||
);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
toast.success("Berhasil update data kategori berita");
|
||||
await kategoriBerita.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(
|
||||
result.message || "Gagal update data kategori berita"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating data kategori berita:", error);
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Terjadi kesalahan saat update data kategori berita"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
kategoriBerita.update.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
kategoriBerita.update.id = "";
|
||||
kategoriBerita.update.form = { ...defaultKategoriBerita };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 5. State global
|
||||
const stateDashboardBerita = proxy({
|
||||
category,
|
||||
kategoriBerita,
|
||||
berita,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,222 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { toast } from "react-toastify";
|
||||
import { proxy } from "valtio";
|
||||
import { z } from "zod";
|
||||
|
||||
const templateKategoriPengumuman = z.object({
|
||||
name: z.string().min(1, "Nama harus diisi"),
|
||||
});
|
||||
|
||||
const defaultKategoriPengumuman = {
|
||||
name: "",
|
||||
};
|
||||
|
||||
const category = proxy({
|
||||
create: {
|
||||
form: { ...defaultKategoriPengumuman },
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateKategoriPengumuman.safeParse(category.create.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
category.create.loading = true;
|
||||
const res = await ApiFetch.api.desa.kategoripengumuman["create"].post(
|
||||
category.create.form
|
||||
);
|
||||
if (res.status === 200) {
|
||||
category.findMany.load();
|
||||
return toast.success("Data Kategori Pengumuman Berhasil Dibuat");
|
||||
}
|
||||
console.log(res);
|
||||
return toast.error("failed create");
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return toast.error("failed create");
|
||||
} finally {
|
||||
category.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: [] as Prisma.CategoryPengumumanGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}>[],
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.desa.kategoripengumuman["findMany"].get();
|
||||
if (res.status === 200) {
|
||||
category.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.CategoryPengumumanGetPayload<{
|
||||
omit: {
|
||||
isActive: true;
|
||||
};
|
||||
}> | null,
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(`/api/desa/kategoripengumuman/${id}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
category.findUnique.data = data.data ?? null;
|
||||
} else {
|
||||
console.error("Failed to fetch data", res.status, res.statusText);
|
||||
category.findUnique.data = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
category.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async delete(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
category.delete.loading = true;
|
||||
|
||||
const response = await fetch(`/api/desa/kategoripengumuman/del/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(
|
||||
result.message || "Data Kategori Pengumuman berhasil dihapus"
|
||||
);
|
||||
await category.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(
|
||||
result?.message || "Gagal menghapus Data Kategori Pengumuman"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus Data Kategori Pengumuman");
|
||||
} finally {
|
||||
category.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...defaultKategoriPengumuman },
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/desa/kategoripengumuman/${id}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result?.success) {
|
||||
const data = result.data;
|
||||
this.id = data.id;
|
||||
this.form = {
|
||||
name: data.name,
|
||||
};
|
||||
return data; // Return the loaded data
|
||||
} else {
|
||||
throw new Error(result?.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading kategori berita:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async update() {
|
||||
const cek = templateKategoriPengumuman.safeParse(category.update.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
category.update.loading = true;
|
||||
|
||||
const response = await fetch(`/api/desa/kategoripengumuman/${this.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: this.form.name,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
errorData.message || `HTTP error! status: ${response.status}`
|
||||
);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
toast.success("Berhasil update data kategori pengumuman");
|
||||
await category.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(
|
||||
result.message || "Gagal update data kategori pengumuman"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating data kategori pengumuman:", error);
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Terjadi kesalahan saat update data kategori pengumuman"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
category.update.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
category.update.id = "";
|
||||
category.update.form = { ...defaultKategoriPengumuman };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const templateFormPengumuman = z.object({
|
||||
judul: z.string().min(3, "Judul minimal 3 karakter"),
|
||||
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
||||
@@ -12,22 +224,6 @@ const templateFormPengumuman = z.object({
|
||||
categoryPengumumanId: z.string().nonempty(),
|
||||
});
|
||||
|
||||
const category = proxy({
|
||||
findMany: {
|
||||
data: null as
|
||||
| null
|
||||
| Prisma.CategoryPengumumanGetPayload<{ omit: { isActive: true } }>[],
|
||||
async load() {
|
||||
const res = await ApiFetch.api.desa.pengumuman.category[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
category.findMany.data = (res.data?.data as any) ?? [];
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
type PengumumanForm = Prisma.PengumumanGetPayload<{
|
||||
select: {
|
||||
judul: true;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,25 +5,21 @@ import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core'
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
function LayoutTabsBerita({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const tabs = [
|
||||
{
|
||||
label: "List Survey",
|
||||
value: "listSurvey",
|
||||
href: "/admin/landing-page/indeks-kepuasan-masyarakat/list-survey"
|
||||
label: "List Berita",
|
||||
value: "list_berita",
|
||||
href: "/admin/desa/berita/list-berita"
|
||||
},
|
||||
{
|
||||
label: "List Bulanan",
|
||||
value: "listBulanan",
|
||||
href: "/admin/landing-page/indeks-kepuasan-masyarakat/list-bulanan"
|
||||
label: "Kategori Berita",
|
||||
value: "kategori_berita",
|
||||
href: "/admin/desa/berita/kategori-berita"
|
||||
},
|
||||
{
|
||||
label: "List Gender Stat",
|
||||
value: "listGenderStat",
|
||||
href: "/admin/landing-page/indeks-kepuasan-masyarakat/list-gender-stat"
|
||||
}
|
||||
|
||||
];
|
||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
||||
@@ -45,7 +41,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Title order={3}>Indeks Kepuasan Masyarakat</Title>
|
||||
<Title order={3}>Gallery</Title>
|
||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
||||
{tabs.map((e, i) => (
|
||||
@@ -64,4 +60,4 @@ function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutTabs;
|
||||
export default LayoutTabsBerita;
|
||||
@@ -0,0 +1,80 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function EditKategoriBerita() {
|
||||
const editState = useProxy(stateDashboardBerita.kategoriBerita)
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const [formData, setFormData] = useState({
|
||||
name: editState.update.form.name || '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const loadKategori = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const data = await editState.update.load(id); // akses langsung, bukan dari proxy
|
||||
if (data) {
|
||||
setFormData({
|
||||
name: data.name || '',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading kategori Berita:", error);
|
||||
toast.error("Gagal memuat data kategori Berita");
|
||||
}
|
||||
};
|
||||
|
||||
loadKategori();
|
||||
}, [params?.id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
editState.update.form = {
|
||||
...editState.update.form,
|
||||
name: formData.name,
|
||||
};
|
||||
await editState.update.update();
|
||||
toast.success('Kategori Berita berhasil diperbarui!');
|
||||
router.push('/admin/desa/berita/kategori-berita');
|
||||
} catch (error) {
|
||||
console.error('Error updating kategori Berita:', error);
|
||||
toast.error('Terjadi kesalahan saat memperbarui kategori Berita');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors["blue-button"]} size={30} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={3}>Edit Kategori Berita</Title>
|
||||
<TextInput
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
label={<Text fz={"sm"} fw={"bold"}>Nama Kategori Berita</Text>}
|
||||
placeholder="masukkan nama kategori Berita"
|
||||
/>
|
||||
|
||||
<Button onClick={handleSubmit}>Simpan</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditKategoriBerita;
|
||||
@@ -0,0 +1,55 @@
|
||||
'use client'
|
||||
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
|
||||
function CreateKategoriBerita() {
|
||||
const createState = useProxy(stateDashboardBerita.kategoriBerita)
|
||||
const router = useRouter();
|
||||
|
||||
const resetForm = () => {
|
||||
createState.create.form = {
|
||||
name: "",
|
||||
};
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await createState.create.create();
|
||||
resetForm();
|
||||
router.push("/admin/desa/berita/kategori-berita")
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Create Kategori Berita</Title>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Nama Kategori Berita</Text>}
|
||||
placeholder='Masukkan nama kategori Berita'
|
||||
value={createState.create.form.name}
|
||||
onChange={(val) => {
|
||||
createState.create.form.name = val.target.value;
|
||||
}}
|
||||
/>
|
||||
<Group>
|
||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateKategoriBerita;
|
||||
128
src/app/admin/(dashboard)/desa/berita/kategori-berita/page.tsx
Normal file
128
src/app/admin/(dashboard)/desa/berita/kategori-berita/page.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import stateDashboardBerita from '../../../_state/desa/berita';
|
||||
|
||||
|
||||
|
||||
function KategoriBerita() {
|
||||
const [search, setSearch] = useState('');
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Kategori Berita'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListKategoriBerita search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListKategoriBerita({ search }: { search: string }) {
|
||||
const listDataState = useProxy(stateDashboardBerita.kategoriBerita)
|
||||
const router = useRouter();
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
listDataState.findMany.load()
|
||||
}, [])
|
||||
|
||||
const handleDelete = () => {
|
||||
if (selectedId) {
|
||||
listDataState.delete.delete(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
|
||||
listDataState.findMany.load()
|
||||
}
|
||||
}
|
||||
|
||||
const filteredData = (listDataState.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
if (!listDataState.findMany.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p="md">
|
||||
<Stack>
|
||||
<JudulList
|
||||
title='List Kategori Berita'
|
||||
href='/admin/desa/berita/kategori-berita/create'
|
||||
/>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>No</TableTh>
|
||||
<TableTh>Nama</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Hapus</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Box w={100}>
|
||||
<Text truncate="end" fz={"sm"}>{index + 1}</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>
|
||||
<Button color='green' onClick={() => router.push(`/admin/pendidikan/perpustakaan-digital/kategori-Berita/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
color='red'
|
||||
disabled={listDataState.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleDelete}
|
||||
text='Apakah anda yakin ingin menghapus kategori Berita ini?'
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default KategoriBerita;
|
||||
13
src/app/admin/(dashboard)/desa/berita/layout.tsx
Normal file
13
src/app/admin/(dashboard)/desa/berita/layout.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
'use client'
|
||||
import React from 'react';
|
||||
import LayoutTabsBerita from './_com/layoutTabs';
|
||||
|
||||
function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<LayoutTabsBerita>
|
||||
{children}
|
||||
</LayoutTabsBerita>
|
||||
);
|
||||
}
|
||||
|
||||
export default Layout;
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
"use client";
|
||||
|
||||
import {
|
||||
@@ -7,7 +8,6 @@ import {
|
||||
Image,
|
||||
Paper,
|
||||
Select,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
@@ -22,9 +22,8 @@ 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 { useShallowEffect } from "@mantine/hooks";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import stateDashboardBerita from "../../../../_state/desa/berita";
|
||||
import stateDashboardBerita from "@/app/admin/(dashboard)/_state/desa/berita";
|
||||
|
||||
|
||||
function EditBerita() {
|
||||
const beritaState = useProxy(stateDashboardBerita);
|
||||
@@ -43,6 +42,7 @@ function EditBerita() {
|
||||
|
||||
// Load berita by id saat pertama kali
|
||||
useEffect(() => {
|
||||
beritaState.kategoriBerita.findMany.load()
|
||||
const loadBerita = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
@@ -99,7 +99,7 @@ function EditBerita() {
|
||||
|
||||
await beritaState.berita.edit.update();
|
||||
toast.success("Berita berhasil diperbarui!");
|
||||
router.push("/admin/desa/berita");
|
||||
router.push("/admin/desa/berita/list-berita");
|
||||
} catch (error) {
|
||||
console.error("Error updating berita:", error);
|
||||
toast.error("Terjadi kesalahan saat memperbarui berita");
|
||||
@@ -154,22 +154,29 @@ function EditBerita() {
|
||||
<Box>
|
||||
<Text fz={"sm"} fw={"bold"}>Konten</Text>
|
||||
<EditEditor
|
||||
value={formData.content}
|
||||
onChange={(htmlContent) => {
|
||||
setFormData((prev) => ({ ...prev, content: htmlContent }));
|
||||
beritaState.berita.edit.form.content = htmlContent;
|
||||
}}
|
||||
value={formData.content}
|
||||
onChange={(htmlContent) => {
|
||||
setFormData((prev) => ({ ...prev, content: htmlContent }));
|
||||
beritaState.berita.edit.form.content = htmlContent;
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<SelectCategory
|
||||
<Select
|
||||
value={formData.kategoriBeritaId}
|
||||
onChange={(val) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
kategoriBeritaId: val?.id || ''
|
||||
});
|
||||
}}
|
||||
onChange={(val) => setFormData({ ...formData, kategoriBeritaId: val || "" })}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Kategori</Text>}
|
||||
placeholder='Pilih kategori'
|
||||
data={
|
||||
beritaState.kategoriBerita.findMany.data?.map((v) => ({
|
||||
value: v.id,
|
||||
label: v.name
|
||||
})) || []
|
||||
}
|
||||
clearable
|
||||
searchable
|
||||
required
|
||||
error={!formData.kategoriBeritaId ? "Pilih kategori" : undefined}
|
||||
/>
|
||||
|
||||
<Button onClick={handleSubmit}>Edit Berita</Button>
|
||||
@@ -179,61 +186,4 @@ function EditBerita() {
|
||||
);
|
||||
}
|
||||
|
||||
interface SelectCategoryProps {
|
||||
onChange: (value: Prisma.KategoriBeritaGetPayload<{
|
||||
select: {
|
||||
name: true;
|
||||
id: true;
|
||||
};
|
||||
}> | null) => void;
|
||||
value?: string | null;
|
||||
defaultValue?: string | null;
|
||||
}
|
||||
|
||||
function SelectCategory({
|
||||
onChange,
|
||||
value,
|
||||
defaultValue,
|
||||
}: SelectCategoryProps) {
|
||||
const categoryState = useProxy(stateDashboardBerita.category);
|
||||
|
||||
useShallowEffect(() => {
|
||||
categoryState.findMany.load().then(() => {
|
||||
console.log("Kategori berhasil dimuat:", categoryState.findMany.data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (!categoryState.findMany.data) {
|
||||
return <Skeleton height={38} />;
|
||||
}
|
||||
|
||||
|
||||
const selectedValue = value || defaultValue;
|
||||
|
||||
return (
|
||||
<Select
|
||||
label={<Text fz={"sm"} fw={"bold"}>Kategori</Text>}
|
||||
placeholder="Pilih kategori"
|
||||
data={categoryState.findMany.data.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
value={selectedValue || null}
|
||||
onChange={(val: string | null) => {
|
||||
if (val) {
|
||||
const selected = categoryState.findMany.data?.find((item) => item.id === val);
|
||||
if (selected) {
|
||||
onChange(selected);
|
||||
}
|
||||
} else {
|
||||
onChange(null);
|
||||
}
|
||||
}}
|
||||
searchable
|
||||
clearable
|
||||
nothingFoundMessage="Tidak ditemukan"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditBerita;
|
||||
@@ -8,8 +8,8 @@ import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
|
||||
import colors from '@/con/colors';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import stateDashboardBerita from '../../../_state/desa/berita';
|
||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
||||
|
||||
function DetailBerita() {
|
||||
const beritaState = useProxy(stateDashboardBerita)
|
||||
@@ -28,7 +28,7 @@ function DetailBerita() {
|
||||
beritaState.berita.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
router.push("/admin/desa/berita")
|
||||
router.push("/admin/desa/berita/list-berita")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ function DetailBerita() {
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (beritaState.berita.findUnique.data) {
|
||||
router.push(`/admin/desa/berita/${beritaState.berita.findUnique.data.id}/edit`);
|
||||
router.push(`/admin/desa/berita/list-berita/${beritaState.berita.findUnique.data.id}/edit`);
|
||||
}
|
||||
}}
|
||||
disabled={!beritaState.berita.findUnique.data}
|
||||
@@ -1,16 +1,16 @@
|
||||
'use client'
|
||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
||||
import colors from '@/con/colors';
|
||||
import ApiFetch from '@/lib/api-fetch';
|
||||
import { Box, Button, Center, FileInput, Image, Paper, Select, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { Box, Button, Center, FileInput, Image, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { IconArrowBack, IconImageInPicture } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import CreateEditor from '../../../_com/createEditor';
|
||||
import stateDashboardBerita from '../../../_state/desa/berita';
|
||||
|
||||
|
||||
export default function CreateBerita() {
|
||||
const beritaState = useProxy(stateDashboardBerita);
|
||||
@@ -18,6 +18,10 @@ export default function CreateBerita() {
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const router = useRouter()
|
||||
|
||||
useShallowEffect(() => {
|
||||
beritaState.kategoriBerita.findMany.load()
|
||||
}, []);
|
||||
|
||||
const resetForm = () => {
|
||||
// Reset state di valtio
|
||||
beritaState.berita.create.form = {
|
||||
@@ -57,7 +61,7 @@ export default function CreateBerita() {
|
||||
|
||||
// Reset form setelah submit
|
||||
resetForm();
|
||||
router.push("/admin/desa/berita")
|
||||
router.push("/admin/desa/berita/list-berita")
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -78,11 +82,27 @@ export default function CreateBerita() {
|
||||
label={<Text fz={"sm"} fw={"bold"}>Judul</Text>}
|
||||
placeholder="masukkan judul"
|
||||
/>
|
||||
<SelectCategory
|
||||
value={beritaState.berita.create.form.kategoriBeritaId}
|
||||
onChange={(val) => {
|
||||
beritaState.berita.create.form.kategoriBeritaId = val?.id || "";
|
||||
<Select
|
||||
label={<Text fz={"sm"} fw={"bold"}>Kategori</Text>}
|
||||
placeholder="Pilih kategori"
|
||||
data={beritaState.kategoriBerita.findMany.data.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
value={beritaState.berita.create.form.kategoriBeritaId || null}
|
||||
onChange={(val: string | null) => {
|
||||
if (val) {
|
||||
const selected = beritaState.kategoriBerita.findMany.data?.find((item) => item.id === val);
|
||||
if (selected) {
|
||||
beritaState.berita.create.form.kategoriBeritaId = selected.id;
|
||||
}
|
||||
} else {
|
||||
beritaState.berita.create.form.kategoriBeritaId = "";
|
||||
}
|
||||
}}
|
||||
searchable
|
||||
clearable
|
||||
nothingFoundMessage="Tidak ditemukan"
|
||||
/>
|
||||
<TextInput
|
||||
value={beritaState.berita.create.form.deskripsi}
|
||||
@@ -126,61 +146,4 @@ export default function CreateBerita() {
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
|
||||
interface SelectCategoryProps {
|
||||
onChange: (value: Prisma.KategoriBeritaGetPayload<{
|
||||
select: {
|
||||
name: true;
|
||||
id: true;
|
||||
};
|
||||
}> | null) => void;
|
||||
value?: string | null;
|
||||
defaultValue?: string | null;
|
||||
}
|
||||
|
||||
function SelectCategory({
|
||||
onChange,
|
||||
value,
|
||||
defaultValue,
|
||||
}: SelectCategoryProps) {
|
||||
const categoryState = useProxy(stateDashboardBerita.category);
|
||||
|
||||
useShallowEffect(() => {
|
||||
categoryState.findMany.load().then(() => {
|
||||
console.log("Kategori berhasil dimuat:", categoryState.findMany.data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (!categoryState.findMany.data) {
|
||||
return <Skeleton height={38} />;
|
||||
}
|
||||
|
||||
|
||||
const selectedValue = value || defaultValue;
|
||||
|
||||
return (
|
||||
<Select
|
||||
label={<Text fz={"sm"} fw={"bold"}>Kategori</Text>}
|
||||
placeholder="Pilih kategori"
|
||||
data={categoryState.findMany.data.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
value={selectedValue || null}
|
||||
onChange={(val: string | null) => {
|
||||
if (val) {
|
||||
const selected = categoryState.findMany.data?.find((item) => item.id === val);
|
||||
if (selected) {
|
||||
onChange(selected);
|
||||
}
|
||||
} else {
|
||||
onChange(null);
|
||||
}
|
||||
}}
|
||||
searchable
|
||||
clearable
|
||||
nothingFoundMessage="Tidak ditemukan"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,9 @@ import { IconCircleDashedPlus, IconDeviceImacCog, IconSearch } from '@tabler/ico
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import stateDashboardBerita from '../../_state/desa/berita';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import stateDashboardBerita from '../../../_state/desa/berita';
|
||||
|
||||
|
||||
|
||||
function Berita() {
|
||||
@@ -38,22 +39,16 @@ function ListBerita({ search }: { search: string }) {
|
||||
} = beritaState.berita.findMany;
|
||||
|
||||
|
||||
// Fetch pertama kali
|
||||
// Fetch data when page or search changes
|
||||
useShallowEffect(() => {
|
||||
load(page, 10); // awal page = 1
|
||||
}, [page]);
|
||||
|
||||
const filteredData = (data || []).filter((item) => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.judul.toLowerCase().includes(keyword) ||
|
||||
item.kategoriBerita?.name.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
|
||||
if (loading || !data) {
|
||||
return <Skeleton h={500} />;
|
||||
}
|
||||
|
||||
const filteredData = data || [];
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
@@ -67,7 +62,7 @@ function ListBerita({ search }: { search: string }) {
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 1 }}>
|
||||
<Button
|
||||
onClick={() => router.push("/admin/desa/berita/create")}
|
||||
onClick={() => router.push("/admin/desa/berita/list-berita/create")}
|
||||
bg={colors["blue-button"]}
|
||||
>
|
||||
<IconCircleDashedPlus size={25} />
|
||||
@@ -107,7 +102,7 @@ function ListBerita({ search }: { search: string }) {
|
||||
<Button
|
||||
bg={"green"}
|
||||
onClick={() =>
|
||||
router.push(`/admin/desa/berita/${item.id}`)
|
||||
router.push(`/admin/desa/berita/list-berita/${item.id}`)
|
||||
}
|
||||
>
|
||||
<IconDeviceImacCog size={25} />
|
||||
@@ -0,0 +1,62 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
function LayoutTabsLayanan({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const tabs = [
|
||||
{
|
||||
label: "List Pengumuman",
|
||||
value: "listpengumuman",
|
||||
href: "/admin/desa/pengumuman/list-pengumuman"
|
||||
},
|
||||
{
|
||||
label: "Kategori Pengumuman",
|
||||
value: "kategoripengumuman",
|
||||
href: "/admin/desa/pengumuman/kategori-pengumuman"
|
||||
},
|
||||
];
|
||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
||||
|
||||
const handleTabChange = (value: string | null) => {
|
||||
const tab = tabs.find(t => t.value === value)
|
||||
if (tab) {
|
||||
router.push(tab.href)
|
||||
}
|
||||
setActiveTab(value)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const match = tabs.find(tab => tab.href === pathname)
|
||||
if (match) {
|
||||
setActiveTab(match.value)
|
||||
}
|
||||
}, [pathname])
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Title order={3}>Pengumuman</Title>
|
||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsPanel key={i} value={e.value}>
|
||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
||||
<></>
|
||||
</TabsPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
{children}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutTabsLayanan;
|
||||
@@ -0,0 +1,80 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function EditKategoriPengumuman() {
|
||||
const editState = useProxy(stateDesaPengumuman.category)
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const [formData, setFormData] = useState({
|
||||
name: editState.update.form.name || '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const loadKategori = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const data = await editState.update.load(id); // akses langsung, bukan dari proxy
|
||||
if (data) {
|
||||
setFormData({
|
||||
name: data.name || '',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading kategori Pengumuman:", error);
|
||||
toast.error("Gagal memuat data kategori Pengumuman");
|
||||
}
|
||||
};
|
||||
|
||||
loadKategori();
|
||||
}, [params?.id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
editState.update.form = {
|
||||
...editState.update.form,
|
||||
name: formData.name,
|
||||
};
|
||||
await editState.update.update();
|
||||
toast.success('Kategori Pengumuman berhasil diperbarui!');
|
||||
router.push('/admin/desa/pengumuman/kategori-pengumuman');
|
||||
} catch (error) {
|
||||
console.error('Error updating kategori Pengumuman:', error);
|
||||
toast.error('Terjadi kesalahan saat memperbarui kategori Pengumuman');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors["blue-button"]} size={30} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={"white"} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={3}>Edit Kategori Pengumuman</Title>
|
||||
<TextInput
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
label={<Text fz={"sm"} fw={"bold"}>Nama Kategori Pengumuman</Text>}
|
||||
placeholder="masukkan nama kategori Pengumuman"
|
||||
/>
|
||||
|
||||
<Button onClick={handleSubmit}>Simpan</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditKategoriPengumuman;
|
||||
@@ -0,0 +1,55 @@
|
||||
'use client'
|
||||
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
|
||||
function CreateKategoriPengumuman() {
|
||||
const createState = useProxy(stateDesaPengumuman.category)
|
||||
const router = useRouter();
|
||||
|
||||
const resetForm = () => {
|
||||
createState.create.form = {
|
||||
name: "",
|
||||
};
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await createState.create.create();
|
||||
resetForm();
|
||||
router.push("/admin/desa/pengumuman/kategori-pengumuman")
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Create Kategori Pengumuman</Title>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Nama Kategori Pengumuman</Text>}
|
||||
placeholder='Masukkan nama kategori Pengumuman'
|
||||
value={createState.create.form.name}
|
||||
onChange={(val) => {
|
||||
createState.create.form.name = val.target.value;
|
||||
}}
|
||||
/>
|
||||
<Group>
|
||||
<Button onClick={handleSubmit} bg={colors['blue-button']}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateKategoriPengumuman;
|
||||
@@ -0,0 +1,128 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import stateDesaPengumuman from '../../../_state/desa/pengumuman';
|
||||
|
||||
|
||||
|
||||
function KategoriPengumuman() {
|
||||
const [search, setSearch] = useState('');
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Kategori Pengumuman'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListKategoriPengumuman search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListKategoriPengumuman({ search }: { search: string }) {
|
||||
const listDataState = useProxy(stateDesaPengumuman.category)
|
||||
const router = useRouter();
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
listDataState.findMany.load()
|
||||
}, [])
|
||||
|
||||
const handleDelete = () => {
|
||||
if (selectedId) {
|
||||
listDataState.delete.delete(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
|
||||
listDataState.findMany.load()
|
||||
}
|
||||
}
|
||||
|
||||
const filteredData = (listDataState.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
if (!listDataState.findMany.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p="md">
|
||||
<Stack>
|
||||
<JudulList
|
||||
title='List Kategori Pengumuman'
|
||||
href='/admin/desa/pengumuman/kategori-pengumuman/create'
|
||||
/>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>No</TableTh>
|
||||
<TableTh>Nama</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Hapus</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item, index) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Box w={100}>
|
||||
<Text truncate="end" fz={"sm"}>{index + 1}</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>
|
||||
<Button color='green' onClick={() => router.push(`/admin/desa/pengumuman/kategori-pengumuman/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
color='red'
|
||||
disabled={listDataState.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleDelete}
|
||||
text='Apakah anda yakin ingin menghapus kategori Pengumuman ini?'
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default KategoriPengumuman;
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import LayoutTabs from './_lib/layoutTabs';
|
||||
import LayoutTabs from './_com/layoutTabs';
|
||||
|
||||
function Layout({children} : {children: React.ReactNode}) {
|
||||
function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<LayoutTabs>
|
||||
{children}
|
||||
@@ -1,4 +1,6 @@
|
||||
'use client'
|
||||
import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor';
|
||||
import stateDesaPengumuman from '@/app/admin/(dashboard)/_state/desa/pengumuman';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
@@ -6,8 +8,7 @@ import { Prisma } from '@prisma/client';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import CreateEditor from '../../../_com/createEditor';
|
||||
import stateDesaPengumuman from '../../../_state/desa/pengumuman';
|
||||
|
||||
|
||||
function CreatePengumuman() {
|
||||
const pengumumanState = useProxy(stateDesaPengumuman)
|
||||
@@ -5,10 +5,10 @@ import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconCircleDashedPlus, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import stateDesaPengumuman from '../../_state/desa/pengumuman';
|
||||
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
||||
import { useState } from 'react';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import stateDesaPengumuman from '../../../_state/desa/pengumuman';
|
||||
|
||||
|
||||
function Pengumuman() {
|
||||
@@ -16,7 +16,7 @@ function Pengumuman() {
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Posisi Organisasi'
|
||||
title='List Pengumuman'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
@@ -113,7 +113,7 @@ function ListPengumuman({ search }: { search: string }) {
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text='Apakah anda yakin ingin menghapus berita ini?'
|
||||
text='Apakah anda yakin ingin menghapus pengumuman ini?'
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
@@ -1,128 +0,0 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan-masyarakat';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Select, Stack, Text, TextInput } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
function EditListBulanan() {
|
||||
const editState = useProxy(indeksKepuasanState.monthlyStatState)
|
||||
const params = useParams()
|
||||
const router = useRouter()
|
||||
const [formData, setFormData] = useState({
|
||||
month: editState.edit.form.month || '',
|
||||
respondentsCount: editState.edit.form.respondentsCount || 0,
|
||||
surveyId: editState.edit.form.surveyId || '',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const loadSurvey = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const data = await editState.edit.load(id); // akses langsung, bukan dari proxy
|
||||
if (data) {
|
||||
setFormData({
|
||||
month: data.month,
|
||||
respondentsCount: data.respondentsCount,
|
||||
surveyId: data.surveyId,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading list bulanan:", error);
|
||||
toast.error("Gagal memuat data list bulanan");
|
||||
}
|
||||
};
|
||||
indeksKepuasanState.surveyState.findMany.load();
|
||||
loadSurvey();
|
||||
}, [params?.id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
|
||||
try {
|
||||
// edit global state with form data
|
||||
editState.edit.form = {
|
||||
...editState.edit.form,
|
||||
month: formData.month,
|
||||
respondentsCount: formData.respondentsCount,
|
||||
surveyId: formData.surveyId,
|
||||
};
|
||||
|
||||
await editState.edit.update();
|
||||
toast.success("list bulanan berhasil diperbarui!");
|
||||
router.push("/admin/landing-page/indeks-kepuasan-masyarakat/list-bulanan");
|
||||
} catch (error) {
|
||||
console.error("Error updating list bulanan:", error);
|
||||
toast.error("Terjadi kesalahan saat memperbarui list bulanan");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Text fz={"xl"} fw={"bold"}>Edit List Survey</Text>
|
||||
<Paper>
|
||||
<Stack gap={"xs"}>
|
||||
<TextInput
|
||||
value={formData.month}
|
||||
onChange={(val) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
month: val.target.value
|
||||
})
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Bulan</Text>}
|
||||
placeholder='Masukkan bulan'
|
||||
/>
|
||||
<TextInput
|
||||
value={formData.respondentsCount}
|
||||
onChange={(val) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
respondentsCount: Number(val.target.value)
|
||||
})
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Total Responden</Text>}
|
||||
placeholder='Masukkan total responden'
|
||||
/>
|
||||
<Select
|
||||
label={<Text fw="bold" fz="sm">Pilih Survey</Text>}
|
||||
placeholder="Pilih survey"
|
||||
value={formData.surveyId}
|
||||
onChange={(value) => {
|
||||
if (value) setFormData({
|
||||
...formData,
|
||||
surveyId: value
|
||||
})
|
||||
}}
|
||||
data={
|
||||
indeksKepuasanState.surveyState.findMany.data?.map((survey) => ({
|
||||
value: survey.id,
|
||||
label: `${survey.title} (${survey.totalRespondents} responden)`,
|
||||
})) || []
|
||||
}
|
||||
/>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditListBulanan;
|
||||
@@ -1,109 +0,0 @@
|
||||
'use client'
|
||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||
import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan-masyarakat';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
function DetailListBulanan() {
|
||||
const detailState = useProxy(indeksKepuasanState.monthlyStatState)
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const params = useParams()
|
||||
const router = useRouter()
|
||||
|
||||
useShallowEffect(() => {
|
||||
detailState.findUnique.load(params?.id as string)
|
||||
}, [])
|
||||
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
detailState.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
router.push("/admin/landing-page/indeks-kepuasan-masyarakat/list-bulanan")
|
||||
}
|
||||
}
|
||||
|
||||
if (!detailState.findUnique.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={40} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
||||
<Stack>
|
||||
<Text fz={"xl"} fw={"bold"}>Detail List Survey</Text>
|
||||
{detailState.findUnique.data ? (
|
||||
<Paper key={detailState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Bulan</Text>
|
||||
<Text fz={"lg"}>{detailState.findUnique.data?.month}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Total Responden</Text>
|
||||
<Text fz={"lg"}>{detailState.findUnique.data?.respondentsCount}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Survey</Text>
|
||||
<Text fz={"lg"}>{detailState.findUnique.data?.survey.title }</Text>
|
||||
</Box>
|
||||
<Flex gap={"xs"} mt={10}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (detailState.findUnique.data) {
|
||||
setSelectedId(detailState.findUnique.data.id);
|
||||
setModalHapus(true);
|
||||
}
|
||||
}}
|
||||
disabled={detailState.delete.loading || !detailState.findUnique.data}
|
||||
color={"red"}
|
||||
>
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (detailState.findUnique.data) {
|
||||
router.push(`/admin/landing-page/indeks-kepuasan-masyarakat/list-bulanan/${detailState.findUnique.data.id}/edit`);
|
||||
}
|
||||
}}
|
||||
disabled={!detailState.findUnique.data}
|
||||
color={"green"}
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Paper>
|
||||
) : null}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text='Apakah anda yakin ingin menghapus list bulanan ini?'
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailListBulanan;
|
||||
@@ -1,84 +0,0 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan-masyarakat';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
function CreateListBulanan() {
|
||||
const router = useRouter();
|
||||
const stateCreate = useProxy(indeksKepuasanState.monthlyStatState)
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
stateCreate.findMany.load();
|
||||
indeksKepuasanState.surveyState.findMany.load();
|
||||
}, []);
|
||||
|
||||
const resetForm = () => {
|
||||
stateCreate.create.form = {
|
||||
month: "",
|
||||
respondentsCount: 0,
|
||||
surveyId: "",
|
||||
};
|
||||
};
|
||||
const handleSubmit = async () => {
|
||||
await stateCreate.create.create();
|
||||
resetForm();
|
||||
router.push("/admin/landing-page/indeks-kepuasan-masyarakat/list-bulanan")
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Create List Bulanan</Title>
|
||||
<TextInput
|
||||
value={stateCreate.create.form.month}
|
||||
onChange={(val) => {
|
||||
stateCreate.create.form.month = val.target.value;
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Bulan</Text>}
|
||||
placeholder='Masukkan bulan'
|
||||
/>
|
||||
<TextInput
|
||||
value={stateCreate.create.form.respondentsCount}
|
||||
onChange={(val) => {
|
||||
stateCreate.create.form.respondentsCount = Number(val.target.value);
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Total Responden</Text>}
|
||||
placeholder='Masukkan total responden'
|
||||
/>
|
||||
<Select
|
||||
label={<Text fw="bold" fz="sm">Pilih Survey</Text>}
|
||||
placeholder="Pilih survey"
|
||||
value={stateCreate.create.form.surveyId}
|
||||
onChange={(value) => {
|
||||
if (value) stateCreate.create.form.surveyId = value;
|
||||
}}
|
||||
data={
|
||||
indeksKepuasanState.surveyState.findMany.data?.map((survey) => ({
|
||||
value: survey.id,
|
||||
label: `${survey.title} (${survey.totalRespondents} responden)`,
|
||||
})) || []
|
||||
}
|
||||
/>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateListBulanan;
|
||||
@@ -1,98 +0,0 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import indeksKepuasanState from '../../../_state/landing-page/indeks-kepuasan-masyarakat';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
|
||||
|
||||
|
||||
function ListBulananLandingPage() {
|
||||
const [search, setSearch] = useState('');
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='List Bulanan'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListBUlanan search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListBUlanan({ search }: { search: string }) {
|
||||
const listState = useProxy(indeksKepuasanState.monthlyStatState)
|
||||
const router = useRouter();
|
||||
useEffect(() => {
|
||||
listState.findMany.load()
|
||||
}, [])
|
||||
|
||||
const filteredData = (listState.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.month.toLowerCase().includes(keyword) ||
|
||||
item.respondentsCount.toString().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
if (!listState.findMany.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<JudulList
|
||||
title='List Bulanan'
|
||||
href='/admin/landing-page/indeks-kepuasan-masyarakat/list-bulanan/create'
|
||||
/>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Bulan</TableTh>
|
||||
<TableTh>Total Responden</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Box w={100}>
|
||||
<Text truncate="end" fz={"sm"}>{item.month}</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text truncate="end" fz={"sm"}>{item.respondentsCount}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push(`/admin/landing-page/indeks-kepuasan-masyarakat/list-bulanan/${item.id}`)}>
|
||||
<IconDeviceImacCog size={25} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default ListBulananLandingPage;
|
||||
@@ -1,186 +0,0 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan-masyarakat';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Select, Stack, Text, TextInput } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
function EditListSurvey() {
|
||||
const editState = useProxy(indeksKepuasanState.genderStatState)
|
||||
const params = useParams()
|
||||
const router = useRouter()
|
||||
const [formData, setFormData] = useState({
|
||||
laki: editState.edit.form.laki || 0,
|
||||
perempuan: editState.edit.form.perempuan || 0,
|
||||
monthlyStatId: editState.edit.form.monthlyStatId || '',
|
||||
total: editState.edit.form.total || 0,
|
||||
percentLaki: editState.edit.form.percentLaki || 0,
|
||||
percentPerempuan: editState.edit.form.percentPerempuan || 0,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
indeksKepuasanState.monthlyStatState.findMany.load();
|
||||
const loadSurvey = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const data = await editState.edit.load(id); // akses langsung, bukan dari proxy
|
||||
if (data) {
|
||||
setFormData({
|
||||
laki: data.laki || 0,
|
||||
perempuan: data.perempuan || 0,
|
||||
monthlyStatId: data.monthlyStatId || '',
|
||||
total: data.total || 0,
|
||||
percentLaki: data.percentLaki || 0,
|
||||
percentPerempuan: data.percentPerempuan || 0,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading list survey:", error);
|
||||
toast.error("Gagal memuat data list survey");
|
||||
}
|
||||
};
|
||||
loadSurvey();
|
||||
}, [params?.id]);
|
||||
|
||||
// Hitung total dan persentase saat nilai laki atau perempuan berubah
|
||||
useEffect(() => {
|
||||
const total = formData.laki + formData.perempuan;
|
||||
const percentLaki = total > 0 ? Math.round((formData.laki / total) * 100) : 0;
|
||||
const percentPerempuan = 100 - percentLaki;
|
||||
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
total,
|
||||
percentLaki,
|
||||
percentPerempuan
|
||||
}));
|
||||
}, [formData.laki, formData.perempuan]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!formData.monthlyStatId) {
|
||||
return toast.error("Silakan pilih bulan terlebih dahulu");
|
||||
}
|
||||
|
||||
if (formData.laki < 0 || formData.perempuan < 0) {
|
||||
return toast.error("Nilai tidak boleh negatif");
|
||||
}
|
||||
|
||||
try {
|
||||
// edit global state with form data
|
||||
editState.edit.form = {
|
||||
...editState.edit.form,
|
||||
laki: formData.laki,
|
||||
perempuan: formData.perempuan,
|
||||
monthlyStatId: formData.monthlyStatId,
|
||||
total: formData.total,
|
||||
percentLaki: formData.percentLaki,
|
||||
percentPerempuan: formData.percentPerempuan,
|
||||
};
|
||||
|
||||
await editState.edit.update();
|
||||
toast.success("Data gender berhasil diperbarui!");
|
||||
router.push("/admin/landing-page/indeks-kepuasan-masyarakat/list-gender-stat");
|
||||
} catch (error) {
|
||||
console.error("Error updating list gender:", error);
|
||||
toast.error("Terjadi kesalahan saat memperbarui data gender");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Text fz={"xl"} fw={"bold"}>Edit List Survey</Text>
|
||||
<Paper>
|
||||
<Stack gap={"xs"}>
|
||||
<TextInput
|
||||
type="number"
|
||||
value={formData.laki}
|
||||
onChange={(val) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
laki: Number(val.target.value)
|
||||
})
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Laki - Laki</Text>}
|
||||
placeholder='Masukkan laki - laki'
|
||||
/>
|
||||
<TextInput
|
||||
type="number"
|
||||
value={formData.perempuan}
|
||||
onChange={(val) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
perempuan: Number(val.target.value)
|
||||
})
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Perempuan</Text>}
|
||||
placeholder='Masukkan perempuan'
|
||||
/>
|
||||
<TextInput
|
||||
type="number"
|
||||
value={formData.total}
|
||||
label={<Text fw="bold" fz="sm">Total</Text>}
|
||||
disabled
|
||||
/>
|
||||
<TextInput
|
||||
type="number"
|
||||
value={formData.percentLaki}
|
||||
label={<Text fw="bold" fz="sm">Persentase Laki Laki</Text>}
|
||||
disabled
|
||||
/>
|
||||
<TextInput
|
||||
type="number"
|
||||
value={formData.percentPerempuan}
|
||||
label={<Text fw="bold" fz="sm">Persentase Perempuan</Text>}
|
||||
disabled
|
||||
/>
|
||||
<Select
|
||||
label={"Pilih Bulan"}
|
||||
placeholder="Pilih bulanan"
|
||||
value={formData.monthlyStatId}
|
||||
onChange={(value) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
monthlyStatId: value || ''
|
||||
}));
|
||||
}}
|
||||
data={
|
||||
indeksKepuasanState.monthlyStatState.findMany.data?.map((monthlyStat) => ({
|
||||
value: monthlyStat.id,
|
||||
label: `${monthlyStat.month} (${monthlyStat.respondentsCount} responden)`,
|
||||
})) || []
|
||||
}
|
||||
required
|
||||
error={!formData.monthlyStatId ? 'Bulan harus dipilih' : undefined}
|
||||
/>
|
||||
<Group>
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
loading={editState.edit.loading}
|
||||
>
|
||||
{editState.edit.loading ? 'Menyimpan...' : 'Simpan Perubahan'}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditListSurvey;
|
||||
@@ -1,113 +0,0 @@
|
||||
'use client'
|
||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||
import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan-masyarakat';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
function DetailGenderStat() {
|
||||
const detailState = useProxy(indeksKepuasanState.genderStatState)
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const params = useParams()
|
||||
const router = useRouter()
|
||||
|
||||
useShallowEffect(() => {
|
||||
detailState.findUnique.load(params?.id as string)
|
||||
}, [])
|
||||
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
detailState.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
router.push("/admin/landing-page/indeks-kepuasan-masyarakat/list-gender-stat")
|
||||
}
|
||||
}
|
||||
|
||||
if (!detailState.findUnique.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={40} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
||||
<Stack>
|
||||
<Text fz={"xl"} fw={"bold"}>Detail List Survey</Text>
|
||||
{detailState.findUnique.data ? (
|
||||
<Paper key={detailState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Laki Laki</Text>
|
||||
<Text fz={"lg"}>{detailState.findUnique.data?.laki}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Perempuan</Text>
|
||||
<Text fz={"lg"}>{detailState.findUnique.data?.perempuan}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Persentase Laki Laki</Text>
|
||||
<Text fz={"lg"}>{detailState.findUnique.data?.percentLaki}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Persentase Perempuan</Text>
|
||||
<Text fz={"lg"}>{detailState.findUnique.data?.percentPerempuan}</Text>
|
||||
</Box>
|
||||
<Flex gap={"xs"} mt={10}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (detailState.findUnique.data) {
|
||||
setSelectedId(detailState.findUnique.data.id);
|
||||
setModalHapus(true);
|
||||
}
|
||||
}}
|
||||
disabled={detailState.delete.loading || !detailState.findUnique.data}
|
||||
color={"red"}
|
||||
>
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (detailState.findUnique.data) {
|
||||
router.push(`/admin/landing-page/indeks-kepuasan-masyarakat/list-gender-stat/${detailState.findUnique.data.id}/edit`);
|
||||
}
|
||||
}}
|
||||
disabled={!detailState.findUnique.data}
|
||||
color={"green"}
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Paper>
|
||||
) : null}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text='Apakah anda yakin ingin menghapus list gender stat ini?'
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailGenderStat;
|
||||
@@ -1,103 +0,0 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan-masyarakat';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
|
||||
function CreateListGenderStat() {
|
||||
const router = useRouter();
|
||||
const stateCreate = useProxy(indeksKepuasanState.genderStatState)
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
stateCreate.findMany.load();
|
||||
indeksKepuasanState.monthlyStatState.findMany.load();
|
||||
}, []);
|
||||
|
||||
const resetForm = () => {
|
||||
stateCreate.create.form = {
|
||||
laki: 0,
|
||||
perempuan: 0,
|
||||
monthlyStatId: "",
|
||||
total: 0,
|
||||
percentLaki: 0,
|
||||
percentPerempuan: 0,
|
||||
};
|
||||
};
|
||||
const handleSubmit = async () => {
|
||||
await stateCreate.create.create();
|
||||
resetForm();
|
||||
router.push("/admin/landing-page/indeks-kepuasan-masyarakat/list-gender-stat")
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Create List Gender Stat</Title>
|
||||
<TextInput
|
||||
value={stateCreate.create.form.laki}
|
||||
onChange={(val) => {
|
||||
stateCreate.create.form.laki = Number(val.target.value);
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Laki Laki</Text>}
|
||||
placeholder='Masukkan laki laki'
|
||||
/>
|
||||
<TextInput
|
||||
value={stateCreate.create.form.perempuan}
|
||||
onChange={(val) => {
|
||||
stateCreate.create.form.perempuan = Number(val.target.value);
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Perempuan</Text>}
|
||||
placeholder='Masukkan perempuan'
|
||||
/>
|
||||
<TextInput
|
||||
value={stateCreate.create.form.total}
|
||||
label={<Text fw="bold" fz="sm">Total</Text>}
|
||||
disabled
|
||||
/>
|
||||
<TextInput
|
||||
value={stateCreate.create.form.percentLaki}
|
||||
label={<Text fw="bold" fz="sm">Persentase Laki Laki</Text>}
|
||||
disabled
|
||||
/>
|
||||
<TextInput
|
||||
value={stateCreate.create.form.percentPerempuan}
|
||||
label={<Text fw="bold" fz="sm">Persentase Perempuan</Text>}
|
||||
disabled
|
||||
/>
|
||||
<Select
|
||||
label={<Text fw="bold" fz="sm">Pilih Bulan</Text>}
|
||||
placeholder="Pilih bulanan"
|
||||
value={stateCreate.create.form.monthlyStatId}
|
||||
onChange={(value) => {
|
||||
if (value) stateCreate.create.form.monthlyStatId = value;
|
||||
}}
|
||||
data={
|
||||
indeksKepuasanState.monthlyStatState.findMany.data?.map((monthlyStat) => ({
|
||||
value: monthlyStat.id,
|
||||
label: `${monthlyStat.month} (${monthlyStat.respondentsCount} responden)`,
|
||||
})) || []
|
||||
}
|
||||
/>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateListGenderStat;
|
||||
@@ -1,106 +0,0 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
import indeksKepuasanState from '../../../_state/landing-page/indeks-kepuasan-masyarakat';
|
||||
|
||||
|
||||
|
||||
function ListGenderStat() {
|
||||
const [search, setSearch] = useState('');
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Gender Stat'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListGender search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListGender({ search }: { search: string }) {
|
||||
const listState = useProxy(indeksKepuasanState.genderStatState)
|
||||
const router = useRouter();
|
||||
useEffect(() => {
|
||||
listState.findMany.load()
|
||||
}, [])
|
||||
|
||||
const filteredData = (listState.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.laki.toString().includes(keyword) ||
|
||||
item.perempuan.toString().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
if (!listState.findMany.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<JudulList
|
||||
title='List Gender Stat'
|
||||
href='/admin/landing-page/indeks-kepuasan-masyarakat/list-gender-stat/create'
|
||||
/>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Laki Laki</TableTh>
|
||||
<TableTh>Perempuan</TableTh>
|
||||
<TableTh>Persentase Laki Laki</TableTh>
|
||||
<TableTh>Persentase Perempuan</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Box w={100}>
|
||||
<Text truncate="end" fz={"sm"}>{item.laki}</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text truncate="end" fz={"sm"}>{item.perempuan}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text truncate="end" fz={"sm"}>{item.percentLaki}%</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text truncate="end" fz={"sm"}>{item.percentPerempuan}%</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push(`/admin/landing-page/indeks-kepuasan-masyarakat/list-gender-stat/${item.id}`)}>
|
||||
<IconDeviceImacCog size={25} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default ListGenderStat;
|
||||
@@ -1,122 +0,0 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan-masyarakat';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
function EditListSurvey() {
|
||||
const editState = useProxy(indeksKepuasanState.surveyState)
|
||||
const params = useParams()
|
||||
const router = useRouter()
|
||||
const [formData, setFormData] = useState({
|
||||
title: editState.edit.form.title || '',
|
||||
totalRespondents: editState.edit.form.totalRespondents || 0,
|
||||
averageScore: editState.edit.form.averageScore || 0,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const loadSurvey = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const data = await editState.edit.load(id); // akses langsung, bukan dari proxy
|
||||
if (data) {
|
||||
setFormData({
|
||||
title: data.title || '',
|
||||
totalRespondents: data.totalRespondents || 0,
|
||||
averageScore: data.averageScore || 0,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading list survey:", error);
|
||||
toast.error("Gagal memuat data list survey");
|
||||
}
|
||||
};
|
||||
|
||||
loadSurvey();
|
||||
}, [params?.id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
|
||||
try {
|
||||
// edit global state with form data
|
||||
editState.edit.form = {
|
||||
...editState.edit.form,
|
||||
title: formData.title,
|
||||
totalRespondents: formData.totalRespondents,
|
||||
averageScore: formData.averageScore,
|
||||
};
|
||||
|
||||
await editState.edit.update();
|
||||
toast.success("list survey berhasil diperbarui!");
|
||||
router.push("/admin/landing-page/indeks-kepuasan-masyarakat/list-survey");
|
||||
} catch (error) {
|
||||
console.error("Error updating list survey:", error);
|
||||
toast.error("Terjadi kesalahan saat memperbarui list survey");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Text fz={"xl"} fw={"bold"}>Edit List Survey</Text>
|
||||
<Paper>
|
||||
<Stack gap={"xs"}>
|
||||
<TextInput
|
||||
value={formData.title}
|
||||
onChange={(val) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
title: val.target.value
|
||||
})
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Judul</Text>}
|
||||
placeholder='Masukkan judul'
|
||||
/>
|
||||
<TextInput
|
||||
value={formData.totalRespondents}
|
||||
onChange={(val) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
totalRespondents: Number(val.target.value)
|
||||
})
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Total Responden</Text>}
|
||||
placeholder='Masukkan total responden'
|
||||
/>
|
||||
<TextInput
|
||||
value={formData.averageScore}
|
||||
onChange={(val) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
averageScore: Number(val.target.value)
|
||||
})
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Skor Rata-rata</Text>}
|
||||
placeholder='Masukkan skor'
|
||||
/>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditListSurvey;
|
||||
@@ -1,109 +0,0 @@
|
||||
'use client'
|
||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||
import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan-masyarakat';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
function DetailKegiatanDesa() {
|
||||
const detailState = useProxy(indeksKepuasanState.surveyState)
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const params = useParams()
|
||||
const router = useRouter()
|
||||
|
||||
useShallowEffect(() => {
|
||||
detailState.findUnique.load(params?.id as string)
|
||||
}, [])
|
||||
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
detailState.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
router.push("/admin/landing-page/indeks-kepuasan-masyarakat/list-survey")
|
||||
}
|
||||
}
|
||||
|
||||
if (!detailState.findUnique.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={40} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
||||
<Stack>
|
||||
<Text fz={"xl"} fw={"bold"}>Detail List Survey</Text>
|
||||
{detailState.findUnique.data ? (
|
||||
<Paper key={detailState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Judul</Text>
|
||||
<Text fz={"lg"}>{detailState.findUnique.data?.title}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Total Responden</Text>
|
||||
<Text fz={"lg"}>{detailState.findUnique.data?.totalRespondents}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Rata-rata Skor</Text>
|
||||
<Text fz={"lg"}>{detailState.findUnique.data?.averageScore}</Text>
|
||||
</Box>
|
||||
<Flex gap={"xs"} mt={10}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (detailState.findUnique.data) {
|
||||
setSelectedId(detailState.findUnique.data.id);
|
||||
setModalHapus(true);
|
||||
}
|
||||
}}
|
||||
disabled={detailState.delete.loading || !detailState.findUnique.data}
|
||||
color={"red"}
|
||||
>
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (detailState.findUnique.data) {
|
||||
router.push(`/admin/landing-page/indeks-kepuasan-masyarakat/list-survey/${detailState.findUnique.data.id}/edit`);
|
||||
}
|
||||
}}
|
||||
disabled={!detailState.findUnique.data}
|
||||
color={"green"}
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Paper>
|
||||
) : null}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text='Apakah anda yakin ingin menghapus list survey ini?'
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailKegiatanDesa;
|
||||
@@ -1,78 +0,0 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan-masyarakat';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
|
||||
function CreateListSurvey() {
|
||||
const router = useRouter();
|
||||
const stateCreate = useProxy(indeksKepuasanState.surveyState)
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
stateCreate.findMany.load();
|
||||
}, []);
|
||||
|
||||
const resetForm = () => {
|
||||
stateCreate.create.form = {
|
||||
title: "",
|
||||
totalRespondents: 0,
|
||||
averageScore: 0,
|
||||
};
|
||||
};
|
||||
const handleSubmit = async () => {
|
||||
await stateCreate.create.create();
|
||||
resetForm();
|
||||
router.push("/admin/landing-page/indeks-kepuasan-masyarakat/list-survey")
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Create List Survey</Title>
|
||||
<TextInput
|
||||
value={stateCreate.create.form.title}
|
||||
onChange={(val) => {
|
||||
stateCreate.create.form.title = val.target.value;
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Judul</Text>}
|
||||
placeholder='Masukkan judul'
|
||||
/>
|
||||
<TextInput
|
||||
value={stateCreate.create.form.totalRespondents}
|
||||
onChange={(val) => {
|
||||
stateCreate.create.form.totalRespondents = Number(val.target.value);
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Total Responden</Text>}
|
||||
placeholder='Masukkan tahun'
|
||||
/>
|
||||
<TextInput
|
||||
value={stateCreate.create.form.averageScore}
|
||||
onChange={(val) => {
|
||||
stateCreate.create.form.averageScore = Number(val.target.value);
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Skor</Text>}
|
||||
placeholder='Masukkan skor'
|
||||
/>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateListSurvey;
|
||||
@@ -1,103 +0,0 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import indeksKepuasanState from '../../../_state/landing-page/indeks-kepuasan-masyarakat';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
|
||||
|
||||
|
||||
function ListSurveyLandingPage() {
|
||||
const [search, setSearch] = useState('');
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='List Survey'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListSurvey search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListSurvey({ search }: { search: string }) {
|
||||
const listState = useProxy(indeksKepuasanState.surveyState)
|
||||
const router = useRouter();
|
||||
useEffect(() => {
|
||||
listState.findMany.load()
|
||||
}, [])
|
||||
|
||||
const filteredData = (listState.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.title.toLowerCase().includes(keyword) ||
|
||||
item.totalRespondents.toString().includes(keyword) ||
|
||||
item.averageScore.toString().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
if (!listState.findMany.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<JudulList
|
||||
title='List Survey'
|
||||
href='/admin/landing-page/indeks-kepuasan-masyarakat/list-survey/create'
|
||||
/>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Judul</TableTh>
|
||||
<TableTh>Total Responden</TableTh>
|
||||
<TableTh>Skor Rata-rata</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Box w={100}>
|
||||
<Text truncate="end" fz={"sm"}>{item.title}</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text truncate="end" fz={"sm"}>{item.totalRespondents}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text truncate="end" fz={"sm"}>{item.averageScore}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push(`/admin/landing-page/indeks-kepuasan-masyarakat/list-survey/${item.id}`)}>
|
||||
<IconDeviceImacCog size={25} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default ListSurveyLandingPage;
|
||||
@@ -103,12 +103,12 @@ export const navBar = [
|
||||
{
|
||||
id: "Desa_3",
|
||||
name: "Berita",
|
||||
path: "/admin/desa/berita"
|
||||
path: "/admin/desa/berita/list-berita"
|
||||
},
|
||||
{
|
||||
id: "Desa_4",
|
||||
name: "Pengumuman",
|
||||
path: "/admin/desa/pengumuman"
|
||||
path: "/admin/desa/pengumuman/list-pengumuman"
|
||||
},
|
||||
{
|
||||
id: "Desa_5",
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
async function kategoriBeritaFindMany() {
|
||||
const data = await prisma.kategoriBerita.findMany();
|
||||
return { data };
|
||||
}
|
||||
|
||||
export default kategoriBeritaFindMany
|
||||
|
||||
@@ -1,44 +1,71 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// /api/berita/findManyPaginated.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function beritaFindMany(context: Context) {
|
||||
// Ambil parameter dari query
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const search = (context.query.search as string) || '';
|
||||
const kategori = (context.query.kategori as string) || ''; // 🔥 Parameter kategori baru
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
// Buat where clause
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Filter berdasarkan kategori (jika ada)
|
||||
if (kategori) {
|
||||
where.kategoriBerita = {
|
||||
name: {
|
||||
equals: kategori,
|
||||
mode: 'insensitive' // Tidak case-sensitive
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ judul: { contains: search, mode: 'insensitive' } },
|
||||
{ deskripsi: { contains: search, mode: 'insensitive' } },
|
||||
{ content: { contains: search, mode: 'insensitive' } },
|
||||
{ kategoriBerita: { name: { contains: search, mode: 'insensitive' } } }
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
// Ambil data dan total count secara paralel
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.berita.findMany({
|
||||
where: { isActive: true },
|
||||
where,
|
||||
include: {
|
||||
image: true,
|
||||
kategoriBerita: true,
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' }, // opsional, kalau mau urut berdasarkan waktu
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.berita.count({
|
||||
where: { isActive: true }
|
||||
})
|
||||
prisma.berita.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success fetch berita with pagination",
|
||||
message: "Berhasil ambil berita dengan pagination",
|
||||
data,
|
||||
page,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Find many paginated error:", e);
|
||||
console.error("Error di findMany paginated:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed fetch berita with pagination",
|
||||
message: "Gagal mengambil data berita",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default beritaFindMany;
|
||||
export default beritaFindMany;
|
||||
@@ -1,30 +1,41 @@
|
||||
import prisma from '@/lib/prisma';
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// find-first.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function beritaFindFirst(context: Context) {
|
||||
const kategori = (context.query.kategori as string) || '';
|
||||
|
||||
const where: any = { isActive: true };
|
||||
|
||||
if (kategori) {
|
||||
where.kategoriBerita = {
|
||||
name: { equals: kategori, mode: 'insensitive' }
|
||||
};
|
||||
}
|
||||
|
||||
export default async function beritaFindFirst() {
|
||||
try {
|
||||
const result = await prisma.berita.findFirst({
|
||||
where: {
|
||||
isActive: true, // opsional kalau kamu punya field ini
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc', // ambil yang paling terbaru
|
||||
},
|
||||
const data = await prisma.berita.findFirst({
|
||||
where,
|
||||
include: {
|
||||
image: true,
|
||||
kategoriBerita: true,
|
||||
}
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Berhasil ambil berita terbaru',
|
||||
data: result,
|
||||
message: "Berhasil ambil berita terbaru",
|
||||
data,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[findFirstBerita] Error:', error);
|
||||
} catch (e) {
|
||||
console.error("Error di findFirst:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Gagal ambil berita terbaru',
|
||||
message: "Gagal ambil berita terbaru",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default beritaFindFirst;
|
||||
14
src/app/api/[[...slugs]]/_lib/desa/berita/findManyUI.ts
Normal file
14
src/app/api/[[...slugs]]/_lib/desa/berita/findManyUI.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function findManyUI() {
|
||||
const data = await prisma.berita.findMany({
|
||||
include: {
|
||||
image: true,
|
||||
kategoriBerita: true,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data,
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import kategoriBeritaFindMany from "./category";
|
||||
import beritaFindMany from "./find-many";
|
||||
import beritaCreate from "./create";
|
||||
import beritaDelete from "./del";
|
||||
@@ -9,8 +8,8 @@ import beritaFindFirst from "./findFirst";
|
||||
import findRecentBerita from "./findRecent";
|
||||
|
||||
const Berita = new Elysia({ prefix: "/berita", tags: ["Desa/Berita"] })
|
||||
.get("/category/find-many", kategoriBeritaFindMany)
|
||||
.get("/find-many", beritaFindMany)
|
||||
.get("/find-many-ui", beritaFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await findBeritaById(new Request(context.request));
|
||||
return response;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default async function kategoriBeritaCreate(context: Context) {
|
||||
const body = (await context.body) as FormCreate;
|
||||
|
||||
try {
|
||||
const result = await prisma.kategoriBerita.create({
|
||||
data: {
|
||||
name: body.name,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil membuat kategori berita",
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error creating kategori berita:", error);
|
||||
throw new Error("Gagal membuat kategori berita: " + (error as Error).message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function kategoriBeritaDelete(context: Context) {
|
||||
const id = context.params.id as string;
|
||||
|
||||
await prisma.kategoriBerita.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
success: true,
|
||||
message: "Success delete kategori berita",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function kategoriBeritaFindMany() {
|
||||
const data = await prisma.kategoriBerita.findMany();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success get all kategori berita",
|
||||
data,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function kategoriBeritaFindUnique(request: Request) {
|
||||
const url = new URL(request.url);
|
||||
const pathSegments = url.pathname.split('/');
|
||||
const id = pathSegments[pathSegments.length - 1];
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID is required",
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof id !== 'string') {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID is required",
|
||||
}
|
||||
}
|
||||
|
||||
const data = await prisma.kategoriBerita.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Data not found",
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success get kategori berita",
|
||||
data,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Find by ID error:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data: " + (error instanceof Error ? error.message : 'Unknown error'),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import kategoriBeritaCreate from "./create";
|
||||
import kategoriBeritaDelete from "./del";
|
||||
import kategoriBeritaFindMany from "./findMany";
|
||||
import kategoriBeritaFindUnique from "./findUnique";
|
||||
import kategoriBeritaUpdate from "./updt";
|
||||
|
||||
const KategoriBerita = new Elysia({
|
||||
prefix: "/kategoriberita",
|
||||
tags: ["Desa / Berita / Kategori Berita"],
|
||||
})
|
||||
|
||||
.post("/create", kategoriBeritaCreate, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
}),
|
||||
})
|
||||
|
||||
.get("/findMany", kategoriBeritaFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await kategoriBeritaFindUnique(
|
||||
new Request(context.request)
|
||||
);
|
||||
return response;
|
||||
})
|
||||
.put("/:id", kategoriBeritaUpdate, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", kategoriBeritaDelete);
|
||||
|
||||
export default KategoriBerita;
|
||||
@@ -0,0 +1,28 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormUpdate = {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default async function kategoriBeritaUpdate(context: Context) {
|
||||
const body = (await context.body) as FormUpdate;
|
||||
const id = context.params.id as string;
|
||||
|
||||
try {
|
||||
const result = await prisma.kategoriBerita.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name: body.name,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil mengupdate kategori berita",
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error updating kategori berita:", error);
|
||||
throw new Error("Gagal mengupdate kategori berita: " + (error as Error).message);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import GalleryVideo from "./gallery/video";
|
||||
import LayananDesa from "./layanan";
|
||||
import Penghargaan from "./penghargaan";
|
||||
import KategoriPotensi from "./potensi/kategori-potensi";
|
||||
import KategoriBerita from "./berita/kategori-berita";
|
||||
import KategoriPengumuman from "./pengumuman/kategori-pengumuman";
|
||||
|
||||
|
||||
const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
|
||||
@@ -20,5 +22,7 @@ const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
|
||||
.use(LayananDesa)
|
||||
.use(Penghargaan)
|
||||
.use(KategoriPotensi)
|
||||
.use(KategoriBerita)
|
||||
.use(KategoriPengumuman)
|
||||
|
||||
export default Desa;
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import Elysia from "elysia";
|
||||
import Elysia, { t } from "elysia";
|
||||
import { pengumumanCreate } from "./create";
|
||||
import pengumumanFindMany from "./find-many";
|
||||
import { t } from "elysia";
|
||||
import pengumumanCategoryFindMany from "./category";
|
||||
import pengumumanDelete from "./del";
|
||||
import pengumumanFindById from "./find-by-id";
|
||||
import pengumumanUpdate from "./updt";
|
||||
import pengumumanFindMany from "./find-many";
|
||||
import pengumumanFindFirst from "./findFirst";
|
||||
import pengumumanFindRecent from "./findRecent";
|
||||
import pengumumanUpdate from "./updt";
|
||||
|
||||
const Pengumuman = new Elysia({ prefix: "/pengumuman", tags: ["Desa/Pengumuman"] })
|
||||
.get("/category/find-many", pengumumanCategoryFindMany)
|
||||
.get("/find-many", pengumumanFindMany)
|
||||
.get("/:id", pengumumanFindById)
|
||||
.delete("/delete/:id", pengumumanDelete)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default async function kategoriPengumumanCreate(context: Context) {
|
||||
const body = (await context.body) as FormCreate;
|
||||
|
||||
try {
|
||||
const result = await prisma.categoryPengumuman.create({
|
||||
data: {
|
||||
name: body.name,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil membuat kategori pengumuman",
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error creating kategori pengumuman:", error);
|
||||
throw new Error("Gagal membuat kategori pengumuman: " + (error as Error).message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function kategoriPengumumanDelete(context: Context) {
|
||||
const id = context.params.id as string;
|
||||
|
||||
await prisma.categoryPengumuman.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
success: true,
|
||||
message: "Success delete kategori pengumuman",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
async function kategoriPengumumanFindMany() {
|
||||
const data = await prisma.categoryPengumuman.findMany();
|
||||
return { data };
|
||||
}
|
||||
|
||||
export default kategoriPengumumanFindMany
|
||||
@@ -0,0 +1,46 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function kategoriPengumumanFindUnique(request: Request) {
|
||||
const url = new URL(request.url);
|
||||
const pathSegments = url.pathname.split('/');
|
||||
const id = pathSegments[pathSegments.length - 1];
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID is required",
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof id !== 'string') {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID is required",
|
||||
}
|
||||
}
|
||||
|
||||
const data = await prisma.categoryPengumuman.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Data not found",
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success get kategori pengumuman",
|
||||
data,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Find by ID error:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data: " + (error instanceof Error ? error.message : 'Unknown error'),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import kategoriPengumumanCreate from "./create";
|
||||
import kategoriPengumumanDelete from "./del";
|
||||
import kategoriPengumumanFindMany from "./findMany";
|
||||
import kategoriPengumumanFindUnique from "./findUnique";
|
||||
import kategoriPengumumanUpdate from "./updt";
|
||||
|
||||
const KategoriPengumuman = new Elysia({
|
||||
prefix: "/kategoripengumuman",
|
||||
tags: ["Desa / Pengumuman / Kategori Pengumuman"],
|
||||
})
|
||||
|
||||
.post("/create", kategoriPengumumanCreate, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
}),
|
||||
})
|
||||
|
||||
.get("/findMany", kategoriPengumumanFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await kategoriPengumumanFindUnique(
|
||||
new Request(context.request)
|
||||
);
|
||||
return response;
|
||||
})
|
||||
.put("/:id", kategoriPengumumanUpdate, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", kategoriPengumumanDelete);
|
||||
|
||||
export default KategoriPengumuman;
|
||||
@@ -0,0 +1,28 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormUpdate = {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default async function kategoriPengumumanUpdate(context: Context) {
|
||||
const body = (await context.body) as FormUpdate;
|
||||
const id = context.params.id as string;
|
||||
|
||||
try {
|
||||
const result = await prisma.categoryPengumuman.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name: body.name,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil mengupdate kategori pengumuman",
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error updating kategori pengumuman:", error);
|
||||
throw new Error("Gagal mengupdate kategori pengumuman: " + (error as Error).message);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const ageStatCreate = async (context: Context) => {
|
||||
const body = (await context.body) as {
|
||||
group: string;
|
||||
count: number;
|
||||
monthlyStatId: string;
|
||||
};
|
||||
|
||||
try {
|
||||
const created = await prisma.ageStat.create({
|
||||
data: {
|
||||
group: body.group,
|
||||
count: body.count,
|
||||
monthlyStatId: body.monthlyStatId,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Age Stat berhasil dibuat",
|
||||
data: created,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal create age stat:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal membuat data age stat",
|
||||
};
|
||||
}
|
||||
};
|
||||
export default ageStatCreate;
|
||||
@@ -1,24 +0,0 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const ageStatDelete = async (context: Context) => {
|
||||
const { id } = context.params as { id: string };
|
||||
|
||||
try {
|
||||
await prisma.ageStat.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Data age berhasil dihapus",
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal hapus Age Stat:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal menghapus data age stat",
|
||||
};
|
||||
}
|
||||
};
|
||||
export default ageStatDelete;
|
||||
@@ -1,26 +0,0 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const ageStatFindMany = async () => {
|
||||
try {
|
||||
const result = await prisma.ageStat.findMany({
|
||||
include: {
|
||||
MonthlyStat: true,
|
||||
},
|
||||
orderBy: {
|
||||
id: "desc",
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal ambil data Age Stat:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data age stat",
|
||||
};
|
||||
}
|
||||
};
|
||||
export default ageStatFindMany;
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const ageStatFindUnique = async (context: Context) => {
|
||||
const { id } = context.params as { id: string };
|
||||
|
||||
try {
|
||||
const result = await prisma.ageStat.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
MonthlyStat: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Data age tidak ditemukan",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal ambil data Age Stat:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Terjadi kesalahan saat mengambil data age stat",
|
||||
};
|
||||
}
|
||||
};
|
||||
export default ageStatFindUnique;
|
||||
@@ -1,31 +0,0 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import ageStatCreate from "./create";
|
||||
import ageStatDelete from "./del";
|
||||
import ageStatFindMany from "./findMany";
|
||||
import ageStatFindUnique from "./findUnique";
|
||||
import ageStatUpdate from "./updt";
|
||||
|
||||
|
||||
const age = new Elysia({
|
||||
prefix: "/age",
|
||||
tags: ["Landing Page/Indeks Kepuasan Masyarakat/Age"],
|
||||
})
|
||||
.post("/create", ageStatCreate, {
|
||||
body: t.Object({
|
||||
monthlyStatId: t.String(),
|
||||
group: t.String(),
|
||||
count: t.Number(),
|
||||
}),
|
||||
})
|
||||
.get("/findMany", ageStatFindMany)
|
||||
.get("/:id", ageStatFindUnique)
|
||||
.put("/:id", ageStatUpdate, {
|
||||
body: t.Object({
|
||||
group: t.String(),
|
||||
count: t.Number(),
|
||||
monthlyStatId: t.String(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", ageStatDelete);
|
||||
|
||||
export default age;
|
||||
@@ -1,35 +0,0 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const ageStatUpdate = async (context: Context) => {
|
||||
const { id } = context.params as { id: string };
|
||||
const body = (await context.body) as {
|
||||
group?: string;
|
||||
count?: number;
|
||||
monthlyStatId?: string;
|
||||
};
|
||||
|
||||
try {
|
||||
const updated = await prisma.ageStat.update({
|
||||
where: { id },
|
||||
data: {
|
||||
group: body.group,
|
||||
count: body.count,
|
||||
monthlyStatId: body.monthlyStatId,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Data age berhasil diperbarui",
|
||||
data: updated,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal update Age Stat:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal memperbarui data age stat",
|
||||
};
|
||||
}
|
||||
};
|
||||
export default ageStatUpdate;
|
||||
@@ -1,40 +0,0 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const genderStatCreate = async (context: Context) => {
|
||||
const body = (await context.body) as {
|
||||
laki: number;
|
||||
perempuan: number;
|
||||
monthlyStatId: string;
|
||||
};
|
||||
|
||||
const total = body.laki + body.perempuan;
|
||||
const percentLaki = total > 0 ? Number(((body.laki / total) * 100).toFixed(2)) : 0;
|
||||
const percentPerempuan = total > 0 ? Number(((body.perempuan / total) * 100).toFixed(2)) : 0;
|
||||
|
||||
try {
|
||||
const created = await prisma.genderStat.create({
|
||||
data: {
|
||||
laki: body.laki,
|
||||
perempuan: body.perempuan,
|
||||
total,
|
||||
percentLaki: Number(percentLaki),
|
||||
percentPerempuan: Number(percentPerempuan),
|
||||
monthlyStatId: body.monthlyStatId,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Gender Stat berhasil dibuat",
|
||||
data: created,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal create gender stat:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal membuat data gender stat",
|
||||
};
|
||||
}
|
||||
};
|
||||
export default genderStatCreate;
|
||||
@@ -1,24 +0,0 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const genderStatDelete = async (context: Context) => {
|
||||
const { id } = context.params as { id: string };
|
||||
|
||||
try {
|
||||
await prisma.genderStat.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Data gender berhasil dihapus",
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal hapus Gender Stat:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal menghapus data gender",
|
||||
};
|
||||
}
|
||||
};
|
||||
export default genderStatDelete;
|
||||
@@ -1,26 +0,0 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const genderStatFindMany = async () => {
|
||||
try {
|
||||
const result = await prisma.genderStat.findMany({
|
||||
include: {
|
||||
MonthlyStat: true,
|
||||
},
|
||||
orderBy: {
|
||||
id: "desc",
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal ambil data Gender Stat:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data gender stat",
|
||||
};
|
||||
}
|
||||
};
|
||||
export default genderStatFindMany;
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const genderStatFindUnique = async (context: Context) => {
|
||||
const { id } = context.params as { id: string };
|
||||
|
||||
try {
|
||||
const result = await prisma.genderStat.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
MonthlyStat: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Data gender tidak ditemukan",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal ambil data Gender Stat:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Terjadi kesalahan saat mengambil data gender stat",
|
||||
};
|
||||
}
|
||||
};
|
||||
export default genderStatFindUnique;
|
||||
@@ -1,37 +0,0 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import genderStatCreate from "./create";
|
||||
import genderStatDelete from "./del";
|
||||
import genderStatFindMany from "./findMany";
|
||||
import genderStatFindUnique from "./findUnique";
|
||||
import genderStatUpdate from "./updt";
|
||||
|
||||
|
||||
const gender = new Elysia({
|
||||
prefix: "/gender",
|
||||
tags: ["Landing Page/Indeks Kepuasan Masyarakat/Gender"],
|
||||
})
|
||||
.post("/create", genderStatCreate, {
|
||||
body: t.Object({
|
||||
monthlyStatId: t.String(),
|
||||
laki: t.Number(),
|
||||
perempuan: t.Number(),
|
||||
total: t.Number(),
|
||||
percentLaki: t.Number(),
|
||||
percentPerempuan: t.Number(),
|
||||
}),
|
||||
})
|
||||
.get("/findMany", genderStatFindMany)
|
||||
.get("/:id", genderStatFindUnique)
|
||||
.put("/:id", genderStatUpdate, {
|
||||
body: t.Object({
|
||||
laki: t.Number(),
|
||||
perempuan: t.Number(),
|
||||
monthlyStatId: t.String(),
|
||||
total: t.Number(),
|
||||
percentLaki: t.Number(),
|
||||
percentPerempuan: t.Number(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", genderStatDelete);
|
||||
|
||||
export default gender;
|
||||
@@ -1,42 +0,0 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const genderStatUpdate = async (context: Context) => {
|
||||
const { id } = context.params as { id: string };
|
||||
const body = (await context.body) as {
|
||||
laki?: number;
|
||||
perempuan?: number;
|
||||
monthlyStatId?: string;
|
||||
};
|
||||
|
||||
const total = (body.laki ?? 0) + (body.perempuan ?? 0);
|
||||
const percentLaki = total > 0 ? Number(((body.laki ?? 0) / total) * 100).toFixed(2) : 0;
|
||||
const percentPerempuan = total > 0 ? Number(((body.perempuan ?? 0) / total) * 100).toFixed(2) : 0;
|
||||
|
||||
try {
|
||||
const updated = await prisma.genderStat.update({
|
||||
where: { id },
|
||||
data: {
|
||||
laki: body.laki,
|
||||
perempuan: body.perempuan,
|
||||
total,
|
||||
percentLaki: Number(percentLaki),
|
||||
percentPerempuan: Number(percentPerempuan),
|
||||
monthlyStatId: body.monthlyStatId,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Data gender berhasil diperbarui",
|
||||
data: updated,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal update Gender Stat:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal memperbarui data gender stat",
|
||||
};
|
||||
}
|
||||
};
|
||||
export default genderStatUpdate;
|
||||
@@ -1,20 +0,0 @@
|
||||
import Elysia from "elysia";
|
||||
import gender from "./gender";
|
||||
import monthlyStat from "./monthly-stat";
|
||||
import survey from "./survey";
|
||||
import response from "./response";
|
||||
import age from "./age";
|
||||
|
||||
|
||||
const IndeksKepuasanMasyarakat = new Elysia({
|
||||
prefix: "/indekskepuasanmasyarakat",
|
||||
tags: ["Landing Page/Profile/Indeks Kepuasan Masyarakat"],
|
||||
})
|
||||
.use(gender)
|
||||
.use(monthlyStat)
|
||||
.use(survey)
|
||||
.use(response)
|
||||
.use(age)
|
||||
|
||||
|
||||
export default IndeksKepuasanMasyarakat
|
||||
@@ -1,33 +0,0 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const createMonthlyStat = async (context: Context) => {
|
||||
const body = await context.body as {
|
||||
month: string;
|
||||
respondentsCount: number;
|
||||
surveyId: string;
|
||||
};
|
||||
|
||||
try {
|
||||
const created = await prisma.monthlyStat.create({
|
||||
data: {
|
||||
month: body.month,
|
||||
respondentsCount: body.respondentsCount,
|
||||
surveyId: body.surveyId,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Monthly stat berhasil dibuat",
|
||||
data: created,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal create monthly stat:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal membuat monthly stat",
|
||||
};
|
||||
}
|
||||
};
|
||||
export default createMonthlyStat;
|
||||
@@ -1,26 +0,0 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const deleteMonthlyStat = async (context: Context) => {
|
||||
const { id } = context.params as { id: string };
|
||||
|
||||
try {
|
||||
await prisma.monthlyStat.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Data statistik bulanan berhasil dihapus",
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal hapus MonthlyStat:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal menghapus data statistik bulanan",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default deleteMonthlyStat;
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const findManyMonthlyStat = async () => {
|
||||
try {
|
||||
const result = await prisma.monthlyStat.findMany({
|
||||
include: {
|
||||
survey: true,
|
||||
},
|
||||
orderBy: {
|
||||
id: "desc",
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal ambil data MonthlyStat:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data statistik bulanan",
|
||||
};
|
||||
}
|
||||
};
|
||||
export default findManyMonthlyStat;
|
||||
@@ -1,35 +0,0 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const findUniqueMonthlyStat = async (context: Context) => {
|
||||
const { id } = context.params as { id: string };
|
||||
|
||||
try {
|
||||
const result = await prisma.monthlyStat.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
survey: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Data tidak ditemukan",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal ambil data MonthlyStat:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Terjadi kesalahan saat mengambil data",
|
||||
};
|
||||
}
|
||||
};
|
||||
export default findUniqueMonthlyStat;
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import createMonthlyStat from "./create";
|
||||
import deleteMonthlyStat from "./del";
|
||||
import findManyMonthlyStat from "./findMany";
|
||||
import findUniqueMonthlyStat from "./findUnique";
|
||||
import updateMonthlyStat from "./updt";
|
||||
|
||||
|
||||
const surveyResponse = new Elysia({
|
||||
prefix: "/monthlystat",
|
||||
tags: ["Landing Page/Indeks Kepuasan Masyarakat/Survey Response"],
|
||||
})
|
||||
.post("/create", createMonthlyStat, {
|
||||
body: t.Object({
|
||||
month: t.String(),
|
||||
respondentsCount: t.Number(),
|
||||
surveyId: t.String(),
|
||||
}),
|
||||
})
|
||||
.get("/findMany", findManyMonthlyStat)
|
||||
.get("/:id", findUniqueMonthlyStat)
|
||||
.put("/:id", updateMonthlyStat, {
|
||||
body: t.Object({
|
||||
month: t.String(),
|
||||
respondentsCount: t.Number(),
|
||||
surveyId: t.String(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", deleteMonthlyStat);
|
||||
|
||||
export default surveyResponse;
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const updateMonthlyStat = async (context: Context) => {
|
||||
const { id } = context.params as { id: string };
|
||||
const body = await context.body as {
|
||||
month?: string;
|
||||
respondentsCount?: number;
|
||||
};
|
||||
|
||||
try {
|
||||
const updated = await prisma.monthlyStat.update({
|
||||
where: { id },
|
||||
data: {
|
||||
month: body.month,
|
||||
respondentsCount: body.respondentsCount,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Data MonthlyStat berhasil diupdate",
|
||||
data: updated,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal update MonthlyStat:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengupdate data statistik bulanan",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default updateMonthlyStat;
|
||||
@@ -1,33 +0,0 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const responseStatCreate = async (context: Context) => {
|
||||
const body = (await context.body) as {
|
||||
label: string;
|
||||
count: number;
|
||||
monthlyStatId: string;
|
||||
};
|
||||
|
||||
try {
|
||||
const created = await prisma.responseStat.create({
|
||||
data: {
|
||||
label: body.label,
|
||||
count: body.count,
|
||||
monthlyStatId: body.monthlyStatId,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Response Stat berhasil dibuat",
|
||||
data: created,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal create response stat:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal membuat data response stat",
|
||||
};
|
||||
}
|
||||
};
|
||||
export default responseStatCreate;
|
||||
@@ -1,24 +0,0 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const responseStatDelete = async (context: Context) => {
|
||||
const { id } = context.params as { id: string };
|
||||
|
||||
try {
|
||||
await prisma.responseStat.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Data response berhasil dihapus",
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal hapus Response Stat:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal menghapus data response stat",
|
||||
};
|
||||
}
|
||||
};
|
||||
export default responseStatDelete;
|
||||
@@ -1,26 +0,0 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const responseStatFindMany = async () => {
|
||||
try {
|
||||
const result = await prisma.responseStat.findMany({
|
||||
include: {
|
||||
MonthlyStat: true,
|
||||
},
|
||||
orderBy: {
|
||||
id: "desc",
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal ambil data Response Stat:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data response stat",
|
||||
};
|
||||
}
|
||||
};
|
||||
export default responseStatFindMany;
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const responseStatFindUnique = async (context: Context) => {
|
||||
const { id } = context.params as { id: string };
|
||||
|
||||
try {
|
||||
const result = await prisma.responseStat.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
MonthlyStat: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Data response tidak ditemukan",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal ambil data Response Stat:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Terjadi kesalahan saat mengambil data response stat",
|
||||
};
|
||||
}
|
||||
};
|
||||
export default responseStatFindUnique;
|
||||
@@ -1,31 +0,0 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import responseStatCreate from "./create";
|
||||
import responseStatDelete from "./del";
|
||||
import responseStatFindMany from "./findMany";
|
||||
import responseStatFindUnique from "./findUnique";
|
||||
import responseStatUpdate from "./updt";
|
||||
|
||||
|
||||
const response = new Elysia({
|
||||
prefix: "/response",
|
||||
tags: ["Landing Page/Indeks Kepuasan Masyarakat/Response"],
|
||||
})
|
||||
.post("/create", responseStatCreate, {
|
||||
body: t.Object({
|
||||
monthlyStatId: t.String(),
|
||||
label: t.String(),
|
||||
count: t.Number(),
|
||||
}),
|
||||
})
|
||||
.get("/findMany", responseStatFindMany)
|
||||
.get("/:id", responseStatFindUnique)
|
||||
.put("/:id", responseStatUpdate, {
|
||||
body: t.Object({
|
||||
label: t.String(),
|
||||
count: t.Number(),
|
||||
monthlyStatId: t.String(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", responseStatDelete);
|
||||
|
||||
export default response;
|
||||
@@ -1,35 +0,0 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const responseStatUpdate = async (context: Context) => {
|
||||
const { id } = context.params as { id: string };
|
||||
const body = (await context.body) as {
|
||||
label?: string;
|
||||
count?: number;
|
||||
monthlyStatId?: string;
|
||||
};
|
||||
|
||||
try {
|
||||
const updated = await prisma.responseStat.update({
|
||||
where: { id },
|
||||
data: {
|
||||
label: body.label,
|
||||
count: body.count,
|
||||
monthlyStatId: body.monthlyStatId,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Data response berhasil diperbarui",
|
||||
data: updated,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal update Response Stat:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal memperbarui data response stat",
|
||||
};
|
||||
}
|
||||
};
|
||||
export default responseStatUpdate;
|
||||
@@ -1,42 +0,0 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
const surveyCreate = async (context: Context) => {
|
||||
const body = await context.body as {
|
||||
title: string;
|
||||
totalRespondents: number;
|
||||
averageScore: number;
|
||||
};
|
||||
|
||||
// Validasi
|
||||
if (!body.title || !body.totalRespondents || body.averageScore === undefined) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Field title, tahun, dan skor wajib diisi",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const created = await prisma.survey.create({
|
||||
data: {
|
||||
title: body.title,
|
||||
totalRespondents: body.totalRespondents,
|
||||
averageScore: body.averageScore,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Survey berhasil dibuat",
|
||||
data: created,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal create survey :", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Terjadi kesalahan saat membuat survey ",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default surveyCreate;
|
||||
@@ -1,24 +0,0 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const surveyDelete = async (context: Context) => {
|
||||
const { id } = context.params as { id: string };
|
||||
|
||||
try {
|
||||
await prisma.survey.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Survey berhasil dihapus",
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal hapus survey:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal menghapus survey",
|
||||
};
|
||||
}
|
||||
};
|
||||
export default surveyDelete;
|
||||
@@ -1,28 +0,0 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const surveyFindMany = async () => {
|
||||
try {
|
||||
const surveys = await prisma.survey.findMany({
|
||||
include: {
|
||||
monthlyStats: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: surveys,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal ambil data survey:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Terjadi kesalahan saat mengambil data survey",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default surveyFindMany;
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Context } from "elysia";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export const surveyFindUnique = async (context: Context) => {
|
||||
const { id } = context.params as { id: string };
|
||||
|
||||
try {
|
||||
const survey = await prisma.survey.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
monthlyStats: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!survey) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Survey tidak ditemukan",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: survey,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal ambil data survey:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Terjadi kesalahan saat mengambil data survey",
|
||||
};
|
||||
}
|
||||
};
|
||||
export default surveyFindUnique;
|
||||
@@ -1,30 +0,0 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import surveyCreate from "./create";
|
||||
import surveyUpdate from "./updt";
|
||||
import surveyFindMany from "./findMany";
|
||||
import surveyFindUnique from "./findUnique";
|
||||
import surveyDelete from "./del";
|
||||
|
||||
const survey = new Elysia({
|
||||
prefix: "/survey",
|
||||
tags: ["Landing Page/Indeks Kepuasan Masyarakat/Survey "],
|
||||
})
|
||||
.post("/create", surveyCreate, {
|
||||
body: t.Object({
|
||||
title: t.String(),
|
||||
totalRespondents: t.Number(),
|
||||
averageScore: t.Number(),
|
||||
}),
|
||||
})
|
||||
.get("/findMany", surveyFindMany)
|
||||
.get("/:id", surveyFindUnique)
|
||||
.put("/:id", surveyUpdate, {
|
||||
body: t.Object({
|
||||
title: t.String(),
|
||||
totalRespondents: t.Number(),
|
||||
averageScore: t.Number(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", surveyDelete);
|
||||
|
||||
export default survey;
|
||||
@@ -1,49 +0,0 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormUpdateSurvey = {
|
||||
title?: string;
|
||||
tahun?: number;
|
||||
totalRespondents?: number;
|
||||
averageScore?: number;
|
||||
};
|
||||
|
||||
const surveyUpdate = async (context: Context) => {
|
||||
const id = context.params?.id as string;
|
||||
const body = await context.body as FormUpdateSurvey;
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID survey wajib diisi",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const updated = await prisma.survey.update({
|
||||
where: { id },
|
||||
data: {
|
||||
title: body.title,
|
||||
totalRespondents: body.totalRespondents,
|
||||
averageScore: body.averageScore,
|
||||
},
|
||||
include: {
|
||||
monthlyStats: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil update survey ",
|
||||
data: updated,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Gagal update survey :", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal update survey ",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default surveyUpdate;
|
||||
@@ -8,7 +8,6 @@ import SDGSDesa from "./sdgs-desa";
|
||||
import APBDes from "./apbdes";
|
||||
import PrestasiDesa from "./prestasi-desa";
|
||||
import KategoriPrestasi from "./prestasi-desa/kategori-prestasi";
|
||||
import IndeksKepuasanMasyarakat from "./indeks-kepuasan-masyarakat";
|
||||
|
||||
const LandingPage = new Elysia({
|
||||
prefix: "/api/landingpage",
|
||||
@@ -24,6 +23,5 @@ const LandingPage = new Elysia({
|
||||
.use(APBDes)
|
||||
.use(PrestasiDesa)
|
||||
.use(KategoriPrestasi)
|
||||
.use(IndeksKepuasanMasyarakat)
|
||||
|
||||
export default LandingPage
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Container, Divider, Grid, GridCol, Group, Image, Pagination, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
||||
import { IconCalendar, IconUser } from '@tabler/icons-react';
|
||||
import React from 'react';
|
||||
|
||||
const dataBeritaTerbaru = [
|
||||
{
|
||||
id: 1,
|
||||
judul: 'FESTIVAL SENI BUDAYA KAB. BADUNG',
|
||||
image: "/api/img/tari-3.jpg",
|
||||
tanggal: "Selasa, 11 Januari 2025",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
judul: 'LATIHAN TARI REJANG GIRI PUTRI',
|
||||
image: "/api/img/tari-3.jpg",
|
||||
tanggal: "Kamis, 13 Januari 2025",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
judul: 'LATIHAN TARI REJANG GIRI PUTRI',
|
||||
image: "/api/img/tari-3.jpg",
|
||||
tanggal: "Kamis, 13 Januari 2025",
|
||||
},
|
||||
]
|
||||
function Budaya() {
|
||||
return (
|
||||
<Box py={20}>
|
||||
<Container size="xl" px={{ base: "md", md: "xl" }}>
|
||||
<Grid gutter={{ base: "md", md: "xl" }} pb={70}>
|
||||
{/* Berita Utama */}
|
||||
<GridCol span={{ base: 12, md: 8 }}>
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Image
|
||||
src="/api/img/budaya-1.jpg"
|
||||
alt="Darmasaba Smart Village"
|
||||
radius="md"
|
||||
fit="cover"
|
||||
h={{ base: 450, md: 610 }}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Budaya</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz={{ base: "xl", md: "2xl" }} fw="bold" lineClamp={2}>
|
||||
FESTIVAL SENI BUDAYA KAB. BADUNG
|
||||
</Text>
|
||||
<Text size="md" lineClamp={3}>
|
||||
Semeton Darmasaba yang suka menikmati seni seperti baleganjur, gong kebyar, tari, dan lainnya. Nih! ada acara keren di Puspem Badung tepatnya di Balai Budaya Giri Nata Mandala yaitu Festival Seni Budaya dari tanggal 1 November 2023 s.d. 16 November 2023.
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</GridCol>
|
||||
|
||||
{/* Berita Sampingan */}
|
||||
<GridCol span={{ base: 12, md: 4 }}>
|
||||
<Stack gap="md">
|
||||
{/* Berita Sampingan 1 */}
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src="/api/img/tari-3.jpg"
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={180}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Budaya</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
PELATIHAN TARI WALI
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Berita Sampingan 2 */}
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src="/api/img/tari-3.jpg"
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={180}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Budaya</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
LATIHAN TARI REJANG GIRI PUTRI
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box pb={30}>
|
||||
<Text fz={"h1"} fw={"bold"}>Berita Terbaru</Text>
|
||||
<Divider color={colors["blue-button"]} />
|
||||
</Box>
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||
{dataBeritaTerbaru.map((v, k) => {
|
||||
return (
|
||||
<Paper key={k} p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src={v.image}
|
||||
alt=""
|
||||
fit="cover"
|
||||
h={282}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Budaya</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
{v.judul}
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">{v.tanggal}</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
</SimpleGrid>
|
||||
<Box py={"xl"}>
|
||||
<Center>
|
||||
<Pagination total={10} />
|
||||
</Center>
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Budaya;
|
||||
@@ -1,185 +0,0 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Container, Divider, Grid, GridCol, Group, Image, Pagination, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
||||
import { IconCalendar, IconUser } from '@tabler/icons-react';
|
||||
import React from 'react';
|
||||
|
||||
const dataBeritaTerbaru = [
|
||||
{
|
||||
id: 1,
|
||||
judul: 'PROGRAM KETAHANAN PANGAN PEMERINTAH DESA DARMASABA TAHUN 2023',
|
||||
image: "/api/img/ekonomi-sampingan-3.png",
|
||||
tanggal: "Selasa, 11 Januari 2025",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
judul: 'Sinergitas Pemkab Badung-TNI Wujudkan Kedaulatan Pangan di Subak Aban Darmasaba',
|
||||
image: "/api/img/ekonomi-sampingan.png",
|
||||
tanggal: "Kamis, 13 Januari 2025",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
judul: 'ANTUSIASME WARGA DARMASABA MELAKUKAN PEMBUKAAN REKENING BANK BPD BALI ',
|
||||
image: "/api/img/ekonomi-sampingan-2.png",
|
||||
tanggal: "Kamis, 13 Januari 2025",
|
||||
},
|
||||
]
|
||||
function Ekonomi() {
|
||||
return (
|
||||
<Box py={20}>
|
||||
<Container size="xl" px={{ base: "md", md: "xl" }}>
|
||||
<Grid gutter={{ base: "md", md: "xl" }} pb={70}>
|
||||
{/* Berita Utama */}
|
||||
<GridCol span={{ base: 12, md: 8 }}>
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Image
|
||||
src="/api/img/ekonomi-utama.png"
|
||||
alt="Darmasaba Smart Village"
|
||||
radius="md"
|
||||
fit="cover"
|
||||
h={{ base: 450, md: 660 }}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Ekonomi</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz={{ base: "xl", md: "2xl" }} fw="bold" lineClamp={2}>
|
||||
PROGRAM KETAHANAN PANGAN PEMERINTAH DESA DARMASABA TAHUN 2023
|
||||
</Text>
|
||||
<Text size="md" lineClamp={2}>
|
||||
Pemerintah Desa Darmasaba melalui kegiatan ketahanan pangan ini menjalankan dua kategori yaitu pertanian dan peternakan untuk kategori pertanian telah membuahkan hasil panen pertama pada hari Kamis, 24 Agustus 2023 melakukan panen bawang merah di lokasi ketahanan pangan Br. Taman, Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung. Adapun varietas bawang yang dipanen adalah Bawang Bali Karet (Batu Ijo) pada lahan seluas kurang lebih 7 are.
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</GridCol>
|
||||
|
||||
{/* Berita Sampingan */}
|
||||
<GridCol span={{ base: 12, md: 4 }}>
|
||||
<Stack gap="md">
|
||||
{/* Berita Sampingan 1 */}
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src="/api/img/ekonomi-sampingan.png"
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={180}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Ekonomi</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
Sinergitas Pemkab Badung-TNI Wujudkan Kedaulatan Pangan di Subak Aban Darmasaba
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Berita Sampingan 2 */}
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src="/api/img/ekonomi-sampingan-2.png"
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={180}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Ekonomi</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={1}>
|
||||
ANTUSIASME WARGA DARMASABA MELAKUKAN PEMBUKAAN REKENING BANK BPD BALI
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box pb={30}>
|
||||
<Text fz={"h1"} fw={"bold"}>Berita Terbaru</Text>
|
||||
<Divider color={colors["blue-button"]} />
|
||||
</Box>
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||
{dataBeritaTerbaru.map((v, k) => {
|
||||
return (
|
||||
<Paper key={k} p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src={v.image}
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={180}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Ekonomi</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
{v.judul}
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">{v.tanggal}</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
</SimpleGrid>
|
||||
<Box py={"xl"}>
|
||||
<Center>
|
||||
<Pagination total={10} />
|
||||
</Center>
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Ekonomi;
|
||||
@@ -1,187 +0,0 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Container, Divider, Grid, GridCol, Group, Image, Pagination, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
||||
import { IconCalendar, IconUser } from '@tabler/icons-react';
|
||||
import React from 'react';
|
||||
|
||||
const dataBeritaTerbaru = [
|
||||
{
|
||||
id: 1,
|
||||
judul: 'PEMBANGUNAN GOT JALAN LINGKUNGAN BR. TAMAN',
|
||||
image: "/api/img/pembangunan-1.jpg",
|
||||
tanggal: "Kamis, 13 Januari 2025",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
judul: 'PEMBANGUNAN BALE GAMBELAN SETRA AGENG DESA ADAT TEGAL',
|
||||
image: "/api/img/pembangunan-3.jpg",
|
||||
tanggal: "Kamis, 13 Januari 2025",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
judul: 'PEMBANGUNAN TROTOARISASI JALAN LINGKUNGAN BR. GULINGAN',
|
||||
image: "/api/img/pembangunan-1.jpg",
|
||||
tanggal: "Selasa, 11 Januari 2025",
|
||||
},
|
||||
]
|
||||
function Pembangunan() {
|
||||
return (
|
||||
<Box py={20}>
|
||||
<Container size="xl" px={{ base: "md", md: "xl" }}>
|
||||
<Grid gutter={{ base: "md", md: "xl" }} pb={70}>
|
||||
{/* Berita Utama */}
|
||||
<GridCol span={{ base: 12, xl: 8 }}>
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Image
|
||||
src="/api/img/pembangunan-1.jpg"
|
||||
alt="PEMBANGUNAN TROTOARISASI JALAN LINGKUNGAN BR. GULINGAN"
|
||||
radius="md"
|
||||
fit="cover"
|
||||
h={{ base: 450, md: 615 }}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Pembangunan</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz={{ base: "xl", md: "2xl" }} fw="bold" lineClamp={2}>
|
||||
PEMBANGUNAN TROTOARISASI JALAN LINGKUNGAN BR. GULINGAN
|
||||
</Text>
|
||||
<Text size="md" lineClamp={3}>
|
||||
Sudah tertata rapi dan bersih niki Semeton Darmasaba! Sekarang pejalan kaki di seputaran jalan lingkungan Br. Gulingan bisa aman dan nyaman nie!!!
|
||||
|
||||
Pemdes Darmasaba dalam rangka memenuhi kebutuhan masyarakat khususnya warga Br. Gulingan yang memerlukan trotoarisasi guna memperlancar saluran air dan memberikan kenyamanan ketika berjalan, kini sudah menyelesaikan pembangunan trotoarisasi jalan lingkungan Br. Gulingan. Pembangunan tersebut menggunakan APBDes Darmasaba T. A. 2023 dengan realisasi sebesar Rp357.011.299,00 dari pagu yang disiapkan sebesar Rp439.066.327,00. Pembangunan trotoar tersebut melibatkan pekerja lokal dan dibantu oleh warga Br. Gulingan.
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</GridCol>
|
||||
|
||||
{/* Berita Sampingan */}
|
||||
<GridCol span={{ base: 12, xl: 4 }}>
|
||||
<Stack gap="md">
|
||||
{/* Berita Sampingan 1 */}
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src="/api/img/pembangunan-2.jpg"
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={300}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Pembangunan</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
PEMBANGUNAN GOT JALAN LINGKUNGAN BR. TAMAN
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Berita Sampingan 2 */}
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src="/api/img/pembangunan-3.jpg"
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={250}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Pembangunan</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
PEMBANGUNAN BALE GAMBELAN SETRA AGENG DESA ADAT TEGAL
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box pb={30}>
|
||||
<Text fz={"h1"} fw={"bold"}>Berita Terbaru</Text>
|
||||
<Divider color={colors["blue-button"]} />
|
||||
</Box>
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||
{dataBeritaTerbaru.map((v, k) => {
|
||||
return (
|
||||
<Paper key={k} p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src={v.image}
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={200}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Pembangunan</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
{v.judul}
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">{v.tanggal}</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
</SimpleGrid>
|
||||
<Box py={"xl"}>
|
||||
<Center>
|
||||
<Pagination total={10} />
|
||||
</Center>
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Pembangunan;
|
||||
@@ -1,185 +0,0 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Container, Divider, Grid, GridCol, Group, Image, Pagination, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
||||
import { IconCalendar, IconUser } from '@tabler/icons-react';
|
||||
import React from 'react';
|
||||
|
||||
const dataBeritaTerbaru = [
|
||||
{
|
||||
id: 1,
|
||||
judul: 'PEMBERIAN HASIL PANEN JAGUNG PROGRAM KETAHANAN PANGAN DESA KEPADA ANAK YATIM, PIATU DAN YATIM-PIATU DESA DARMASABA',
|
||||
image: "/api/img/berita-pemerintahan.jpg",
|
||||
tanggal: "Selasa, 11 Januari 2025",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
judul: 'Tim Voli Darmasaba Raih Juara Turnamen Desa',
|
||||
image: "/api/img/pemrintahan-5.png",
|
||||
tanggal: "Kamis, 13 Januari 2025",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
judul: 'PEMBERIAN HASIL PANEN JAGUNG PROGRAM KETAHANAN PANGAN DESA KEPADA ANAK YATIM, PIATU DAN YATIM-PIATU DESA DARMASABA',
|
||||
image: "/api/img/berita-pemerintahan.jpg",
|
||||
tanggal: "Kamis, 13 Januari 2025",
|
||||
},
|
||||
]
|
||||
function Pemerintahan() {
|
||||
return (
|
||||
<Box py={20}>
|
||||
<Container size="xl" px={{ base: "md", md: "xl" }}>
|
||||
<Grid gutter={{ base: "md", md: "xl" }} pb={70}>
|
||||
{/* Berita Utama */}
|
||||
<GridCol span={{ base: 12, xl: 8 }}>
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Image
|
||||
src="/api/img/berita-pemerintahan.jpg"
|
||||
alt="Darmasaba Smart Village"
|
||||
radius="md"
|
||||
fit="cover"
|
||||
h={{ base: 450, md: 615 }}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Pemerintahan</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz={{ base: "xl", md: "2xl" }} fw="bold" lineClamp={2}>
|
||||
PEMBERIAN HASIL PANEN JAGUNG PROGRAM KETAHANAN PANGAN DESA KEPADA ANAK YATIM, PIATU DAN YATIM-PIATU DESA DARMASABA
|
||||
</Text>
|
||||
<Text size="md" lineClamp={3}>
|
||||
Pada hari Selasa, 19 November 2024, Desa Darmasaba memberikan hasil panen jagung sebagai bagian dari kegiatan Ketahanan Pangan Desa kepada anak-anak yatim, piatu, dan yatim-piatu yang berusia 0-18 Tahun. Hasil panen ini diserahkan langsung oleh I. B. Surya Prabhawa Manuaba, S.H., M.H., NL.P selaku Perbekel Darmasaba, didampingi Ketua BPD dan LPM Desa Darmasaba, serta seluruh perangkat dan staf desa. Melalui kegiatan ini, kami berharap dapat memberikan manfaat langsung bagi mereka yang membutuhkan, serta memperkuat rasa kebersamaan dan kepedulian dalam membangun ketahanan pangan di Desa Darmasaba.
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</GridCol>
|
||||
|
||||
{/* Berita Sampingan */}
|
||||
<GridCol span={{ base: 12, xl: 4 }}>
|
||||
<Stack gap="md">
|
||||
{/* Berita Sampingan 1 */}
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src="/api/img/beritapemerintahan-2.jpeg"
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={300}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Pemerintahan</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
Evaluasi dan Verifikasi APBDes Darmasaba T.A. 2025
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Berita Sampingan 2 */}
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src="/api/img/pemerintahan-3.jpg"
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={250}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Pemerintahan</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
JAKSA GARDA DESA (JAGA DESA) T.A. 2025
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box pb={30}>
|
||||
<Text fz={"h1"} fw={"bold"}>Berita Terbaru</Text>
|
||||
<Divider color={colors["blue-button"]} />
|
||||
</Box>
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||
{dataBeritaTerbaru.map((v, k) => {
|
||||
return (
|
||||
<Paper key={k} p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src={v.image}
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={200}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Pemerintahan</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
{v.judul}
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">{v.tanggal}</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
</SimpleGrid>
|
||||
<Box py={"xl"}>
|
||||
<Center>
|
||||
<Pagination total={10} />
|
||||
</Center>
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Pemerintahan;
|
||||
@@ -1,188 +0,0 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Container, Divider, Grid, GridCol, Group, Image, Pagination, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
||||
import { IconCalendar, IconUser } from '@tabler/icons-react';
|
||||
import React from 'react';
|
||||
|
||||
const dataBeritaTerbaru = [
|
||||
{
|
||||
id: 1,
|
||||
judul: 'Darmasaba Luncurkan Aplikasi Smart Village untuk Pelayanan Publik',
|
||||
image: "/api/img/berita-1.png",
|
||||
tanggal: "Selasa, 11 Januari 2025",
|
||||
kategori: "Teknologi"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
judul: 'Tim Voli Darmasaba Raih Juara Turnamen Desa',
|
||||
image: "/api/img/prestasi-voli.jpeg",
|
||||
tanggal: "Kamis, 13 Januari 2025",
|
||||
kategori: "Sosial"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
judul: 'DARMASABA DIGITAL PROJECT IS COMING!',
|
||||
image: "/api/img/teknologi-1.png",
|
||||
tanggal: "Kamis, 13 Januari 2025",
|
||||
kategori: "Teknologi"
|
||||
},
|
||||
]
|
||||
function Semua() {
|
||||
return (
|
||||
<Box py={20}>
|
||||
<Container size="xl" px={{ base: "md", md: "xl" }}>
|
||||
<Grid gutter={{ base: "md", md: "xl" }} pb={70}>
|
||||
{/* Berita Utama */}
|
||||
<GridCol span={{ base: 12, xl: 8 }}>
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Image
|
||||
src="/api/img/berita-1.png"
|
||||
alt="Darmasaba Smart Village"
|
||||
radius="md"
|
||||
fit="cover"
|
||||
h={{ base: 450, md: 610 }}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Teknologi</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz={{ base: "xl", md: "2xl" }} fw="bold" lineClamp={2}>
|
||||
Darmasaba Luncurkan Aplikasi Smart Village untuk Pelayanan Publik
|
||||
</Text>
|
||||
<Text size="md" lineClamp={3}>
|
||||
Desa Darmasaba mengambil langkah maju dalam transformasi digital dengan meluncurkan aplikasi Smart Village yang memudahkan warga dalam mengakses layanan publik secara online.
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</GridCol>
|
||||
|
||||
{/* Berita Sampingan */}
|
||||
<GridCol span={{ base: 12, xl: 4 }}>
|
||||
<Stack gap="md">
|
||||
{/* Berita Sampingan 1 */}
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src="/api/img/pembangunan-2.jpg"
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={180}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Pembangunan</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
Darmasaba Luncurkan Aplikasi Smart Village untuk Pelayanan Publik
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Berita Sampingan 2 */}
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src="/api/img/prestasi-voli.jpeg"
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={180}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Sosial</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
Tim Voli Darmasaba Raih Juara Turnamen Desa
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box pb={30}>
|
||||
<Text fz={"h1"} fw={"bold"}>Berita Terbaru</Text>
|
||||
<Divider color={colors["blue-button"]} />
|
||||
</Box>
|
||||
<SimpleGrid cols={{ base: 1, xl: 3 }}>
|
||||
{dataBeritaTerbaru.map((v, k) => {
|
||||
return (
|
||||
<Paper key={k} p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src={v.image}
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={{ base: 180, xl: 250 }}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">{v.kategori}</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
{v.judul}
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">{v.tanggal}</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
</SimpleGrid>
|
||||
<Box py={"xl"}>
|
||||
<Center>
|
||||
<Pagination total={10} />
|
||||
</Center>
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Semua;
|
||||
@@ -1,204 +0,0 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Container, Divider, Grid, GridCol, Group, Image, Pagination, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
||||
import { IconCalendar, IconUser } from '@tabler/icons-react';
|
||||
import React from 'react';
|
||||
|
||||
const dataBeritaTerbaru = [
|
||||
{
|
||||
id: 1,
|
||||
judul: 'AKSI BERSIH DESA MEMPERINGATI HARI PEDULI SAMPAH NASIONAL 2025',
|
||||
image: "/api/img/sosial-4.png",
|
||||
tanggal: "Selasa, 11 Januari 2025",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
judul: 'Sosialisasi Pengelolaan Sampah di SD No. 3 Darmasaba dalam Kolaborasi Mahasiswa KKN-PMM I Universitas Warmadewa di Desa Darmasaba',
|
||||
image: "/api/img/sosial-2.jpeg",
|
||||
tanggal: "Kamis, 13 Januari 2025",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
judul: 'Tim Voli Darmasaba Raih Juara Turnamen Desa',
|
||||
image: "/api/img/prestasi-voli.jpeg",
|
||||
tanggal: "Kamis, 13 Januari 2025",
|
||||
},
|
||||
]
|
||||
function Sosial() {
|
||||
return (
|
||||
<Box py={20}>
|
||||
<Container size="xl" px={{ base: "md", md: "xl" }}>
|
||||
<Grid gutter={{ base: "md", md: "xl" }} pb={70}>
|
||||
{/* Berita Utama */}
|
||||
<GridCol span={{ base: 12, md: 8 }}>
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Image
|
||||
src="/api/img/sosial-1.jpeg"
|
||||
alt="Darmasaba Smart Village"
|
||||
radius="md"
|
||||
fit="cover"
|
||||
h={{ base: 450, md: 635 }}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Sosial</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz={{ base: "xl", md: "2xl" }} fw="bold" lineClamp={2}>
|
||||
AKSI BERSIH DESA MEMPERINGATI HARI PEDULI SAMPAH NASIONAL 2025
|
||||
</Text>
|
||||
<Text size="md" lineClamp={3}>
|
||||
AKSI BERSIH DESA MEMPERINGATI HARI PEDULI SAMPAH NASIONAL 2025 ♻️
|
||||
Dalam semangat Hari Peduli Sampah Nasional (HPSN) 2025, Aksi Bersih Desa sukses digelar di Lombok Utara serta beberapa wilayah lain, termasuk Kabupaten Badung yang berpusat di TPS 3R Pudak Mesari, Desa Darmasaba. ?✨
|
||||
|
||||
Kegiatan ini melibatkan berbagai pihak seperti Pusdal LH BN - KLH, DKLH Provinsi Bali, DLHK Kab. Badung, Pemdes Darmasaba, BPD, LPM, TPS 3R Pudak Mesari, Lembaga Kemasyarakatan Desa, serta masyarakat Darmasaba.
|
||||
|
||||
Selain aksi bersih, Desa Darmasaba juga meluncurkan Pemuda Peduli Lingkungan sebagai garda terdepan dalam gerakan sadar lingkungan!
|
||||
|
||||
Mengapa ini penting?
|
||||
- Pengelolaan sampah harus dimulai dari sumbernya melalui pemilahan, pemanfaatan, hingga penyelesaian residu.
|
||||
- Inovasi "Galah Melah" & "BARES" menjadi contoh nyata dalam menciptakan lingkungan yang bersih & berkelanjutan.
|
||||
- Darmasaba terpilih sebagai 1 dari 8 desa se-Indonesia untuk peringatan HPSN 2025, sebuah kebanggaan sekaligus tanggung jawab bersama! ✨
|
||||
|
||||
"Kami berharap aksi ini bukan sekadar seremoni, tetapi menjadi budaya masyarakat dalam memilah dan mengelola sampah dari sumbernya." - Ibu Cokorda Istri Muter Handayani, Plt. Kabid Wilayah Bali, Pusat Pengendalian LH Bali & Nusra.
|
||||
|
||||
"Ke depan, kami menargetkan setiap desa di Badung memiliki komunitas Pemuda Peduli Lingkungan agar pengelolaan sampah lebih optimal dan berkelanjutan." - Bapak Ida Bagus Gede Arjana, Plt. Kadis DLHK Kab. Badung.
|
||||
|
||||
"Darmasaba menjadi contoh desa aktif dalam pengelolaan sampah. Kami berharap inovasi ini menginspirasi desa lain." - Ida Bagus Surya Prabhawa Manuaba, Perbekel Darmasaba.
|
||||
|
||||
Mari bersama wujudkan lingkungan yang lebih bersih, sehat, dan lestari!
|
||||
Jaga Bumi, Pilah Sampah dari Sekarang! ♻️
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</GridCol>
|
||||
|
||||
{/* Berita Sampingan */}
|
||||
<GridCol span={{ base: 12, md: 4 }}>
|
||||
<Stack gap="md">
|
||||
{/* Berita Sampingan 1 */}
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src="/api/img/sosial-2.jpeg"
|
||||
alt="Darmasaba Smart Village"
|
||||
fit="cover"
|
||||
h={180}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Sosial</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
Sosialisasi Pengelolaan Sampah di SD No. 3 Darmasaba dalam Kolaborasi Mahasiswa KKN-PMM I Universitas Warmadewa di Desa Darmasaba
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Berita Sampingan 2 */}
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src="/api/img/prestasi-voli.jpeg"
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={180}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Sosial</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
Tim Voli Darmasaba Raih Juara Turnamen Desa
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box pb={30}>
|
||||
<Text fz={"h1"} fw={"bold"}>Berita Terbaru</Text>
|
||||
<Divider color={colors["blue-button"]} />
|
||||
</Box>
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||
{dataBeritaTerbaru.map((v, k) => {
|
||||
return (
|
||||
<Paper key={k} p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src={v.image}
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={180}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Sosial</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
{v.judul}
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">{v.tanggal}</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
</SimpleGrid>
|
||||
<Box py={"xl"}>
|
||||
<Center>
|
||||
<Pagination total={10} />
|
||||
</Center>
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Sosial;
|
||||
@@ -1,186 +0,0 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Container, Divider, Grid, GridCol, Group, Image, Pagination, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
|
||||
import { IconCalendar, IconUser } from '@tabler/icons-react';
|
||||
import React from 'react';
|
||||
|
||||
const dataBeritaTerbaru = [
|
||||
{
|
||||
id: 1,
|
||||
judul: 'Darmasaba Luncurkan Aplikasi Smart Village untuk Pelayanan Publik',
|
||||
image: "/api/img/teknologi-3.png",
|
||||
tanggal: "Selasa, 11 Januari 2025",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
judul: 'DARMASABA DIGITAL PROJECT IS COMING!',
|
||||
image: "/api/img/teknologi-1.png",
|
||||
tanggal: "Kamis, 13 Januari 2025",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
judul: 'DARMASABA MENUJU DESA DIGITAL',
|
||||
image: "/api/img/teknologi-2.png",
|
||||
tanggal: "Kamis, 13 Januari 2025",
|
||||
},
|
||||
]
|
||||
function Teknologi() {
|
||||
return (
|
||||
<Box py={20}>
|
||||
<Container size="xl" px={{ base: "md", md: "xl" }}>
|
||||
<Grid gutter={{ base: "md", md: "xl" }} pb={70}>
|
||||
{/* Berita Utama */}
|
||||
<GridCol span={{ base: 12, md: 8 }}>
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Image
|
||||
src="/api/img/berita-1.png"
|
||||
alt="Darmasaba Smart Village"
|
||||
radius="md"
|
||||
fit="cover"
|
||||
h={{ base: 450, md: 660 }}
|
||||
/>
|
||||
</Box>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Teknologi</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz={{ base: "xl", md: "2xl" }} fw="bold" lineClamp={2}>
|
||||
Darmasaba Luncurkan Aplikasi Smart Village untuk Pelayanan Publik
|
||||
</Text>
|
||||
<Text size="md" lineClamp={3}>
|
||||
Desa Darmasaba mengambil langkah maju dalam transformasi digital dengan meluncurkan aplikasi Smart Village yang memudahkan warga dalam mengakses layanan publik secara online.
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</GridCol>
|
||||
|
||||
{/* Berita Sampingan */}
|
||||
<GridCol span={{ base: 12, md: 4 }}>
|
||||
<Stack gap="md">
|
||||
{/* Berita Sampingan 1 */}
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src="/api/img/teknologi-1.png"
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={180}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Teknologi</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
DARMASABA DIGITAL PROJECT IS COMING! Desa Darmasaba nggak pernah berhenti berinovasi! ?
|
||||
Kali ini, kami siap menghadirkan gebrakan baru yang akan mempermudah sistem kerja di lingkungan
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Berita Sampingan 2 */}
|
||||
<Paper p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src="/api/img/teknologi-2.png"
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={180}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Teknologi</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={1}>
|
||||
DARMASABA MENUJU DESA DIGITAL
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">Selasa, 11 Januari 2025</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box pb={30}>
|
||||
<Text fz={"h1"} fw={"bold"}>Berita Terbaru</Text>
|
||||
<Divider color={colors["blue-button"]} />
|
||||
</Box>
|
||||
<SimpleGrid cols={{ base: 1, md: 3 }}>
|
||||
{dataBeritaTerbaru.map((v, k) => {
|
||||
return (
|
||||
<Paper key={k} p="md" shadow="sm" radius="md">
|
||||
<Stack gap="sm">
|
||||
<Image
|
||||
radius="md"
|
||||
src={v.image}
|
||||
alt="Prestasi Voli"
|
||||
fit="cover"
|
||||
h={180}
|
||||
/>
|
||||
<Group>
|
||||
<Paper px={10} py={5} radius="xl" bg={colors['BG-trans']}>
|
||||
<Text size="sm">Teknologi</Text>
|
||||
</Paper>
|
||||
</Group>
|
||||
<Text fz="lg" fw="bold" lineClamp={2}>
|
||||
{v.judul}
|
||||
</Text>
|
||||
<Group>
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">{v.tanggal}</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconUser size={18} />
|
||||
<Text size="sm">Admin Desa</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
</SimpleGrid>
|
||||
<Box py={"xl"}>
|
||||
<Center>
|
||||
<Pagination total={10} />
|
||||
</Center>
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Teknologi;
|
||||
168
src/app/darmasaba/(pages)/desa/berita/[kategori]/Content.tsx
Normal file
168
src/app/darmasaba/(pages)/desa/berita/[kategori]/Content.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
||||
import {
|
||||
Badge,
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
Center,
|
||||
Container,
|
||||
Divider,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Image,
|
||||
Pagination,
|
||||
Paper,
|
||||
SimpleGrid,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Text,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { IconArrowRight, IconCalendar } from '@tabler/icons-react';
|
||||
import { useTransitionRouter } from 'next-view-transitions';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
export default function Content({ kategori }: { kategori: string }) {
|
||||
const router = useTransitionRouter();
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
const state = useProxy(stateDashboardBerita.berita);
|
||||
const featuredState = useProxy(stateDashboardBerita.berita.findFirst);
|
||||
|
||||
const featured = featuredState.data;
|
||||
const paginatedNews = state.findMany.data || [];
|
||||
const totalPages = state.findMany.totalPages || 1;
|
||||
|
||||
// Load data
|
||||
useEffect(() => {
|
||||
stateDashboardBerita.berita.findFirst.load(kategori);
|
||||
}, [kategori]);
|
||||
|
||||
useEffect(() => {
|
||||
state.findMany.load(page, 3, '', kategori);
|
||||
}, [page, kategori]);
|
||||
|
||||
return (
|
||||
<Box py={20}>
|
||||
<Container size="xl" px={{ base: 'md', md: 'xl' }}>
|
||||
{/* === Berita Utama === */}
|
||||
{featuredState.loading ? (
|
||||
<Center><Skeleton h={400} /></Center>
|
||||
) : featured ? (
|
||||
<Box mb={50}>
|
||||
<Text fz="h2" fw={700} mb="md">Berita Utama</Text>
|
||||
<Paper shadow="md" radius="md" withBorder>
|
||||
<Grid gutter={0}>
|
||||
<GridCol span={{ base: 12, md: 6 }}>
|
||||
<Image
|
||||
src={featured.image?.link}
|
||||
alt={featured.judul || 'Berita Utama'}
|
||||
height={400}
|
||||
fit="cover"
|
||||
radius="md"
|
||||
style={{ borderBottomRightRadius: 0, borderTopRightRadius: 0 }}
|
||||
/>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 6 }} p="xl">
|
||||
<Stack h="100%" justify="space-between">
|
||||
<div>
|
||||
<Badge color="blue" variant="light" mb="md">
|
||||
{featured.kategoriBerita?.name || kategori}
|
||||
</Badge>
|
||||
<Title order={2} mb="md">{featured.judul}</Title>
|
||||
<Text color="dimmed" lineClamp={3} mb="md">{featured.deskripsi}</Text>
|
||||
</div>
|
||||
<Group justify="apart" mt="auto">
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={18} />
|
||||
<Text size="sm">
|
||||
{new Date(featured.createdAt).toLocaleDateString('id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</Text>
|
||||
</Group>
|
||||
<Button
|
||||
variant="light"
|
||||
rightSection={<IconArrowRight size={16} />}
|
||||
onClick={() => router.push(`/darmasaba/desa/berita/${kategori}/${featured.id}`)}
|
||||
>
|
||||
Baca Selengkapnya
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</Box>
|
||||
) : null}
|
||||
|
||||
{/* === Daftar Berita === */}
|
||||
<Box mt={50}>
|
||||
<Title order={2} mb="md">Daftar Berita</Title>
|
||||
<Divider mb="xl" />
|
||||
|
||||
{state.findMany.loading ? (
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl">
|
||||
{Array(3).fill(0).map((_, i) => (
|
||||
<Skeleton key={i} h={300} radius="md" />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
) : paginatedNews.length === 0 ? (
|
||||
<Text c="dimmed" ta="center">Belum ada berita di kategori "{kategori}".</Text>
|
||||
) : (
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="xl" verticalSpacing="xl">
|
||||
{paginatedNews.map((item) => (
|
||||
<Card
|
||||
key={item.id}
|
||||
shadow="sm"
|
||||
p="lg"
|
||||
radius="md"
|
||||
withBorder
|
||||
onClick={() => router.push(`/desa/berita/${item.id}`)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<Card.Section>
|
||||
<Image src={item.image?.link} height={200} alt={item.judul} fit="cover" />
|
||||
</Card.Section>
|
||||
<Badge color="blue" variant="light" mt="md">
|
||||
{item.kategoriBerita?.name || kategori}
|
||||
</Badge>
|
||||
<Text fw={600} size="lg" mt="sm" lineClamp={2}>{item.judul}</Text>
|
||||
<Text size="sm" color="dimmed" lineClamp={3} mt="xs">{item.deskripsi}</Text>
|
||||
<Group justify="apart" mt="md" gap="xs">
|
||||
<Text size="xs" color="dimmed">
|
||||
{new Date(item.createdAt).toLocaleDateString('id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</Text>
|
||||
<Badge color="gray" variant="outline">Baca Selengkapnya</Badge>
|
||||
</Group>
|
||||
</Card>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
)}
|
||||
|
||||
{/* Pagination */}
|
||||
<Center mt="xl">
|
||||
<Pagination
|
||||
total={totalPages}
|
||||
value={page}
|
||||
onChange={(newPage) => setPage(newPage)}
|
||||
siblings={1}
|
||||
boundaries={1}
|
||||
withEdges
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Center, Container, Image, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../../layanan/_com/BackButto';
|
||||
|
||||
|
||||
|
||||
function Page() {
|
||||
const params = useParams<{ id: string }>();
|
||||
const id = Array.isArray(params.id) ? params.id[0] : params.id;
|
||||
const state = useProxy(stateDashboardBerita.berita)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
if (!id) return;
|
||||
try {
|
||||
setLoading(true);
|
||||
await state.findUnique.load(id);
|
||||
} catch (error) {
|
||||
console.error('Error loading data:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
loadData()
|
||||
}, [id])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Center>
|
||||
<Skeleton height={500} />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
if (!state.findUnique.data) {
|
||||
return (
|
||||
<Center>
|
||||
<Text>Data tidak ditemukan</Text>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} px={{ base: "md", md: 0 }}>
|
||||
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
|
||||
<Container w={{ base: "100%", md: "50%" }} >
|
||||
<Box pb={20}>
|
||||
<Text ta={"center"} fz={"2.4rem"} c={colors["blue-button"]} fw={"bold"}>
|
||||
{state.findUnique.data?.judul}
|
||||
</Text>
|
||||
<Text
|
||||
ta={"center"}
|
||||
fw={"bold"}
|
||||
fz={"1.5rem"}
|
||||
>
|
||||
Informasi dan Pelayanan Administrasi Digital
|
||||
</Text>
|
||||
</Box>
|
||||
<Image src={state.findUnique.data?.image?.link || ''} alt='' w={"100%"} />
|
||||
</Container>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={"xs"}>
|
||||
<Text py={20} fz={{ base: "sm", md: "lg" }} ta={"justify"} dangerouslySetInnerHTML={{ __html: state.findUnique.data?.content || '' }} />
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
13
src/app/darmasaba/(pages)/desa/berita/[kategori]/page.tsx
Normal file
13
src/app/darmasaba/(pages)/desa/berita/[kategori]/page.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
// src/app/darmasaba/(pages)/desa/berita/[kategori]/page.tsx
|
||||
import { Suspense } from "react";
|
||||
import Content from "./Content";
|
||||
|
||||
export default async function Page({ params }: { params: Promise<{ kategori: string }> }) {
|
||||
const { kategori } = await params;
|
||||
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Content kategori={kategori} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
181
src/app/darmasaba/(pages)/desa/berita/_lib/layoutTabs.tsx
Normal file
181
src/app/darmasaba/(pages)/desa/berita/_lib/layoutTabs.tsx
Normal file
@@ -0,0 +1,181 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Container, Grid, GridCol, Stack, Tabs, TabsList, TabsTab, Text, TextInput } from '@mantine/core';
|
||||
import { IconSearch } from '@tabler/icons-react';
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import BackButton from '../../layanan/_com/BackButto';
|
||||
|
||||
type HeaderSearchProps = {
|
||||
placeholder?: string;
|
||||
searchIcon?: React.ReactNode;
|
||||
value?: string;
|
||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
function LayoutTabsBerita({
|
||||
children,
|
||||
placeholder = "pencarian",
|
||||
searchIcon = <IconSearch size={20} />
|
||||
}: HeaderSearchProps) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
// Get active tab from URL path
|
||||
const activeTab = pathname.split('/').pop() || 'semua';
|
||||
|
||||
// Get initial search value from URL
|
||||
const initialSearch = searchParams.get('search') || '';
|
||||
const [searchValue, setSearchValue] = useState(initialSearch);
|
||||
const [searchTimeout, setSearchTimeout] = useState<number | null>(null);
|
||||
|
||||
// Update active tab state when pathname changes
|
||||
const [activeTabState, setActiveTabState] = useState(activeTab);
|
||||
useEffect(() => {
|
||||
setActiveTabState(activeTab);
|
||||
}, [activeTab]);
|
||||
|
||||
// Clean up timeouts on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (searchTimeout !== null) {
|
||||
clearTimeout(searchTimeout);
|
||||
}
|
||||
};
|
||||
}, [searchTimeout]);
|
||||
|
||||
// Handle search input change with debounce
|
||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = event.target.value;
|
||||
setSearchValue(value);
|
||||
|
||||
// Clear previous timeout
|
||||
if (searchTimeout !== null) {
|
||||
clearTimeout(searchTimeout);
|
||||
}
|
||||
|
||||
// Set new timeout
|
||||
const newTimeout = window.setTimeout(() => {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
|
||||
if (value) {
|
||||
params.set('search', value);
|
||||
} else {
|
||||
params.delete('search');
|
||||
}
|
||||
|
||||
// Only update URL if the search value has actually changed
|
||||
if (params.toString() !== searchParams.toString()) {
|
||||
router.push(`/darmasaba/desa/berita/${activeTab}?${params.toString()}`);
|
||||
}
|
||||
}, 500); // 500ms debounce delay
|
||||
|
||||
setSearchTimeout(newTimeout);
|
||||
};
|
||||
const tabs = [
|
||||
{
|
||||
label: "Semua",
|
||||
value: "semua",
|
||||
href: "/darmasaba/desa/berita/semua"
|
||||
},
|
||||
{
|
||||
label: "Budaya",
|
||||
value: "budaya",
|
||||
href: "/darmasaba/desa/berita/budaya"
|
||||
},
|
||||
{
|
||||
label: "Pemerintahan",
|
||||
value: "pemerintahan",
|
||||
href: "/darmasaba/desa/berita/pemerintahan"
|
||||
},
|
||||
{
|
||||
label: "Ekonomi",
|
||||
value: "ekonomi",
|
||||
href: "/darmasaba/desa/berita/ekonomi"
|
||||
},
|
||||
{
|
||||
label: "Pembangunan",
|
||||
value: "pembangunan",
|
||||
href: "/darmasaba/desa/berita/pembangunan"
|
||||
},
|
||||
{
|
||||
label: "Sosial",
|
||||
value: "sosial",
|
||||
href: "/darmasaba/desa/berita/sosial"
|
||||
},
|
||||
{
|
||||
label: "Teknologi",
|
||||
value: "teknologi",
|
||||
href: "/darmasaba/desa/berita/teknologi"
|
||||
},
|
||||
|
||||
];
|
||||
const handleTabChange = (value: string | null) => {
|
||||
if (!value) return;
|
||||
const tab = tabs.find(t => t.value === value);
|
||||
if (tab) {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
router.push(`/darmasaba/desa/berita/${value}${params.toString() ? `?${params.toString()}` : ''}`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
{/* Header */}
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Container size="lg" px="md">
|
||||
<Stack align="center" gap="0" >
|
||||
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
||||
Portal Berita Darmasaba
|
||||
</Text>
|
||||
<Text ta="center" px="md">
|
||||
Temukan berbagai potensi dan keunggulan yang dimiliki Desa Darmasaba
|
||||
</Text>
|
||||
</Stack>
|
||||
</Container>
|
||||
|
||||
<Tabs
|
||||
color={colors['blue-button']}
|
||||
variant="pills"
|
||||
value={activeTabState}
|
||||
onChange={handleTabChange}
|
||||
>
|
||||
<Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']}>
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 9, lg: 8, xl: 9 }}>
|
||||
<TabsList>
|
||||
{tabs.map((tab, index) => (
|
||||
<TabsTab
|
||||
key={index}
|
||||
value={tab.value}
|
||||
onClick={() => router.push(tab.href)}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 3, lg: 4, xl: 3 }}>
|
||||
<TextInput
|
||||
radius="lg"
|
||||
placeholder={placeholder}
|
||||
leftSection={searchIcon}
|
||||
w="100%"
|
||||
value={searchValue}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
{children}
|
||||
</Tabs>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutTabsBerita;
|
||||
12
src/app/darmasaba/(pages)/desa/berita/layout.tsx
Normal file
12
src/app/darmasaba/(pages)/desa/berita/layout.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
// app/desa/berita/BeritaLayoutClient.tsx
|
||||
'use client'
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const LayoutTabsBerita = dynamic(
|
||||
() => import('./_lib/layoutTabs'),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
export default function BeritaLayoutClient({ children }: { children: React.ReactNode }) {
|
||||
return <LayoutTabsBerita>{children}</LayoutTabsBerita>;
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Container, Grid, GridCol, Stack, Tabs, TabsList, TabsPanel, TabsTab, Text, TextInput } from '@mantine/core';
|
||||
import { IconSearch } from '@tabler/icons-react';
|
||||
import BackButton from '../layanan/_com/BackButto';
|
||||
import Budaya from './(tabs)/budaya';
|
||||
import Ekonomi from './(tabs)/ekonomi';
|
||||
import Pemerintahan from './(tabs)/pemerintahan';
|
||||
import Semua from './(tabs)/semua';
|
||||
import Sosial from './(tabs)/sosial';
|
||||
import Teknologi from './(tabs)/teknologi';
|
||||
import Pembangunan from './(tabs)/pembangunan';
|
||||
|
||||
|
||||
function Page() {
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
{/* Header */}
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Container size="lg" px="md">
|
||||
<Stack align="center" gap="0" >
|
||||
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
||||
Portal Berita Darmasaba
|
||||
</Text>
|
||||
<Text ta="center" px="md">
|
||||
Temukan berbagai potensi dan keunggulan yang dimiliki Desa Darmasaba
|
||||
</Text>
|
||||
</Stack>
|
||||
</Container>
|
||||
|
||||
{/* Tabs Menu */}
|
||||
<Tabs color={colors['blue-button']} variant="pills" defaultValue="semua">
|
||||
<Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']} >
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 9, lg: 8, xl: 9 }}>
|
||||
<TabsList>
|
||||
<TabsTab value="semua">
|
||||
Semua
|
||||
</TabsTab>
|
||||
<TabsTab value="pemerintahan">
|
||||
Pemerintahan
|
||||
</TabsTab>
|
||||
<TabsTab value="pembangunan" >
|
||||
Pembangunan
|
||||
</TabsTab>
|
||||
<TabsTab value="ekonomi" >
|
||||
Ekonomi
|
||||
</TabsTab>
|
||||
<TabsTab value="sosial" >
|
||||
Sosial
|
||||
</TabsTab>
|
||||
<TabsTab value="budaya" >
|
||||
Budaya
|
||||
</TabsTab>
|
||||
<TabsTab value="teknologi" >
|
||||
Teknologi
|
||||
</TabsTab>
|
||||
</TabsList>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 3, lg: 4, xl: 3 }}>
|
||||
<TextInput
|
||||
w={{ base: "100%", md: "100%" }}
|
||||
radius="lg"
|
||||
placeholder="Cari Berita"
|
||||
leftSection={<IconSearch size={18} />}
|
||||
/>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
<TabsPanel value='semua'>
|
||||
<Semua />
|
||||
</TabsPanel>
|
||||
<TabsPanel value='pemerintahan'>
|
||||
<Pemerintahan />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="pembangunan" >
|
||||
<Pembangunan />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="ekonomi" >
|
||||
<Ekonomi />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="sosial" >
|
||||
<Sosial />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="budaya" >
|
||||
<Budaya />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="teknologi" >
|
||||
<Teknologi />
|
||||
</TabsPanel>
|
||||
|
||||
</Tabs>
|
||||
|
||||
|
||||
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
128
src/app/darmasaba/(pages)/desa/berita/page2.txt
Normal file
128
src/app/darmasaba/(pages)/desa/berita/page2.txt
Normal file
@@ -0,0 +1,128 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Container, Grid, GridCol, Skeleton, Stack, Tabs, TabsList, TabsPanel, TabsTab, Text, TextInput } from '@mantine/core';
|
||||
import { IconSearch } from '@tabler/icons-react';
|
||||
import BackButton from '../layanan/_com/BackButto';
|
||||
import Budaya from './budaya/page';
|
||||
import Ekonomi from './ekonomi/page';
|
||||
import Pemerintahan from './pemerintahan/page';
|
||||
import Semua from './semua/page';
|
||||
import Sosial from './sosial/page';
|
||||
import Teknologi from './teknologi/page';
|
||||
import Pembangunan from './pembangunan/page';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import stateDashboardBerita from '@/app/admin/(dashboard)/_state/desa/berita';
|
||||
|
||||
|
||||
function Page() {
|
||||
const [selectedKategori, setSelectedKategori] = useState<string>('Semua');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const state = useProxy(stateDashboardBerita);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
state.kategoriBerita.findMany.load()
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await stateDashboardBerita.berita.findMany.load(1, 100);
|
||||
} catch (error) {
|
||||
console.error('Error loading data:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
const data = state.berita.findMany.data || [];
|
||||
|
||||
const categories = [...new Set(
|
||||
data
|
||||
.filter(item => item.kategoriBerita?.name)
|
||||
.map(item => item.kategoriBerita?.name)
|
||||
)];
|
||||
|
||||
const filteredData = selectedKategori === 'Semua'
|
||||
? data
|
||||
: data.filter(item => item.kategoriBerita?.name === selectedKategori);
|
||||
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="22">
|
||||
{/* Header */}
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<BackButton />
|
||||
</Box>
|
||||
<Container size="lg" px="md">
|
||||
<Stack align="center" gap="0" >
|
||||
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
||||
Portal Berita Darmasaba
|
||||
</Text>
|
||||
<Text ta="center" px="md">
|
||||
Temukan berbagai potensi dan keunggulan yang dimiliki Desa Darmasaba
|
||||
</Text>
|
||||
</Stack>
|
||||
</Container>
|
||||
|
||||
{/* Tabs Menu */}
|
||||
<Tabs color={colors['blue-button']} variant="pills" defaultValue="semua">
|
||||
<Box px={{ base: "md", md: 100 }} py="md" bg={colors['BG-trans']} >
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 9, lg: 8, xl: 9 }}>
|
||||
<TabsList>
|
||||
{categories.map((kategori) => (
|
||||
<TabsTab key={kategori} value={kategori || ''}>
|
||||
{kategori}
|
||||
</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 3, lg: 4, xl: 3 }}>
|
||||
{loading ? (
|
||||
<Skeleton height={42} />
|
||||
) : filteredData.length > 0 ? (
|
||||
<TextInput
|
||||
w={{ base: "100%", md: "100%" }}
|
||||
radius="lg"
|
||||
placeholder="Cari Berita"
|
||||
leftSection={<IconSearch size={18} />}
|
||||
/>
|
||||
) : (
|
||||
<Text>Tidak ada data</Text>
|
||||
)}
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
<TabsPanel value='semua'>
|
||||
<Semua />
|
||||
</TabsPanel>
|
||||
<TabsPanel value='pemerintahan'>
|
||||
<Pemerintahan />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="pembangunan" >
|
||||
<Pembangunan />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="ekonomi" >
|
||||
<Ekonomi />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="sosial" >
|
||||
<Sosial />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="budaya" >
|
||||
<Budaya />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="teknologi" >
|
||||
<Teknologi />
|
||||
</TabsPanel>
|
||||
|
||||
</Tabs>
|
||||
|
||||
|
||||
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user