Migrate grateWizard from React/Next.js to native Nuxt integration
- Port all React components to Vue 3 (GwTabs, GwMN, GwCRA, GwCRS, GwMap, GwRelations, GwPerimeterList) - Port hooks to Vue composables (useCesiumProfiles, useSavedPerimeters) - Copy pure TS services and utils (duniter/, ss58, gratewizard utils) - Add Leaflet + Geoman + MarkerCluster dependencies - Serve grateWizard as popup via /gratewizard?popup (layout: false) and info page on /gratewizard (with Librodrome layout) - Remove public/gratewizard-app/ static Next.js export - Refine UI: compact tabs, buttons, inputs, cards, perimeter list - Use Ğ1 breve everywhere, French locale for all dates and amounts - Rename roles: vendeur→offre / acheteur→reçoit le produit ou service - Rename prix→évaluation in all visible text - Add calculated result column in CRA and CRS relation tables - DU/Ğ1 selector uses toggle switch (same as role toggle) - Auto-scroll to monetary data card on polygon selection Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
69
app/utils/gratewizard.ts
Normal file
69
app/utils/gratewizard.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/** Ray-casting algorithm to test if a point is inside a polygon */
|
||||
export function pointInPolygon(lat: number, lng: number, polygon: [number, number][]): boolean {
|
||||
let inside = false;
|
||||
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
||||
const [yi, xi] = polygon[i];
|
||||
const [yj, xj] = polygon[j];
|
||||
if ((yi > lat) !== (yj > lat) && lng < ((xj - xi) * (lat - yi)) / (yj - yi) + xi) {
|
||||
inside = !inside;
|
||||
}
|
||||
}
|
||||
return inside;
|
||||
}
|
||||
|
||||
/** Format a number in French locale */
|
||||
export const fr = (n: number, decimals = 2) =>
|
||||
n.toLocaleString('fr-FR', { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
|
||||
|
||||
export type CurrencyUnit = 'DU' | 'G1';
|
||||
|
||||
/** Format a G1 value in the given unit, with k/M suffix */
|
||||
export function formatValue(g1Value: number, unit: CurrencyUnit, duDaily: number): string {
|
||||
const val = unit === 'DU' ? g1Value / duDaily : g1Value;
|
||||
const suffix = unit === 'DU' ? 'DU' : '\u011e1';
|
||||
if (val >= 1_000_000) return fr(val / 1_000_000) + ' M' + suffix;
|
||||
if (val >= 1_000) return fr(val / 1_000) + ' k' + suffix;
|
||||
return fr(val) + ' ' + suffix;
|
||||
}
|
||||
|
||||
/** Binary-search count of udBlocks entries >= joinBlock (udBlocks is sorted ascending). */
|
||||
export function countUdSince(udBlocks: number[], joinBlock: number): number {
|
||||
let lo = 0, hi = udBlocks.length;
|
||||
while (lo < hi) {
|
||||
const mid = (lo + hi) >> 1;
|
||||
if (udBlocks[mid] < joinBlock) lo = mid + 1;
|
||||
else hi = mid;
|
||||
}
|
||||
return udBlocks.length - lo;
|
||||
}
|
||||
|
||||
/** Date to ISO-like string (yyyy-mm-dd) */
|
||||
export const dateToString = (date: Date) =>
|
||||
date.getFullYear() + '-' + ('0' + (date.getMonth() + 1)).slice(-2) + '-' + ('0' + date.getDate()).slice(-2);
|
||||
|
||||
/** Number of days between a date and today */
|
||||
export const getDays = (date: string | undefined) => {
|
||||
if (!date) return 0;
|
||||
const d = new Date(date);
|
||||
const today = new Date();
|
||||
return Math.floor(Math.abs(d.getTime() - today.getTime()) / (1000 * 3600 * 24));
|
||||
};
|
||||
|
||||
/** Seniority ratio between two dates (days from today) */
|
||||
export const getRatio = (date1: string | undefined, date2: string | undefined) => {
|
||||
return getDays(date1) / Math.max(getDays(date2), 1);
|
||||
};
|
||||
|
||||
export const Block0Date = '2017-03-08';
|
||||
|
||||
export type Friend = {
|
||||
name: string;
|
||||
date: string;
|
||||
};
|
||||
|
||||
export type TableFriend = Friend & {
|
||||
[key: string]: string | number;
|
||||
displayName: string;
|
||||
displayDate: string;
|
||||
du: number;
|
||||
};
|
||||
Reference in New Issue
Block a user