frontend phase 3: geography — addresses, map-pin picker & nurse coverage areas

Three domain services (mirroring the patients/nurse template): services/geography
(cached province→city→district lookups; Infinity staleTime + shared geographyKeys),
services/addresses (address book CRUD + set-primary; single-primary invariant), and
services/serviceAreas (coverage add/remove; areaExists dup-guard, districtId=null = whole city).

Four tested composites in src/components/geography: CascadingRegionSelect (drives the
cascade queries), AddressMapPicker (map-pin stand-in emitting real lat/lng), AddressForm,
AddressCard. Screens: customer address book (/addresses, reached from the profile hub) and
nurse coverage editor (/nurse/coverage, new sidebar tab, inline duplicate block + 409).

Adds geo/address/coverage i18n namespaces (both locales), location/delete/coverage icons,
ADDRESSES/NURSE_COVERAGE routes. Consumes the b4 geography-addresses contract; filed REQ-008
(accept the map pin on create/update) and REQ-009 (provinceId on CustomerAddressDto) for gaps.

Gate: npm run check + npm run test:ci (129, +17) + npm run build all green. A 5-dimension
adversarial review fixed 3 findings (map-marker RTL transform, page_size→pageSize pagination
casing, coverage districts-scope dead-end on district-less cities).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamid
2026-07-05 14:50:41 +03:30
parent 1c266523bc
commit b8934f531d
56 changed files with 2627 additions and 3 deletions
+70
View File
@@ -6,6 +6,7 @@
"wallet": "کیف‌پول",
"profile": "پروفایل",
"bank": "حساب بانکی",
"coverage": "پوشش",
"dashboard": "داشبورد",
"verification": "احراز هویت",
"visits": "ویزیت‌ها",
@@ -165,6 +166,75 @@
"empty_title": "هنوز حسابی ثبت نشده",
"empty_body": "برای دریافت درآمد، یک حساب بانکی به نام خودتان اضافه کنید."
},
"geo": {
"province": "استان",
"city": "شهر",
"district": "منطقه",
"whole_city": "کل شهر",
"city_needs_province": "ابتدا استان را انتخاب کنید",
"district_needs_city": "ابتدا شهر را انتخاب کنید",
"district_optional": "اختیاری",
"no_districts": "این شهر منطقه‌بندی ندارد — کل شهر"
},
"address": {
"title": "آدرس‌ها",
"subtitle": "جایی که پرستار به آن مراجعه می‌کند.",
"add": "افزودن آدرس",
"add_title": "افزودن آدرس",
"edit_title": "ویرایش آدرس",
"edit": "ویرایش",
"delete": "حذف",
"empty_title": "هنوز آدرسی ثبت نشده",
"empty_body": "اولین آدرس را اضافه کنید تا پرستار بداند کجا بیاید.",
"primary": "آدرس اصلی",
"set_primary": "انتخاب به‌عنوان آدرس اصلی",
"primary_set": "آدرس اصلی به‌روزرسانی شد",
"delete_title": "حذف آدرس؟",
"delete_body": "این آدرس از فهرست شما حذف می‌شود.",
"delete_confirm": "حذف",
"deleted": "آدرس حذف شد",
"saved": "آدرس ذخیره شد",
"save_error": "ذخیرهٔ این آدرس ممکن نشد. دوباره تلاش کنید.",
"unavailable": "این آدرس در دسترس شما نیست.",
"title_label": "عنوان آدرس",
"title_placeholder": "مثلاً خانه، محل کار",
"title_required": "برای این آدرس یک عنوان وارد کنید",
"line_label": "نشانی کامل",
"line_hint": "پلاک، خیابان، واحد — جزئیاتی که پرستار برای یافتن در نیاز دارد.",
"line_required": "نشانی کامل را وارد کنید",
"city_required": "شهر را انتخاب کنید",
"set_primary_toggle": "به‌عنوان آدرس اصلی تنظیم شود",
"map_hint": "برای تعیین محل دقیق بیمار، نشانگر را بکشید یا روی نقشه بزنید.",
"map_required": "روی نقشه یک پین بگذارید",
"map_lat": "عرض",
"map_lng": "طول",
"manage_title": "آدرس‌های شما",
"manage_body": "مکان‌هایی که پرستار به آن‌ها می‌رود را مدیریت کنید.",
"manage_cta": "مدیریت آدرس‌ها"
},
"coverage": {
"title": "مناطق تحت پوشش",
"subtitle": "شهرها و مناطقی که به آن‌ها می‌روید.",
"areas_heading": "مناطق شما",
"add_title": "افزودن منطقهٔ تحت پوشش",
"scope_label": "گستره",
"scope_whole_city": "کل شهر",
"scope_districts": "مناطق خاص",
"add": "افزودن منطقه",
"adding": "در حال افزودن…",
"whole_city_chip": "کل شهر",
"empty_title": "هنوز منطقه‌ای ثبت نشده",
"empty_warning": "تا زمانی که حداقل یک منطقهٔ تحت پوشش اضافه نکنید، در جست‌وجو نمایش داده نمی‌شوید.",
"duplicate": "این منطقه از قبل تحت پوشش شماست.",
"city_required": "شهر را انتخاب کنید",
"district_required": "یک منطقه انتخاب کنید یا به کل شهر تغییر دهید.",
"added": "منطقهٔ تحت پوشش اضافه شد",
"add_error": "افزودن این منطقه ممکن نشد. دوباره تلاش کنید.",
"remove_title": "حذف منطقهٔ تحت پوشش؟",
"remove_body": "دیگر برای ویزیت در این منطقه انتخاب نمی‌شوید.",
"remove_confirm": "حذف",
"removed": "منطقهٔ تحت پوشش حذف شد"
},
"auth": {
"customer_title": "ورود به بلینیار",
"customer_subtitle": "با شماره موبایل خود وارد شوید",