@cofondateurauchomage/libs 1.1.171 → 1.1.177
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/api.validate.js +13 -4
- package/build/crm/searchText.js +3 -3
- package/build/db.model.d.ts +11 -2
- package/build/geo/distance.d.ts +8 -0
- package/build/geo/distance.js +15 -0
- package/build/geo/fetch.d.ts +6 -0
- package/build/geo/fetch.js +158 -0
- package/build/geo/index.d.ts +4 -0
- package/build/geo/index.js +20 -0
- package/build/geo/legacyCityLabel.d.ts +4 -0
- package/build/geo/legacyCityLabel.js +36 -0
- package/build/geo/parse.d.ts +40 -0
- package/build/geo/parse.js +94 -0
- package/build/geo/types.d.ts +12 -0
- package/build/geo/types.js +2 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/package.json +1 -1
package/build/api.validate.js
CHANGED
|
@@ -38,6 +38,15 @@ function optionalStr(schema) {
|
|
|
38
38
|
}
|
|
39
39
|
const zSkillsRequired = zod_1.z.array(zSkill).min(1).max(2);
|
|
40
40
|
const zSkillsOptional = zod_1.z.array(zSkill).min(1).max(2).optional();
|
|
41
|
+
const zProfileLocation = zod_1.z.object({
|
|
42
|
+
lat: zod_1.z.number().min(-90).max(90),
|
|
43
|
+
lng: zod_1.z.number().min(-180).max(180),
|
|
44
|
+
label: zStrMax50,
|
|
45
|
+
regionCode: zStrMax50,
|
|
46
|
+
departmentCode: zStrMax50,
|
|
47
|
+
cityCode: zStrMax50.optional(),
|
|
48
|
+
postcode: zStrMax50.optional(),
|
|
49
|
+
});
|
|
41
50
|
const contactCreate = {
|
|
42
51
|
email: zod_1.z.string(),
|
|
43
52
|
emailProspect: zod_1.z.string(),
|
|
@@ -65,7 +74,7 @@ const projectBody = {
|
|
|
65
74
|
skills: zSkillsRequired,
|
|
66
75
|
yearsOfExperience: zod_1.z.number().min(0).max(100).optional(),
|
|
67
76
|
availability: zAvailabilityWithNeverMind.optional(),
|
|
68
|
-
|
|
77
|
+
location: zProfileLocation,
|
|
69
78
|
invest: zod_1.z.number().min(0).max(1_000_000).optional(),
|
|
70
79
|
partner: zStrMax1000.optional(),
|
|
71
80
|
logo: zod_1.z.string().optional(),
|
|
@@ -76,7 +85,7 @@ const userBody = {
|
|
|
76
85
|
skills: zSkillsRequired,
|
|
77
86
|
yearsOfExperience: zod_1.z.number().min(0).max(100).optional(),
|
|
78
87
|
availability: zAvailability.optional(),
|
|
79
|
-
|
|
88
|
+
location: zProfileLocation,
|
|
80
89
|
invest: zod_1.z.number().min(0).max(1_000_000).optional(),
|
|
81
90
|
motivations: zStrMax1000.optional(),
|
|
82
91
|
partner: zStrMax1000.optional(),
|
|
@@ -93,7 +102,6 @@ const FIELDS_LABEL = {
|
|
|
93
102
|
availability: "Disponibilité",
|
|
94
103
|
bestStrength: "Meilleur atout",
|
|
95
104
|
business: "Business",
|
|
96
|
-
city: "Ville",
|
|
97
105
|
description: "Description",
|
|
98
106
|
email: "Email",
|
|
99
107
|
firstName: "Prénom",
|
|
@@ -101,6 +109,7 @@ const FIELDS_LABEL = {
|
|
|
101
109
|
invest: "Investissement",
|
|
102
110
|
lastName: "Nom",
|
|
103
111
|
linkedin: "Linkedin",
|
|
112
|
+
location: "Localisation",
|
|
104
113
|
motivations: "Motivations",
|
|
105
114
|
name: "Nom",
|
|
106
115
|
partner: "Partenaire",
|
|
@@ -207,7 +216,7 @@ const schemasForAllRoutes = {
|
|
|
207
216
|
skills: zSkillsOptional,
|
|
208
217
|
yearsOfExperience: zod_1.z.number().min(0).max(100).optional(),
|
|
209
218
|
availability: zAvailabilityWithNeverMind.optional(),
|
|
210
|
-
|
|
219
|
+
location: zProfileLocation.optional(),
|
|
211
220
|
invest: zod_1.z.number().min(0).max(1_000_000).optional(),
|
|
212
221
|
partner: zStrMax1000.optional(),
|
|
213
222
|
partner_swanbase: zod_1.z.boolean().optional(),
|
package/build/crm/searchText.js
CHANGED
|
@@ -15,14 +15,14 @@ function buildCrmProfileSearchText(doc) {
|
|
|
15
15
|
pushPart(parts, profile.email);
|
|
16
16
|
pushPart(parts, profile.firstName);
|
|
17
17
|
pushPart(parts, profile.lastName);
|
|
18
|
-
pushPart(parts, profile.
|
|
18
|
+
pushPart(parts, profile.location?.label);
|
|
19
19
|
pushPart(parts, profile.linkedin);
|
|
20
20
|
}
|
|
21
21
|
else if (doc.type === "user") {
|
|
22
22
|
const profile = doc.profile;
|
|
23
23
|
pushPart(parts, profile.firstName);
|
|
24
24
|
pushPart(parts, profile.lastName);
|
|
25
|
-
pushPart(parts, profile.
|
|
25
|
+
pushPart(parts, profile.location?.label);
|
|
26
26
|
pushPart(parts, doc.contact?.email);
|
|
27
27
|
pushPart(parts, doc.contact?.linkedin);
|
|
28
28
|
}
|
|
@@ -31,7 +31,7 @@ function buildCrmProfileSearchText(doc) {
|
|
|
31
31
|
pushPart(parts, profile.name);
|
|
32
32
|
pushPart(parts, profile.firstName);
|
|
33
33
|
pushPart(parts, profile.lastName);
|
|
34
|
-
pushPart(parts, profile.
|
|
34
|
+
pushPart(parts, profile.location?.label);
|
|
35
35
|
pushPart(parts, doc.contact?.email);
|
|
36
36
|
pushPart(parts, doc.contact?.linkedin);
|
|
37
37
|
}
|
package/build/db.model.d.ts
CHANGED
|
@@ -29,13 +29,22 @@ export interface IClicks {
|
|
|
29
29
|
share: number;
|
|
30
30
|
website: number;
|
|
31
31
|
}
|
|
32
|
+
export interface IProfileLocation {
|
|
33
|
+
lat: number;
|
|
34
|
+
lng: number;
|
|
35
|
+
label: string;
|
|
36
|
+
regionCode: string;
|
|
37
|
+
departmentCode: string;
|
|
38
|
+
cityCode?: string;
|
|
39
|
+
postcode?: string;
|
|
40
|
+
}
|
|
32
41
|
export interface IUser {
|
|
33
42
|
lastName: string;
|
|
34
43
|
firstName: string;
|
|
35
44
|
skills: TSkill[];
|
|
36
45
|
yearsOfExperience?: number;
|
|
37
46
|
availability?: TAvailability;
|
|
38
|
-
|
|
47
|
+
location?: IProfileLocation;
|
|
39
48
|
invest?: number;
|
|
40
49
|
motivations?: string;
|
|
41
50
|
bestStrength?: string;
|
|
@@ -69,7 +78,7 @@ export interface IProject {
|
|
|
69
78
|
skills: TSkill[];
|
|
70
79
|
yearsOfExperience?: number;
|
|
71
80
|
availability?: (TAvailability | 'never_mind');
|
|
72
|
-
|
|
81
|
+
location?: IProfileLocation;
|
|
73
82
|
invest?: number;
|
|
74
83
|
partner?: string;
|
|
75
84
|
logo?: string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.haversineKm = haversineKm;
|
|
4
|
+
const EARTH_RADIUS_KM = 6371;
|
|
5
|
+
/** Great-circle distance between two WGS84 points (V2 radius filter). */
|
|
6
|
+
function haversineKm(a, b) {
|
|
7
|
+
const toRad = (deg) => (deg * Math.PI) / 180;
|
|
8
|
+
const dLat = toRad(b.lat - a.lat);
|
|
9
|
+
const dLng = toRad(b.lng - a.lng);
|
|
10
|
+
const lat1 = toRad(a.lat);
|
|
11
|
+
const lat2 = toRad(b.lat);
|
|
12
|
+
const h = Math.sin(dLat / 2) ** 2 +
|
|
13
|
+
Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLng / 2) ** 2;
|
|
14
|
+
return 2 * EARTH_RADIUS_KM * Math.asin(Math.sqrt(h));
|
|
15
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ILocationSuggestion } from "./types";
|
|
2
|
+
export declare function getRegionCodeForDepartment(departmentCode: string): Promise<string | undefined>;
|
|
3
|
+
/** Search regions, departments (geo.api) and cities/postcodes (API Adresse). */
|
|
4
|
+
export declare function searchLocationSuggestions(query: string): Promise<ILocationSuggestion[]>;
|
|
5
|
+
/** Geocode a legacy free-text city label (migration script). */
|
|
6
|
+
export declare function geocodeCityLabel(cityLabel: string): Promise<ILocationSuggestion | null>;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getRegionCodeForDepartment = getRegionCodeForDepartment;
|
|
4
|
+
exports.searchLocationSuggestions = searchLocationSuggestions;
|
|
5
|
+
exports.geocodeCityLabel = geocodeCityLabel;
|
|
6
|
+
const legacyCityLabel_1 = require("./legacyCityLabel");
|
|
7
|
+
const parse_1 = require("./parse");
|
|
8
|
+
const GEO_API = "https://geo.api.gouv.fr";
|
|
9
|
+
const ADRESSE_API = "https://api-adresse.data.gouv.fr";
|
|
10
|
+
const departmentRegionCache = new Map();
|
|
11
|
+
async function getRegionCodeForDepartment(departmentCode) {
|
|
12
|
+
if (!departmentCode) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
if (departmentRegionCache.has(departmentCode)) {
|
|
16
|
+
return departmentRegionCache.get(departmentCode);
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const res = await fetch(`${GEO_API}/departements/${departmentCode}`);
|
|
20
|
+
if (!res.ok) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
const data = (await res.json());
|
|
24
|
+
departmentRegionCache.set(departmentCode, data.codeRegion);
|
|
25
|
+
return data.codeRegion;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function fetchGeoApi(path) {
|
|
32
|
+
try {
|
|
33
|
+
const res = await fetch(`${GEO_API}${path}`);
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
const data = await res.json();
|
|
38
|
+
return Array.isArray(data) ? data : [];
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function fetchAdresseFeatures(q) {
|
|
45
|
+
try {
|
|
46
|
+
const params = new URLSearchParams({ q, limit: "8", autocomplete: "1" });
|
|
47
|
+
const res = await fetch(`${ADRESSE_API}/search/?${params}`);
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
const data = (await res.json());
|
|
52
|
+
return data.features ?? [];
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function rankSuggestion(suggestion, q) {
|
|
59
|
+
const normalized = q.trim().toLowerCase();
|
|
60
|
+
if (/^\d{5}$/.test(normalized) && suggestion.postcode === normalized) {
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
if (suggestion.type === "department" &&
|
|
64
|
+
suggestion.departmentCode?.toLowerCase() === normalized) {
|
|
65
|
+
return 1;
|
|
66
|
+
}
|
|
67
|
+
if (suggestion.label.toLowerCase().startsWith(normalized)) {
|
|
68
|
+
return 2;
|
|
69
|
+
}
|
|
70
|
+
return 3;
|
|
71
|
+
}
|
|
72
|
+
function dedupeSuggestions(suggestions) {
|
|
73
|
+
const seen = new Set();
|
|
74
|
+
return suggestions.filter((s) => {
|
|
75
|
+
const key = `${s.type}:${s.regionCode}:${s.departmentCode ?? ""}:${s.cityCode ?? ""}:${s.postcode ?? ""}`;
|
|
76
|
+
if (seen.has(key)) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
seen.add(key);
|
|
80
|
+
return true;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/** Search regions, departments (geo.api) and cities/postcodes (API Adresse). */
|
|
84
|
+
async function searchLocationSuggestions(query) {
|
|
85
|
+
const q = query.trim();
|
|
86
|
+
if (q.length < 2) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
const encoded = encodeURIComponent(q);
|
|
90
|
+
const [regions, departments, features] = await Promise.all([
|
|
91
|
+
fetchGeoApi(`/regions?nom=${encoded}`),
|
|
92
|
+
fetchGeoApi(`/departements?nom=${encoded}`),
|
|
93
|
+
fetchAdresseFeatures(q),
|
|
94
|
+
]);
|
|
95
|
+
const suggestions = [
|
|
96
|
+
...regions.map(parse_1.parseGeoApiRegion),
|
|
97
|
+
...departments.map(parse_1.parseGeoApiDepartment),
|
|
98
|
+
];
|
|
99
|
+
// Match department by code (e.g. "35", "2A")
|
|
100
|
+
if (/^\d{1,3}[ab]?$/i.test(q)) {
|
|
101
|
+
try {
|
|
102
|
+
const res = await fetch(`${GEO_API}/departements/${q.toUpperCase()}`);
|
|
103
|
+
if (res.ok) {
|
|
104
|
+
const dept = (await res.json());
|
|
105
|
+
suggestions.push((0, parse_1.parseGeoApiDepartment)(dept));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// ignore
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const departmentCodes = new Set();
|
|
113
|
+
for (const feature of features) {
|
|
114
|
+
const ctx = feature.properties.context?.split(",")[0]?.trim();
|
|
115
|
+
if (ctx) {
|
|
116
|
+
departmentCodes.add(ctx);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const regionByDepartment = new Map();
|
|
120
|
+
await Promise.all([...departmentCodes].map(async (code) => {
|
|
121
|
+
const regionCode = await getRegionCodeForDepartment(code);
|
|
122
|
+
if (regionCode) {
|
|
123
|
+
regionByDepartment.set(code, regionCode);
|
|
124
|
+
}
|
|
125
|
+
}));
|
|
126
|
+
for (const feature of features) {
|
|
127
|
+
const deptCode = feature.properties.context?.split(",")[0]?.trim() ?? "";
|
|
128
|
+
const regionCode = regionByDepartment.get(deptCode);
|
|
129
|
+
if (!regionCode) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const parsed = (0, parse_1.parseAdresseFeature)(feature, regionCode);
|
|
133
|
+
if (parsed) {
|
|
134
|
+
suggestions.push(parsed);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return dedupeSuggestions(suggestions)
|
|
138
|
+
.sort((a, b) => rankSuggestion(a, q) - rankSuggestion(b, q))
|
|
139
|
+
.slice(0, 12);
|
|
140
|
+
}
|
|
141
|
+
/** Geocode a legacy free-text city label (migration script). */
|
|
142
|
+
async function geocodeCityLabel(cityLabel) {
|
|
143
|
+
if ((0, legacyCityLabel_1.isUngeocodableLegacyCityLabel)(cityLabel)) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
const q = `${cityLabel.trim()}, France`;
|
|
147
|
+
const features = await fetchAdresseFeatures(q);
|
|
148
|
+
if (!features.length) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
const feature = features[0];
|
|
152
|
+
const deptCode = feature.properties.context?.split(",")[0]?.trim() ?? "";
|
|
153
|
+
const regionCode = await getRegionCodeForDepartment(deptCode);
|
|
154
|
+
if (!regionCode) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
return (0, parse_1.parseAdresseFeature)(feature, regionCode);
|
|
158
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./distance"), exports);
|
|
18
|
+
__exportStar(require("./fetch"), exports);
|
|
19
|
+
__exportStar(require("./parse"), exports);
|
|
20
|
+
__exportStar(require("./types"), exports);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Normalize legacy free-text city labels for comparison. */
|
|
2
|
+
export declare function normalizeLegacyCityLabel(label: string): string;
|
|
3
|
+
/** Returns true when a legacy city string is too vague to geocode reliably. */
|
|
4
|
+
export declare function isUngeocodableLegacyCityLabel(cityLabel: string): boolean;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeLegacyCityLabel = normalizeLegacyCityLabel;
|
|
4
|
+
exports.isUngeocodableLegacyCityLabel = isUngeocodableLegacyCityLabel;
|
|
5
|
+
/** Normalize legacy free-text city labels for comparison. */
|
|
6
|
+
function normalizeLegacyCityLabel(label) {
|
|
7
|
+
return label
|
|
8
|
+
.trim()
|
|
9
|
+
.toLowerCase()
|
|
10
|
+
.normalize("NFD")
|
|
11
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
12
|
+
.replace(/[.!,;:]+$/g, "")
|
|
13
|
+
.replace(/\s+/g, " ");
|
|
14
|
+
}
|
|
15
|
+
/** Country-wide or non-local labels that must not be sent to API Adresse. */
|
|
16
|
+
const UNGEOCODABLE_LEGACY_CITY_LABELS = new Set([
|
|
17
|
+
"france",
|
|
18
|
+
"toute la france",
|
|
19
|
+
"en france",
|
|
20
|
+
"france entiere",
|
|
21
|
+
"france metropolitaine",
|
|
22
|
+
"metropole",
|
|
23
|
+
"remote",
|
|
24
|
+
"teletravail",
|
|
25
|
+
"partout",
|
|
26
|
+
"n/a",
|
|
27
|
+
"na",
|
|
28
|
+
]);
|
|
29
|
+
/** Returns true when a legacy city string is too vague to geocode reliably. */
|
|
30
|
+
function isUngeocodableLegacyCityLabel(cityLabel) {
|
|
31
|
+
const normalized = normalizeLegacyCityLabel(cityLabel);
|
|
32
|
+
if (!normalized) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
return UNGEOCODABLE_LEGACY_CITY_LABELS.has(normalized);
|
|
36
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { IProfileLocation } from "../db.model";
|
|
2
|
+
import type { ILocationSuggestion } from "./types";
|
|
3
|
+
type GeoApiCentre = {
|
|
4
|
+
coordinates?: [number, number];
|
|
5
|
+
};
|
|
6
|
+
type GeoApiRegion = {
|
|
7
|
+
code: string;
|
|
8
|
+
nom: string;
|
|
9
|
+
centre?: GeoApiCentre;
|
|
10
|
+
};
|
|
11
|
+
type GeoApiDepartment = {
|
|
12
|
+
code: string;
|
|
13
|
+
nom: string;
|
|
14
|
+
codeRegion: string;
|
|
15
|
+
centre?: GeoApiCentre;
|
|
16
|
+
};
|
|
17
|
+
type AdresseFeature = {
|
|
18
|
+
properties: {
|
|
19
|
+
label: string;
|
|
20
|
+
score?: number;
|
|
21
|
+
city?: string;
|
|
22
|
+
postcode?: string;
|
|
23
|
+
citycode?: string;
|
|
24
|
+
context?: string;
|
|
25
|
+
};
|
|
26
|
+
geometry: {
|
|
27
|
+
coordinates: [number, number];
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
export declare function parseGeoApiRegion(region: GeoApiRegion): ILocationSuggestion;
|
|
31
|
+
export declare function parseGeoApiDepartment(department: GeoApiDepartment): ILocationSuggestion;
|
|
32
|
+
/** Parse API Adresse `context`: "68, Haut-Rhin, Grand Est". */
|
|
33
|
+
export declare function parseAdresseContext(context?: string): {
|
|
34
|
+
departmentCode: string;
|
|
35
|
+
regionName: string;
|
|
36
|
+
};
|
|
37
|
+
export declare function parseAdresseFeature(feature: AdresseFeature, regionCode: string): ILocationSuggestion | null;
|
|
38
|
+
export declare function suggestionToProfileLocation(suggestion: ILocationSuggestion): IProfileLocation;
|
|
39
|
+
export declare function normalizeProfileLocation(location: IProfileLocation): IProfileLocation;
|
|
40
|
+
export type { AdresseFeature, GeoApiDepartment, GeoApiRegion };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseGeoApiRegion = parseGeoApiRegion;
|
|
4
|
+
exports.parseGeoApiDepartment = parseGeoApiDepartment;
|
|
5
|
+
exports.parseAdresseContext = parseAdresseContext;
|
|
6
|
+
exports.parseAdresseFeature = parseAdresseFeature;
|
|
7
|
+
exports.suggestionToProfileLocation = suggestionToProfileLocation;
|
|
8
|
+
exports.normalizeProfileLocation = normalizeProfileLocation;
|
|
9
|
+
const coordsFromCentre = (centre) => {
|
|
10
|
+
const [lng, lat] = centre?.coordinates ?? [0, 0];
|
|
11
|
+
return { lat, lng };
|
|
12
|
+
};
|
|
13
|
+
function parseGeoApiRegion(region) {
|
|
14
|
+
const { lat, lng } = coordsFromCentre(region.centre);
|
|
15
|
+
return {
|
|
16
|
+
type: "region",
|
|
17
|
+
label: region.nom,
|
|
18
|
+
regionCode: region.code,
|
|
19
|
+
lat,
|
|
20
|
+
lng,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function parseGeoApiDepartment(department) {
|
|
24
|
+
const { lat, lng } = coordsFromCentre(department.centre);
|
|
25
|
+
return {
|
|
26
|
+
type: "department",
|
|
27
|
+
label: `${department.nom} (${department.code})`,
|
|
28
|
+
regionCode: department.codeRegion,
|
|
29
|
+
departmentCode: department.code,
|
|
30
|
+
lat,
|
|
31
|
+
lng,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Parse API Adresse `context`: "68, Haut-Rhin, Grand Est". */
|
|
35
|
+
function parseAdresseContext(context) {
|
|
36
|
+
if (!context) {
|
|
37
|
+
return { departmentCode: "", regionName: "" };
|
|
38
|
+
}
|
|
39
|
+
const parts = context.split(",").map((p) => p.trim());
|
|
40
|
+
return {
|
|
41
|
+
departmentCode: parts[0] ?? "",
|
|
42
|
+
regionName: parts[2] ?? parts[1] ?? "",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function parseAdresseFeature(feature, regionCode) {
|
|
46
|
+
const { properties, geometry } = feature;
|
|
47
|
+
const city = properties.city?.trim();
|
|
48
|
+
if (!city) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const { departmentCode } = parseAdresseContext(properties.context);
|
|
52
|
+
const [lng, lat] = geometry.coordinates;
|
|
53
|
+
const postcode = properties.postcode?.trim();
|
|
54
|
+
const cityCode = properties.citycode?.trim();
|
|
55
|
+
const label = postcode ? `${city} (${postcode})` : city;
|
|
56
|
+
return {
|
|
57
|
+
type: "city",
|
|
58
|
+
label,
|
|
59
|
+
regionCode,
|
|
60
|
+
departmentCode: departmentCode || undefined,
|
|
61
|
+
cityCode: cityCode || undefined,
|
|
62
|
+
lat,
|
|
63
|
+
lng,
|
|
64
|
+
postcode,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function suggestionToProfileLocation(suggestion) {
|
|
68
|
+
const displayLabel = suggestion.label
|
|
69
|
+
.replace(/\s*\(\d{5}\)$/, "")
|
|
70
|
+
.replace(/\s*\(\d{1,3}[ABab]?\)$/, "")
|
|
71
|
+
.trim();
|
|
72
|
+
const base = {
|
|
73
|
+
lat: suggestion.lat,
|
|
74
|
+
lng: suggestion.lng,
|
|
75
|
+
label: displayLabel,
|
|
76
|
+
regionCode: suggestion.regionCode,
|
|
77
|
+
departmentCode: suggestion.departmentCode ?? "",
|
|
78
|
+
postcode: suggestion.postcode,
|
|
79
|
+
};
|
|
80
|
+
if (suggestion.type === "city" && suggestion.cityCode) {
|
|
81
|
+
return { ...base, cityCode: suggestion.cityCode };
|
|
82
|
+
}
|
|
83
|
+
return base;
|
|
84
|
+
}
|
|
85
|
+
function normalizeProfileLocation(location) {
|
|
86
|
+
return {
|
|
87
|
+
...location,
|
|
88
|
+
label: location.label.trim(),
|
|
89
|
+
regionCode: location.regionCode.trim(),
|
|
90
|
+
departmentCode: location.departmentCode.trim(),
|
|
91
|
+
cityCode: location.cityCode?.trim() || undefined,
|
|
92
|
+
postcode: location.postcode?.trim() || undefined,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type TLocationSuggestionType = "region" | "department" | "city";
|
|
2
|
+
/** Unified autocomplete row and filter selection. */
|
|
3
|
+
export interface ILocationSuggestion {
|
|
4
|
+
type: TLocationSuggestionType;
|
|
5
|
+
label: string;
|
|
6
|
+
regionCode: string;
|
|
7
|
+
departmentCode?: string;
|
|
8
|
+
cityCode?: string;
|
|
9
|
+
lat: number;
|
|
10
|
+
lng: number;
|
|
11
|
+
postcode?: string;
|
|
12
|
+
}
|
package/build/index.d.ts
CHANGED
package/build/index.js
CHANGED
|
@@ -19,5 +19,6 @@ __exportStar(require("./api.validate"), exports);
|
|
|
19
19
|
__exportStar(require("./const"), exports);
|
|
20
20
|
__exportStar(require("./crm"), exports);
|
|
21
21
|
__exportStar(require("./db.model"), exports);
|
|
22
|
+
__exportStar(require("./geo"), exports);
|
|
22
23
|
__exportStar(require("./regex"), exports);
|
|
23
24
|
__exportStar(require("./utils"), exports);
|