301 lines
8.0 KiB
TypeScript
301 lines
8.0 KiB
TypeScript
import { locations, PrismaClient, unit_type, units } from '@prisma/client';
|
|
import * as XLSX from 'xlsx';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import axios from 'axios';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
import { createClient } from '../../app/_utils/supabase/client';
|
|
import { generateId, generateIdWithDbCounter } from '../../app/_utils/common';
|
|
|
|
// Interface untuk data Excel row
|
|
interface ExcelRow {
|
|
KESATUAN: string;
|
|
[key: string]: any; // Untuk kolom dinamis seperti "JAN CT", "JAN CC", dll
|
|
}
|
|
|
|
// Interface untuk mapping bulan
|
|
interface MonthMap {
|
|
[key: string]: number;
|
|
}
|
|
|
|
// Interface untuk statistik unit
|
|
interface UnitStatistics {
|
|
unit_id: string;
|
|
month: number;
|
|
year: number;
|
|
crime_total: number;
|
|
crime_cleared: number;
|
|
percentage: number;
|
|
pending: number;
|
|
}
|
|
|
|
interface CreateLocationDto {
|
|
district_id: string;
|
|
code_unit: string;
|
|
name?: string;
|
|
description?: string;
|
|
address?: string;
|
|
type?: string;
|
|
phone?: string;
|
|
latitude: number;
|
|
longitude: number;
|
|
land_area?: number;
|
|
polygon?: Record<string, any>;
|
|
geometry?: Record<string, any>;
|
|
location?: string;
|
|
}
|
|
|
|
export class UnitSeeder {
|
|
private mapboxToken: string;
|
|
|
|
constructor(
|
|
private prisma: PrismaClient,
|
|
private supabase = createClient()
|
|
) {
|
|
this.mapboxToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN || '';
|
|
}
|
|
|
|
async run(): Promise<void> {
|
|
await this.prisma.units.deleteMany({});
|
|
|
|
const districts = await this.prisma.districts.findMany({
|
|
include: {
|
|
cities: true,
|
|
},
|
|
});
|
|
|
|
const city = await this.prisma.cities.findFirst({
|
|
where: {
|
|
name: 'Jember',
|
|
},
|
|
include: {
|
|
districts: true,
|
|
},
|
|
});
|
|
|
|
if (!city) {
|
|
console.error('City not found: Jember');
|
|
return;
|
|
}
|
|
|
|
const location = await this.getUnitsLocation(city.name);
|
|
|
|
if (!location) {
|
|
console.warn(`No location found for city: ${city.name}`);
|
|
return;
|
|
}
|
|
|
|
const [lng, lat] = [location.lng, location.lat];
|
|
const address = location.address;
|
|
const phone = location.telepon?.replace(/-/g, '');
|
|
|
|
const newId = await generateIdWithDbCounter('units', {
|
|
prefix: 'UT',
|
|
segments: {
|
|
sequentialDigits: 4,
|
|
},
|
|
format: '{prefix}-{sequence}',
|
|
separator: '-',
|
|
uniquenessStrategy: 'counter',
|
|
});
|
|
|
|
let locationData: CreateLocationDto = {
|
|
district_id: city.districts[0].id, // This will be replaced with Patrang's ID
|
|
code_unit: newId,
|
|
name: `Polres ${city.name}`,
|
|
description: `Unit ${city.name} is categorized as POLRES and operates in the ${city.name} area.`,
|
|
type: 'polres',
|
|
address,
|
|
phone,
|
|
longitude: lng,
|
|
latitude: lat,
|
|
location: `POINT(${lng} ${lat})`,
|
|
};
|
|
|
|
// Find the Patrang district
|
|
const patrangDistrict = await this.prisma.districts.findFirst({
|
|
where: {
|
|
name: 'Patrang',
|
|
city_id: city.id,
|
|
},
|
|
});
|
|
|
|
if (!patrangDistrict) {
|
|
console.error('District not found: Patrang');
|
|
return;
|
|
}
|
|
|
|
locationData.district_id = patrangDistrict.id;
|
|
|
|
const { error } = await this.supabase
|
|
.from('units')
|
|
.insert([locationData])
|
|
.select();
|
|
|
|
if (error) {
|
|
console.error(`Error inserting into Supabase locations:`, error);
|
|
return;
|
|
}
|
|
|
|
let district;
|
|
|
|
for (district of districts) {
|
|
const location = await this.getUnitsLocation(district.name);
|
|
|
|
if (!location) {
|
|
console.warn(`No location found for district: ${district.name}`);
|
|
continue;
|
|
}
|
|
|
|
const [lng, lat] = [location.lng, location.lat];
|
|
const address = location.address;
|
|
const phone = location.telepon?.replace(/-/g, '');
|
|
|
|
const newId = await generateIdWithDbCounter('units', {
|
|
prefix: 'UT',
|
|
segments: {
|
|
sequentialDigits: 4,
|
|
},
|
|
format: '{prefix}-{sequence}',
|
|
separator: '-',
|
|
uniquenessStrategy: 'counter',
|
|
});
|
|
|
|
const locationData: CreateLocationDto = {
|
|
district_id: district.id,
|
|
code_unit: newId,
|
|
name: `Polsek ${district.name}`,
|
|
description: `Unit ${district.name} is categorized as POLSEK and operates in the ${district.name} area.`,
|
|
type: 'polsek',
|
|
address: address,
|
|
phone,
|
|
longitude: lng,
|
|
latitude: lat,
|
|
location: `POINT(${lng} ${lat})`,
|
|
};
|
|
|
|
const { error } = await this.supabase
|
|
.from('units')
|
|
.insert([locationData])
|
|
.select();
|
|
|
|
if (error) {
|
|
console.error(`Error inserting into Supabase locations:`, error);
|
|
continue;
|
|
}
|
|
|
|
console.log(
|
|
`Inserted unit for district: ${district.name}, newId: ${newId} at ${lng}, ${lat}`
|
|
);
|
|
}
|
|
}
|
|
|
|
private parseNumber(value: any): number {
|
|
if (value === undefined || value === null) return 0;
|
|
const num = Number(value);
|
|
return isNaN(num) ? 0 : num;
|
|
}
|
|
|
|
private async getUnitsLocation(districtName: string): Promise<{
|
|
lng: number;
|
|
lat: number;
|
|
address: string;
|
|
telepon: string;
|
|
} | null> {
|
|
// const getCoordinatesFromMapbox = async (
|
|
// query: string
|
|
// ): Promise<{ lng: number; lat: number } | null> => {
|
|
// const suggestUrl = `https://api.mapbox.com/search/searchbox/v1/suggest?q=${encodeURIComponent(
|
|
// query
|
|
// )}&session_token=${uuidv4()}&access_token=${this.mapboxToken}`;
|
|
|
|
// // console.log(`Suggest URL: ${suggestUrl}`);
|
|
|
|
// const res = await axios.get(suggestUrl);
|
|
// const suggestions = res?.data?.suggestions;
|
|
|
|
// if (!suggestions || suggestions.length === 0) return null;
|
|
|
|
// const mapboxId = suggestions[0].mapbox_id;
|
|
|
|
// // console.log(`Mapbox ID for ${query}: ${mapboxId}`);
|
|
|
|
// if (!mapboxId) return null;
|
|
|
|
// const retrieveUrl = `https://api.mapbox.com/search/searchbox/v1/retrieve/${mapboxId}?session_token=${uuidv4()}&access_token=${this.mapboxToken}`;
|
|
|
|
// // console.log(`Retrieve URL: ${retrieveUrl}`);
|
|
|
|
// const retrieveResponse = await axios.get(retrieveUrl);
|
|
// const features = retrieveResponse?.data.features;
|
|
|
|
// const [lng, lat] = features[0].geometry.coordinates;
|
|
|
|
// return { lng, lat };
|
|
// };
|
|
|
|
// Buka file Excel untuk ambil data nama dan alamat Polsek
|
|
const fallbackFilePath = path.join(
|
|
__dirname,
|
|
'../data/excels/administrations/polsek_jember.xlsx'
|
|
);
|
|
|
|
const workbook = XLSX.readFile(fallbackFilePath);
|
|
const sheetName = workbook.SheetNames[0];
|
|
const sheet = workbook.Sheets[sheetName];
|
|
const rows = XLSX.utils.sheet_to_json(sheet) as {
|
|
'Nama Unit': string;
|
|
Alamat: string;
|
|
Telepon: string;
|
|
lat: number;
|
|
long: number;
|
|
}[];
|
|
|
|
// Temukan Polsek berdasarkan kecamatan (nama kecamatan ada dalam alamat)
|
|
const matchedRow = rows.find(
|
|
(row) =>
|
|
row.Alamat?.toLowerCase().includes(districtName.toLowerCase()) ||
|
|
row['Nama Unit'].toLowerCase().includes(districtName.toLowerCase())
|
|
);
|
|
|
|
// console.log(`Matched row for ${districtName}:`, matchedRow);
|
|
|
|
if (!matchedRow) {
|
|
console.warn(
|
|
`No matching Polsek found in sheet for district: ${districtName}`
|
|
);
|
|
return null;
|
|
}
|
|
|
|
const polsekName = matchedRow['Nama Unit'];
|
|
const polsekAddress = matchedRow.Alamat;
|
|
const telepon = matchedRow.Telepon;
|
|
const coordinates = {
|
|
lng: matchedRow.long,
|
|
lat: matchedRow.lat,
|
|
};
|
|
|
|
// Ambil koordinat dari Mapbox, tapi alamat tetap pakai dari sheet
|
|
// const coordinates = await getCoordinatesFromMapbox(polsekName);
|
|
|
|
// if (!coordinates) {
|
|
// console.warn(`Mapbox couldn't find coordinates for: ${polsekName}`);
|
|
// return null;
|
|
// }
|
|
|
|
// console.log(
|
|
// `Coordinates for ${polsekName} (${districtName}): ${coordinates.lng}, ${coordinates.lat}`
|
|
// );
|
|
|
|
console.log(
|
|
`Polsek Name: ${polsekName}, Address: ${polsekAddress}, Telepon: ${telepon}`
|
|
);
|
|
|
|
return {
|
|
...coordinates,
|
|
telepon,
|
|
address: polsekAddress, // ambil dari sheet, bukan dari Mapbox
|
|
};
|
|
}
|
|
}
|