"use client";
import { IconX, IconDeviceFloppy, IconBookmark } from "@tabler/icons-react";
import type { Icon } from "@tabler/icons-react";
import { useCallback } from "react";
import { Button } from "@kandev/ui/button";
import { SavedQueryLoadStatus, type SavedQueryLoadState } from "./saved-query-load-status";
import { cn } from "@/lib/utils";
import { PR_PRESETS, ISSUE_PRESETS, type PresetOption, type PresetGroup } from "./search-bar";
import type { SavedPreset } from "./saved-preset-model";
import { useTranslation } from "react-i18next";
import { useResponsiveBreakpoint } from "@/hooks/use-responsive-breakpoint";
import { SavedQueryDefaultButton } from "@/components/integrations/saved-query-default-button";
import { SavedTaskViewDeleteConfirmation } from "@/components/confirmation/saved-task-view-delete-confirmation";
import {
useSavedTaskViewDeleteConfirmation,
type SavedTaskViewDeleteTarget,
} from "@/components/confirmation/use-saved-task-view-delete-confirmation";
export type SidebarSelection = {
kind: "pr" | "issue";
source: "preset" | "saved";
id: string;
};
export type SidebarSelectionRequest =
| SidebarSelection
| { kind: SidebarSelection["kind"]; source: "kind-switch" };
export type PresetsSidebarProps = {
selected: SidebarSelection;
onSelect: (request: SidebarSelectionRequest) => void;
savedPresets: SavedPreset[];
onDeleteSaved: (id: string) => void;
canSaveCurrent: boolean;
onSaveCurrent: () => void;
onToggleSavedDefault: (preset: SavedPreset) => void;
defaultMutationPendingId: string | null;
prPresets?: PresetOption[];
issuePresets?: PresetOption[];
savedQueryLoad?: SavedQueryLoadState;
};
function KindToggle({
kind,
onChange,
}: {
kind: "pr" | "issue";
onChange: (k: "pr" | "issue") => void;
}) {
const { t } = useTranslation();
return (
{(["pr", "issue"] as const).map((value) => (
))}
);
}
function SectionHeader({ title }: { title: string }) {
return (
{title}
);
}
function PresetItem({
label,
Icon,
active,
onClick,
trailing,
}: {
label: string;
Icon: Icon;
active: boolean;
onClick: () => void;
trailing?: React.ReactNode;
}) {
return (
{trailing}
);
}
function PresetGroupList({
presets,
group,
selected,
onSelect,
kind,
}: {
presets: PresetOption[];
group: PresetGroup;
selected: SidebarSelection;
onSelect: (s: SidebarSelection) => void;
kind: "pr" | "issue";
}) {
const { t } = useTranslation();
const items = presets.filter((p) => p.group === group);
if (items.length === 0) return null;
return (
<>
{items.map((p) => (
onSelect({ kind, source: "preset", id: p.value })}
/>
))}
>
);
}
function SavedSection({
saved,
selected,
onSelect,
onDelete,
kind,
onToggleSavedDefault,
defaultMutationPendingId,
savedQueryLoad,
}: {
saved: SavedPreset[];
selected: SidebarSelection;
onSelect: (s: SidebarSelection) => void;
onDelete: (id: string) => void;
kind: "pr" | "issue";
onToggleSavedDefault: (preset: SavedPreset) => void;
defaultMutationPendingId: string | null;
savedQueryLoad?: SavedQueryLoadState;
}) {
const { t } = useTranslation();
const deletion = useSavedTaskViewDeleteConfirmation(saved);
const { isMobile } = useResponsiveBreakpoint();
const defaultMutationPending = defaultMutationPendingId !== null;
return (
<>
{savedQueryLoad && }
{saved.length === 0 && !savedQueryLoad?.loading && !savedQueryLoad?.error && (
{t("github:noSavedQueriesYet")}
)}
{saved.map((preset) => (
onSelect({ kind, source: "saved", id: preset.id })}
onDelete={onDelete}
onToggleDefault={() => void onToggleSavedDefault(preset)}
/>
))}
{isMobile && deletion.target && (
{
if (!open) deletion.close();
}}
onConfirm={onDelete}
/>
)}
>
);
}
type GitHubSavedDeletion = {
target: SavedTaskViewDeleteTarget | null;
anchorRef: React.RefObject;
close: () => void;
registerAnchor: (id: string, element: HTMLButtonElement | null) => void;
request: (target: SavedTaskViewDeleteTarget) => void;
};
function GitHubSavedPresetEntry({
preset,
active,
deletion,
defaultMutationPending,
defaultMutationPendingForPreset,
onSelect,
onDelete,
onToggleDefault,
}: {
preset: SavedPreset;
active: boolean;
deletion: GitHubSavedDeletion;
defaultMutationPending: boolean;
defaultMutationPendingForPreset: boolean;
onSelect: () => void;
onDelete: (id: string) => void;
onToggleDefault: () => void;
}) {
const { t } = useTranslation();
const { isMobile } = useResponsiveBreakpoint();
if (!isMobile && deletion.target?.id === preset.id) {
return (
{
if (!open) deletion.close();
}}
onConfirm={onDelete}
/>
);
}
const deleteLabel = t("integrations:deleteSavedQueryNamed", { label: preset.label });
const accessibleDeleteLabel = defaultMutationPending
? t("integrations:savedQueryDefaultUpdateInProgress", { action: deleteLabel })
: deleteLabel;
return (
}
/>
);
}
export function PresetsSidebar({
selected,
onSelect,
savedPresets,
onDeleteSaved,
canSaveCurrent,
onSaveCurrent,
onToggleSavedDefault,
defaultMutationPendingId,
prPresets = PR_PRESETS,
issuePresets = ISSUE_PRESETS,
savedQueryLoad,
}: PresetsSidebarProps) {
const { t } = useTranslation();
const presets = selected.kind === "pr" ? prPresets : issuePresets;
const saved = savedPresets.filter((p) => p.kind === selected.kind);
const onKindChange = useCallback(
(kind: "pr" | "issue") => {
if (kind === selected.kind) return;
onSelect({ kind, source: "kind-switch" });
},
[onSelect, selected.kind],
);
return (
);
}