{"version":3,"sources":["../../src/hooks/useSchedulingAdmin.ts"],"names":[],"mappings":"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","file":"useSchedulingAdmin.mjs","sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n  resolveSchedulingBasePath,\n  schedulingDelete,\n  schedulingGet,\n  schedulingPatch,\n  schedulingPost,\n  SchedulingClientError,\n} from '../lib/schedulingClient';\nimport type {\n  SchedulingResource,\n  SchedulingResourceServiceLink,\n  SchedulingService,\n  SchedulingSettings,\n  SchedulingTimeOff,\n  SchedulingWorkingHours,\n} from '../types/scheduling';\n\nexport interface UseSchedulingAdminOptions {\n  apiBase?: string;\n  /** Skip initial GETs on mount; consumer drives via .refresh(). */\n  skipInitialLoad?: boolean;\n  /**\n   * When true (default), creating a service auto-fans-out\n   * scheduling_resource_service rows linking the new service to every\n   * active resource — and creating a resource fans out to every active\n   * service. Default = \"every stylist does every service\" — owners opt OUT\n   * via the services picker, not in.\n   *\n   * Set to false to disable the convenience and force the consumer to\n   * write resource_service rows manually after each create.\n   */\n  autoLinkResourceServices?: boolean;\n}\n\ninterface AdminEntity {\n  id: string;\n}\n\nexport interface CrudResult<T extends AdminEntity> {\n  list: T[];\n  loading: boolean;\n  error: string | null;\n  refresh: () => Promise<void>;\n  create: (input: Partial<T>) => Promise<T | null>;\n  update: (id: string, patch: Partial<T>) => Promise<T | null>;\n  remove: (id: string) => Promise<boolean>;\n}\n\nexport interface SingletonResult<T> {\n  value: T | null;\n  loading: boolean;\n  error: string | null;\n  refresh: () => Promise<void>;\n  update: (patch: Partial<T>) => Promise<T | null>;\n}\n\nexport interface UseSchedulingAdminResult {\n  basePath: string;\n  services: CrudResult<SchedulingService>;\n  resources: CrudResult<SchedulingResource>;\n  resourceServices: CrudResult<SchedulingResourceServiceLink & { id: string }>;\n  workingHours: CrudResult<SchedulingWorkingHours>;\n  timeOff: CrudResult<SchedulingTimeOff>;\n  settings: SingletonResult<SchedulingSettings>;\n}\n\ninterface ListResponse<T> {\n  records?: T[];\n  // Some pipelines return collection-named keys (e.g. { services: [...] }).\n  // The hook tolerates either by reading whichever key is non-empty.\n  [k: string]: unknown;\n}\n\nfunction pickList<T>(data: ListResponse<T> | null, key: string): T[] {\n  if (!data) return [];\n  const namespaced = (data as any)[key];\n  if (Array.isArray(namespaced)) return namespaced as T[];\n  if (Array.isArray(data.records)) return data.records as T[];\n  return [];\n}\n\ninterface CrudConfig<T extends AdminEntity> {\n  basePath: string;\n  collection: string; // 'services' | 'resources' | …\n  responseKey: string; // 'services' | 'records' | …\n  initialLoad: boolean;\n}\n\nfunction useCrudResource<T extends AdminEntity>(\n  cfg: CrudConfig<T>,\n): CrudResult<T> {\n  const { basePath, collection, responseKey, initialLoad } = cfg;\n  const [list, setList] = useState<T[]>([]);\n  const [loading, setLoading] = useState(false);\n  const [error, setError] = useState<string | null>(null);\n\n  const setErr = useCallback((err: unknown, fallback: string) => {\n    if (err instanceof SchedulingClientError) setError(err.message);\n    else if (err instanceof Error) setError(err.message);\n    else setError(fallback);\n  }, []);\n\n  const refresh = useCallback(async () => {\n    setLoading(true);\n    setError(null);\n    try {\n      const data = await schedulingGet<ListResponse<T>>(\n        basePath,\n        `/admin/${collection}`,\n      );\n      setList(pickList<T>(data, responseKey));\n    } catch (err) {\n      setErr(err, `Failed to load ${collection}.`);\n    } finally {\n      setLoading(false);\n    }\n  }, [basePath, collection, responseKey, setErr]);\n\n  useEffect(() => {\n    if (!initialLoad) return;\n    refresh();\n  }, [refresh, initialLoad]);\n\n  const create = useCallback(\n    async (input: Partial<T>): Promise<T | null> => {\n      setError(null);\n      try {\n        const data = await schedulingPost<{ record?: T } & Record<string, unknown>>(\n          basePath,\n          `/admin/${collection}`,\n          input,\n        );\n        const created = (data?.record ?? (data as unknown as T)) as T;\n        if (created && created.id) {\n          setList((prev) => [...prev, created]);\n        }\n        return created ?? null;\n      } catch (err) {\n        setErr(err, `Failed to create ${collection.replace(/s$/, '')}.`);\n        return null;\n      }\n    },\n    [basePath, collection, setErr],\n  );\n\n  // Optimistic update: apply the patch locally, send the request, revert on error.\n  // Per the Phase C-2 pipeline shape, PATCH /admin/<collection> reads the\n  // record id from the request body (form_handler), not the URL path. Putting\n  // the id in the path 404s — there's no per-id route.\n  const update = useCallback(\n    async (id: string, patch: Partial<T>): Promise<T | null> => {\n      setError(null);\n      const snapshot = list;\n      setList((prev) =>\n        prev.map((row) => (row.id === id ? ({ ...row, ...patch } as T) : row)),\n      );\n      try {\n        const data = await schedulingPatch<{ record?: T } & Record<string, unknown>>(\n          basePath,\n          `/admin/${collection}`,\n          { id, ...patch },\n        );\n        const updated = (data?.record ?? (data as unknown as T)) as T;\n        if (updated && updated.id) {\n          setList((prev) =>\n            prev.map((row) => (row.id === id ? updated : row)),\n          );\n        }\n        return updated ?? null;\n      } catch (err) {\n        setList(snapshot);\n        setErr(err, `Failed to update ${collection.replace(/s$/, '')}.`);\n        return null;\n      }\n    },\n    [basePath, collection, list, setErr],\n  );\n\n  const remove = useCallback(\n    async (id: string): Promise<boolean> => {\n      setError(null);\n      const snapshot = list;\n      setList((prev) => prev.filter((row) => row.id !== id));\n      try {\n        // Same shape as update: pipeline reads id from the request body,\n        // not the URL path.\n        await schedulingDelete(basePath, `/admin/${collection}`, { id });\n        return true;\n      } catch (err) {\n        setList(snapshot);\n        setErr(err, `Failed to remove ${collection.replace(/s$/, '')}.`);\n        return false;\n      }\n    },\n    [basePath, collection, list, setErr],\n  );\n\n  return { list, loading, error, refresh, create, update, remove };\n}\n\nfunction useSingletonResource<T>(\n  basePath: string,\n  collection: string,\n  initialLoad: boolean,\n): SingletonResult<T> {\n  const [value, setValue] = useState<T | null>(null);\n  const [loading, setLoading] = useState(false);\n  const [error, setError] = useState<string | null>(null);\n\n  const setErr = useCallback((err: unknown, fallback: string) => {\n    if (err instanceof SchedulingClientError) setError(err.message);\n    else if (err instanceof Error) setError(err.message);\n    else setError(fallback);\n  }, []);\n\n  const refresh = useCallback(async () => {\n    setLoading(true);\n    setError(null);\n    try {\n      const data = await schedulingGet<{ record?: T } & Record<string, unknown>>(\n        basePath,\n        `/admin/${collection}`,\n      );\n      const next = (data?.record ?? (data as unknown as T)) ?? null;\n      setValue(next as T | null);\n    } catch (err) {\n      setErr(err, `Failed to load ${collection}.`);\n    } finally {\n      setLoading(false);\n    }\n  }, [basePath, collection, setErr]);\n\n  useEffect(() => {\n    if (!initialLoad) return;\n    refresh();\n  }, [refresh, initialLoad]);\n\n  const update = useCallback(\n    async (patch: Partial<T>): Promise<T | null> => {\n      setError(null);\n      const snapshot = value;\n      setValue((prev) => (prev ? ({ ...prev, ...patch } as T) : (patch as T)));\n      try {\n        const data = await schedulingPatch<{ record?: T } & Record<string, unknown>>(\n          basePath,\n          `/admin/${collection}`,\n          patch,\n        );\n        const next = (data?.record ?? (data as unknown as T)) ?? null;\n        setValue(next as T | null);\n        return next as T | null;\n      } catch (err) {\n        setValue(snapshot);\n        setErr(err, `Failed to update ${collection}.`);\n        return null;\n      }\n    },\n    [basePath, collection, value, setErr],\n  );\n\n  return { value, loading, error, refresh, update };\n}\n\nexport function useSchedulingAdmin(\n  opts: UseSchedulingAdminOptions = {},\n): UseSchedulingAdminResult {\n  const basePath = useMemo(\n    () => opts.apiBase ?? resolveSchedulingBasePath(),\n    [opts.apiBase],\n  );\n  const initialLoad = !opts.skipInitialLoad;\n\n  const services = useCrudResource<SchedulingService>({\n    basePath,\n    collection: 'services',\n    responseKey: 'services',\n    initialLoad,\n  });\n  const resources = useCrudResource<SchedulingResource>({\n    basePath,\n    collection: 'resources',\n    responseKey: 'resources',\n    initialLoad,\n  });\n  // The provisioned pipelines (per Phase C-2) live at the underscore paths\n  // matching the schema names: scheduling_resource_service /\n  // scheduling_working_hours / scheduling_time_off. The hook previously\n  // hyphenated these, which 404'd against every real deployment.\n  const resourceServices = useCrudResource<\n    SchedulingResourceServiceLink & { id: string }\n  >({\n    basePath,\n    collection: 'resource_services',\n    responseKey: 'resource_services',\n    initialLoad,\n  });\n  const workingHours = useCrudResource<SchedulingWorkingHours>({\n    basePath,\n    collection: 'working_hours',\n    responseKey: 'working_hours',\n    initialLoad,\n  });\n  const timeOff = useCrudResource<SchedulingTimeOff>({\n    basePath,\n    collection: 'time_off',\n    responseKey: 'time_off',\n    initialLoad,\n  });\n  const settings = useSingletonResource<SchedulingSettings>(\n    basePath,\n    'settings',\n    initialLoad,\n  );\n\n  // Auto-link convenience.\n  //\n  // When `autoLinkResourceServices` is on (default), wrapping the create()s\n  // for services + resources spawns the matching resource_service rows so\n  // the booking flow returns non-empty resource lists out of the box.\n  //\n  // The wrapper:\n  //   1. delegates to the original create()\n  //   2. on success, reads the OPPOSITE catalog (resources for service-create,\n  //      services for resource-create) and POSTs one resource_service row per\n  //      active counterpart\n  //   3. refreshes resourceServices once at the end so the picker chips show\n  //      the new links without a re-fetch round-trip per row\n  //\n  // Failures of individual link creates are non-fatal — the create itself\n  // succeeded, the link spawn is convenience. Errors surface via the\n  // resourceServices.error channel.\n  const autoLink = opts.autoLinkResourceServices !== false;\n\n  // Snapshot the latest list refs so the wrappers always read post-create\n  // catalogs without stale-closure issues.\n  const resourcesListRef = useRef(resources.list);\n  resourcesListRef.current = resources.list;\n  const servicesListRef = useRef(services.list);\n  servicesListRef.current = services.list;\n\n  const wrappedServices: CrudResult<SchedulingService> = useMemo(\n    () =>\n      autoLink\n        ? {\n            ...services,\n            create: async (input) => {\n              const created = await services.create(input);\n              if (!created || !created.id) return created;\n              const activeResources = resourcesListRef.current.filter(\n                (r) => r.active !== false,\n              );\n              if (activeResources.length === 0) return created;\n              await Promise.all(\n                activeResources.map((r) =>\n                  resourceServices.create({\n                    resource_id: r.id,\n                    service_id: created.id,\n                  } as Partial<SchedulingResourceServiceLink & { id: string }>),\n                ),\n              );\n              return created;\n            },\n          }\n        : services,\n    // We intentionally read live list state via refs above; depending on the\n    // refs themselves keeps this memoized identity stable across list churn.\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [autoLink, services, resourceServices],\n  );\n\n  const wrappedResources: CrudResult<SchedulingResource> = useMemo(\n    () =>\n      autoLink\n        ? {\n            ...resources,\n            create: async (input) => {\n              const created = await resources.create(input);\n              if (!created || !created.id) return created;\n              const activeServices = servicesListRef.current.filter(\n                (s) => s.active !== false,\n              );\n              if (activeServices.length === 0) return created;\n              await Promise.all(\n                activeServices.map((s) =>\n                  resourceServices.create({\n                    resource_id: created.id,\n                    service_id: s.id,\n                  } as Partial<SchedulingResourceServiceLink & { id: string }>),\n                ),\n              );\n              return created;\n            },\n          }\n        : resources,\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [autoLink, resources, resourceServices],\n  );\n\n  // Keep a stable reference so consumers can put the result in a Context\n  // without churn between renders.\n  const lastRef = useRef<UseSchedulingAdminResult | null>(null);\n  const next: UseSchedulingAdminResult = {\n    basePath,\n    services: wrappedServices,\n    resources: wrappedResources,\n    resourceServices,\n    workingHours,\n    timeOff,\n    settings,\n  };\n  lastRef.current = next;\n  return next;\n}\n"]}