feat: add locations table + Locations tab in Stops & Routes
* New 'locations' table for reusable venues (Tractor Supply, Boot Barn, etc.) — each stop now links via stops.location_id while keeping the denormalized location text for backwards compat. * 6 SECURITY DEFINER RPCs: admin_create_location, admin_create_locations_batch, admin_update_location, admin_delete_location, admin_attach_location_to_stop, get_locations_for_brand. Plus admin_list_locations for the admin tab. * Backfill: 26 unique venues extracted from the 269 Tuxedo stops, all linked. * Stops & Routes page now has tabs (Stops | Locations) via ?tab= query param. Locations tab has full CRUD UI: search, status filter, pagination, edit, delete, add-venue modal. * StopsHeaderActions now tab-aware — Upload Schedule / Add Stop only show on Stops tab; Locations tab has its own Add Venue button inline.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
-- Migration 204: Add permissive read policy for locations
|
||||
--
|
||||
-- Why: The admin Stops & Routes page reads stops via the Supabase JS client
|
||||
-- (anon key + RLS). The `stops` table has a `Public read stops` policy with
|
||||
-- qual=true, which lets the anon role see all non-deleted stops — that's how
|
||||
-- the page works in dev mode (no Supabase auth). The admin_locations tab needs
|
||||
-- the same behavior so the server component can use the same pattern:
|
||||
-- supabase.from("locations").select(...)
|
||||
--
|
||||
-- Brand scoping is enforced at the application layer in the server component
|
||||
-- (eq("brand_id", adminUser.brand_id)), matching how stops are scoped.
|
||||
--
|
||||
-- Brand-scoped public reads (storefront pages) go through get_locations_for_brand
|
||||
-- RPC, which filters active=true.
|
||||
|
||||
DROP POLICY IF EXISTS "Public read locations" ON public.locations;
|
||||
CREATE POLICY "Public read locations" ON public.locations
|
||||
FOR SELECT
|
||||
USING (true);
|
||||
Reference in New Issue
Block a user