feat(noc): implement sync management UI and backend integration
This commit is contained in:
@@ -1,7 +1,5 @@
|
|||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": ["Bash(bun *)"]
|
||||||
"Bash(bun *)"
|
}
|
||||||
]
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ describe("NOC API Module", () => {
|
|||||||
|
|
||||||
it("should return diagram jumlah document", async () => {
|
it("should return diagram jumlah document", async () => {
|
||||||
const response = await api.handle(
|
const response = await api.handle(
|
||||||
new Request(`http://localhost/api/noc/diagram-jumlah-document?idDesa=${idDesa}`),
|
new Request(
|
||||||
|
`http://localhost/api/noc/diagram-jumlah-document?idDesa=${idDesa}`,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
@@ -42,7 +44,9 @@ describe("NOC API Module", () => {
|
|||||||
|
|
||||||
it("should return diagram progres kegiatan", async () => {
|
it("should return diagram progres kegiatan", async () => {
|
||||||
const response = await api.handle(
|
const response = await api.handle(
|
||||||
new Request(`http://localhost/api/noc/diagram-progres-kegiatan?idDesa=${idDesa}`),
|
new Request(
|
||||||
|
`http://localhost/api/noc/diagram-progres-kegiatan?idDesa=${idDesa}`,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
@@ -51,7 +55,9 @@ describe("NOC API Module", () => {
|
|||||||
|
|
||||||
it("should return latest discussion", async () => {
|
it("should return latest discussion", async () => {
|
||||||
const response = await api.handle(
|
const response = await api.handle(
|
||||||
new Request(`http://localhost/api/noc/latest-discussion?idDesa=${idDesa}`),
|
new Request(
|
||||||
|
`http://localhost/api/noc/latest-discussion?idDesa=${idDesa}`,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|||||||
855
generated/api.ts
855
generated/api.ts
@@ -36,6 +36,134 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/api/noc/sync": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get?: never;
|
||||||
|
put?: never;
|
||||||
|
post: operations["postApiNocSync"];
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/noc/last-sync": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: operations["getApiNocLast-sync"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/noc/active-divisions": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: operations["getApiNocActive-divisions"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/noc/latest-projects": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: operations["getApiNocLatest-projects"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/noc/upcoming-events": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: operations["getApiNocUpcoming-events"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/noc/diagram-jumlah-document": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: operations["getApiNocDiagram-jumlah-document"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/noc/diagram-progres-kegiatan": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: operations["getApiNocDiagram-progres-kegiatan"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/noc/latest-discussion": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: operations["getApiNocLatest-discussion"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
"/api/apikey/": {
|
"/api/apikey/": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -457,102 +585,6 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
"/api/noc/active-divisions": {
|
|
||||||
parameters: {
|
|
||||||
query?: never;
|
|
||||||
header?: never;
|
|
||||||
path?: never;
|
|
||||||
cookie?: never;
|
|
||||||
};
|
|
||||||
get: operations["getApiNocActive-divisions"];
|
|
||||||
put?: never;
|
|
||||||
post?: never;
|
|
||||||
delete?: never;
|
|
||||||
options?: never;
|
|
||||||
head?: never;
|
|
||||||
patch?: never;
|
|
||||||
trace?: never;
|
|
||||||
};
|
|
||||||
"/api/noc/latest-projects": {
|
|
||||||
parameters: {
|
|
||||||
query?: never;
|
|
||||||
header?: never;
|
|
||||||
path?: never;
|
|
||||||
cookie?: never;
|
|
||||||
};
|
|
||||||
get: operations["getApiNocLatest-projects"];
|
|
||||||
put?: never;
|
|
||||||
post?: never;
|
|
||||||
delete?: never;
|
|
||||||
options?: never;
|
|
||||||
head?: never;
|
|
||||||
patch?: never;
|
|
||||||
trace?: never;
|
|
||||||
};
|
|
||||||
"/api/noc/upcoming-events": {
|
|
||||||
parameters: {
|
|
||||||
query?: never;
|
|
||||||
header?: never;
|
|
||||||
path?: never;
|
|
||||||
cookie?: never;
|
|
||||||
};
|
|
||||||
get: operations["getApiNocUpcoming-events"];
|
|
||||||
put?: never;
|
|
||||||
post?: never;
|
|
||||||
delete?: never;
|
|
||||||
options?: never;
|
|
||||||
head?: never;
|
|
||||||
patch?: never;
|
|
||||||
trace?: never;
|
|
||||||
};
|
|
||||||
"/api/noc/diagram-jumlah-document": {
|
|
||||||
parameters: {
|
|
||||||
query?: never;
|
|
||||||
header?: never;
|
|
||||||
path?: never;
|
|
||||||
cookie?: never;
|
|
||||||
};
|
|
||||||
get: operations["getApiNocDiagram-jumlah-document"];
|
|
||||||
put?: never;
|
|
||||||
post?: never;
|
|
||||||
delete?: never;
|
|
||||||
options?: never;
|
|
||||||
head?: never;
|
|
||||||
patch?: never;
|
|
||||||
trace?: never;
|
|
||||||
};
|
|
||||||
"/api/noc/diagram-progres-kegiatan": {
|
|
||||||
parameters: {
|
|
||||||
query?: never;
|
|
||||||
header?: never;
|
|
||||||
path?: never;
|
|
||||||
cookie?: never;
|
|
||||||
};
|
|
||||||
get: operations["getApiNocDiagram-progres-kegiatan"];
|
|
||||||
put?: never;
|
|
||||||
post?: never;
|
|
||||||
delete?: never;
|
|
||||||
options?: never;
|
|
||||||
head?: never;
|
|
||||||
patch?: never;
|
|
||||||
trace?: never;
|
|
||||||
};
|
|
||||||
"/api/noc/latest-discussion": {
|
|
||||||
parameters: {
|
|
||||||
query?: never;
|
|
||||||
header?: never;
|
|
||||||
path?: never;
|
|
||||||
cookie?: never;
|
|
||||||
};
|
|
||||||
get: operations["getApiNocLatest-discussion"];
|
|
||||||
put?: never;
|
|
||||||
post?: never;
|
|
||||||
delete?: never;
|
|
||||||
options?: never;
|
|
||||||
head?: never;
|
|
||||||
patch?: never;
|
|
||||||
trace?: never;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
export type webhooks = Record<string, never>;
|
export type webhooks = Record<string, never>;
|
||||||
export interface components {
|
export interface components {
|
||||||
@@ -619,6 +651,362 @@ export interface operations {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
postApiNocSync: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
success: boolean;
|
||||||
|
message?: string;
|
||||||
|
error?: string;
|
||||||
|
lastSyncedAt?: string;
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
success: boolean;
|
||||||
|
message?: string;
|
||||||
|
error?: string;
|
||||||
|
lastSyncedAt?: string;
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
success: boolean;
|
||||||
|
message?: string;
|
||||||
|
error?: string;
|
||||||
|
lastSyncedAt?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
401: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"getApiNocLast-sync": {
|
||||||
|
parameters: {
|
||||||
|
query: {
|
||||||
|
idDesa: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
lastSyncedAt: (string | null) | null;
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
lastSyncedAt: (string | null) | null;
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
lastSyncedAt: (string | null) | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"getApiNocActive-divisions": {
|
||||||
|
parameters: {
|
||||||
|
query: {
|
||||||
|
idDesa: string;
|
||||||
|
limit?: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
activityCount: number;
|
||||||
|
color: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
activityCount: number;
|
||||||
|
color: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
activityCount: number;
|
||||||
|
color: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"getApiNocLatest-projects": {
|
||||||
|
parameters: {
|
||||||
|
query: {
|
||||||
|
idDesa: string;
|
||||||
|
limit?: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
status: string;
|
||||||
|
progress: number;
|
||||||
|
divisionName: string;
|
||||||
|
createdAt: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
status: string;
|
||||||
|
progress: number;
|
||||||
|
divisionName: string;
|
||||||
|
createdAt: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
status: string;
|
||||||
|
progress: number;
|
||||||
|
divisionName: string;
|
||||||
|
createdAt: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"getApiNocUpcoming-events": {
|
||||||
|
parameters: {
|
||||||
|
query: {
|
||||||
|
idDesa: string;
|
||||||
|
limit?: string;
|
||||||
|
filter?: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
startDate: string;
|
||||||
|
location: (string | null) | null;
|
||||||
|
eventType: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
startDate: string;
|
||||||
|
location: (string | null) | null;
|
||||||
|
eventType: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
startDate: string;
|
||||||
|
location: (string | null) | null;
|
||||||
|
eventType: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"getApiNocDiagram-jumlah-document": {
|
||||||
|
parameters: {
|
||||||
|
query: {
|
||||||
|
idDesa: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: {
|
||||||
|
category: string;
|
||||||
|
count: number;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: {
|
||||||
|
category: string;
|
||||||
|
count: number;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: {
|
||||||
|
category: string;
|
||||||
|
count: number;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"getApiNocDiagram-progres-kegiatan": {
|
||||||
|
parameters: {
|
||||||
|
query: {
|
||||||
|
idDesa: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: {
|
||||||
|
status: string;
|
||||||
|
avgProgress: number;
|
||||||
|
count: number;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: {
|
||||||
|
status: string;
|
||||||
|
avgProgress: number;
|
||||||
|
count: number;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: {
|
||||||
|
status: string;
|
||||||
|
avgProgress: number;
|
||||||
|
count: number;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"getApiNocLatest-discussion": {
|
||||||
|
parameters: {
|
||||||
|
query: {
|
||||||
|
idDesa: string;
|
||||||
|
limit?: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
data: {
|
||||||
|
id: string;
|
||||||
|
message: string;
|
||||||
|
senderName: string;
|
||||||
|
senderImage: (string | null) | null;
|
||||||
|
divisionName: string;
|
||||||
|
createdAt: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
"multipart/form-data": {
|
||||||
|
data: {
|
||||||
|
id: string;
|
||||||
|
message: string;
|
||||||
|
senderName: string;
|
||||||
|
senderImage: (string | null) | null;
|
||||||
|
divisionName: string;
|
||||||
|
createdAt: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
"text/plain": {
|
||||||
|
data: {
|
||||||
|
id: string;
|
||||||
|
message: string;
|
||||||
|
senderName: string;
|
||||||
|
senderImage: (string | null) | null;
|
||||||
|
divisionName: string;
|
||||||
|
createdAt: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
getApiApikey: {
|
getApiApikey: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -2070,279 +2458,4 @@ export interface operations {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
"getApiNocActive-divisions": {
|
|
||||||
parameters: {
|
|
||||||
query: {
|
|
||||||
idDesa: string;
|
|
||||||
limit?: string;
|
|
||||||
};
|
|
||||||
header?: never;
|
|
||||||
path?: never;
|
|
||||||
cookie?: never;
|
|
||||||
};
|
|
||||||
requestBody?: never;
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
headers: {
|
|
||||||
[name: string]: unknown;
|
|
||||||
};
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
data: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
activityCount: number;
|
|
||||||
color: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
"multipart/form-data": {
|
|
||||||
data: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
activityCount: number;
|
|
||||||
color: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
"text/plain": {
|
|
||||||
data: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
activityCount: number;
|
|
||||||
color: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
"getApiNocLatest-projects": {
|
|
||||||
parameters: {
|
|
||||||
query: {
|
|
||||||
idDesa: string;
|
|
||||||
limit?: string;
|
|
||||||
};
|
|
||||||
header?: never;
|
|
||||||
path?: never;
|
|
||||||
cookie?: never;
|
|
||||||
};
|
|
||||||
requestBody?: never;
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
headers: {
|
|
||||||
[name: string]: unknown;
|
|
||||||
};
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
data: {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
status: string;
|
|
||||||
progress: number;
|
|
||||||
divisionName: string;
|
|
||||||
createdAt: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
"multipart/form-data": {
|
|
||||||
data: {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
status: string;
|
|
||||||
progress: number;
|
|
||||||
divisionName: string;
|
|
||||||
createdAt: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
"text/plain": {
|
|
||||||
data: {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
status: string;
|
|
||||||
progress: number;
|
|
||||||
divisionName: string;
|
|
||||||
createdAt: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
"getApiNocUpcoming-events": {
|
|
||||||
parameters: {
|
|
||||||
query: {
|
|
||||||
idDesa: string;
|
|
||||||
limit?: string;
|
|
||||||
filter?: string;
|
|
||||||
};
|
|
||||||
header?: never;
|
|
||||||
path?: never;
|
|
||||||
cookie?: never;
|
|
||||||
};
|
|
||||||
requestBody?: never;
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
headers: {
|
|
||||||
[name: string]: unknown;
|
|
||||||
};
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
data: {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
startDate: string;
|
|
||||||
location: (string | null) | null;
|
|
||||||
eventType: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
"multipart/form-data": {
|
|
||||||
data: {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
startDate: string;
|
|
||||||
location: (string | null) | null;
|
|
||||||
eventType: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
"text/plain": {
|
|
||||||
data: {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
startDate: string;
|
|
||||||
location: (string | null) | null;
|
|
||||||
eventType: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
"getApiNocDiagram-jumlah-document": {
|
|
||||||
parameters: {
|
|
||||||
query: {
|
|
||||||
idDesa: string;
|
|
||||||
};
|
|
||||||
header?: never;
|
|
||||||
path?: never;
|
|
||||||
cookie?: never;
|
|
||||||
};
|
|
||||||
requestBody?: never;
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
headers: {
|
|
||||||
[name: string]: unknown;
|
|
||||||
};
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
data: {
|
|
||||||
category: string;
|
|
||||||
count: number;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
"multipart/form-data": {
|
|
||||||
data: {
|
|
||||||
category: string;
|
|
||||||
count: number;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
"text/plain": {
|
|
||||||
data: {
|
|
||||||
category: string;
|
|
||||||
count: number;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
"getApiNocDiagram-progres-kegiatan": {
|
|
||||||
parameters: {
|
|
||||||
query: {
|
|
||||||
idDesa: string;
|
|
||||||
};
|
|
||||||
header?: never;
|
|
||||||
path?: never;
|
|
||||||
cookie?: never;
|
|
||||||
};
|
|
||||||
requestBody?: never;
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
headers: {
|
|
||||||
[name: string]: unknown;
|
|
||||||
};
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
data: {
|
|
||||||
status: string;
|
|
||||||
avgProgress: number;
|
|
||||||
count: number;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
"multipart/form-data": {
|
|
||||||
data: {
|
|
||||||
status: string;
|
|
||||||
avgProgress: number;
|
|
||||||
count: number;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
"text/plain": {
|
|
||||||
data: {
|
|
||||||
status: string;
|
|
||||||
avgProgress: number;
|
|
||||||
count: number;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
"getApiNocLatest-discussion": {
|
|
||||||
parameters: {
|
|
||||||
query: {
|
|
||||||
idDesa: string;
|
|
||||||
limit?: string;
|
|
||||||
};
|
|
||||||
header?: never;
|
|
||||||
path?: never;
|
|
||||||
cookie?: never;
|
|
||||||
};
|
|
||||||
requestBody?: never;
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
headers: {
|
|
||||||
[name: string]: unknown;
|
|
||||||
};
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
data: {
|
|
||||||
id: string;
|
|
||||||
message: string;
|
|
||||||
senderName: string;
|
|
||||||
senderImage: (string | null) | null;
|
|
||||||
divisionName: string;
|
|
||||||
createdAt: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
"multipart/form-data": {
|
|
||||||
data: {
|
|
||||||
id: string;
|
|
||||||
message: string;
|
|
||||||
senderName: string;
|
|
||||||
senderImage: (string | null) | null;
|
|
||||||
divisionName: string;
|
|
||||||
createdAt: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
"text/plain": {
|
|
||||||
data: {
|
|
||||||
id: string;
|
|
||||||
message: string;
|
|
||||||
senderName: string;
|
|
||||||
senderImage: (string | null) | null;
|
|
||||||
divisionName: string;
|
|
||||||
createdAt: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
269
generated/noc-external.ts
Normal file
269
generated/noc-external.ts
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
/**
|
||||||
|
* This file was auto-generated by openapi-typescript.
|
||||||
|
* Do not make direct changes to the file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface paths {
|
||||||
|
"/api/noc/active-divisions": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Divisi Teraktif
|
||||||
|
* @description Menu Beranda - Mendapatkan daftar divisi teraktif berdasarkan jumlah proyek pada desa tertentu.
|
||||||
|
*/
|
||||||
|
get: operations["getApiNocActive-divisions"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/noc/latest-projects": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Latest Projects General
|
||||||
|
* @description Menu kinerja divisi - Mendapatkan daftar proyek umum terbaru dari berbagai grup pada desa tertentu.
|
||||||
|
*/
|
||||||
|
get: operations["getApiNocLatest-projects"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/noc/upcoming-events": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Events (Today & Upcoming)
|
||||||
|
* @description Menu beranda dan kinerja divisi - Mendapatkan daftar event pada hari ini dan yang akan datang untuk semua divisi pada desa tertentu.
|
||||||
|
*/
|
||||||
|
get: operations["getApiNocUpcoming-events"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/noc/diagram-jumlah-document": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Diagram Jumlah Document
|
||||||
|
* @description Menu kinerja divisi - Mendapatkan diagram jumlah document pada desa tertentu.
|
||||||
|
*/
|
||||||
|
get: operations["getApiNocDiagram-jumlah-document"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/noc/diagram-progres-kegiatan": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Diagram Progres Kegiatan
|
||||||
|
* @description Menu kinerja divisi - Mendapatkan diagram progres kegiatan pada desa tertentu.
|
||||||
|
*/
|
||||||
|
get: operations["getApiNocDiagram-progres-kegiatan"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/noc/latest-discussion": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Latest Discussion
|
||||||
|
* @description Menu kinerja divisi - Mendapatkan latest discussion pada desa tertentu.
|
||||||
|
*/
|
||||||
|
get: operations["getApiNocLatest-discussion"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export type webhooks = Record<string, never>;
|
||||||
|
export interface components {
|
||||||
|
schemas: never;
|
||||||
|
responses: never;
|
||||||
|
parameters: never;
|
||||||
|
requestBodies: never;
|
||||||
|
headers: never;
|
||||||
|
pathItems: never;
|
||||||
|
}
|
||||||
|
export type $defs = Record<string, never>;
|
||||||
|
export interface operations {
|
||||||
|
"getApiNocActive-divisions": {
|
||||||
|
parameters: {
|
||||||
|
query: {
|
||||||
|
/** @description ID Desa yang ingin dicari */
|
||||||
|
idDesa: string;
|
||||||
|
/** @description Jumlah maksimal data (default: 5) */
|
||||||
|
limit?: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"getApiNocLatest-projects": {
|
||||||
|
parameters: {
|
||||||
|
query: {
|
||||||
|
/** @description ID Desa yang ingin dicari */
|
||||||
|
idDesa: string;
|
||||||
|
/** @description Jumlah maksimal proyek (default: 5, maks: 50) */
|
||||||
|
limit?: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"getApiNocUpcoming-events": {
|
||||||
|
parameters: {
|
||||||
|
query: {
|
||||||
|
/** @description ID Desa yang ingin dicari */
|
||||||
|
idDesa: string;
|
||||||
|
/** @description Jumlah maksimal event (default: 10, maks: 50) */
|
||||||
|
limit?: string;
|
||||||
|
/** @description Filter event: 'today' atau 'upcoming' */
|
||||||
|
filter?: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"getApiNocDiagram-jumlah-document": {
|
||||||
|
parameters: {
|
||||||
|
query: {
|
||||||
|
/** @description ID Desa yang ingin dicari */
|
||||||
|
idDesa: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"getApiNocDiagram-progres-kegiatan": {
|
||||||
|
parameters: {
|
||||||
|
query: {
|
||||||
|
/** @description ID Desa yang ingin dicari */
|
||||||
|
idDesa: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"getApiNocLatest-discussion": {
|
||||||
|
parameters: {
|
||||||
|
query: {
|
||||||
|
/** @description ID Desa yang ingin dicari */
|
||||||
|
idDesa: string;
|
||||||
|
/** @description Limit data */
|
||||||
|
limit?: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@
|
|||||||
"check": "biome check --write .",
|
"check": "biome check --write .",
|
||||||
"format": "biome format --write .",
|
"format": "biome format --write .",
|
||||||
"gen:api": "bun scripts/generate-schema.ts && bun x openapi-typescript generated/schema.json -o generated/api.ts",
|
"gen:api": "bun scripts/generate-schema.ts && bun x openapi-typescript generated/schema.json -o generated/api.ts",
|
||||||
|
"sync:noc": "bun scripts/sync-noc.ts",
|
||||||
"test": "bun test __tests__/api",
|
"test": "bun test __tests__/api",
|
||||||
"test:ui": "bun test --ui __tests__/api",
|
"test:ui": "bun test --ui __tests__/api",
|
||||||
"test:e2e": "bun run build && playwright test",
|
"test:e2e": "bun run build && playwright test",
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- A unique constraint covering the columns `[externalId]` on the table `activity` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- A unique constraint covering the columns `[externalId]` on the table `discussion` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- A unique constraint covering the columns `[externalId]` on the table `division` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- A unique constraint covering the columns `[externalId]` on the table `document` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- A unique constraint covering the columns `[externalId]` on the table `event` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "activity" ADD COLUMN "externalId" TEXT,
|
||||||
|
ADD COLUMN "villageId" TEXT DEFAULT 'darmasaba';
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "discussion" ADD COLUMN "externalId" TEXT,
|
||||||
|
ADD COLUMN "villageId" TEXT DEFAULT 'darmasaba';
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "division" ADD COLUMN "externalId" TEXT,
|
||||||
|
ADD COLUMN "villageId" TEXT DEFAULT 'darmasaba';
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "document" ADD COLUMN "externalId" TEXT,
|
||||||
|
ADD COLUMN "villageId" TEXT DEFAULT 'darmasaba';
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "event" ADD COLUMN "externalId" TEXT,
|
||||||
|
ADD COLUMN "villageId" TEXT DEFAULT 'darmasaba';
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "activity_externalId_key" ON "activity"("externalId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "discussion_externalId_key" ON "discussion"("externalId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "division_externalId_key" ON "division"("externalId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "document_externalId_key" ON "document"("externalId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "event_externalId_key" ON "event"("externalId");
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "division" ADD COLUMN "lastSyncedAt" TIMESTAMP(3);
|
||||||
@@ -42,10 +42,13 @@ model User {
|
|||||||
|
|
||||||
model Division {
|
model Division {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
|
externalId String? @unique // ID asli dari server NOC
|
||||||
|
villageId String? @default("darmasaba") // ID Desa dari sistem NOC
|
||||||
name String @unique
|
name String @unique
|
||||||
description String?
|
description String?
|
||||||
color String @default("#1E3A5F")
|
color String @default("#1E3A5F")
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
|
lastSyncedAt DateTime? // Terakhir kali sinkronisasi dilakukan
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
@@ -59,6 +62,8 @@ model Division {
|
|||||||
|
|
||||||
model Activity {
|
model Activity {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
|
externalId String? @unique // ID asli dari server NOC
|
||||||
|
villageId String? @default("darmasaba")
|
||||||
title String
|
title String
|
||||||
description String?
|
description String?
|
||||||
divisionId String
|
divisionId String
|
||||||
@@ -82,6 +87,8 @@ model Activity {
|
|||||||
|
|
||||||
model Document {
|
model Document {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
|
externalId String? @unique // ID asli dari server NOC
|
||||||
|
villageId String? @default("darmasaba")
|
||||||
title String
|
title String
|
||||||
category DocumentCategory
|
category DocumentCategory
|
||||||
type String // "Gambar", "Dokumen", "PDF", etc
|
type String // "Gambar", "Dokumen", "PDF", etc
|
||||||
@@ -101,6 +108,8 @@ model Document {
|
|||||||
|
|
||||||
model Discussion {
|
model Discussion {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
|
externalId String? @unique // ID asli dari server NOC
|
||||||
|
villageId String? @default("darmasaba")
|
||||||
message String
|
message String
|
||||||
senderId String
|
senderId String
|
||||||
parentId String? // For threaded discussions
|
parentId String? // For threaded discussions
|
||||||
@@ -121,6 +130,8 @@ model Discussion {
|
|||||||
|
|
||||||
model Event {
|
model Event {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
|
externalId String? @unique // ID asli dari server NOC
|
||||||
|
villageId String? @default("darmasaba")
|
||||||
title String
|
title String
|
||||||
description String?
|
description String?
|
||||||
eventType EventType
|
eventType EventType
|
||||||
|
|||||||
@@ -2,20 +2,32 @@ import "dotenv/config";
|
|||||||
import { PrismaClient } from "../generated/prisma";
|
import { PrismaClient } from "../generated/prisma";
|
||||||
|
|
||||||
// Import all seeders
|
// Import all seeders
|
||||||
import { seedAdminUser, seedDemoUsers, seedApiKeys } from "./seeders/seed-auth";
|
import { seedAdminUser, seedApiKeys, seedDemoUsers } from "./seeders/seed-auth";
|
||||||
import { seedBanjars, seedResidents, getBanjarIds } from "./seeders/seed-demographics";
|
import { seedDashboardMetrics } from "./seeders/seed-dashboard-metrics";
|
||||||
import { seedDivisions, seedActivities, getDivisionIds } from "./seeders/seed-division-performance";
|
|
||||||
import {
|
import {
|
||||||
|
getBanjarIds,
|
||||||
|
seedBanjars,
|
||||||
|
seedResidents,
|
||||||
|
} from "./seeders/seed-demographics";
|
||||||
|
import {
|
||||||
|
seedDiscussions,
|
||||||
|
seedDivisionMetrics,
|
||||||
|
seedDocuments,
|
||||||
|
} from "./seeders/seed-discussions";
|
||||||
|
import {
|
||||||
|
getDivisionIds,
|
||||||
|
seedActivities,
|
||||||
|
seedDivisions,
|
||||||
|
} from "./seeders/seed-division-performance";
|
||||||
|
import { seedPhase2 } from "./seeders/seed-phase2";
|
||||||
|
import {
|
||||||
|
getComplaintIds,
|
||||||
seedComplaints,
|
seedComplaints,
|
||||||
seedServiceLetters,
|
seedComplaintUpdates,
|
||||||
seedEvents,
|
seedEvents,
|
||||||
seedInnovationIdeas,
|
seedInnovationIdeas,
|
||||||
seedComplaintUpdates,
|
seedServiceLetters,
|
||||||
getComplaintIds,
|
|
||||||
} from "./seeders/seed-public-services";
|
} from "./seeders/seed-public-services";
|
||||||
import { seedDocuments, seedDiscussions, seedDivisionMetrics } from "./seeders/seed-discussions";
|
|
||||||
import { seedDashboardMetrics } from "./seeders/seed-dashboard-metrics";
|
|
||||||
import { seedPhase2 } from "./seeders/seed-phase2";
|
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
@@ -45,7 +57,9 @@ export async function runSeed() {
|
|||||||
// Check if data already exists
|
// Check if data already exists
|
||||||
const existingData = await hasExistingData();
|
const existingData = await hasExistingData();
|
||||||
if (existingData) {
|
if (existingData) {
|
||||||
console.log("⏭️ Existing data detected. Skipping seed to prevent duplicates.\n");
|
console.log(
|
||||||
|
"⏭️ Existing data detected. Skipping seed to prevent duplicates.\n",
|
||||||
|
);
|
||||||
console.log("💡 To re-seed, either:");
|
console.log("💡 To re-seed, either:");
|
||||||
console.log(" 1. Run: bun x prisma migrate reset (resets database)");
|
console.log(" 1. Run: bun x prisma migrate reset (resets database)");
|
||||||
console.log(" 2. Manually delete data from tables\n");
|
console.log(" 2. Manually delete data from tables\n");
|
||||||
@@ -114,7 +128,9 @@ export async function runSpecificSeeder(name: string) {
|
|||||||
// Check if data already exists for specific seeder
|
// Check if data already exists for specific seeder
|
||||||
const existingData = await hasExistingData();
|
const existingData = await hasExistingData();
|
||||||
if (existingData && name !== "auth") {
|
if (existingData && name !== "auth") {
|
||||||
console.log("⚠️ Warning: Existing data detected for this seeder category.\n");
|
console.log(
|
||||||
|
"⚠️ Warning: Existing data detected for this seeder category.\n",
|
||||||
|
);
|
||||||
console.log("💡 To re-seed, either:");
|
console.log("💡 To re-seed, either:");
|
||||||
console.log(" 1. Run: bun x prisma migrate reset (resets database)");
|
console.log(" 1. Run: bun x prisma migrate reset (resets database)");
|
||||||
console.log(" 2. Manually delete data from tables\n");
|
console.log(" 2. Manually delete data from tables\n");
|
||||||
@@ -124,33 +140,36 @@ export async function runSpecificSeeder(name: string) {
|
|||||||
|
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case "auth":
|
case "auth":
|
||||||
case "users":
|
case "users": {
|
||||||
console.log("📁 Authentication & Users");
|
console.log("📁 Authentication & Users");
|
||||||
const adminId = await seedAdminUser();
|
const adminId = await seedAdminUser();
|
||||||
await seedDemoUsers();
|
await seedDemoUsers();
|
||||||
await seedApiKeys(adminId);
|
await seedApiKeys(adminId);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "demographics":
|
case "demographics":
|
||||||
case "population":
|
case "population": {
|
||||||
console.log("📁 Demographics & Population");
|
console.log("📁 Demographics & Population");
|
||||||
await seedBanjars();
|
await seedBanjars();
|
||||||
const banjarIds = await getBanjarIds();
|
const banjarIds = await getBanjarIds();
|
||||||
await seedResidents(banjarIds);
|
await seedResidents(banjarIds);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "divisions":
|
case "divisions":
|
||||||
case "performance":
|
case "performance": {
|
||||||
console.log("📁 Division Performance");
|
console.log("📁 Division Performance");
|
||||||
const divisions = await seedDivisions();
|
const divisions = await seedDivisions();
|
||||||
const divisionIds = divisions.map((d) => d.id);
|
const divisionIds = divisions.map((d) => d.id);
|
||||||
await seedActivities(divisionIds);
|
await seedActivities(divisionIds);
|
||||||
await seedDivisionMetrics(divisionIds);
|
await seedDivisionMetrics(divisionIds);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "complaints":
|
case "complaints":
|
||||||
case "services":
|
case "services":
|
||||||
case "public":
|
case "public": {
|
||||||
console.log("📁 Public Services");
|
console.log("📁 Public Services");
|
||||||
const pubAdminId = await seedAdminUser();
|
const pubAdminId = await seedAdminUser();
|
||||||
await seedComplaints(pubAdminId);
|
await seedComplaints(pubAdminId);
|
||||||
@@ -160,9 +179,10 @@ export async function runSpecificSeeder(name: string) {
|
|||||||
const compIds = await getComplaintIds();
|
const compIds = await getComplaintIds();
|
||||||
await seedComplaintUpdates(compIds, pubAdminId);
|
await seedComplaintUpdates(compIds, pubAdminId);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "documents":
|
case "documents":
|
||||||
case "discussions":
|
case "discussions": {
|
||||||
console.log("📁 Documents & Discussions");
|
console.log("📁 Documents & Discussions");
|
||||||
const docAdminId = await seedAdminUser();
|
const docAdminId = await seedAdminUser();
|
||||||
const divs = await seedDivisions();
|
const divs = await seedDivisions();
|
||||||
@@ -170,6 +190,7 @@ export async function runSpecificSeeder(name: string) {
|
|||||||
await seedDocuments(divIds, docAdminId);
|
await seedDocuments(divIds, docAdminId);
|
||||||
await seedDiscussions(divIds, docAdminId);
|
await seedDiscussions(divIds, docAdminId);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "dashboard":
|
case "dashboard":
|
||||||
case "metrics":
|
case "metrics":
|
||||||
@@ -178,17 +199,20 @@ export async function runSpecificSeeder(name: string) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "phase2":
|
case "phase2":
|
||||||
case "features":
|
case "features": {
|
||||||
console.log("📁 Phase 2+ Features");
|
console.log("📁 Phase 2+ Features");
|
||||||
const p2AdminId = await seedAdminUser();
|
const p2AdminId = await seedAdminUser();
|
||||||
await seedBanjars();
|
await seedBanjars();
|
||||||
const p2BanjarIds = await getBanjarIds();
|
const p2BanjarIds = await getBanjarIds();
|
||||||
await seedPhase2(p2BanjarIds, p2AdminId);
|
await seedPhase2(p2BanjarIds, p2AdminId);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.error(`❌ Unknown seeder: ${name}`);
|
console.error(`❌ Unknown seeder: ${name}`);
|
||||||
console.log("Available seeders: auth, demographics, divisions, complaints, documents, dashboard, phase2");
|
console.log(
|
||||||
|
"Available seeders: auth, demographics, divisions, complaints, documents, dashboard, phase2",
|
||||||
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PrismaClient, Gender, Religion } from "../../generated/prisma";
|
import { Gender, PrismaClient, Religion } from "../../generated/prisma";
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import {
|
import { ActivityStatus, Priority, PrismaClient } from "../../generated/prisma";
|
||||||
ActivityStatus,
|
|
||||||
Priority,
|
|
||||||
PrismaClient,
|
|
||||||
} from "../../generated/prisma";
|
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
|||||||
@@ -25,13 +25,14 @@ export async function seedComplaints(adminId: string) {
|
|||||||
console.log("Seeding Complaints...");
|
console.log("Seeding Complaints...");
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
const complaints = [
|
const complaints = [
|
||||||
// Recent complaints (this month)
|
// Recent complaints (this month)
|
||||||
{
|
{
|
||||||
complaintNumber: `COMP-20260327-001`,
|
complaintNumber: `COMP-20260327-001`,
|
||||||
title: "Lampu Jalan Mati",
|
title: "Lampu Jalan Mati",
|
||||||
description: "Lampu jalan di depan Balai Banjar Manesa mati sejak 3 hari lalu.",
|
description:
|
||||||
|
"Lampu jalan di depan Balai Banjar Manesa mati sejak 3 hari lalu.",
|
||||||
category: ComplaintCategory.INFRASTRUKTUR,
|
category: ComplaintCategory.INFRASTRUKTUR,
|
||||||
status: ComplaintStatus.BARU,
|
status: ComplaintStatus.BARU,
|
||||||
priority: Priority.SEDANG,
|
priority: Priority.SEDANG,
|
||||||
@@ -187,7 +188,9 @@ export async function seedComplaints(adminId: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("✅ Complaints seeded successfully (12 complaints across 7 months)");
|
console.log(
|
||||||
|
"✅ Complaints seeded successfully (12 complaints across 7 months)",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -354,7 +357,10 @@ export async function seedInnovationIdeas(adminId: string) {
|
|||||||
* Seed Complaint Updates
|
* Seed Complaint Updates
|
||||||
* Creates status update history for complaints
|
* Creates status update history for complaints
|
||||||
*/
|
*/
|
||||||
export async function seedComplaintUpdates(complaintIds: string[], userId: string) {
|
export async function seedComplaintUpdates(
|
||||||
|
complaintIds: string[],
|
||||||
|
userId: string,
|
||||||
|
) {
|
||||||
console.log("Seeding Complaint Updates...");
|
console.log("Seeding Complaint Updates...");
|
||||||
|
|
||||||
if (complaintIds.length === 0) {
|
if (complaintIds.length === 0) {
|
||||||
|
|||||||
225
scripts/sync-noc.ts
Normal file
225
scripts/sync-noc.ts
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
import { prisma } from "../src/utils/db";
|
||||||
|
import { nocExternalClient } from "../src/utils/noc-external-client";
|
||||||
|
import logger from "../src/utils/logger";
|
||||||
|
|
||||||
|
const ID_DESA = "darmasaba";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper untuk mendapatkan system user ID untuk relasi
|
||||||
|
*/
|
||||||
|
async function getSystemUserId() {
|
||||||
|
const user = await prisma.user.findFirst({
|
||||||
|
where: { role: "admin" },
|
||||||
|
});
|
||||||
|
if (!user) {
|
||||||
|
// Buat system user jika tidak ada
|
||||||
|
const newUser = await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
email: "system@darmasaba.id",
|
||||||
|
name: "System Sync",
|
||||||
|
role: "admin",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return newUser.id;
|
||||||
|
}
|
||||||
|
return user.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Sync Divisions
|
||||||
|
*/
|
||||||
|
async function syncActiveDivisions() {
|
||||||
|
logger.info("Syncing Divisions...");
|
||||||
|
const { data, error } = await nocExternalClient.GET("/api/noc/active-divisions", {
|
||||||
|
params: { query: { idDesa: ID_DESA } },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error || !data) {
|
||||||
|
logger.error({ error }, "Failed to fetch divisions from NOC");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: External API response is untyped
|
||||||
|
const divisions = (data as any).data;
|
||||||
|
for (const div of divisions) {
|
||||||
|
await prisma.division.upsert({
|
||||||
|
where: { externalId: div.id },
|
||||||
|
update: {
|
||||||
|
name: div.name,
|
||||||
|
color: div.color,
|
||||||
|
villageId: ID_DESA,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
externalId: div.id,
|
||||||
|
name: div.name,
|
||||||
|
color: div.color,
|
||||||
|
villageId: ID_DESA,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
logger.info(`Synced ${divisions.length} divisions`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2. Sync Activities
|
||||||
|
*/
|
||||||
|
async function syncLatestProjects() {
|
||||||
|
logger.info("Syncing Activities...");
|
||||||
|
const { data, error } = await nocExternalClient.GET("/api/noc/latest-projects", {
|
||||||
|
params: { query: { idDesa: ID_DESA, limit: "50" } },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error || !data) {
|
||||||
|
logger.error({ error }, "Failed to fetch projects from NOC");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: External API response
|
||||||
|
const projects = (data as any).data;
|
||||||
|
for (const proj of projects) {
|
||||||
|
// Temukan divisi lokal berdasarkan nama atau externalId (asumsi externalId divisi sinkron)
|
||||||
|
// Karena kita sinkron divisi dulu, kita cari berdasarkan nama jika externalId belum pasti
|
||||||
|
const division = await prisma.division.findFirst({
|
||||||
|
where: { name: proj.divisionName },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!division) continue;
|
||||||
|
|
||||||
|
await prisma.activity.upsert({
|
||||||
|
where: { externalId: proj.id },
|
||||||
|
update: {
|
||||||
|
title: proj.title,
|
||||||
|
status: proj.status as any,
|
||||||
|
progress: proj.progress,
|
||||||
|
divisionId: division.id,
|
||||||
|
villageId: ID_DESA,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
externalId: proj.id,
|
||||||
|
title: proj.title,
|
||||||
|
status: proj.status as any,
|
||||||
|
progress: proj.progress,
|
||||||
|
divisionId: division.id,
|
||||||
|
villageId: ID_DESA,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
logger.info(`Synced ${projects.length} activities`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3. Sync Events
|
||||||
|
*/
|
||||||
|
async function syncUpcomingEvents() {
|
||||||
|
logger.info("Syncing Events...");
|
||||||
|
const systemUserId = await getSystemUserId();
|
||||||
|
const { data, error } = await nocExternalClient.GET("/api/noc/upcoming-events", {
|
||||||
|
params: { query: { idDesa: ID_DESA, limit: "50" } },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error || !data) {
|
||||||
|
logger.error({ error }, "Failed to fetch events from NOC");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: External API response
|
||||||
|
const events = (data as any).data;
|
||||||
|
for (const event of events) {
|
||||||
|
await prisma.event.upsert({
|
||||||
|
where: { externalId: event.id },
|
||||||
|
update: {
|
||||||
|
title: event.title,
|
||||||
|
startDate: new Date(event.startDate),
|
||||||
|
location: event.location,
|
||||||
|
eventType: event.eventType as any,
|
||||||
|
villageId: ID_DESA,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
externalId: event.id,
|
||||||
|
title: event.title,
|
||||||
|
startDate: new Date(event.startDate),
|
||||||
|
location: event.location,
|
||||||
|
eventType: event.eventType as any,
|
||||||
|
createdBy: systemUserId,
|
||||||
|
villageId: ID_DESA,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
logger.info(`Synced ${events.length} events`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 4. Sync Discussions
|
||||||
|
*/
|
||||||
|
async function syncLatestDiscussion() {
|
||||||
|
logger.info("Syncing Discussions...");
|
||||||
|
const systemUserId = await getSystemUserId();
|
||||||
|
const { data, error } = await nocExternalClient.GET("/api/noc/latest-discussion", {
|
||||||
|
params: { query: { idDesa: ID_DESA, limit: "50" } },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error || !data) {
|
||||||
|
logger.error({ error }, "Failed to fetch discussions from NOC");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: External API response
|
||||||
|
const discussions = (data as any).data;
|
||||||
|
for (const disc of discussions) {
|
||||||
|
const division = await prisma.division.findFirst({
|
||||||
|
where: { name: disc.divisionName },
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.discussion.upsert({
|
||||||
|
where: { externalId: disc.id },
|
||||||
|
update: {
|
||||||
|
message: disc.message,
|
||||||
|
divisionId: division?.id,
|
||||||
|
villageId: ID_DESA,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
externalId: disc.id,
|
||||||
|
message: disc.message,
|
||||||
|
senderId: systemUserId,
|
||||||
|
divisionId: division?.id,
|
||||||
|
villageId: ID_DESA,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
logger.info(`Synced ${discussions.length} discussions`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 5. Update lastSyncedAt timestamp
|
||||||
|
*/
|
||||||
|
async function syncLastTimestamp() {
|
||||||
|
logger.info("Updating sync timestamp...");
|
||||||
|
await prisma.division.updateMany({
|
||||||
|
where: { villageId: ID_DESA },
|
||||||
|
data: { lastSyncedAt: new Date() },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main Sync Function
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
try {
|
||||||
|
logger.info("Starting NOC Data Synchronization...");
|
||||||
|
|
||||||
|
await syncActiveDivisions();
|
||||||
|
await syncLatestProjects();
|
||||||
|
await syncUpcomingEvents();
|
||||||
|
await syncLatestDiscussion();
|
||||||
|
await syncLastTimestamp();
|
||||||
|
|
||||||
|
logger.info("NOC Data Synchronization Completed Successfully");
|
||||||
|
} catch (err) {
|
||||||
|
logger.error({ err }, "Fatal error during NOC synchronization");
|
||||||
|
process.exit(1);
|
||||||
|
} finally {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -8,9 +8,9 @@ import { complaint } from "./complaint";
|
|||||||
import { dashboard } from "./dashboard";
|
import { dashboard } from "./dashboard";
|
||||||
import { division } from "./division";
|
import { division } from "./division";
|
||||||
import { event } from "./event";
|
import { event } from "./event";
|
||||||
|
import { noc } from "./noc";
|
||||||
import { profile } from "./profile";
|
import { profile } from "./profile";
|
||||||
import { resident } from "./resident";
|
import { resident } from "./resident";
|
||||||
import { noc } from "./noc";
|
|
||||||
|
|
||||||
const isProduction = process.env.NODE_ENV === "production";
|
const isProduction = process.env.NODE_ENV === "production";
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,67 @@
|
|||||||
import { Elysia, t } from "elysia";
|
import { Elysia, t } from "elysia";
|
||||||
import { prisma } from "../utils/db";
|
import { prisma } from "../utils/db";
|
||||||
|
import { $ } from "bun";
|
||||||
|
|
||||||
export const noc = new Elysia({ prefix: "/noc" })
|
export const noc = new Elysia({ prefix: "/noc" })
|
||||||
|
.post(
|
||||||
|
"/sync",
|
||||||
|
async ({ set, user }) => {
|
||||||
|
if (!user || user.role !== "admin") {
|
||||||
|
set.status = 401;
|
||||||
|
return { error: "Unauthorized" };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Jalankan script sinkronisasi
|
||||||
|
await $`bun run sync:noc`.quiet();
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Sinkronisasi berhasil diselesaikan",
|
||||||
|
lastSyncedAt: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: "Sinkronisasi gagal dijalankan" };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
response: {
|
||||||
|
200: t.Object({
|
||||||
|
success: t.Boolean(),
|
||||||
|
message: t.Optional(t.String()),
|
||||||
|
error: t.Optional(t.String()),
|
||||||
|
lastSyncedAt: t.Optional(t.String()),
|
||||||
|
}),
|
||||||
|
401: t.Object({ error: t.String() }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.get(
|
||||||
|
"/last-sync",
|
||||||
|
async ({ query }) => {
|
||||||
|
const { idDesa } = query;
|
||||||
|
const latest = await prisma.division.findFirst({
|
||||||
|
where: { villageId: idDesa },
|
||||||
|
select: { lastSyncedAt: true },
|
||||||
|
orderBy: { lastSyncedAt: "desc" },
|
||||||
|
});
|
||||||
|
|
||||||
|
return { lastSyncedAt: latest?.lastSyncedAt?.toISOString() || null };
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: t.Object({ idDesa: t.String() }),
|
||||||
|
response: {
|
||||||
|
200: t.Object({
|
||||||
|
lastSyncedAt: t.Nullable(t.String()),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
.get(
|
.get(
|
||||||
"/active-divisions",
|
"/active-divisions",
|
||||||
async ({ query }) => {
|
async ({ query }) => {
|
||||||
const { idDesa, limit } = query;
|
const { idDesa, limit } = query;
|
||||||
// TODO: Filter by idDesa once schema supports it
|
|
||||||
const data = await prisma.division.findMany({
|
const data = await prisma.division.findMany({
|
||||||
|
where: { villageId: idDesa },
|
||||||
include: {
|
include: {
|
||||||
_count: {
|
_count: {
|
||||||
select: { activities: true },
|
select: { activities: true },
|
||||||
@@ -53,8 +107,8 @@ export const noc = new Elysia({ prefix: "/noc" })
|
|||||||
"/latest-projects",
|
"/latest-projects",
|
||||||
async ({ query }) => {
|
async ({ query }) => {
|
||||||
const { idDesa, limit } = query;
|
const { idDesa, limit } = query;
|
||||||
// TODO: Filter by idDesa once schema supports it
|
|
||||||
const data = await prisma.activity.findMany({
|
const data = await prisma.activity.findMany({
|
||||||
|
where: { villageId: idDesa },
|
||||||
orderBy: { createdAt: "desc" },
|
orderBy: { createdAt: "desc" },
|
||||||
take: limit ? Number.parseInt(limit) : 5,
|
take: limit ? Number.parseInt(limit) : 5,
|
||||||
include: { division: true },
|
include: { division: true },
|
||||||
@@ -96,9 +150,8 @@ export const noc = new Elysia({ prefix: "/noc" })
|
|||||||
"/upcoming-events",
|
"/upcoming-events",
|
||||||
async ({ query }) => {
|
async ({ query }) => {
|
||||||
const { idDesa, limit, filter } = query;
|
const { idDesa, limit, filter } = query;
|
||||||
// TODO: Filter by idDesa once schema supports it
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const where: any = {};
|
const where: any = { villageId: idDesa };
|
||||||
|
|
||||||
if (filter === "today") {
|
if (filter === "today") {
|
||||||
const startOfDay = new Date(now.setHours(0, 0, 0, 0));
|
const startOfDay = new Date(now.setHours(0, 0, 0, 0));
|
||||||
@@ -154,8 +207,8 @@ export const noc = new Elysia({ prefix: "/noc" })
|
|||||||
"/diagram-jumlah-document",
|
"/diagram-jumlah-document",
|
||||||
async ({ query }) => {
|
async ({ query }) => {
|
||||||
const { idDesa } = query;
|
const { idDesa } = query;
|
||||||
// TODO: Filter by idDesa once schema supports it
|
|
||||||
const data = await prisma.document.groupBy({
|
const data = await prisma.document.groupBy({
|
||||||
|
where: { villageId: idDesa },
|
||||||
by: ["category"],
|
by: ["category"],
|
||||||
_count: {
|
_count: {
|
||||||
_all: true,
|
_all: true,
|
||||||
@@ -189,8 +242,8 @@ export const noc = new Elysia({ prefix: "/noc" })
|
|||||||
"/diagram-progres-kegiatan",
|
"/diagram-progres-kegiatan",
|
||||||
async ({ query }) => {
|
async ({ query }) => {
|
||||||
const { idDesa } = query;
|
const { idDesa } = query;
|
||||||
// TODO: Filter by idDesa once schema supports it
|
|
||||||
const data = await prisma.activity.groupBy({
|
const data = await prisma.activity.groupBy({
|
||||||
|
where: { villageId: idDesa },
|
||||||
by: ["status"],
|
by: ["status"],
|
||||||
_avg: {
|
_avg: {
|
||||||
progress: true,
|
progress: true,
|
||||||
@@ -229,8 +282,8 @@ export const noc = new Elysia({ prefix: "/noc" })
|
|||||||
"/latest-discussion",
|
"/latest-discussion",
|
||||||
async ({ query }) => {
|
async ({ query }) => {
|
||||||
const { idDesa, limit } = query;
|
const { idDesa, limit } = query;
|
||||||
// TODO: Filter by idDesa once schema supports it
|
|
||||||
const data = await prisma.discussion.findMany({
|
const data = await prisma.discussion.findMany({
|
||||||
|
where: { villageId: idDesa },
|
||||||
orderBy: { createdAt: "desc" },
|
orderBy: { createdAt: "desc" },
|
||||||
take: limit ? Number.parseInt(limit) : 5,
|
take: limit ? Number.parseInt(limit) : 5,
|
||||||
include: {
|
include: {
|
||||||
@@ -273,4 +326,5 @@ export const noc = new Elysia({ prefix: "/noc" })
|
|||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Grid, Image, Loader, Stack, Center } from "@mantine/core";
|
import { Center, Grid, Image, Loader, Stack } from "@mantine/core";
|
||||||
import { CheckCircle, FileText, MessageCircle, Users } from "lucide-react";
|
import { CheckCircle, FileText, MessageCircle, Users } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
@@ -18,20 +18,21 @@ export function DashboardContent() {
|
|||||||
loading: true,
|
loading: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [sdgsData, setSdgsData] = useState<{ title: string; score: number; image: string | null }[]>([]);
|
const [sdgsData, setSdgsData] = useState<
|
||||||
|
{ title: string; score: number; image: string | null }[]
|
||||||
|
>([]);
|
||||||
const [sdgsLoading, setSdgsLoading] = useState(true);
|
const [sdgsLoading, setSdgsLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchStats() {
|
async function fetchStats() {
|
||||||
try {
|
try {
|
||||||
const [complaintRes, residentRes, weeklyServiceRes, sdgsRes] = await Promise.all(
|
const [complaintRes, residentRes, weeklyServiceRes, sdgsRes] =
|
||||||
[
|
await Promise.all([
|
||||||
apiClient.GET("/api/complaint/stats"),
|
apiClient.GET("/api/complaint/stats"),
|
||||||
apiClient.GET("/api/resident/stats"),
|
apiClient.GET("/api/resident/stats"),
|
||||||
apiClient.GET("/api/complaint/service-weekly"),
|
apiClient.GET("/api/complaint/service-weekly"),
|
||||||
apiClient.GET("/api/dashboard/sdgs"),
|
apiClient.GET("/api/dashboard/sdgs"),
|
||||||
],
|
]);
|
||||||
);
|
|
||||||
|
|
||||||
setStats({
|
setStats({
|
||||||
complaints: (complaintRes.data as { data: typeof stats.complaints })
|
complaints: (complaintRes.data as { data: typeof stats.complaints })
|
||||||
@@ -138,7 +139,9 @@ export function DashboardContent() {
|
|||||||
{sdgsData.map((sdg) => (
|
{sdgsData.map((sdg) => (
|
||||||
<Grid.Col key={sdg.title} span={{ base: 9, md: 3 }}>
|
<Grid.Col key={sdg.title} span={{ base: 9, md: 3 }}>
|
||||||
<SDGSCard
|
<SDGSCard
|
||||||
image={sdg.image ? <Image src={sdg.image} alt={sdg.title} /> : null}
|
image={
|
||||||
|
sdg.image ? <Image src={sdg.image} alt={sdg.title} /> : null
|
||||||
|
}
|
||||||
title={sdg.title}
|
title={sdg.title}
|
||||||
score={sdg.score}
|
score={sdg.score}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -96,7 +96,13 @@ export function ChartAPBDes() {
|
|||||||
</Bar>
|
</Bar>
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
<Text size="sm" fw={600} w={40} ta="right" c={dark ? "white" : "gray.9"}>
|
<Text
|
||||||
|
size="sm"
|
||||||
|
fw={600}
|
||||||
|
w={40}
|
||||||
|
ta="right"
|
||||||
|
c={dark ? "white" : "gray.9"}
|
||||||
|
>
|
||||||
{item.value}%
|
{item.value}%
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -51,8 +51,14 @@ export function ChartSurat() {
|
|||||||
console.log("📊 Service trends response:", res);
|
console.log("📊 Service trends response:", res);
|
||||||
|
|
||||||
// Check if response has data
|
// Check if response has data
|
||||||
if (res.data?.data && Array.isArray(res.data.data) && res.data.data.length > 0) {
|
if (
|
||||||
const chartData = (res.data.data as { month: string; count: number }[]).map((d) => ({
|
res.data?.data &&
|
||||||
|
Array.isArray(res.data.data) &&
|
||||||
|
res.data.data.length > 0
|
||||||
|
) {
|
||||||
|
const chartData = (
|
||||||
|
res.data.data as { month: string; count: number }[]
|
||||||
|
).map((d) => ({
|
||||||
month: d.month,
|
month: d.month,
|
||||||
value: Number(d.count),
|
value: Number(d.count),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
import { Card, Group, Loader, Stack, Text, useMantineColorScheme } from "@mantine/core";
|
import {
|
||||||
|
Card,
|
||||||
|
Group,
|
||||||
|
Loader,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
useMantineColorScheme,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { id } from "date-fns/locale";
|
||||||
import { MessageCircle } from "lucide-react";
|
import { MessageCircle } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { format } from "date-fns";
|
|
||||||
import { id } from "date-fns/locale";
|
|
||||||
|
|
||||||
interface DiscussionItem {
|
interface DiscussionItem {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import { Card, Group, Loader, Text, useMantineColorScheme } from "@mantine/core";
|
import {
|
||||||
|
Card,
|
||||||
|
Group,
|
||||||
|
Loader,
|
||||||
|
Text,
|
||||||
|
useMantineColorScheme,
|
||||||
|
} from "@mantine/core";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Bar,
|
Bar,
|
||||||
|
|||||||
@@ -47,10 +47,26 @@ export function ProgressChart() {
|
|||||||
if (res.data?.data) {
|
if (res.data?.data) {
|
||||||
const stats = res.data.data as ActivityStats;
|
const stats = res.data.data as ActivityStats;
|
||||||
const chartData: ProgressData[] = [
|
const chartData: ProgressData[] = [
|
||||||
{ name: "Selesai", value: stats.percentages.selesai, color: "#22C55E" },
|
{
|
||||||
{ name: "Dikerjakan", value: stats.percentages.berjalan, color: "#F59E0B" },
|
name: "Selesai",
|
||||||
{ name: "Segera Dikerjakan", value: stats.percentages.tertunda, color: "#3B82F6" },
|
value: stats.percentages.selesai,
|
||||||
{ name: "Dibatalkan", value: stats.percentages.dibatalkan, color: "#EF4444" },
|
color: "#22C55E",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Dikerjakan",
|
||||||
|
value: stats.percentages.berjalan,
|
||||||
|
color: "#F59E0B",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Segera Dikerjakan",
|
||||||
|
value: stats.percentages.tertunda,
|
||||||
|
color: "#3B82F6",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Dibatalkan",
|
||||||
|
value: stats.percentages.dibatalkan,
|
||||||
|
color: "#EF4444",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
setData(chartData);
|
setData(chartData);
|
||||||
}
|
}
|
||||||
|
|||||||
176
src/components/pengaturan/sinkronisasi.tsx
Normal file
176
src/components/pengaturan/sinkronisasi.tsx
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Group,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Alert,
|
||||||
|
Loader,
|
||||||
|
Badge,
|
||||||
|
Divider,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { IconRefresh, IconCheck, IconAlertCircle, IconClock } from "@tabler/icons-react";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { apiClient } from "@/utils/api-client";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import "dayjs/locale/id";
|
||||||
|
import relativeTime from "dayjs/plugin/relativeTime";
|
||||||
|
|
||||||
|
dayjs.extend(relativeTime);
|
||||||
|
dayjs.locale("id");
|
||||||
|
|
||||||
|
const SinkronisasiSettings = () => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [lastSync, setLastSync] = useState<string | null>(null);
|
||||||
|
const [status, setStatus] = useState<{
|
||||||
|
type: "success" | "error" | null;
|
||||||
|
message: string;
|
||||||
|
}>({ type: null, message: "" });
|
||||||
|
|
||||||
|
const fetchLastSync = async () => {
|
||||||
|
const { data } = await apiClient.GET("/api/noc/last-sync", {
|
||||||
|
params: { query: { idDesa: "darmasaba" } },
|
||||||
|
});
|
||||||
|
if (data?.lastSyncedAt) {
|
||||||
|
setLastSync(data.lastSyncedAt);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchLastSync();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSync = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
setStatus({ type: null, message: "" });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data, error } = await apiClient.POST("/api/noc/sync");
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
setStatus({
|
||||||
|
type: "error",
|
||||||
|
message: (error as any).error || "Gagal melakukan sinkronisasi",
|
||||||
|
});
|
||||||
|
} else if (data?.success) {
|
||||||
|
setStatus({
|
||||||
|
type: "success",
|
||||||
|
message: data.message || "Sinkronisasi berhasil dilakukan",
|
||||||
|
});
|
||||||
|
if (data.lastSyncedAt) {
|
||||||
|
setLastSync(data.lastSyncedAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setStatus({
|
||||||
|
type: "error",
|
||||||
|
message: "Terjadi kesalahan sistem saat sinkronisasi",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box pr={"50%"}>
|
||||||
|
<Title order={2} mb="lg">
|
||||||
|
Sinkronisasi Data NOC
|
||||||
|
</Title>
|
||||||
|
|
||||||
|
<Text c="dimmed" mb="xl">
|
||||||
|
Gunakan fitur ini untuk memperbarui data dashboard dengan data terbaru dari
|
||||||
|
server Network Operation Center (NOC) darmasaba.muku.id.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Card withBorder padding="lg" radius="md" mb="xl">
|
||||||
|
<Stack gap="md">
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Group>
|
||||||
|
<IconClock size={20} color="gray" />
|
||||||
|
<Text fw={500}>Status Terakhir</Text>
|
||||||
|
</Group>
|
||||||
|
<Badge color={lastSync ? "green" : "gray"} variant="light">
|
||||||
|
{lastSync ? "Terkoneksi" : "Belum Pernah Sinkron"}
|
||||||
|
</Badge>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
Waktu Sinkronisasi Terakhir:
|
||||||
|
</Text>
|
||||||
|
<Text fw={700} size="lg">
|
||||||
|
{lastSync
|
||||||
|
? dayjs(lastSync).format("DD MMMM YYYY, HH:mm:ss")
|
||||||
|
: "Belum pernah dilakukan"}
|
||||||
|
</Text>
|
||||||
|
{lastSync && (
|
||||||
|
<Text size="xs" c="dimmed" mt={4}>
|
||||||
|
({dayjs(lastSync).fromNow()})
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{status.type && (
|
||||||
|
<Alert
|
||||||
|
icon={
|
||||||
|
status.type === "success" ? (
|
||||||
|
<IconCheck size={16} />
|
||||||
|
) : (
|
||||||
|
<IconAlertCircle size={16} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
title={status.type === "success" ? "Berhasil" : "Kesalahan"}
|
||||||
|
color={status.type === "success" ? "green" : "red"}
|
||||||
|
onClose={() => setStatus({ type: null, message: "" })}
|
||||||
|
withCloseButton
|
||||||
|
>
|
||||||
|
{status.message}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
leftSection={
|
||||||
|
loading ? <Loader size={16} color="white" /> : <IconRefresh size={16} />
|
||||||
|
}
|
||||||
|
onClick={handleSync}
|
||||||
|
loading={loading}
|
||||||
|
fullWidth
|
||||||
|
mt="md"
|
||||||
|
>
|
||||||
|
Sinkronkan Sekarang
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Title order={2} mb="lg">
|
||||||
|
Informasi API
|
||||||
|
</Title>
|
||||||
|
|
||||||
|
<Card withBorder padding="md" radius="md" bg="gray.0">
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Group>
|
||||||
|
<Text fw={600} size="sm" w={100}>URL Sumber:</Text>
|
||||||
|
<Text size="sm" style={{ wordBreak: 'break-all' }}>https://darmasaba.muku.id/api/noc/</Text>
|
||||||
|
</Group>
|
||||||
|
<Group>
|
||||||
|
<Text fw={600} size="sm" w={100}>ID Desa:</Text>
|
||||||
|
<Text size="sm">darmasaba</Text>
|
||||||
|
</Group>
|
||||||
|
<Group>
|
||||||
|
<Text fw={600} size="sm" w={100}>Model Data:</Text>
|
||||||
|
<Badge size="xs" variant="outline">Divisi</Badge>
|
||||||
|
<Badge size="xs" variant="outline">Kegiatan</Badge>
|
||||||
|
<Badge size="xs" variant="outline">Event</Badge>
|
||||||
|
<Badge size="xs" variant="outline">Diskusi</Badge>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SinkronisasiSettings;
|
||||||
@@ -48,6 +48,7 @@ export function Sidebar({ className }: SidebarProps) {
|
|||||||
{ name: "Notifikasi", path: "/pengaturan/notifikasi" },
|
{ name: "Notifikasi", path: "/pengaturan/notifikasi" },
|
||||||
{ name: "Keamanan", path: "/pengaturan/keamanan" },
|
{ name: "Keamanan", path: "/pengaturan/keamanan" },
|
||||||
{ name: "Akses & Tim", path: "/pengaturan/akses-dan-tim" },
|
{ name: "Akses & Tim", path: "/pengaturan/akses-dan-tim" },
|
||||||
|
{ name: "Sinkronisasi NOC", path: "/pengaturan/sinkronisasi" },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Check if any settings submenu is active
|
// Check if any settings submenu is active
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import { Route as AdminIndexRouteImport } from './routes/admin/index'
|
|||||||
import { Route as UsersIdRouteImport } from './routes/users/$id'
|
import { Route as UsersIdRouteImport } from './routes/users/$id'
|
||||||
import { Route as ProfileEditRouteImport } from './routes/profile/edit'
|
import { Route as ProfileEditRouteImport } from './routes/profile/edit'
|
||||||
import { Route as PengaturanUmumRouteImport } from './routes/pengaturan/umum'
|
import { Route as PengaturanUmumRouteImport } from './routes/pengaturan/umum'
|
||||||
|
import { Route as PengaturanSinkronisasiRouteImport } from './routes/pengaturan/sinkronisasi'
|
||||||
import { Route as PengaturanNotifikasiRouteImport } from './routes/pengaturan/notifikasi'
|
import { Route as PengaturanNotifikasiRouteImport } from './routes/pengaturan/notifikasi'
|
||||||
import { Route as PengaturanKeamananRouteImport } from './routes/pengaturan/keamanan'
|
import { Route as PengaturanKeamananRouteImport } from './routes/pengaturan/keamanan'
|
||||||
import { Route as PengaturanAksesDanTimRouteImport } from './routes/pengaturan/akses-dan-tim'
|
import { Route as PengaturanAksesDanTimRouteImport } from './routes/pengaturan/akses-dan-tim'
|
||||||
@@ -142,6 +143,11 @@ const PengaturanUmumRoute = PengaturanUmumRouteImport.update({
|
|||||||
path: '/umum',
|
path: '/umum',
|
||||||
getParentRoute: () => PengaturanRouteRoute,
|
getParentRoute: () => PengaturanRouteRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
const PengaturanSinkronisasiRoute = PengaturanSinkronisasiRouteImport.update({
|
||||||
|
id: '/sinkronisasi',
|
||||||
|
path: '/sinkronisasi',
|
||||||
|
getParentRoute: () => PengaturanRouteRoute,
|
||||||
|
} as any)
|
||||||
const PengaturanNotifikasiRoute = PengaturanNotifikasiRouteImport.update({
|
const PengaturanNotifikasiRoute = PengaturanNotifikasiRouteImport.update({
|
||||||
id: '/notifikasi',
|
id: '/notifikasi',
|
||||||
path: '/notifikasi',
|
path: '/notifikasi',
|
||||||
@@ -195,6 +201,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/pengaturan/akses-dan-tim': typeof PengaturanAksesDanTimRoute
|
'/pengaturan/akses-dan-tim': typeof PengaturanAksesDanTimRoute
|
||||||
'/pengaturan/keamanan': typeof PengaturanKeamananRoute
|
'/pengaturan/keamanan': typeof PengaturanKeamananRoute
|
||||||
'/pengaturan/notifikasi': typeof PengaturanNotifikasiRoute
|
'/pengaturan/notifikasi': typeof PengaturanNotifikasiRoute
|
||||||
|
'/pengaturan/sinkronisasi': typeof PengaturanSinkronisasiRoute
|
||||||
'/pengaturan/umum': typeof PengaturanUmumRoute
|
'/pengaturan/umum': typeof PengaturanUmumRoute
|
||||||
'/profile/edit': typeof ProfileEditRoute
|
'/profile/edit': typeof ProfileEditRoute
|
||||||
'/users/$id': typeof UsersIdRoute
|
'/users/$id': typeof UsersIdRoute
|
||||||
@@ -222,6 +229,7 @@ export interface FileRoutesByTo {
|
|||||||
'/pengaturan/akses-dan-tim': typeof PengaturanAksesDanTimRoute
|
'/pengaturan/akses-dan-tim': typeof PengaturanAksesDanTimRoute
|
||||||
'/pengaturan/keamanan': typeof PengaturanKeamananRoute
|
'/pengaturan/keamanan': typeof PengaturanKeamananRoute
|
||||||
'/pengaturan/notifikasi': typeof PengaturanNotifikasiRoute
|
'/pengaturan/notifikasi': typeof PengaturanNotifikasiRoute
|
||||||
|
'/pengaturan/sinkronisasi': typeof PengaturanSinkronisasiRoute
|
||||||
'/pengaturan/umum': typeof PengaturanUmumRoute
|
'/pengaturan/umum': typeof PengaturanUmumRoute
|
||||||
'/profile/edit': typeof ProfileEditRoute
|
'/profile/edit': typeof ProfileEditRoute
|
||||||
'/users/$id': typeof UsersIdRoute
|
'/users/$id': typeof UsersIdRoute
|
||||||
@@ -252,6 +260,7 @@ export interface FileRoutesById {
|
|||||||
'/pengaturan/akses-dan-tim': typeof PengaturanAksesDanTimRoute
|
'/pengaturan/akses-dan-tim': typeof PengaturanAksesDanTimRoute
|
||||||
'/pengaturan/keamanan': typeof PengaturanKeamananRoute
|
'/pengaturan/keamanan': typeof PengaturanKeamananRoute
|
||||||
'/pengaturan/notifikasi': typeof PengaturanNotifikasiRoute
|
'/pengaturan/notifikasi': typeof PengaturanNotifikasiRoute
|
||||||
|
'/pengaturan/sinkronisasi': typeof PengaturanSinkronisasiRoute
|
||||||
'/pengaturan/umum': typeof PengaturanUmumRoute
|
'/pengaturan/umum': typeof PengaturanUmumRoute
|
||||||
'/profile/edit': typeof ProfileEditRoute
|
'/profile/edit': typeof ProfileEditRoute
|
||||||
'/users/$id': typeof UsersIdRoute
|
'/users/$id': typeof UsersIdRoute
|
||||||
@@ -283,6 +292,7 @@ export interface FileRouteTypes {
|
|||||||
| '/pengaturan/akses-dan-tim'
|
| '/pengaturan/akses-dan-tim'
|
||||||
| '/pengaturan/keamanan'
|
| '/pengaturan/keamanan'
|
||||||
| '/pengaturan/notifikasi'
|
| '/pengaturan/notifikasi'
|
||||||
|
| '/pengaturan/sinkronisasi'
|
||||||
| '/pengaturan/umum'
|
| '/pengaturan/umum'
|
||||||
| '/profile/edit'
|
| '/profile/edit'
|
||||||
| '/users/$id'
|
| '/users/$id'
|
||||||
@@ -310,6 +320,7 @@ export interface FileRouteTypes {
|
|||||||
| '/pengaturan/akses-dan-tim'
|
| '/pengaturan/akses-dan-tim'
|
||||||
| '/pengaturan/keamanan'
|
| '/pengaturan/keamanan'
|
||||||
| '/pengaturan/notifikasi'
|
| '/pengaturan/notifikasi'
|
||||||
|
| '/pengaturan/sinkronisasi'
|
||||||
| '/pengaturan/umum'
|
| '/pengaturan/umum'
|
||||||
| '/profile/edit'
|
| '/profile/edit'
|
||||||
| '/users/$id'
|
| '/users/$id'
|
||||||
@@ -339,6 +350,7 @@ export interface FileRouteTypes {
|
|||||||
| '/pengaturan/akses-dan-tim'
|
| '/pengaturan/akses-dan-tim'
|
||||||
| '/pengaturan/keamanan'
|
| '/pengaturan/keamanan'
|
||||||
| '/pengaturan/notifikasi'
|
| '/pengaturan/notifikasi'
|
||||||
|
| '/pengaturan/sinkronisasi'
|
||||||
| '/pengaturan/umum'
|
| '/pengaturan/umum'
|
||||||
| '/profile/edit'
|
| '/profile/edit'
|
||||||
| '/users/$id'
|
| '/users/$id'
|
||||||
@@ -516,6 +528,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof PengaturanUmumRouteImport
|
preLoaderRoute: typeof PengaturanUmumRouteImport
|
||||||
parentRoute: typeof PengaturanRouteRoute
|
parentRoute: typeof PengaturanRouteRoute
|
||||||
}
|
}
|
||||||
|
'/pengaturan/sinkronisasi': {
|
||||||
|
id: '/pengaturan/sinkronisasi'
|
||||||
|
path: '/sinkronisasi'
|
||||||
|
fullPath: '/pengaturan/sinkronisasi'
|
||||||
|
preLoaderRoute: typeof PengaturanSinkronisasiRouteImport
|
||||||
|
parentRoute: typeof PengaturanRouteRoute
|
||||||
|
}
|
||||||
'/pengaturan/notifikasi': {
|
'/pengaturan/notifikasi': {
|
||||||
id: '/pengaturan/notifikasi'
|
id: '/pengaturan/notifikasi'
|
||||||
path: '/notifikasi'
|
path: '/notifikasi'
|
||||||
@@ -583,6 +602,7 @@ interface PengaturanRouteRouteChildren {
|
|||||||
PengaturanAksesDanTimRoute: typeof PengaturanAksesDanTimRoute
|
PengaturanAksesDanTimRoute: typeof PengaturanAksesDanTimRoute
|
||||||
PengaturanKeamananRoute: typeof PengaturanKeamananRoute
|
PengaturanKeamananRoute: typeof PengaturanKeamananRoute
|
||||||
PengaturanNotifikasiRoute: typeof PengaturanNotifikasiRoute
|
PengaturanNotifikasiRoute: typeof PengaturanNotifikasiRoute
|
||||||
|
PengaturanSinkronisasiRoute: typeof PengaturanSinkronisasiRoute
|
||||||
PengaturanUmumRoute: typeof PengaturanUmumRoute
|
PengaturanUmumRoute: typeof PengaturanUmumRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -590,6 +610,7 @@ const PengaturanRouteRouteChildren: PengaturanRouteRouteChildren = {
|
|||||||
PengaturanAksesDanTimRoute: PengaturanAksesDanTimRoute,
|
PengaturanAksesDanTimRoute: PengaturanAksesDanTimRoute,
|
||||||
PengaturanKeamananRoute: PengaturanKeamananRoute,
|
PengaturanKeamananRoute: PengaturanKeamananRoute,
|
||||||
PengaturanNotifikasiRoute: PengaturanNotifikasiRoute,
|
PengaturanNotifikasiRoute: PengaturanNotifikasiRoute,
|
||||||
|
PengaturanSinkronisasiRoute: PengaturanSinkronisasiRoute,
|
||||||
PengaturanUmumRoute: PengaturanUmumRoute,
|
PengaturanUmumRoute: PengaturanUmumRoute,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
6
src/routes/pengaturan/sinkronisasi.tsx
Normal file
6
src/routes/pengaturan/sinkronisasi.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import SinkronisasiSettings from "@/components/pengaturan/sinkronisasi";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/pengaturan/sinkronisasi")({
|
||||||
|
component: SinkronisasiSettings,
|
||||||
|
});
|
||||||
@@ -38,25 +38,24 @@ function ProfileLayout() {
|
|||||||
paddingRight: "1rem",
|
paddingRight: "1rem",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Group h="100%" justify="space-between">
|
||||||
<Group h="100%" justify="space-between">
|
<Group>
|
||||||
<Group>
|
<Button
|
||||||
<Button
|
variant="subtle"
|
||||||
variant="subtle"
|
color="gray"
|
||||||
color="gray"
|
leftSection={<IconChevronLeft size={16} />}
|
||||||
leftSection={<IconChevronLeft size={16} />}
|
onClick={() => navigate({ to: "/" })}
|
||||||
onClick={() => navigate({ to: "/" })}
|
>
|
||||||
>
|
Kembali ke Dashboard
|
||||||
Kembali ke Dashboard
|
</Button>
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
|
|
||||||
<Text fw={700} size="lg" c="orange.6">
|
|
||||||
PENGATURAN AKUN
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Box w={150} />
|
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
|
<Text fw={700} size="lg" c="orange.6">
|
||||||
|
PENGATURAN AKUN
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Box w={150} />
|
||||||
|
</Group>
|
||||||
</AppShell.Header>
|
</AppShell.Header>
|
||||||
|
|
||||||
<AppShell.Main>
|
<AppShell.Main>
|
||||||
|
|||||||
19
src/utils/noc-external-client.ts
Normal file
19
src/utils/noc-external-client.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import createClient from "openapi-fetch";
|
||||||
|
import type { paths } from "../../generated/noc-external";
|
||||||
|
import { getEnv } from "./env";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOC External Client
|
||||||
|
* Digunakan khusus untuk menarik data dari server NOC darmasaba.muku.id
|
||||||
|
*/
|
||||||
|
const externalBaseUrl = getEnv(
|
||||||
|
"NOC_API_URL",
|
||||||
|
"https://darmasaba.muku.id/api/noc",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Hilangkan suffix /docs/json jika ada di URL
|
||||||
|
const cleanBaseUrl = externalBaseUrl.replace("/docs/json", "");
|
||||||
|
|
||||||
|
export const nocExternalClient = createClient<paths>({
|
||||||
|
baseUrl: cleanBaseUrl,
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user