Ctrl K
ComunidadGuías…

src/components/dives/dives-page-content.tsx

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"use client";

import { useCallback, useRef, useState } from "react";
import { useAuthGuard } from "@/hooks/useAuthGuard";
import { useInfiniteResource } from "@/hooks/useInfiniteResource";
import { useDeleteResource } from "@/hooks/useDeleteResource";
import { useTags } from "@/hooks/useTags";
import { divesAPI, Dive } from "@/lib/api/dives";
import { DELETE_DIVE_CONFIRMATION } from "@/lib/dive-recordings";
import { DiveCard } from "@/components/dives/dive-card";
import { DiveNumberingCard } from "@/components/dives/dive-numbering-card";
import { DiveDraftsCard } from "@/components/dives/dive-drafts-card";
import { DivesPageFrame } from "@/components/dives/dives-page-frame";
import {
  hasDiveFilters,
  NO_DIVE_FILTERS,
  type DiveListFilters,
} from "@/components/dives/dives-filters";
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
import { PageSpinner } from "@/components/ui/page-spinner";

export function DivesPageContent() {
  const { user, isAuthenticated, isLoading: isAuthLoading } = useAuthGuard();
  // Bumped whenever the log changes, to re-describe its numbering: deleting a
  // dive leaves the number it held unused, which brings out the card above the
  // list to say so.
  const [numberingToken, setNumberingToken] = useState(0);
  const reloadNumbering = useCallback(
    () => setNumberingToken((n) => n + 1),
    [],
  );

  // Where focus goes when the numbering card removes itself from under it: the
  // card holds the button the renumber dialog just handed focus back to.
  const headingRef = useRef<HTMLHeadingElement>(null);
  const focusHeading = useCallback(() => headingRef.current?.focus(), []);

  // No debounce: each control commits a whole value at once.
  const [filters, setFilters] = useState<DiveListFilters>(NO_DIVE_FILTERS);
  // Latched by the filter panel's first open, since nothing before that needs
  // the diver's tags.
  const [wantsTags, setWantsTags] = useState(false);
  const { tags } = useTags(!!user && wantsTags);

  // Changing a filter changes this callback's identity, which is what makes
  // `useInfiniteResource` throw every loaded page away and read the new query
  // from the first - rows of one order are not rows of another.
  const fetchDives = useCallback(
    (page: number, perPage: number) =>
      divesAPI.getDives(page, perPage, filters),
    [filters],
  );

  const {
    items: dives,
    isLoading: isLoadingDives,
    isLoadingMore,
    totalCount,
    isCountNarrowed,
    itemsPerPage,
    hasMore,
    loadFailed,
    loadMore,
    reload,
    removeItem,
  } = useInfiniteResource<Dive>(fetchDives, {
    enabled: !!user,
    isNarrowed: hasDiveFilters(filters),
    errorMessage: "Failed to load dives. Please try again.",
    keyOf: (dive) => dive.uuid,
  });

  const {
    deletingId,
    pendingId,
    confirmMessage,
    requestDelete: requestDeleteDive,
    cancelDelete: cancelDeleteDive,
    confirmDelete: confirmDeleteDive,
  } = useDeleteResource(divesAPI.deleteDive, {
    confirmMessage: DELETE_DIVE_CONFIRMATION,
    successMessage: "Dive deleted successfully.",
    errorMessage: "Failed to delete dive. Please try again.",
    // The card goes locally rather than by re-reading: a diver who has scrolled
    // several pages in should not have the list collapse back to the first one
    // under them. The numbering above the list is re-read, because the number
    // the deleted dive held is now a gap and that card says so.
    onDeleted: (id) => {
      removeItem(id);
      reloadNumbering();
    },
  });

  if (isAuthLoading) {
    return <PageSpinner variant="inset" />;
  }

  if (!isAuthenticated) {
    return null; // Will redirect to signin
  }

  return (
    <>
      <DivesPageFrame
        isLoading={isLoadingDives}
        totalCount={totalCount}
        isCountNarrowed={isCountNarrowed}
        itemsPerPage={itemsPerPage}
        isLoadingMore={isLoadingMore}
        loadFailed={loadFailed}
        hasMore={hasMore}
        onLoadMore={loadMore}
        filters={filters}
        onFiltersChange={setFilters}
        onFiltersOpened={() => setWantsTags(true)}
        tags={tags ?? undefined}
        headingRef={headingRef}
        drafts={<DiveDraftsCard enabled={!!user} onVanished={focusHeading} />}
        numbering={
          <DiveNumberingCard
            enabled={!!user}
            reloadToken={numberingToken}
            onRenumbered={reload}
            onVanished={focusHeading}
          />
        }
        cards={dives.map((dive) => (
          <DiveCard
            key={dive.uuid}
            dive={dive}
            onDelete={() => requestDeleteDive(dive.uuid)}
            isDeleting={deletingId === dive.uuid}
          />
        ))}
      />

      <ConfirmDialog
        open={pendingId !== null}
        onOpenChange={(open) => !open && cancelDeleteDive()}
        title="Delete dive"
        description={confirmMessage}
        confirmText="Delete"
        isLoading={deletingId === pendingId}
        onConfirm={confirmDeleteDive}
      />
    </>
  );
}