-
Notifications
You must be signed in to change notification settings - Fork 40
feat: Implement Interactive Supply Chain Map (Geo-Spatial Tracking) #131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
kalyan-1845
wants to merge
7
commits into
daviddprtma:main
Choose a base branch
from
kalyan-1845:feature/supply-chain-map
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
0bfded0
feat: implement interactive supply chain map with react-leaflet
kalyan-1845 c52c606
fix: resolve merge conflicts with upstream main
kalyan-1845 992e83d
fix: resolve eslint no-explicit-any error in SupplyChainMap
kalyan-1845 daae4f0
chore: remove accidental files and install leaflet dependencies
kalyan-1845 e696075
fix: restore package-lock.json from main to fix CI
kalyan-1845 49611d2
chore: update package-lock.json with leaflet dependencies
kalyan-1845 7e52ad6
fix: resolve merge conflicts with upstream main
kalyan-1845 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet'; | ||
| import 'leaflet/dist/leaflet.css'; | ||
| import L from 'leaflet'; | ||
| import { Card } from '@/components/ui/card'; | ||
| import { Badge } from '@/components/ui/badge'; | ||
| import { format } from 'date-fns'; | ||
|
|
||
| // Fix for default marker icon in react-leaflet | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| delete (L.Icon.Default.prototype as any)._getIconUrl; | ||
| L.Icon.Default.mergeOptions({ | ||
| iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png', | ||
| iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png', | ||
| shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png', | ||
| }); | ||
|
|
||
| // A simple dictionary to map common string locations to approx coordinates (Lat, Lng) | ||
| const REGION_COORDINATES: Record<string, [number, number]> = { | ||
| 'Lampung': [-4.5586, 105.4068], | ||
| 'Sumatra': [-0.5897, 101.3431], | ||
| 'Java': [-7.6145, 110.7122], | ||
| 'Bali': [-8.4095, 115.1889], | ||
| 'Kalimantan': [1.4326, 114.1511], | ||
| 'Sulawesi': [-1.4300, 121.4456], | ||
| 'Papua': [-4.2699, 138.0803], | ||
| 'Jakarta': [-6.2088, 106.8456], | ||
| 'Bandung': [-6.9175, 107.6191], | ||
| 'Surabaya': [-7.2504, 112.7688], | ||
| 'Default': [-0.7893, 113.9213] // Center of Indonesia | ||
| }; | ||
|
|
||
| export interface MapBatch { | ||
| id: string; | ||
| batch_name: string; | ||
| location: string; | ||
| quantity: number; | ||
| harvest_date: string; | ||
| status: string; | ||
| farmer_id: string; | ||
| hcs_tx_id?: string; | ||
| ai_analysis?: { | ||
| riskLevel?: string; | ||
| }; | ||
| } | ||
|
|
||
| interface SupplyChainMapProps { | ||
| batches: MapBatch[]; | ||
| } | ||
|
|
||
| function getCoordinates(locationStr: string): [number, number] { | ||
| if (!locationStr) return REGION_COORDINATES['Default']; | ||
|
|
||
| const loc = locationStr.toLowerCase(); | ||
| for (const [region, coords] of Object.entries(REGION_COORDINATES)) { | ||
| if (loc.includes(region.toLowerCase())) { | ||
| // Add a tiny random jitter so markers in the same region don't completely overlap | ||
| const jitterLat = (Math.random() - 0.5) * 0.5; | ||
| const jitterLng = (Math.random() - 0.5) * 0.5; | ||
| return [coords[0] + jitterLat, coords[1] + jitterLng]; | ||
| } | ||
| } | ||
|
|
||
| // If not found, place in center of Indonesia with wider jitter | ||
| const jitterLat = (Math.random() - 0.5) * 5; | ||
| const jitterLng = (Math.random() - 0.5) * 10; | ||
| return [REGION_COORDINATES['Default'][0] + jitterLat, REGION_COORDINATES['Default'][1] + jitterLng]; | ||
| } | ||
|
|
||
| export function SupplyChainMap({ batches }: SupplyChainMapProps) { | ||
| // Center of Indonesia | ||
| const centerPosition: [number, number] = [-0.7893, 113.9213]; | ||
|
|
||
| return ( | ||
| <Card className="w-full h-[600px] overflow-hidden rounded-xl border border-border shadow-sm"> | ||
| <MapContainer | ||
| center={centerPosition} | ||
| zoom={5} | ||
| scrollWheelZoom={true} | ||
| style={{ height: '100%', width: '100%', zIndex: 0 }} | ||
| > | ||
| <TileLayer | ||
| attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' | ||
| url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" | ||
| /> | ||
|
|
||
| {batches.map((batch) => { | ||
| const position = getCoordinates(batch.location); | ||
| const riskLevel = batch.ai_analysis?.riskLevel || 'Unknown'; | ||
|
|
||
| let riskColor = 'bg-gray-500'; | ||
| if (riskLevel.toLowerCase() === 'low') riskColor = 'bg-emerald-500'; | ||
| if (riskLevel.toLowerCase() === 'medium') riskColor = 'bg-yellow-500'; | ||
| if (riskLevel.toLowerCase() === 'high') riskColor = 'bg-red-500'; | ||
|
|
||
| return ( | ||
| <Marker key={batch.id} position={position}> | ||
| <Popup className="custom-popup"> | ||
| <div className="flex flex-col space-y-2 p-1 min-w-[200px]"> | ||
| <h3 className="font-bold text-lg leading-tight">{batch.batch_name}</h3> | ||
| <div className="flex items-center space-x-2"> | ||
| <Badge variant="outline" className={riskColor + " text-white border-none"}> | ||
| {riskLevel} Risk | ||
| </Badge> | ||
| <Badge variant="secondary">{batch.status}</Badge> | ||
| </div> | ||
| <div className="text-sm text-muted-foreground mt-2"> | ||
| <p><strong>Location:</strong> {batch.location || 'Unknown'}</p> | ||
| <p><strong>Quantity:</strong> {batch.quantity} kg</p> | ||
| <p><strong>Harvest:</strong> {batch.harvest_date ? format(new Date(batch.harvest_date), 'MMM d, yyyy') : 'N/A'}</p> | ||
| <p><strong>Farmer ID:</strong> {batch.farmer_id.substring(0, 8)}...</p> | ||
| </div> | ||
| {batch.hcs_tx_id && ( | ||
| <a | ||
| href={`https://hashscan.io/testnet/transaction/${batch.hcs_tx_id}`} | ||
| target="_blank" | ||
| rel="noreferrer" | ||
| className="text-xs text-blue-500 hover:underline mt-2 inline-block" | ||
| > | ||
| View on HashScan | ||
| </a> | ||
| )} | ||
| </div> | ||
| </Popup> | ||
| </Marker> | ||
| ); | ||
| })} | ||
| </MapContainer> | ||
| </Card> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import { Helmet } from 'react-helmet-async'; | ||
| import { useQuery } from '@tanstack/react-query'; | ||
| import { getBatches } from '@/lib/api'; | ||
| import Navbar from '@/components/Navbar'; | ||
| import Footer from '@/components/Footer'; | ||
| import { SupplyChainMap, MapBatch } from '@/components/SupplyChainMap'; | ||
| import { Loader2 } from 'lucide-react'; | ||
|
|
||
| export default function MapExplore() { | ||
| const { data: batches, isLoading, error } = useQuery({ | ||
| queryKey: ['explore-batches'], | ||
| queryFn: getBatches, | ||
| }); | ||
|
|
||
| return ( | ||
| <div className="min-h-screen bg-background text-foreground flex flex-col"> | ||
| <Helmet> | ||
| <title>Supply Chain Map | AgroDex</title> | ||
| </Helmet> | ||
| <Navbar /> | ||
|
|
||
| <main className="flex-grow container mx-auto px-4 py-8 max-w-7xl"> | ||
| <div className="mb-6"> | ||
| <h1 className="text-3xl font-extrabold text-gray-900 dark:text-white tracking-tight"> | ||
| Supply Chain Map | ||
| </h1> | ||
| <p className="text-muted-foreground mt-2"> | ||
| Explore registered agricultural batches across Indonesia. Click on any pin to view provenance, fraud risk, and Hedera verification status. | ||
| </p> | ||
| </div> | ||
|
|
||
| {isLoading ? ( | ||
| <div className="flex items-center justify-center h-[600px] border rounded-xl bg-gray-50/50 dark:bg-gray-900/50"> | ||
| <Loader2 className="w-10 h-10 animate-spin text-emerald-500" /> | ||
| <span className="ml-3 text-lg text-gray-500">Loading supply chain data...</span> | ||
| </div> | ||
| ) : error ? ( | ||
| <div className="flex items-center justify-center h-[600px] border border-red-200 rounded-xl bg-red-50 text-red-600"> | ||
| Failed to load map data. Please try again later. | ||
| </div> | ||
| ) : ( | ||
| <SupplyChainMap batches={batches as MapBatch[] || []} /> | ||
| )} | ||
| </main> | ||
|
|
||
| <Footer /> | ||
| </div> | ||
| ); | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delete this file as it is not needed.