Files
g1flux/src/data/mockData.ts
T
syoul 8d9a9a3c07 feat: show country flag next to city names in top villes
Determine country from geoPoint coordinates using bounding boxes
for the main Ğ1 community countries (FR, BE, CH, ES, DE, IT, ...).
Display the emoji flag before each city name in the top villes panel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 18:52:00 +01:00

117 lines
4.4 KiB
TypeScript

export interface Transaction {
id: string;
timestamp: number; // Unix ms (entier)
lat: number;
lng: number;
amount: number; // Ğ1 (pas en centimes)
city: string;
countryCode: string; // ISO 3166-1 alpha-2, ex: "FR"
fromKey: string; // SS58 Ğ1v2 : préfixe "g1", ~50 chars
toKey: string;
}
// French + European cities where Ğ1 is used
const CITIES: { name: string; lat: number; lng: number; weight: number }[] = [
{ name: 'Paris', lat: 48.8566, lng: 2.3522, weight: 12 },
{ name: 'Lyon', lat: 45.7640, lng: 4.8357, weight: 9 },
{ name: 'Bordeaux', lat: 44.8378, lng: -0.5792, weight: 8 },
{ name: 'Toulouse', lat: 43.6047, lng: 1.4442, weight: 8 },
{ name: 'Montpellier', lat: 43.6108, lng: 3.8767, weight: 7 },
{ name: 'Nantes', lat: 47.2184, lng: -1.5536, weight: 6 },
{ name: 'Rennes', lat: 48.1173, lng: -1.6778, weight: 6 },
{ name: 'Grenoble', lat: 45.1885, lng: 5.7245, weight: 5 },
{ name: 'Marseille', lat: 43.2965, lng: 5.3698, weight: 7 },
{ name: 'Strasbourg', lat: 48.5734, lng: 7.7521, weight: 4 },
{ name: 'Lille', lat: 50.6292, lng: 3.0573, weight: 4 },
{ name: 'Rouen', lat: 49.4432, lng: 1.0993, weight: 3 },
{ name: 'Clermont-Ferrand', lat: 45.7772, lng: 3.0870, weight: 4 },
{ name: 'Tours', lat: 47.3941, lng: 0.6848, weight: 3 },
{ name: 'Poitiers', lat: 46.5802, lng: 0.3404, weight: 3 },
{ name: 'Besançon', lat: 47.2378, lng: 6.0241, weight: 3 },
{ name: 'Caen', lat: 49.1829, lng: -0.3707, weight: 2 },
{ name: 'Nice', lat: 43.7102, lng: 7.2620, weight: 4 },
{ name: 'Barcelone', lat: 41.3851, lng: 2.1734, weight: 3 },
{ name: 'Bruxelles', lat: 50.8503, lng: 4.3517, weight: 3 },
{ name: 'Genève', lat: 46.2044, lng: 6.1432, weight: 2 },
{ name: 'Saint-Étienne', lat: 45.4397, lng: 4.3872, weight: 3 },
{ name: 'Dijon', lat: 47.3220, lng: 5.0415, weight: 3 },
{ name: 'Angers', lat: 47.4784, lng: -0.5632, weight: 2 },
];
function randomBetween(min: number, max: number): number {
return Math.random() * (max - min) + min;
}
function weightedRandom<T extends { weight: number }>(items: T[]): T {
const totalWeight = items.reduce((sum, item) => sum + item.weight, 0);
let rand = Math.random() * totalWeight;
for (const item of items) {
rand -= item.weight;
if (rand <= 0) return item;
}
return items[items.length - 1];
}
// Génère une clé SS58 Ğ1v2 simulée : préfixe "g1" + 48 chars base58
function generateKey(): string {
const chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
const suffix = Array.from({ length: 47 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
return 'g1' + suffix;
}
function generateTransactions(count: number, maxAgeMs: number): Transaction[] {
const now = Date.now();
const transactions: Transaction[] = [];
for (let i = 0; i < count; i++) {
const city = weightedRandom(CITIES);
const lat = city.lat + randomBetween(-0.08, 0.08);
const lng = city.lng + randomBetween(-0.12, 0.12);
const amount = Math.round(randomBetween(0.5, 150) * 100) / 100;
transactions.push({
id: `tx-${i}-${Math.random().toString(36).slice(2)}`,
timestamp: Math.floor(now - Math.random() * maxAgeMs),
lat,
lng,
amount,
city: city.name,
countryCode: 'FR',
fromKey: generateKey(),
toKey: generateKey(),
});
}
return transactions.sort((a, b) => b.timestamp - a.timestamp);
}
const TRANSACTION_POOL = generateTransactions(2400, 30 * 24 * 60 * 60 * 1000);
export function getTransactionsForPeriod(periodDays: number): Transaction[] {
const cutoff = Date.now() - periodDays * 24 * 60 * 60 * 1000;
return TRANSACTION_POOL.filter((tx) => tx.timestamp >= cutoff);
}
export function computeStats(transactions: Transaction[]) {
const totalVolume = transactions.reduce((sum, tx) => sum + tx.amount, 0);
const transactionCount = transactions.length;
const cityVolumes: Record<string, { volume: number; count: number; countryCode: string }> = {};
for (const tx of transactions) {
if (!cityVolumes[tx.city]) {
cityVolumes[tx.city] = { volume: 0, count: 0, countryCode: tx.countryCode ?? '' };
}
cityVolumes[tx.city].volume += tx.amount;
cityVolumes[tx.city].count += 1;
}
const topCities = Object.entries(cityVolumes)
.sort((a, b) => b[1].volume - a[1].volume)
.slice(0, 3)
.map(([name, data]) => ({ name, ...data }));
return { totalVolume, transactionCount, topCities };
}
export type { };