import { $Enums } from '@prisma/client'; import { CRIME_RATE_COLORS } from '@/app/_utils/const/map'; import type { ICrimes } from '@/app/_utils/types/crimes'; import { IDistrictFeature } from './types/map'; // Process crime data by district export const processCrimeDataByDistrict = (crimes: ICrimes[]) => { return crimes.reduce( (acc, crime) => { const districtId = crime.district_id; acc[districtId] = { number_of_crime: crime.number_of_crime, level: crime.level, }; return acc; }, {} as Record< string, { number_of_crime?: number; level?: $Enums.crime_rates } > ); }; // Get color for crime rate level export const getCrimeRateColor = (level?: $Enums.crime_rates) => { if (!level) return CRIME_RATE_COLORS.default; switch (level) { case 'low': return CRIME_RATE_COLORS.low; case 'medium': return CRIME_RATE_COLORS.medium; case 'high': return CRIME_RATE_COLORS.high; default: return CRIME_RATE_COLORS.default; } }; // Create fill color expression for district layer export const createFillColorExpression = ( focusedDistrictId: string | null, crimeDataByDistrict: Record< string, { number_of_crime?: number; level?: $Enums.crime_rates } > ) => { const colorEntries = focusedDistrictId ? [ [ focusedDistrictId, getCrimeRateColor(crimeDataByDistrict[focusedDistrictId]?.level), ], 'rgba(0,0,0,0.05)', ] : Object.entries(crimeDataByDistrict).flatMap(([districtId, data]) => { return [districtId, getCrimeRateColor(data.level)]; }); return [ 'case', ['has', 'kode_kec'], [ 'match', ['get', 'kode_kec'], ...colorEntries, focusedDistrictId ? 'rgba(0,0,0,0.05)' : CRIME_RATE_COLORS.default, ], CRIME_RATE_COLORS.default, ]; }; // Extract crime incidents for GeoJSON export const extractCrimeIncidents = ( crimes: ICrimes[], filterCategory: string | 'all' ) => { return crimes.flatMap((crime) => { if (!crime.crime_incidents) return []; let filteredIncidents = crime.crime_incidents; if (filterCategory !== 'all') { filteredIncidents = crime.crime_incidents.filter( (incident) => incident.crime_categories && incident.crime_categories.name === filterCategory ); } return filteredIncidents .map((incident) => { if (!incident.locations) { console.warn('Missing location for incident:', incident.id); return null; } return { type: 'Feature' as const, properties: { id: incident.id, district: crime.districts?.name || 'Unknown', category: incident.crime_categories?.name || 'Unknown', incidentType: incident.crime_categories?.type || 'Unknown', level: crime.level || 'low', description: incident.description || '', status: incident.status || '', }, geometry: { type: 'Point' as const, coordinates: [ incident.locations.longitude || 0, incident.locations.latitude || 0, ], }, }; }) .filter(Boolean); }); }; // Process district feature from map click export const processDistrictFeature = ( feature: any, e: any, districtId: string, crimeDataByDistrict: Record< string, { number_of_crime?: number; level?: $Enums.crime_rates } >, crimes: ICrimes[], year: string, month: string ): IDistrictFeature | null => { const crimeData = crimeDataByDistrict[districtId] || {}; let crime_incidents: Array<{ id: string; timestamp: Date; description: string; status: string; category: string; type: string; address: string; latitude: number; longitude: number; }> = []; const districtCrimes = crimes.filter( (crime) => crime.district_id === districtId ); districtCrimes.forEach((crimeRecord) => { if (crimeRecord && crimeRecord.crime_incidents) { const incidents = crimeRecord.crime_incidents.map((incident) => ({ id: incident.id, timestamp: incident.timestamp, description: incident.description || '', status: incident.status || '', category: incident.crime_categories?.name || '', type: incident.crime_categories?.type || '', address: incident.locations?.address || '', latitude: incident.locations?.latitude || 0, longitude: incident.locations?.longitude || 0, })); crime_incidents = [...crime_incidents, ...incidents]; } }); const firstDistrictCrime = districtCrimes.length > 0 ? districtCrimes[0] : null; if (!firstDistrictCrime) return null; const selectedYearNum = year ? Number.parseInt(year) : new Date().getFullYear(); let demographics = firstDistrictCrime?.districts.demographics?.find( (d) => d.year === selectedYearNum ); if (!demographics && firstDistrictCrime?.districts.demographics?.length) { demographics = firstDistrictCrime.districts.demographics.sort( (a, b) => b.year - a.year )[0]; console.log( `Tidak ada data demografis untuk tahun ${selectedYearNum}, menggunakan data tahun ${demographics.year}` ); } let geographics = firstDistrictCrime?.districts.geographics?.find( (g) => g.year === selectedYearNum ); if (!geographics && firstDistrictCrime?.districts.geographics?.length) { const validGeographics = firstDistrictCrime.districts.geographics .filter((g) => g.year !== null) .sort((a, b) => (b.year || 0) - (a.year || 0)); geographics = validGeographics.length > 0 ? validGeographics[0] : firstDistrictCrime.districts.geographics[0]; console.log( `Tidak ada data geografis untuk tahun ${selectedYearNum}, menggunakan data ${geographics.year ? `tahun ${geographics.year}` : 'tanpa tahun'}` ); } const clickLng = e.lngLat ? e.lngLat.lng : null; const clickLat = e.lngLat ? e.lngLat.lat : null; if (!geographics) { console.error('Missing geographics data for district:', districtId); return null; } if (!demographics) { console.error('Missing demographics data for district:', districtId); return null; } return { id: districtId, name: feature.properties.nama || feature.properties.kecamatan || 'Unknown District', longitude: geographics.longitude || clickLng || 0, latitude: geographics.latitude || clickLat || 0, number_of_crime: crimeData.number_of_crime || 0, level: crimeData.level || $Enums.crime_rates.low, demographics: { number_of_unemployed: demographics.number_of_unemployed, population: demographics.population, population_density: demographics.population_density, year: demographics.year, }, geographics: { address: geographics.address || '', land_area: geographics.land_area || 0, year: geographics.year || 0, latitude: geographics.latitude, longitude: geographics.longitude, }, crime_incidents: crime_incidents || [], selectedYear: year, selectedMonth: month, isFocused: true, }; };