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; city_id: string; name?: string; description?: string; address?: string; type?: string; phone?: string; latitude: number; longitude: number; land_area?: number; polygon?: Record; geometry?: Record; 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 { 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; } // 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; } // Prepare arrays for batch operations const unitsToInsert = []; // First, get the Polres unit data const polresLocation = await this.getUnitsLocation(city.name); if (polresLocation) { const [lng, lat] = [polresLocation.lng, polresLocation.lat]; const address = polresLocation.address; const phone = polresLocation.telepon?.replace(/-/g, ''); const polresId = await generateIdWithDbCounter('units', { prefix: 'UT', segments: { sequentialDigits: 4, }, format: '{prefix}-{sequence}', separator: '-', uniquenessStrategy: 'counter', }); unitsToInsert.push({ city_id: city.id, code_unit: polresId, 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})`, }); } else { console.warn(`No location found for city: ${city.name}`); } // Now prepare data for all Polseks const locationPromises = districts.map((district) => this.getUnitsLocation(district.name) .then((location) => ({ district, location })) .catch(() => ({ district, location: null })) ); // Wait for all location lookups to complete const results = await Promise.all(locationPromises); // Generate all IDs upfront const idPromises = Array(results.length) .fill(0) .map(() => generateIdWithDbCounter('units', { prefix: 'UT', segments: { sequentialDigits: 4, }, format: '{prefix}-{sequence}', separator: '-', uniquenessStrategy: 'counter', }) ); const ids = await Promise.all(idPromises); // Process results and add to unitsToInsert results.forEach(({ district, location }, index) => { if (!location) { console.warn(`No location found for district: ${district.name}`); return; } const [lng, lat] = [location.lng, location.lat]; const address = location.address; const phone = location.telepon?.replace(/-/g, ''); const newId = ids[index]; unitsToInsert.push({ district_id: district.id, city_id: district.city_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})`, }); console.log( `Prepared unit data for district: ${district.name}, ID: ${newId}` ); }); // Insert units in smaller batches if (unitsToInsert.length > 0) { const batchSize = 10; for (let i = 0; i < unitsToInsert.length; i += batchSize) { const batch = unitsToInsert.slice(i, i + batchSize); try { const { error } = await this.supabase .from('units') .insert(batch) .select(); if (error) { console.error(`Error inserting units batch ${i / batchSize + 1}:`, error); } else { console.log(`Successfully inserted batch ${i / batchSize + 1} (${batch.length} units)`); } // Small delay between batches await new Promise(resolve => setTimeout(resolve, 300)); } catch (err) { console.error(`Exception when inserting units batch ${i / batchSize + 1}:`, err); } } } else { console.warn('No unit data to insert'); } } 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 }; } }