chore: fix linting and type safety across the project
This commit is contained in:
@@ -2,15 +2,21 @@
|
|||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
|
|
||||||
// Fungsi untuk mencari string terpanjang dalam objek (biasanya balasan AI)
|
// Fungsi untuk mencari string terpanjang dalam objek (biasanya balasan AI)
|
||||||
function findLongestString(obj: any): string {
|
function findLongestString(obj: unknown): string {
|
||||||
let longest = "";
|
let longest = "";
|
||||||
const search = (item: any) => {
|
const search = (item: unknown) => {
|
||||||
if (typeof item === "string") {
|
if (typeof item === "string") {
|
||||||
if (item.length > longest.length) longest = item;
|
if (item.length > longest.length) {
|
||||||
|
longest = item;
|
||||||
|
}
|
||||||
} else if (Array.isArray(item)) {
|
} else if (Array.isArray(item)) {
|
||||||
item.forEach(search);
|
for (const child of item) {
|
||||||
} else if (item && typeof item === "object") {
|
search(child);
|
||||||
Object.values(item).forEach(search);
|
}
|
||||||
|
} else if (item !== null && typeof item === "object") {
|
||||||
|
for (const value of Object.values(item)) {
|
||||||
|
search(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
search(obj);
|
search(obj);
|
||||||
|
|||||||
464
generated/api.ts
464
generated/api.ts
@@ -383,7 +383,17 @@ export interface operations {
|
|||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content?: never;
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
ok: boolean;
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
ok: boolean;
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
ok: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -400,7 +410,17 @@ export interface operations {
|
|||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content?: never;
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: unknown;
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: unknown;
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -894,7 +914,33 @@ export interface operations {
|
|||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content?: never;
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
500: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -911,7 +957,33 @@ export interface operations {
|
|||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content?: never;
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
500: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -928,7 +1000,33 @@ export interface operations {
|
|||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content?: never;
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
500: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -945,7 +1043,48 @@ export interface operations {
|
|||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content?: never;
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: {
|
||||||
|
total: number;
|
||||||
|
baru: number;
|
||||||
|
proses: number;
|
||||||
|
selesai: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: {
|
||||||
|
total: number;
|
||||||
|
baru: number;
|
||||||
|
proses: number;
|
||||||
|
selesai: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: {
|
||||||
|
total: number;
|
||||||
|
baru: number;
|
||||||
|
proses: number;
|
||||||
|
selesai: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
500: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -962,7 +1101,33 @@ export interface operations {
|
|||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content?: never;
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
500: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -979,7 +1144,33 @@ export interface operations {
|
|||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content?: never;
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
500: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -996,7 +1187,33 @@ export interface operations {
|
|||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content?: never;
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
500: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1013,7 +1230,33 @@ export interface operations {
|
|||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content?: never;
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
500: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1030,7 +1273,39 @@ export interface operations {
|
|||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content?: never;
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: {
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: {
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: {
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
500: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1047,7 +1322,45 @@ export interface operations {
|
|||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content?: never;
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: {
|
||||||
|
total: number;
|
||||||
|
heads: number;
|
||||||
|
poor: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: {
|
||||||
|
total: number;
|
||||||
|
heads: number;
|
||||||
|
poor: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: {
|
||||||
|
total: number;
|
||||||
|
heads: number;
|
||||||
|
poor: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
500: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1064,7 +1377,33 @@ export interface operations {
|
|||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content?: never;
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
500: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1081,7 +1420,48 @@ export interface operations {
|
|||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content?: never;
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: {
|
||||||
|
religion: unknown[];
|
||||||
|
gender: unknown[];
|
||||||
|
occupation: unknown[];
|
||||||
|
ageGroups: unknown[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: {
|
||||||
|
religion: unknown[];
|
||||||
|
gender: unknown[];
|
||||||
|
occupation: unknown[];
|
||||||
|
ageGroups: unknown[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: {
|
||||||
|
religion: unknown[];
|
||||||
|
gender: unknown[];
|
||||||
|
occupation: unknown[];
|
||||||
|
ageGroups: unknown[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
500: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1098,7 +1478,33 @@ export interface operations {
|
|||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content?: never;
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
500: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1115,7 +1521,33 @@ export interface operations {
|
|||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content?: never;
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: unknown[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
500: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -165,7 +165,7 @@ async function seedResidents(banjarIds: string[]) {
|
|||||||
gender: Gender.LAKI_LAKI,
|
gender: Gender.LAKI_LAKI,
|
||||||
religion: Religion.HINDU,
|
religion: Religion.HINDU,
|
||||||
occupation: "Wiraswasta",
|
occupation: "Wiraswasta",
|
||||||
banjarId: banjarIds[0],
|
banjarId: banjarIds[0] || "",
|
||||||
rt: "001",
|
rt: "001",
|
||||||
rw: "000",
|
rw: "000",
|
||||||
address: "Jl. Raya Darmasaba No. 1",
|
address: "Jl. Raya Darmasaba No. 1",
|
||||||
@@ -180,7 +180,7 @@ async function seedResidents(banjarIds: string[]) {
|
|||||||
gender: Gender.PEREMPUAN,
|
gender: Gender.PEREMPUAN,
|
||||||
religion: Religion.HINDU,
|
religion: Religion.HINDU,
|
||||||
occupation: "Guru",
|
occupation: "Guru",
|
||||||
banjarId: banjarIds[1],
|
banjarId: banjarIds[1] || banjarIds[0] || "",
|
||||||
rt: "002",
|
rt: "002",
|
||||||
rw: "000",
|
rw: "000",
|
||||||
address: "Gg. Manesa No. 5",
|
address: "Gg. Manesa No. 5",
|
||||||
@@ -203,7 +203,7 @@ async function seedActivities(divisionIds: string[]) {
|
|||||||
{
|
{
|
||||||
title: "Rapat Koordinasi 2025",
|
title: "Rapat Koordinasi 2025",
|
||||||
description: "Penyusunan rencana kerja tahunan",
|
description: "Penyusunan rencana kerja tahunan",
|
||||||
divisionId: divisionIds[0],
|
divisionId: divisionIds[0] || "",
|
||||||
progress: 100,
|
progress: 100,
|
||||||
status: ActivityStatus.SELESAI,
|
status: ActivityStatus.SELESAI,
|
||||||
priority: Priority.TINGGI,
|
priority: Priority.TINGGI,
|
||||||
@@ -211,7 +211,7 @@ async function seedActivities(divisionIds: string[]) {
|
|||||||
{
|
{
|
||||||
title: "Pemutakhiran Indeks Desa",
|
title: "Pemutakhiran Indeks Desa",
|
||||||
description: "Pendataan SDG's Desa 2025",
|
description: "Pendataan SDG's Desa 2025",
|
||||||
divisionId: divisionIds[0],
|
divisionId: divisionIds[0] || "",
|
||||||
progress: 65,
|
progress: 65,
|
||||||
status: ActivityStatus.BERJALAN,
|
status: ActivityStatus.BERJALAN,
|
||||||
priority: Priority.SEDANG,
|
priority: Priority.SEDANG,
|
||||||
@@ -219,7 +219,7 @@ async function seedActivities(divisionIds: string[]) {
|
|||||||
{
|
{
|
||||||
title: "Pembangunan Jalan Banjar Cabe",
|
title: "Pembangunan Jalan Banjar Cabe",
|
||||||
description: "Pengaspalan jalan utama",
|
description: "Pengaspalan jalan utama",
|
||||||
divisionId: divisionIds[1],
|
divisionId: divisionIds[1] || divisionIds[0] || "",
|
||||||
progress: 40,
|
progress: 40,
|
||||||
status: ActivityStatus.BERJALAN,
|
status: ActivityStatus.BERJALAN,
|
||||||
priority: Priority.DARURAT,
|
priority: Priority.DARURAT,
|
||||||
@@ -296,7 +296,7 @@ async function seedEvents(adminId: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
export async function runSeed() {
|
||||||
console.log("Starting seed...");
|
console.log("Starting seed...");
|
||||||
|
|
||||||
const adminId = await seedAdminUser();
|
const adminId = await seedAdminUser();
|
||||||
@@ -315,11 +315,13 @@ async function main() {
|
|||||||
console.log("Seed finished successfully!");
|
console.log("Seed finished successfully!");
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
if (import.meta.main) {
|
||||||
.catch((e) => {
|
runSeed()
|
||||||
console.error(e);
|
.catch((e) => {
|
||||||
process.exit(1);
|
console.error(e);
|
||||||
})
|
process.exit(1);
|
||||||
.finally(async () => {
|
})
|
||||||
await prisma.$disconnect();
|
.finally(async () => {
|
||||||
});
|
await prisma.$disconnect();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import Elysia from "elysia";
|
import Elysia, { t } from "elysia";
|
||||||
import { prisma } from "../utils/db";
|
import { prisma } from "../utils/db";
|
||||||
import logger from "../utils/logger";
|
import logger from "../utils/logger";
|
||||||
|
|
||||||
@@ -23,6 +23,17 @@ export const complaint = new Elysia({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
response: {
|
||||||
|
200: t.Object({
|
||||||
|
data: t.Object({
|
||||||
|
total: t.Number(),
|
||||||
|
baru: t.Number(),
|
||||||
|
proses: t.Number(),
|
||||||
|
selesai: t.Number(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
500: t.Object({ error: t.String() }),
|
||||||
|
},
|
||||||
detail: { summary: "Get complaint statistics" },
|
detail: { summary: "Get complaint statistics" },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -42,6 +53,12 @@ export const complaint = new Elysia({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
response: {
|
||||||
|
200: t.Object({
|
||||||
|
data: t.Array(t.Any()),
|
||||||
|
}),
|
||||||
|
500: t.Object({ error: t.String() }),
|
||||||
|
},
|
||||||
detail: { summary: "Get recent complaints" },
|
detail: { summary: "Get recent complaints" },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -61,6 +78,12 @@ export const complaint = new Elysia({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
response: {
|
||||||
|
200: t.Object({
|
||||||
|
data: t.Array(t.Any()),
|
||||||
|
}),
|
||||||
|
500: t.Object({ error: t.String() }),
|
||||||
|
},
|
||||||
detail: { summary: "Get service letter statistics by type" },
|
detail: { summary: "Get service letter statistics by type" },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -80,6 +103,12 @@ export const complaint = new Elysia({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
response: {
|
||||||
|
200: t.Object({
|
||||||
|
data: t.Array(t.Any()),
|
||||||
|
}),
|
||||||
|
500: t.Object({ error: t.String() }),
|
||||||
|
},
|
||||||
detail: { summary: "Get recent innovation ideas" },
|
detail: { summary: "Get recent innovation ideas" },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -88,7 +117,9 @@ export const complaint = new Elysia({
|
|||||||
async ({ set }) => {
|
async ({ set }) => {
|
||||||
try {
|
try {
|
||||||
// Get last 6 months trends for service letters
|
// Get last 6 months trends for service letters
|
||||||
const trends = await prisma.$queryRaw<any[]>`
|
const trends = await prisma.$queryRaw<
|
||||||
|
{ month: string; month_num: number; count: number }[]
|
||||||
|
>`
|
||||||
SELECT
|
SELECT
|
||||||
TO_CHAR("createdAt", 'Mon') as month,
|
TO_CHAR("createdAt", 'Mon') as month,
|
||||||
EXTRACT(MONTH FROM "createdAt") as month_num,
|
EXTRACT(MONTH FROM "createdAt") as month_num,
|
||||||
@@ -106,6 +137,12 @@ export const complaint = new Elysia({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
response: {
|
||||||
|
200: t.Object({
|
||||||
|
data: t.Array(t.Any()),
|
||||||
|
}),
|
||||||
|
500: t.Object({ error: t.String() }),
|
||||||
|
},
|
||||||
detail: { summary: "Get service letter trends for last 6 months" },
|
detail: { summary: "Get service letter trends for last 6 months" },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -132,6 +169,14 @@ export const complaint = new Elysia({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
response: {
|
||||||
|
200: t.Object({
|
||||||
|
data: t.Object({
|
||||||
|
count: t.Number(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
500: t.Object({ error: t.String() }),
|
||||||
|
},
|
||||||
detail: { summary: "Get service letter count for current week" },
|
detail: { summary: "Get service letter count for current week" },
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import Elysia from "elysia";
|
import Elysia, { t } from "elysia";
|
||||||
import { prisma } from "../utils/db";
|
import { prisma } from "../utils/db";
|
||||||
import logger from "../utils/logger";
|
import logger from "../utils/logger";
|
||||||
|
|
||||||
@@ -24,6 +24,12 @@ export const division = new Elysia({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
response: {
|
||||||
|
200: t.Object({
|
||||||
|
data: t.Array(t.Any()),
|
||||||
|
}),
|
||||||
|
500: t.Object({ error: t.String() }),
|
||||||
|
},
|
||||||
detail: { summary: "Get all divisions" },
|
detail: { summary: "Get all divisions" },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -48,6 +54,12 @@ export const division = new Elysia({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
response: {
|
||||||
|
200: t.Object({
|
||||||
|
data: t.Array(t.Any()),
|
||||||
|
}),
|
||||||
|
500: t.Object({ error: t.String() }),
|
||||||
|
},
|
||||||
detail: { summary: "Get recent activities" },
|
detail: { summary: "Get recent activities" },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -66,6 +78,12 @@ export const division = new Elysia({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
response: {
|
||||||
|
200: t.Object({
|
||||||
|
data: t.Array(t.Any()),
|
||||||
|
}),
|
||||||
|
500: t.Object({ error: t.String() }),
|
||||||
|
},
|
||||||
detail: { summary: "Get division performance metrics" },
|
detail: { summary: "Get division performance metrics" },
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import Elysia from "elysia";
|
import Elysia, { t } from "elysia";
|
||||||
import { prisma } from "../utils/db";
|
import { prisma } from "../utils/db";
|
||||||
import logger from "../utils/logger";
|
import logger from "../utils/logger";
|
||||||
|
|
||||||
@@ -21,6 +21,12 @@ export const event = new Elysia({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
response: {
|
||||||
|
200: t.Object({
|
||||||
|
data: t.Array(t.Any()),
|
||||||
|
}),
|
||||||
|
500: t.Object({ error: t.String() }),
|
||||||
|
},
|
||||||
detail: { summary: "Get upcoming events" },
|
detail: { summary: "Get upcoming events" },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -49,6 +55,12 @@ export const event = new Elysia({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
response: {
|
||||||
|
200: t.Object({
|
||||||
|
data: t.Array(t.Any()),
|
||||||
|
}),
|
||||||
|
500: t.Object({ error: t.String() }),
|
||||||
|
},
|
||||||
detail: { summary: "Get events for today" },
|
detail: { summary: "Get events for today" },
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { cors } from "@elysiajs/cors";
|
import { cors } from "@elysiajs/cors";
|
||||||
import { swagger } from "@elysiajs/swagger";
|
import { swagger } from "@elysiajs/swagger";
|
||||||
import Elysia from "elysia";
|
import Elysia, { t } from "elysia";
|
||||||
import { apiMiddleware } from "../middleware/apiMiddleware";
|
import { apiMiddleware } from "../middleware/apiMiddleware";
|
||||||
import { auth } from "../utils/auth";
|
import { auth } from "../utils/auth";
|
||||||
import { apikey } from "./apikey";
|
import { apikey } from "./apikey";
|
||||||
@@ -16,12 +16,24 @@ const api = new Elysia({
|
|||||||
prefix: "/api",
|
prefix: "/api",
|
||||||
})
|
})
|
||||||
.use(cors())
|
.use(cors())
|
||||||
.get("/health", () => ({ ok: true }))
|
.get("/health", () => ({ ok: true }), {
|
||||||
.all("/auth/*", ({ request }) => auth.handler(request))
|
response: {
|
||||||
.get("/session", async ({ request }) => {
|
200: t.Object({ ok: t.Boolean() }),
|
||||||
const data = await auth.api.getSession({ headers: request.headers });
|
},
|
||||||
return { data };
|
|
||||||
})
|
})
|
||||||
|
.all("/auth/*", ({ request }) => auth.handler(request))
|
||||||
|
.get(
|
||||||
|
"/session",
|
||||||
|
async ({ request }) => {
|
||||||
|
const data = await auth.api.getSession({ headers: request.headers });
|
||||||
|
return { data };
|
||||||
|
},
|
||||||
|
{
|
||||||
|
response: {
|
||||||
|
200: t.Object({ data: t.Any() }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
.use(apiMiddleware)
|
.use(apiMiddleware)
|
||||||
.use(apikey)
|
.use(apikey)
|
||||||
.use(profile)
|
.use(profile)
|
||||||
|
|||||||
@@ -1,80 +1,69 @@
|
|||||||
import Elysia, { t } from "elysia";
|
import Elysia, { t } from "elysia";
|
||||||
|
import { apiMiddleware } from "../middleware/apiMiddleware";
|
||||||
import { prisma } from "../utils/db";
|
import { prisma } from "../utils/db";
|
||||||
import logger from "../utils/logger";
|
import logger from "../utils/logger";
|
||||||
|
|
||||||
interface AuthenticatedUser {
|
|
||||||
id: string;
|
|
||||||
email: string;
|
|
||||||
name?: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const profile = new Elysia({
|
export const profile = new Elysia({
|
||||||
prefix: "/profile",
|
prefix: "/profile",
|
||||||
}).post(
|
})
|
||||||
"/update",
|
.use(apiMiddleware)
|
||||||
async ({
|
.post(
|
||||||
body,
|
"/update",
|
||||||
set,
|
async ({ body, set, user }) => {
|
||||||
user,
|
try {
|
||||||
}: {
|
if (!user) {
|
||||||
body: { name?: string; image?: string };
|
set.status = 401;
|
||||||
set: any;
|
return { error: "Unauthorized" };
|
||||||
user?: AuthenticatedUser;
|
}
|
||||||
}) => {
|
|
||||||
try {
|
const { name, image } = body;
|
||||||
if (!user) {
|
|
||||||
set.status = 401;
|
const updatedUser = await prisma.user.update({
|
||||||
return { error: "Unauthorized" };
|
where: { id: user.id },
|
||||||
|
data: {
|
||||||
|
name: name || undefined,
|
||||||
|
image: image || undefined,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
email: true,
|
||||||
|
image: true,
|
||||||
|
role: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info({ userId: user.id }, "Profile updated successfully");
|
||||||
|
|
||||||
|
return { user: updatedUser };
|
||||||
|
} catch (error) {
|
||||||
|
logger.error({ error, userId: user?.id }, "Failed to update profile");
|
||||||
|
set.status = 500;
|
||||||
|
return { error: "Failed to update profile" };
|
||||||
}
|
}
|
||||||
|
},
|
||||||
const { name, image } = body;
|
{
|
||||||
|
body: t.Object({
|
||||||
const updatedUser = await prisma.user.update({
|
name: t.Optional(t.String()),
|
||||||
where: { id: user.id },
|
image: t.Optional(t.String()),
|
||||||
data: {
|
|
||||||
name: name || undefined,
|
|
||||||
image: image || undefined,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
image: true,
|
|
||||||
role: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.info({ userId: user.id }, "Profile updated successfully");
|
|
||||||
|
|
||||||
return { user: updatedUser };
|
|
||||||
} catch (error) {
|
|
||||||
logger.error({ error, userId: user?.id }, "Failed to update profile");
|
|
||||||
set.status = 500;
|
|
||||||
return { error: "Failed to update profile" };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
body: t.Object({
|
|
||||||
name: t.Optional(t.String()),
|
|
||||||
image: t.Optional(t.String()),
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: t.Object({
|
|
||||||
user: t.Object({
|
|
||||||
id: t.String(),
|
|
||||||
name: t.Any(),
|
|
||||||
email: t.String(),
|
|
||||||
image: t.Any(),
|
|
||||||
role: t.Any(),
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
401: t.Object({ error: t.String() }),
|
response: {
|
||||||
500: t.Object({ error: t.String() }),
|
200: t.Object({
|
||||||
},
|
user: t.Object({
|
||||||
|
id: t.String(),
|
||||||
|
name: t.Any(),
|
||||||
|
email: t.String(),
|
||||||
|
image: t.Any(),
|
||||||
|
role: t.Any(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
401: t.Object({ error: t.String() }),
|
||||||
|
500: t.Object({ error: t.String() }),
|
||||||
|
},
|
||||||
|
|
||||||
detail: {
|
detail: {
|
||||||
summary: "Update user profile",
|
summary: "Update user profile",
|
||||||
description: "Update the authenticated user's name or profile image",
|
description: "Update the authenticated user's name or profile image",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
);
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import Elysia from "elysia";
|
import Elysia, { t } from "elysia";
|
||||||
import { prisma } from "../utils/db";
|
import { prisma } from "../utils/db";
|
||||||
import logger from "../utils/logger";
|
import logger from "../utils/logger";
|
||||||
|
|
||||||
@@ -22,6 +22,16 @@ export const resident = new Elysia({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
response: {
|
||||||
|
200: t.Object({
|
||||||
|
data: t.Object({
|
||||||
|
total: t.Number(),
|
||||||
|
heads: t.Number(),
|
||||||
|
poor: t.Number(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
500: t.Object({ error: t.String() }),
|
||||||
|
},
|
||||||
detail: { summary: "Get resident statistics" },
|
detail: { summary: "Get resident statistics" },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -46,6 +56,12 @@ export const resident = new Elysia({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
response: {
|
||||||
|
200: t.Object({
|
||||||
|
data: t.Array(t.Any()),
|
||||||
|
}),
|
||||||
|
500: t.Object({ error: t.String() }),
|
||||||
|
},
|
||||||
detail: { summary: "Get population data per banjar" },
|
detail: { summary: "Get population data per banjar" },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -69,7 +85,7 @@ export const resident = new Elysia({
|
|||||||
take: 10,
|
take: 10,
|
||||||
}),
|
}),
|
||||||
// Group by age ranges (simplified calculation)
|
// Group by age ranges (simplified calculation)
|
||||||
prisma.$queryRaw<any[]>`
|
prisma.$queryRaw<{ range: string; count: number }[]>`
|
||||||
SELECT
|
SELECT
|
||||||
CASE
|
CASE
|
||||||
WHEN date_part('year', age(now(), "birthDate")) BETWEEN 0 AND 16 THEN '0-16'
|
WHEN date_part('year', age(now(), "birthDate")) BETWEEN 0 AND 16 THEN '0-16'
|
||||||
@@ -94,6 +110,17 @@ export const resident = new Elysia({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
response: {
|
||||||
|
200: t.Object({
|
||||||
|
data: t.Object({
|
||||||
|
religion: t.Array(t.Any()),
|
||||||
|
gender: t.Array(t.Any()),
|
||||||
|
occupation: t.Array(t.Any()),
|
||||||
|
ageGroups: t.Array(t.Any()),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
500: t.Object({ error: t.String() }),
|
||||||
|
},
|
||||||
detail: {
|
detail: {
|
||||||
summary:
|
summary:
|
||||||
"Get demographics including religion, gender, occupation and age",
|
"Get demographics including religion, gender, occupation and age",
|
||||||
|
|||||||
@@ -53,18 +53,22 @@ export function DashboardContent() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
setStats({
|
setStats({
|
||||||
complaints: (complaintRes.data as any)?.data || {
|
complaints: (complaintRes.data as { data: typeof stats.complaints })
|
||||||
|
?.data || {
|
||||||
total: 0,
|
total: 0,
|
||||||
baru: 0,
|
baru: 0,
|
||||||
proses: 0,
|
proses: 0,
|
||||||
selesai: 0,
|
selesai: 0,
|
||||||
},
|
},
|
||||||
residents: (residentRes.data as any)?.data || {
|
residents: (residentRes.data as { data: typeof stats.residents })
|
||||||
|
?.data || {
|
||||||
total: 0,
|
total: 0,
|
||||||
heads: 0,
|
heads: 0,
|
||||||
poor: 0,
|
poor: 0,
|
||||||
},
|
},
|
||||||
weeklyService: (weeklyServiceRes.data as any)?.data?.count || 0,
|
weeklyService:
|
||||||
|
(weeklyServiceRes.data as { data: { count: number } })?.data
|
||||||
|
?.count || 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -31,10 +31,12 @@ export function ActivityList() {
|
|||||||
const res = await apiClient.GET("/api/event/");
|
const res = await apiClient.GET("/api/event/");
|
||||||
if (res.data?.data) {
|
if (res.data?.data) {
|
||||||
setData(
|
setData(
|
||||||
(res.data.data as any[]).map((e) => ({
|
(res.data.data as { startDate: string; title: string }[]).map(
|
||||||
date: dayjs(e.startDate).format("D MMMM YYYY"),
|
(e) => ({
|
||||||
title: e.title,
|
date: dayjs(e.startDate).format("D MMMM YYYY"),
|
||||||
})),
|
title: e.title,
|
||||||
|
}),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -55,7 +55,10 @@ export function ChartAPBDes() {
|
|||||||
<XAxis type="number" hide domain={[0, 100]} />
|
<XAxis type="number" hide domain={[0, 100]} />
|
||||||
<YAxis type="category" hide dataKey="name" />
|
<YAxis type="category" hide dataKey="name" />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value: number) => [`${value}%`, ""]}
|
formatter={(value: number | string | undefined) => [
|
||||||
|
`${value}%`,
|
||||||
|
"",
|
||||||
|
]}
|
||||||
contentStyle={{
|
contentStyle={{
|
||||||
backgroundColor: dark ? "#1E293B" : "white",
|
backgroundColor: dark ? "#1E293B" : "white",
|
||||||
borderColor: dark ? "#334155" : "#e5e7eb",
|
borderColor: dark ? "#334155" : "#e5e7eb",
|
||||||
|
|||||||
@@ -20,11 +20,16 @@ import {
|
|||||||
} from "recharts";
|
} from "recharts";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
|
|
||||||
|
interface ChartData {
|
||||||
|
month: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
export function ChartSurat() {
|
export function ChartSurat() {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === "dark";
|
const dark = colorScheme === "dark";
|
||||||
|
|
||||||
const [data, setData] = useState<any[]>([]);
|
const [data, setData] = useState<ChartData[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -33,7 +38,7 @@ export function ChartSurat() {
|
|||||||
const res = await apiClient.GET("/api/complaint/service-trends");
|
const res = await apiClient.GET("/api/complaint/service-trends");
|
||||||
if (res.data?.data) {
|
if (res.data?.data) {
|
||||||
setData(
|
setData(
|
||||||
(res.data.data as any[]).map((d) => ({
|
(res.data.data as { month: string; count: number }[]).map((d) => ({
|
||||||
month: d.month,
|
month: d.month,
|
||||||
value: Number(d.count),
|
value: Number(d.count),
|
||||||
})),
|
})),
|
||||||
@@ -79,7 +84,10 @@ export function ChartSurat() {
|
|||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
role="img"
|
||||||
|
aria-label="Tampilkan Detail"
|
||||||
>
|
>
|
||||||
|
<title>Tampilkan Detail</title>
|
||||||
<path
|
<path
|
||||||
d="M8 5L13 10L8 15"
|
d="M8 5L13 10L8 15"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
|
|||||||
@@ -17,6 +17,14 @@ interface DivisionData {
|
|||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DivisionApiResponse {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
_count?: {
|
||||||
|
activities: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function DivisionProgress() {
|
export function DivisionProgress() {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === "dark";
|
const dark = colorScheme === "dark";
|
||||||
@@ -30,7 +38,7 @@ export function DivisionProgress() {
|
|||||||
const res = await apiClient.GET("/api/division/");
|
const res = await apiClient.GET("/api/division/");
|
||||||
if (res.data?.data) {
|
if (res.data?.data) {
|
||||||
setData(
|
setData(
|
||||||
(res.data.data as any[]).map((d) => ({
|
(res.data.data as DivisionApiResponse[]).map((d) => ({
|
||||||
name: d.name,
|
name: d.name,
|
||||||
value: d._count?.activities || 0,
|
value: d._count?.activities || 0,
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
Badge,
|
|
||||||
Box,
|
Box,
|
||||||
Card,
|
Card,
|
||||||
Grid,
|
Grid,
|
||||||
GridCol,
|
|
||||||
Group,
|
Group,
|
||||||
Loader,
|
Loader,
|
||||||
Progress,
|
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
ThemeIcon,
|
ThemeIcon,
|
||||||
@@ -45,6 +42,49 @@ const sektorUnggulanData = [
|
|||||||
{ sektor: "Jasa", value: 52 },
|
{ sektor: "Jasa", value: 52 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
interface AgeData {
|
||||||
|
ageRange: string;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface JobData {
|
||||||
|
job: string;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReligionData {
|
||||||
|
name: string;
|
||||||
|
value: number;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BanjarData {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
totalPopulation: number;
|
||||||
|
totalKK: number;
|
||||||
|
totalPoor: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReligionResponse {
|
||||||
|
religion: string;
|
||||||
|
_count: {
|
||||||
|
_all: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OccupationResponse {
|
||||||
|
occupation: string | null;
|
||||||
|
_count: {
|
||||||
|
_all: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AgeGroupResponse {
|
||||||
|
range: string;
|
||||||
|
count: number | string;
|
||||||
|
}
|
||||||
|
|
||||||
const DemografiPekerjaan = () => {
|
const DemografiPekerjaan = () => {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === "dark";
|
const dark = colorScheme === "dark";
|
||||||
@@ -54,10 +94,10 @@ const DemografiPekerjaan = () => {
|
|||||||
heads: 0,
|
heads: 0,
|
||||||
poor: 0,
|
poor: 0,
|
||||||
});
|
});
|
||||||
const [ageData, setAgeData] = useState<any[]>([]);
|
const [ageData, setAgeData] = useState<AgeData[]>([]);
|
||||||
const [jobData, setJobData] = useState<any[]>([]);
|
const [jobData, setJobData] = useState<JobData[]>([]);
|
||||||
const [religionData, setReligionData] = useState<any[]>([]);
|
const [religionData, setReligionData] = useState<ReligionData[]>([]);
|
||||||
const [banjarData, setBanjarData] = useState<any[]>([]);
|
const [banjarData, setBanjarData] = useState<BanjarData[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -70,7 +110,8 @@ const DemografiPekerjaan = () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if (statsRes.data?.data) setStats(statsRes.data.data);
|
if (statsRes.data?.data) setStats(statsRes.data.data);
|
||||||
if (banjarRes.data?.data) setBanjarData(banjarRes.data.data);
|
if (banjarRes.data?.data)
|
||||||
|
setBanjarData(banjarRes.data.data as BanjarData[]);
|
||||||
if (demoRes.data?.data) {
|
if (demoRes.data?.data) {
|
||||||
const { religion, occupation, ageGroups } = demoRes.data.data;
|
const { religion, occupation, ageGroups } = demoRes.data.data;
|
||||||
|
|
||||||
@@ -85,7 +126,7 @@ const DemografiPekerjaan = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
setReligionData(
|
setReligionData(
|
||||||
(religion as any[]).map((r) => ({
|
(religion as ReligionResponse[]).map((r) => ({
|
||||||
name: r.religion,
|
name: r.religion,
|
||||||
value: r._count._all,
|
value: r._count._all,
|
||||||
color: religionColors[r.religion] || "#94A3B8",
|
color: religionColors[r.religion] || "#94A3B8",
|
||||||
@@ -93,14 +134,14 @@ const DemografiPekerjaan = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
setJobData(
|
setJobData(
|
||||||
(occupation as any[]).map((o) => ({
|
(occupation as OccupationResponse[]).map((o) => ({
|
||||||
job: o.occupation || "Lainnya",
|
job: o.occupation || "Lainnya",
|
||||||
total: o._count._all,
|
total: o._count._all,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
setAgeData(
|
setAgeData(
|
||||||
(ageGroups as any[]).map((a) => ({
|
(ageGroups as AgeGroupResponse[]).map((a) => ({
|
||||||
ageRange: a.range,
|
ageRange: a.range,
|
||||||
total: Number(a.count),
|
total: Number(a.count),
|
||||||
})),
|
})),
|
||||||
@@ -401,8 +442,8 @@ const DemografiPekerjaan = () => {
|
|||||||
</Title>
|
</Title>
|
||||||
</Group>
|
</Group>
|
||||||
<Grid gutter="sm">
|
<Grid gutter="sm">
|
||||||
{dynamicStats.map((stat, index) => (
|
{dynamicStats.map((stat) => (
|
||||||
<Grid.Col key={index} span={6}>
|
<Grid.Col key={stat.title} span={6}>
|
||||||
<Card
|
<Card
|
||||||
p="sm"
|
p="sm"
|
||||||
radius="lg"
|
radius="lg"
|
||||||
@@ -480,8 +521,8 @@ const DemografiPekerjaan = () => {
|
|||||||
paddingAngle={2}
|
paddingAngle={2}
|
||||||
dataKey="value"
|
dataKey="value"
|
||||||
>
|
>
|
||||||
{religionData.map((entry, index) => (
|
{religionData.map((entry) => (
|
||||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
<Cell key={`cell-${entry.name}`} fill={entry.color} />
|
||||||
))}
|
))}
|
||||||
</Pie>
|
</Pie>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@@ -496,8 +537,8 @@ const DemografiPekerjaan = () => {
|
|||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
<Stack gap="xs" mt="md">
|
<Stack gap="xs" mt="md">
|
||||||
{!loading &&
|
{!loading &&
|
||||||
religionData.map((item, index) => (
|
religionData.map((item) => (
|
||||||
<Group key={index} justify="space-between">
|
<Group key={item.name} justify="space-between">
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<Box
|
<Box
|
||||||
w={10}
|
w={10}
|
||||||
@@ -601,12 +642,12 @@ const DemografiPekerjaan = () => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{banjarData.map((item, index) => (
|
{banjarData.map((item) => (
|
||||||
<tr
|
<tr
|
||||||
key={item.id || index}
|
key={item.id}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
index % 2 === 0
|
banjarData.indexOf(item) % 2 === 0
|
||||||
? dark
|
? dark
|
||||||
? "#334155"
|
? "#334155"
|
||||||
: "#F8FAFC"
|
: "#F8FAFC"
|
||||||
@@ -725,8 +766,8 @@ const DemografiPekerjaan = () => {
|
|||||||
radius={[0, 8, 8, 0]}
|
radius={[0, 8, 8, 0]}
|
||||||
maxBarSize={40}
|
maxBarSize={40}
|
||||||
>
|
>
|
||||||
{sektorUnggulanData.map((entry, index) => (
|
{sektorUnggulanData.map((entry) => (
|
||||||
<Cell key={`cell-${index}`} fill="#396aaaff" />
|
<Cell key={`cell-${entry.sektor}`} fill="#396aaaff" />
|
||||||
))}
|
))}
|
||||||
</Bar>
|
</Bar>
|
||||||
</BarChart>
|
</BarChart>
|
||||||
|
|||||||
@@ -129,7 +129,6 @@ export function DevInspector({ children }: { children: React.ReactNode }) {
|
|||||||
tt.style.left = `${rect.left + window.scrollX}px`;
|
tt.style.left = `${rect.left + window.scrollX}px`;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// biome-ignore lint/correctness/useExhaustiveDependencies: updateOverlay is stable
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!active) return;
|
if (!active) return;
|
||||||
|
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ export function ImageWithFallback(
|
|||||||
<div className="flex items-center justify-center w-full h-full">
|
<div className="flex items-center justify-center w-full h-full">
|
||||||
<img
|
<img
|
||||||
src={ERROR_IMG_SRC}
|
src={ERROR_IMG_SRC}
|
||||||
alt="Error loading image"
|
alt="Error loading content"
|
||||||
{...rest}
|
{...rest}
|
||||||
data-original-url={src}
|
data-original-url={src}
|
||||||
/>
|
/>{" "}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Card,
|
Card,
|
||||||
Grid,
|
Grid,
|
||||||
GridCol,
|
|
||||||
Group,
|
Group,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
@@ -354,8 +353,8 @@ const KeuanganAnggaran = () => {
|
|||||||
Pendapatan
|
Pendapatan
|
||||||
</Title>
|
</Title>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
{apbdReport.income.map((item, index) => (
|
{apbdReport.income.map((item) => (
|
||||||
<Group key={index} justify="space-between">
|
<Group key={item.category} justify="space-between">
|
||||||
<Text size="sm" c={dark ? "gray.3" : "gray.7"}>
|
<Text size="sm" c={dark ? "gray.3" : "gray.7"}>
|
||||||
{item.category}
|
{item.category}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -390,8 +389,8 @@ const KeuanganAnggaran = () => {
|
|||||||
Belanja
|
Belanja
|
||||||
</Title>
|
</Title>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
{apbdReport.expenses.map((item, index) => (
|
{apbdReport.expenses.map((item) => (
|
||||||
<Group key={index} justify="space-between">
|
<Group key={item.category} justify="space-between">
|
||||||
<Text size="sm" c={dark ? "gray.3" : "gray.7"}>
|
<Text size="sm" c={dark ? "gray.3" : "gray.7"}>
|
||||||
{item.category}
|
{item.category}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -473,9 +472,9 @@ const KeuanganAnggaran = () => {
|
|||||||
</Title>
|
</Title>
|
||||||
</Group>
|
</Group>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
{assistanceFundData.map((fund, index) => (
|
{assistanceFundData.map((fund) => (
|
||||||
<Card
|
<Card
|
||||||
key={index}
|
key={fund.source}
|
||||||
p="sm"
|
p="sm"
|
||||||
radius="lg"
|
radius="lg"
|
||||||
bg={dark ? "#334155" : "#F1F5F9"}
|
bg={dark ? "#334155" : "#F1F5F9"}
|
||||||
|
|||||||
@@ -18,9 +18,23 @@ const archiveData = [
|
|||||||
{ name: "Notulensi Rapat" },
|
{ name: "Notulensi Rapat" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
interface Activity {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
createdAt: string;
|
||||||
|
progress: number;
|
||||||
|
status: "SELESAI" | "BERJALAN" | "TERTUNDA";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EventData {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
startDate: string;
|
||||||
|
}
|
||||||
|
|
||||||
const KinerjaDivisi = () => {
|
const KinerjaDivisi = () => {
|
||||||
const [activities, setActivities] = useState<any[]>([]);
|
const [activities, setActivities] = useState<Activity[]>([]);
|
||||||
const [todayEvents, setTodayEvents] = useState<any[]>([]);
|
const [todayEvents, setTodayEvents] = useState<EventData[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -32,10 +46,10 @@ const KinerjaDivisi = () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if (activityRes.data?.data) {
|
if (activityRes.data?.data) {
|
||||||
setActivities(activityRes.data.data);
|
setActivities(activityRes.data.data as Activity[]);
|
||||||
}
|
}
|
||||||
if (eventRes.data?.data) {
|
if (eventRes.data?.data) {
|
||||||
setTodayEvents(eventRes.data.data);
|
setTodayEvents(eventRes.data.data as EventData[]);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch kinerja divisi data", error);
|
console.error("Failed to fetch kinerja divisi data", error);
|
||||||
@@ -57,11 +71,8 @@ const KinerjaDivisi = () => {
|
|||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
{/* SECTION 1 — PROGRAM KEGIATAN */}
|
{/* SECTION 1 — PROGRAM KEGIATAN */}
|
||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
{activities.slice(0, 4).map((kegiatan, index) => (
|
{activities.slice(0, 4).map((kegiatan) => (
|
||||||
<Grid.Col
|
<Grid.Col key={kegiatan.id} span={{ base: 12, md: 6, lg: 3 }}>
|
||||||
key={kegiatan.id || index}
|
|
||||||
span={{ base: 12, md: 6, lg: 3 }}
|
|
||||||
>
|
|
||||||
<ActivityCard
|
<ActivityCard
|
||||||
title={kegiatan.title}
|
title={kegiatan.title}
|
||||||
date={dayjs(kegiatan.createdAt).format("D MMMM YYYY")}
|
date={dayjs(kegiatan.createdAt).format("D MMMM YYYY")}
|
||||||
@@ -111,8 +122,8 @@ const KinerjaDivisi = () => {
|
|||||||
|
|
||||||
{/* SECTION 5 — ARSIP DIGITAL PERANGKAT DESA */}
|
{/* SECTION 5 — ARSIP DIGITAL PERANGKAT DESA */}
|
||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
{archiveData.map((item, index) => (
|
{archiveData.map((item) => (
|
||||||
<Grid.Col key={index} span={{ base: 12, md: 6 }}>
|
<Grid.Col key={item.name} span={{ base: 12, md: 6 }}>
|
||||||
<ArchiveCard item={item} />
|
<ArchiveCard item={item} />
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,11 +1,4 @@
|
|||||||
import {
|
import { Card, Group, Stack, Text, useMantineColorScheme } from "@mantine/core";
|
||||||
Box,
|
|
||||||
Card,
|
|
||||||
Group,
|
|
||||||
Stack,
|
|
||||||
Text,
|
|
||||||
useMantineColorScheme,
|
|
||||||
} from "@mantine/core";
|
|
||||||
import { MessageCircle } from "lucide-react";
|
import { MessageCircle } from "lucide-react";
|
||||||
|
|
||||||
interface DiscussionItem {
|
interface DiscussionItem {
|
||||||
@@ -57,9 +50,9 @@ export function DiscussionPanel() {
|
|||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
{discussions.map((discussion, index) => (
|
{discussions.map((discussion) => (
|
||||||
<Card
|
<Card
|
||||||
key={index}
|
key={`${discussion.sender}-${discussion.date}`}
|
||||||
p="sm"
|
p="sm"
|
||||||
radius="md"
|
radius="md"
|
||||||
withBorder
|
withBorder
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ export function DivisionList() {
|
|||||||
try {
|
try {
|
||||||
const { data } = await apiClient.GET("/api/division/");
|
const { data } = await apiClient.GET("/api/division/");
|
||||||
if (data?.data) {
|
if (data?.data) {
|
||||||
const mapped = data.data.map(
|
const mapped = (
|
||||||
(div: { name: string; _count?: { activities: number } }) => ({
|
data.data as { name: string; _count?: { activities: number } }[]
|
||||||
name: div.name,
|
).map((div) => ({
|
||||||
count: div._count?.activities || 0,
|
name: div.name,
|
||||||
}),
|
count: div._count?.activities || 0,
|
||||||
);
|
}));
|
||||||
setDivisions(mapped);
|
setDivisions(mapped);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -68,9 +68,9 @@ export function DivisionList() {
|
|||||||
<Loader size="sm" />
|
<Loader size="sm" />
|
||||||
</Group>
|
</Group>
|
||||||
) : divisions.length > 0 ? (
|
) : divisions.length > 0 ? (
|
||||||
divisions.map((division, index) => (
|
divisions.map((division) => (
|
||||||
<Group
|
<Group
|
||||||
key={index}
|
key={division.name}
|
||||||
justify="space-between"
|
justify="space-between"
|
||||||
align="center"
|
align="center"
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ export function DocumentChart() {
|
|||||||
labelStyle={{ color: dark ? "#E2E8F0" : "#374151" }}
|
labelStyle={{ color: dark ? "#E2E8F0" : "#374151" }}
|
||||||
/>
|
/>
|
||||||
<Bar dataKey="jumlah" radius={[4, 4, 0, 0]}>
|
<Bar dataKey="jumlah" radius={[4, 4, 0, 0]}>
|
||||||
{documentData.map((entry, index) => (
|
{documentData.map((entry) => (
|
||||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
<Cell key={`cell-${entry.name}`} fill={entry.color} />
|
||||||
))}
|
))}
|
||||||
</Bar>
|
</Bar>
|
||||||
</BarChart>
|
</BarChart>
|
||||||
|
|||||||
@@ -43,8 +43,12 @@ export function EventCard({ agendas = [] }: EventCardProps) {
|
|||||||
</Group>
|
</Group>
|
||||||
{agendas.length > 0 ? (
|
{agendas.length > 0 ? (
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
{agendas.map((agenda, index) => (
|
{agendas.map((agenda) => (
|
||||||
<Group key={index} align="flex-start" gap="md">
|
<Group
|
||||||
|
key={`${agenda.time}-${agenda.event}`}
|
||||||
|
align="flex-start"
|
||||||
|
gap="md"
|
||||||
|
>
|
||||||
<Box w={60}>
|
<Box w={60}>
|
||||||
<Text size="sm" fw={600} c={dark ? "white" : "#1E3A5F"}>
|
<Text size="sm" fw={600} c={dark ? "white" : "#1E3A5F"}>
|
||||||
{agenda.time}
|
{agenda.time}
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ export function ProgressChart() {
|
|||||||
paddingAngle={2}
|
paddingAngle={2}
|
||||||
dataKey="value"
|
dataKey="value"
|
||||||
>
|
>
|
||||||
{progressData.map((entry, index) => (
|
{progressData.map((entry) => (
|
||||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
<Cell key={`cell-${entry.name}`} fill={entry.color} />
|
||||||
))}
|
))}
|
||||||
</Pie>
|
</Pie>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@@ -61,8 +61,8 @@ export function ProgressChart() {
|
|||||||
</PieChart>
|
</PieChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
<Stack gap="xs" mt="md">
|
<Stack gap="xs" mt="md">
|
||||||
{progressData.map((item, index) => (
|
{progressData.map((item) => (
|
||||||
<Group key={index} justify="space-between">
|
<Group key={item.name} justify="space-between">
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<Box
|
<Box
|
||||||
w={12}
|
w={12}
|
||||||
|
|||||||
@@ -57,6 +57,26 @@ const ideInovatif = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
interface Complaint {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
category: string;
|
||||||
|
status: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ServiceStat {
|
||||||
|
jenis: string;
|
||||||
|
jumlah: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ServiceApiResponse {
|
||||||
|
letterType: string;
|
||||||
|
_count: {
|
||||||
|
_all: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const getStatusColor = (status: string) => {
|
const getStatusColor = (status: string) => {
|
||||||
switch (status.toLowerCase()) {
|
switch (status.toLowerCase()) {
|
||||||
case "baru":
|
case "baru":
|
||||||
@@ -81,8 +101,8 @@ const PengaduanLayananPublik = () => {
|
|||||||
proses: 0,
|
proses: 0,
|
||||||
selesai: 0,
|
selesai: 0,
|
||||||
});
|
});
|
||||||
const [recentComplaints, setRecentComplaints] = useState<any[]>([]);
|
const [recentComplaints, setRecentComplaints] = useState<Complaint[]>([]);
|
||||||
const [serviceStats, setServiceStats] = useState<any[]>([]);
|
const [serviceStats, setServiceStats] = useState<ServiceStat[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -95,9 +115,12 @@ const PengaduanLayananPublik = () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if (statsRes.data?.data) setStats(statsRes.data.data);
|
if (statsRes.data?.data) setStats(statsRes.data.data);
|
||||||
if (recentRes.data?.data) setRecentComplaints(recentRes.data.data);
|
if (recentRes.data?.data)
|
||||||
|
setRecentComplaints(recentRes.data.data as Complaint[]);
|
||||||
if (serviceRes.data?.data) {
|
if (serviceRes.data?.data) {
|
||||||
const mappedService = serviceRes.data.data.map((item: any) => ({
|
const mappedService = (
|
||||||
|
serviceRes.data.data as ServiceApiResponse[]
|
||||||
|
).map((item) => ({
|
||||||
jenis: item.letterType,
|
jenis: item.letterType,
|
||||||
jumlah: item._count?._all || 0,
|
jumlah: item._count?._all || 0,
|
||||||
}));
|
}));
|
||||||
@@ -148,8 +171,8 @@ const PengaduanLayananPublik = () => {
|
|||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
{/* TOP SECTION - 4 STAT CARDS */}
|
{/* TOP SECTION - 4 STAT CARDS */}
|
||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
{summaryData.map((item, index) => (
|
{summaryData.map((item) => (
|
||||||
<Grid.Col key={index} span={{ base: 12, sm: 6, lg: 3 }}>
|
<Grid.Col key={item.title} span={{ base: 12, sm: 6, lg: 3 }}>
|
||||||
<Card
|
<Card
|
||||||
p="md"
|
p="md"
|
||||||
radius="xl"
|
radius="xl"
|
||||||
@@ -330,9 +353,9 @@ const PengaduanLayananPublik = () => {
|
|||||||
<Loader />
|
<Loader />
|
||||||
</Group>
|
</Group>
|
||||||
) : recentComplaints.length > 0 ? (
|
) : recentComplaints.length > 0 ? (
|
||||||
recentComplaints.map((item, index) => (
|
recentComplaints.map((item) => (
|
||||||
<Card
|
<Card
|
||||||
key={item.id || index}
|
key={item.id}
|
||||||
p="sm"
|
p="sm"
|
||||||
radius="md"
|
radius="md"
|
||||||
withBorder
|
withBorder
|
||||||
@@ -392,9 +415,9 @@ const PengaduanLayananPublik = () => {
|
|||||||
Ajuan Ide Inovatif
|
Ajuan Ide Inovatif
|
||||||
</Title>
|
</Title>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
{ideInovatif.map((item, index) => (
|
{ideInovatif.map((item) => (
|
||||||
<Card
|
<Card
|
||||||
key={index}
|
key={item.judul}
|
||||||
p="sm"
|
p="sm"
|
||||||
radius="md"
|
radius="md"
|
||||||
withBorder
|
withBorder
|
||||||
|
|||||||
@@ -1,24 +1,18 @@
|
|||||||
import {
|
import {
|
||||||
Alert,
|
|
||||||
Box,
|
|
||||||
Button,
|
Button,
|
||||||
Card,
|
|
||||||
Checkbox,
|
|
||||||
Grid,
|
Grid,
|
||||||
GridCol,
|
GridCol,
|
||||||
Group,
|
Group,
|
||||||
Space,
|
|
||||||
Stack,
|
Stack,
|
||||||
Switch,
|
Switch,
|
||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
useMantineColorScheme,
|
useMantineColorScheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { IconInfoCircle } from "@tabler/icons-react";
|
|
||||||
|
|
||||||
const NotifikasiSettings = () => {
|
const NotifikasiSettings = () => {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === "dark";
|
const _dark = colorScheme === "dark";
|
||||||
return (
|
return (
|
||||||
<Stack pr={"20%"} gap={"xs"}>
|
<Stack pr={"20%"} gap={"xs"}>
|
||||||
<Grid gutter={{ base: 5, xs: "md", md: "xl", xl: 50 }}>
|
<Grid gutter={{ base: 5, xs: "md", md: "xl", xl: 50 }}>
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
Badge,
|
|
||||||
Box,
|
Box,
|
||||||
Collapse,
|
Collapse,
|
||||||
Group,
|
|
||||||
Image,
|
Image,
|
||||||
Input,
|
Input,
|
||||||
NavLink as MantineNavLink,
|
NavLink as MantineNavLink,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
|
||||||
useMantineColorScheme,
|
useMantineColorScheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useLocation, useNavigate } from "@tanstack/react-router";
|
import { useLocation, useNavigate } from "@tanstack/react-router";
|
||||||
@@ -80,11 +77,11 @@ export function Sidebar({ className }: SidebarProps) {
|
|||||||
|
|
||||||
{/* Menu Items */}
|
{/* Menu Items */}
|
||||||
<Stack gap={0} px="xs" style={{ overflowY: "auto" }}>
|
<Stack gap={0} px="xs" style={{ overflowY: "auto" }}>
|
||||||
{menuItems.map((item, index) => {
|
{menuItems.map((item) => {
|
||||||
const isActive = location.pathname === item.path;
|
const isActive = location.pathname === item.path;
|
||||||
return (
|
return (
|
||||||
<MantineNavLink
|
<MantineNavLink
|
||||||
key={index}
|
key={item.path}
|
||||||
onClick={() => navigate({ to: item.path })}
|
onClick={() => navigate({ to: item.path })}
|
||||||
label={item.name}
|
label={item.name}
|
||||||
active={isActive}
|
active={isActive}
|
||||||
@@ -146,11 +143,11 @@ export function Sidebar({ className }: SidebarProps) {
|
|||||||
ml="lg"
|
ml="lg"
|
||||||
style={{ overflowY: "auto", maxHeight: "200px" }}
|
style={{ overflowY: "auto", maxHeight: "200px" }}
|
||||||
>
|
>
|
||||||
{settingsItems.map((item, index) => {
|
{settingsItems.map((item) => {
|
||||||
const isActive = location.pathname === item.path;
|
const isActive = location.pathname === item.path;
|
||||||
return (
|
return (
|
||||||
<MantineNavLink
|
<MantineNavLink
|
||||||
key={index}
|
key={item.path}
|
||||||
onClick={() => navigate({ to: item.path })}
|
onClick={() => navigate({ to: item.path })}
|
||||||
label={item.name}
|
label={item.name}
|
||||||
active={isActive}
|
active={isActive}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
ThemeIcon,
|
ThemeIcon,
|
||||||
Title,
|
|
||||||
useMantineColorScheme,
|
useMantineColorScheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { IconAward } from "@tabler/icons-react";
|
import { IconAward } from "@tabler/icons-react";
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ export const HealthStats = ({ data }: HealthStatsProps) => {
|
|||||||
Statistik Kesehatan
|
Statistik Kesehatan
|
||||||
</Title>
|
</Title>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
{displayData.map((item, index) => (
|
{displayData.map((item) => (
|
||||||
<div key={index}>
|
<div key={item.label}>
|
||||||
<Group justify="space-between" mb={5}>
|
<Group justify="space-between" mb={5}>
|
||||||
<Text size="sm" fw={500} c={dark ? "dark.0" : "#1e3a5f"}>
|
<Text size="sm" fw={500} c={dark ? "dark.0" : "#1e3a5f"}>
|
||||||
{item.label}
|
{item.label}
|
||||||
|
|||||||
@@ -53,8 +53,6 @@ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
|||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
data-slot="breadcrumb-page"
|
data-slot="breadcrumb-page"
|
||||||
role="link"
|
|
||||||
aria-disabled="true"
|
|
||||||
aria-current="page"
|
aria-current="page"
|
||||||
className={cn("text-foreground font-normal", className)}
|
className={cn("text-foreground font-normal", className)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
mantineVariant = "transparent";
|
mantineVariant = "transparent";
|
||||||
mantineColor = "blue"; // Assuming primary maps to blue in Mantine for now
|
mantineColor = "blue"; // Assuming primary maps to blue in Mantine for now
|
||||||
break;
|
break;
|
||||||
case "default":
|
|
||||||
default:
|
default:
|
||||||
mantineVariant = "filled";
|
mantineVariant = "filled";
|
||||||
mantineColor = "blue"; // Assuming primary maps to blue in Mantine for now
|
mantineColor = "blue"; // Assuming primary maps to blue in Mantine for now
|
||||||
|
|||||||
@@ -117,16 +117,16 @@ function Carousel({
|
|||||||
canScrollNext,
|
canScrollNext,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
{/* biome-ignore lint/a11y/useAriaPropsSupportedByRole: section with aria-roledescription is standard for carousels. */}
|
||||||
|
<section
|
||||||
onKeyDownCapture={handleKeyDown}
|
onKeyDownCapture={handleKeyDown}
|
||||||
className={cn("relative", className)}
|
className={cn("relative", className)}
|
||||||
role="region"
|
|
||||||
aria-roledescription="carousel"
|
aria-roledescription="carousel"
|
||||||
data-slot="carousel"
|
data-slot="carousel"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</section>{" "}
|
||||||
</CarouselContext.Provider>
|
</CarouselContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -156,6 +156,7 @@ function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
const { orientation } = useCarousel();
|
const { orientation } = useCarousel();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// biome-ignore lint/a11y/useSemanticElements: role='group' and aria-roledescription='slide' is standard for carousel slides.
|
||||||
<div
|
<div
|
||||||
role="group"
|
role="group"
|
||||||
aria-roledescription="slide"
|
aria-roledescription="slide"
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<style
|
<style
|
||||||
|
// biome-ignore lint/security/noDangerouslySetInnerHtml: This is a safe use of dangerouslySetInnerHTML for generating dynamic CSS variables for charts.
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: Object.entries(THEMES)
|
__html: Object.entries(THEMES)
|
||||||
.map(
|
.map(
|
||||||
@@ -125,6 +126,11 @@ function ChartTooltipContent({
|
|||||||
indicator?: "line" | "dot" | "dashed";
|
indicator?: "line" | "dot" | "dashed";
|
||||||
nameKey?: string;
|
nameKey?: string;
|
||||||
labelKey?: string;
|
labelKey?: string;
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: Recharts payload is complex and better handled as any[] for this wrapper.
|
||||||
|
payload?: any[];
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: Recharts label can be any type.
|
||||||
|
label?: any;
|
||||||
|
active?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { config } = useChart();
|
const { config } = useChart();
|
||||||
|
|
||||||
@@ -257,9 +263,11 @@ function ChartLegendContent({
|
|||||||
verticalAlign = "bottom",
|
verticalAlign = "bottom",
|
||||||
nameKey,
|
nameKey,
|
||||||
}: React.ComponentProps<"div"> &
|
}: React.ComponentProps<"div"> &
|
||||||
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
|
Pick<RechartsPrimitive.LegendProps, "verticalAlign"> & {
|
||||||
hideIcon?: boolean;
|
hideIcon?: boolean;
|
||||||
nameKey?: string;
|
nameKey?: string;
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: Recharts legend payload.
|
||||||
|
payload?: any[];
|
||||||
}) {
|
}) {
|
||||||
const { config } = useChart();
|
const { config } = useChart();
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ function InputOTPSlot({
|
|||||||
|
|
||||||
function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
|
function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div data-slot="input-otp-separator" role="separator" {...props}>
|
<div data-slot="input-otp-separator" {...props}>
|
||||||
<MinusIcon />
|
<MinusIcon />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
MoreHorizontalIcon,
|
MoreHorizontalIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import type * as React from "react";
|
import type * as React from "react";
|
||||||
import { Button } from "./button";
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
const baseClasses =
|
const baseClasses =
|
||||||
@@ -13,7 +12,6 @@ const baseClasses =
|
|||||||
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
|
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
|
||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
role="navigation"
|
|
||||||
aria-label="pagination"
|
aria-label="pagination"
|
||||||
data-slot="pagination"
|
data-slot="pagination"
|
||||||
className={cn("mx-auto flex w-full justify-center", className)}
|
className={cn("mx-auto flex w-full justify-center", className)}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
Progress as MantineProgress,
|
Progress as MantineProgress,
|
||||||
type ProgressProps as MantineProgressProps,
|
type ProgressProps as MantineProgressProps,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import React from "react";
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
// Original ProgressProps likely had 'value' and 'max'.
|
// Original ProgressProps likely had 'value' and 'max'.
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ function SidebarProvider({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This sets the cookie to keep the sidebar state.
|
// This sets the cookie to keep the sidebar state.
|
||||||
|
// biome-ignore lint/suspicious/noDocumentCookie: This is a safe use of document.cookie for persisting sidebar state across page loads.
|
||||||
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
|
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
|
||||||
},
|
},
|
||||||
[setOpenProp, open],
|
[setOpenProp, open],
|
||||||
@@ -85,7 +86,7 @@ function SidebarProvider({
|
|||||||
// Helper to toggle the sidebar.
|
// Helper to toggle the sidebar.
|
||||||
const toggleSidebar = React.useCallback(() => {
|
const toggleSidebar = React.useCallback(() => {
|
||||||
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
|
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
|
||||||
}, [isMobile, setOpen, setOpenMobile]);
|
}, [isMobile, setOpen]);
|
||||||
|
|
||||||
// Adds a keyboard shortcut to toggle the sidebar.
|
// Adds a keyboard shortcut to toggle the sidebar.
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -117,7 +118,7 @@ function SidebarProvider({
|
|||||||
setOpenMobile,
|
setOpenMobile,
|
||||||
toggleSidebar,
|
toggleSidebar,
|
||||||
}),
|
}),
|
||||||
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
|
[state, open, setOpen, isMobile, openMobile, toggleSidebar],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ function Slider({
|
|||||||
{Array.from({ length: _values.length }, (_, index) => (
|
{Array.from({ length: _values.length }, (_, index) => (
|
||||||
<SliderPrimitive.Thumb
|
<SliderPrimitive.Thumb
|
||||||
data-slot="slider-thumb"
|
data-slot="slider-thumb"
|
||||||
|
// biome-ignore lint/suspicious/noArrayIndexKey: slider thumbs are stable and index is an appropriate key here.
|
||||||
key={index}
|
key={index}
|
||||||
className="border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
|
className="border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useTheme } from "next-themes";
|
import { useMantineColorScheme } from "@mantine/core";
|
||||||
import { Toaster as Sonner, type ToasterProps } from "sonner";
|
import { Toaster as Sonner, type ToasterProps } from "sonner";
|
||||||
|
|
||||||
const Toaster = ({ ...props }: ToasterProps) => {
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
const { theme = "system" } = useTheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sonner
|
<Sonner
|
||||||
theme={theme as ToasterProps["theme"]}
|
theme={
|
||||||
|
colorScheme === "auto"
|
||||||
|
? "system"
|
||||||
|
: (colorScheme as ToasterProps["theme"])
|
||||||
|
}
|
||||||
className="toaster group"
|
className="toaster group"
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Tooltip as MantineTooltip, type TooltipProps } from "@mantine/core";
|
import { Tooltip as MantineTooltip, type TooltipProps } from "@mantine/core";
|
||||||
import React from "react";
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
interface CustomTooltipProps extends TooltipProps {
|
interface CustomTooltipProps extends TooltipProps {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
useMantineColorScheme,
|
useMantineColorScheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import {
|
import {
|
||||||
IconCategory,
|
|
||||||
IconCurrencyDollar,
|
IconCurrencyDollar,
|
||||||
IconTrendingUp,
|
IconTrendingUp,
|
||||||
IconUsers,
|
IconUsers,
|
||||||
@@ -149,8 +148,8 @@ export const SummaryCards = ({ data }: SummaryCardsProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
{kpiData.map((kpi, index) => (
|
{kpiData.map((kpi) => (
|
||||||
<GridCol key={index} span={{ base: 12, sm: 6, lg: 3 }}>
|
<GridCol key={kpi.title} span={{ base: 12, sm: 6, lg: 3 }}>
|
||||||
<KpiCard {...kpi} />
|
<KpiCard {...kpi} />
|
||||||
</GridCol>
|
</GridCol>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ function PengaturanLayout() {
|
|||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
|
|
||||||
const isMobile = useMediaQuery("(max-width: 48em)");
|
const isMobile = useMediaQuery("(max-width: 48em)");
|
||||||
const routerState = useRouterState();
|
const _routerState = useRouterState();
|
||||||
|
|
||||||
const headerBgColor = colorScheme === "dark" ? "#11192D" : "#19355E";
|
const headerBgColor = colorScheme === "dark" ? "#11192D" : "#19355E";
|
||||||
const navbarBgColor = colorScheme === "dark" ? "#11192D" : "white";
|
const navbarBgColor = colorScheme === "dark" ? "#11192D" : "white";
|
||||||
@@ -36,7 +36,7 @@ function PengaturanLayout() {
|
|||||||
if (isMobile && opened) {
|
if (isMobile && opened) {
|
||||||
toggleMobile();
|
toggleMobile();
|
||||||
}
|
}
|
||||||
}, [routerState.location.pathname, isMobile, opened, toggleMobile]);
|
}, [isMobile, opened, toggleMobile]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ function EditProfile() {
|
|||||||
authStore.user = {
|
authStore.user = {
|
||||||
...authStore.user,
|
...authStore.user,
|
||||||
...data.user,
|
...data.user,
|
||||||
} as any;
|
} as NonNullable<typeof authStore.user>;
|
||||||
navigate({ to: "/profile" });
|
navigate({ to: "/profile" });
|
||||||
} else if (error) {
|
} else if (error) {
|
||||||
console.error("Update error:", error);
|
console.error("Update error:", error);
|
||||||
|
|||||||
@@ -77,13 +77,21 @@ function Profile() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface InfoFieldProps {
|
||||||
|
icon: React.ElementType;
|
||||||
|
label: string;
|
||||||
|
value: string | null | undefined;
|
||||||
|
copyable?: boolean;
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
const InfoField = ({
|
const InfoField = ({
|
||||||
icon: Icon,
|
icon: Icon,
|
||||||
label,
|
label,
|
||||||
value,
|
value,
|
||||||
copyable = false,
|
copyable = false,
|
||||||
id = "",
|
id = "",
|
||||||
}: any) => (
|
}: InfoFieldProps) => (
|
||||||
<Paper
|
<Paper
|
||||||
withBorder
|
withBorder
|
||||||
p="md"
|
p="md"
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ export function inspectorPlugin(): Plugin {
|
|||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
let line = lines[i];
|
let line = lines[i];
|
||||||
|
if (line === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// Match JSX opening tags: <Component, <div, or <item.icon
|
// Match JSX opening tags: <Component, <div, or <item.icon
|
||||||
// Allow dots and hyphens in the tag name
|
// Allow dots and hyphens in the tag name
|
||||||
const jsxPattern = /(<(?:[A-Za-z][a-zA-Z0-9.-]*))\b/g;
|
const jsxPattern = /(<(?:[A-Za-z][a-zA-Z0-9.-]*))\b/g;
|
||||||
@@ -32,7 +35,8 @@ export function inspectorPlugin(): Plugin {
|
|||||||
// biome-ignore lint/suspicious/noAssignInExpressions: match loop
|
// biome-ignore lint/suspicious/noAssignInExpressions: match loop
|
||||||
while ((match = jsxPattern.exec(line)) !== null) {
|
while ((match = jsxPattern.exec(line)) !== null) {
|
||||||
// Skip if character before `<` is an identifier char (likely a TypeScript generic)
|
// Skip if character before `<` is an identifier char (likely a TypeScript generic)
|
||||||
const charBefore = match.index > 0 ? line[match.index - 1] : "";
|
const charBefore =
|
||||||
|
match.index > 0 ? (line[match.index - 1] ?? "") : "";
|
||||||
if (/[a-zA-Z0-9_$.]/.test(charBefore)) continue;
|
if (/[a-zA-Z0-9_$.]/.test(charBefore)) continue;
|
||||||
|
|
||||||
const col = match.index + 1;
|
const col = match.index + 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user