feat(21-03): remove slide-out panels from root layout and clean UIStore
- Remove Item and Candidate SlideOutPanel instances from __root.tsx - Remove SlideOutPanel, ItemForm, CandidateForm imports from root - Remove panel state (panelMode, editingItemId, candidatePanelMode, editingCandidateId) from UIStore - Remove panel actions (openEditPanel, openAddPanel, closePanel, etc.) from UIStore - Preserve currentThreadId derivation for CandidateDeleteDialog - Update CollectionView empty state to use catalog search instead of add panel - Update ItemForm/CandidateForm with onClose prop replacing store panel close - Clean all dead panel references across src/client/
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useCreateCandidate, useUpdateCandidate } from "../hooks/useCandidates";
|
import { useCreateCandidate, useUpdateCandidate } from "../hooks/useCandidates";
|
||||||
import { useThread } from "../hooks/useThreads";
|
import { useThread } from "../hooks/useThreads";
|
||||||
import { useUIStore } from "../stores/uiStore";
|
|
||||||
import { CategoryPicker } from "./CategoryPicker";
|
import { CategoryPicker } from "./CategoryPicker";
|
||||||
import { ImageUpload } from "./ImageUpload";
|
import { ImageUpload } from "./ImageUpload";
|
||||||
|
|
||||||
@@ -9,6 +8,7 @@ interface CandidateFormProps {
|
|||||||
mode: "add" | "edit";
|
mode: "add" | "edit";
|
||||||
threadId: number;
|
threadId: number;
|
||||||
candidateId?: number | null;
|
candidateId?: number | null;
|
||||||
|
onClose?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FormData {
|
interface FormData {
|
||||||
@@ -39,11 +39,11 @@ export function CandidateForm({
|
|||||||
mode,
|
mode,
|
||||||
threadId,
|
threadId,
|
||||||
candidateId,
|
candidateId,
|
||||||
|
onClose,
|
||||||
}: CandidateFormProps) {
|
}: CandidateFormProps) {
|
||||||
const { data: thread } = useThread(threadId);
|
const { data: thread } = useThread(threadId);
|
||||||
const createCandidate = useCreateCandidate(threadId);
|
const createCandidate = useCreateCandidate(threadId);
|
||||||
const updateCandidate = useUpdateCandidate(threadId);
|
const updateCandidate = useUpdateCandidate(threadId);
|
||||||
const closeCandidatePanel = useUIStore((s) => s.closeCandidatePanel);
|
|
||||||
|
|
||||||
const [form, setForm] = useState<FormData>(INITIAL_FORM);
|
const [form, setForm] = useState<FormData>(INITIAL_FORM);
|
||||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||||
@@ -124,13 +124,13 @@ export function CandidateForm({
|
|||||||
createCandidate.mutate(payload, {
|
createCandidate.mutate(payload, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setForm(INITIAL_FORM);
|
setForm(INITIAL_FORM);
|
||||||
closeCandidatePanel();
|
onClose?.();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else if (candidateId != null) {
|
} else if (candidateId != null) {
|
||||||
updateCandidate.mutate(
|
updateCandidate.mutate(
|
||||||
{ candidateId, ...payload },
|
{ candidateId, ...payload },
|
||||||
{ onSuccess: () => closeCandidatePanel() },
|
{ onSuccess: () => onClose?.() },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export function CollectionView() {
|
|||||||
const { data: totals } = useTotals();
|
const { data: totals } = useTotals();
|
||||||
const { data: categories } = useCategories();
|
const { data: categories } = useCategories();
|
||||||
const { weight, price } = useFormatters();
|
const { weight, price } = useFormatters();
|
||||||
const openAddPanel = useUIStore((s) => s.openAddPanel);
|
const openCatalogSearch = useUIStore((s) => s.openCatalogSearch);
|
||||||
|
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
const [categoryFilter, setCategoryFilter] = useState<number | null>(null);
|
const [categoryFilter, setCategoryFilter] = useState<number | null>(null);
|
||||||
@@ -66,7 +66,7 @@ export function CollectionView() {
|
|||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={openAddPanel}
|
onClick={() => openCatalogSearch("collection")}
|
||||||
className="inline-flex items-center gap-2 px-5 py-2.5 bg-gray-700 hover:bg-gray-800 text-white text-sm font-medium rounded-lg transition-colors"
|
className="inline-flex items-center gap-2 px-5 py-2.5 bg-gray-700 hover:bg-gray-800 text-white text-sm font-medium rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { ImageUpload } from "./ImageUpload";
|
|||||||
interface ItemFormProps {
|
interface ItemFormProps {
|
||||||
mode: "add" | "edit";
|
mode: "add" | "edit";
|
||||||
itemId?: number | null;
|
itemId?: number | null;
|
||||||
|
onClose?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FormData {
|
interface FormData {
|
||||||
@@ -31,11 +32,10 @@ const INITIAL_FORM: FormData = {
|
|||||||
imageFilename: null,
|
imageFilename: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ItemForm({ mode, itemId }: ItemFormProps) {
|
export function ItemForm({ mode, itemId, onClose }: ItemFormProps) {
|
||||||
const { data: items } = useItems();
|
const { data: items } = useItems();
|
||||||
const createItem = useCreateItem();
|
const createItem = useCreateItem();
|
||||||
const updateItem = useUpdateItem();
|
const updateItem = useUpdateItem();
|
||||||
const closePanel = useUIStore((s) => s.closePanel);
|
|
||||||
const openConfirmDelete = useUIStore((s) => s.openConfirmDelete);
|
const openConfirmDelete = useUIStore((s) => s.openConfirmDelete);
|
||||||
|
|
||||||
const [form, setForm] = useState<FormData>(INITIAL_FORM);
|
const [form, setForm] = useState<FormData>(INITIAL_FORM);
|
||||||
@@ -112,13 +112,13 @@ export function ItemForm({ mode, itemId }: ItemFormProps) {
|
|||||||
createItem.mutate(payload, {
|
createItem.mutate(payload, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setForm(INITIAL_FORM);
|
setForm(INITIAL_FORM);
|
||||||
closePanel();
|
onClose?.();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else if (itemId != null) {
|
} else if (itemId != null) {
|
||||||
updateItem.mutate(
|
updateItem.mutate(
|
||||||
{ id: itemId, ...payload },
|
{ id: itemId, ...payload },
|
||||||
{ onSuccess: () => closePanel() },
|
{ onSuccess: () => onClose?.() },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,11 @@ import {
|
|||||||
} from "@tanstack/react-router";
|
} from "@tanstack/react-router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import "../app.css";
|
import "../app.css";
|
||||||
import { CandidateForm } from "../components/CandidateForm";
|
|
||||||
import { CatalogSearchOverlay } from "../components/CatalogSearchOverlay";
|
import { CatalogSearchOverlay } from "../components/CatalogSearchOverlay";
|
||||||
import { ConfirmDialog } from "../components/ConfirmDialog";
|
import { ConfirmDialog } from "../components/ConfirmDialog";
|
||||||
import { ExternalLinkDialog } from "../components/ExternalLinkDialog";
|
import { ExternalLinkDialog } from "../components/ExternalLinkDialog";
|
||||||
import { FabMenu } from "../components/FabMenu";
|
import { FabMenu } from "../components/FabMenu";
|
||||||
import { ItemForm } from "../components/ItemForm";
|
|
||||||
import { OnboardingWizard } from "../components/OnboardingWizard";
|
import { OnboardingWizard } from "../components/OnboardingWizard";
|
||||||
import { SlideOutPanel } from "../components/SlideOutPanel";
|
|
||||||
import { TotalsBar } from "../components/TotalsBar";
|
import { TotalsBar } from "../components/TotalsBar";
|
||||||
import { useAuth } from "../hooks/useAuth";
|
import { useAuth } from "../hooks/useAuth";
|
||||||
import { useDeleteCandidate } from "../hooks/useCandidates";
|
import { useDeleteCandidate } from "../hooks/useCandidates";
|
||||||
@@ -79,16 +76,6 @@ function RootLayout() {
|
|||||||
const { data: auth, isLoading: authLoading } = useAuth();
|
const { data: auth, isLoading: authLoading } = useAuth();
|
||||||
const isAuthenticated = !!auth?.user;
|
const isAuthenticated = !!auth?.user;
|
||||||
|
|
||||||
// Item panel state
|
|
||||||
const panelMode = useUIStore((s) => s.panelMode);
|
|
||||||
const editingItemId = useUIStore((s) => s.editingItemId);
|
|
||||||
const closePanel = useUIStore((s) => s.closePanel);
|
|
||||||
|
|
||||||
// Candidate panel state
|
|
||||||
const candidatePanelMode = useUIStore((s) => s.candidatePanelMode);
|
|
||||||
const editingCandidateId = useUIStore((s) => s.editingCandidateId);
|
|
||||||
const closeCandidatePanel = useUIStore((s) => s.closeCandidatePanel);
|
|
||||||
|
|
||||||
// Candidate delete state
|
// Candidate delete state
|
||||||
const confirmDeleteCandidateId = useUIStore(
|
const confirmDeleteCandidateId = useUIStore(
|
||||||
(s) => s.confirmDeleteCandidateId,
|
(s) => s.confirmDeleteCandidateId,
|
||||||
@@ -114,9 +101,6 @@ function RootLayout() {
|
|||||||
!wizardDismissed &&
|
!wizardDismissed &&
|
||||||
isAuthenticated;
|
isAuthenticated;
|
||||||
|
|
||||||
const isItemPanelOpen = panelMode !== "closed";
|
|
||||||
const isCandidatePanelOpen = candidatePanelMode !== "closed";
|
|
||||||
|
|
||||||
// Route matching for contextual behavior
|
// Route matching for contextual behavior
|
||||||
const matchRoute = useMatchRoute();
|
const matchRoute = useMatchRoute();
|
||||||
|
|
||||||
@@ -186,40 +170,6 @@ function RootLayout() {
|
|||||||
<TotalsBar {...finalTotalsProps} />
|
<TotalsBar {...finalTotalsProps} />
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
|
||||||
{/* Item Slide-out Panel */}
|
|
||||||
<SlideOutPanel
|
|
||||||
isOpen={isItemPanelOpen}
|
|
||||||
onClose={closePanel}
|
|
||||||
title={panelMode === "add" ? "Add Item" : "Edit Item"}
|
|
||||||
>
|
|
||||||
{panelMode === "add" && <ItemForm mode="add" />}
|
|
||||||
{panelMode === "edit" && (
|
|
||||||
<ItemForm mode="edit" itemId={editingItemId} />
|
|
||||||
)}
|
|
||||||
</SlideOutPanel>
|
|
||||||
|
|
||||||
{/* Candidate Slide-out Panel */}
|
|
||||||
{currentThreadId != null && (
|
|
||||||
<SlideOutPanel
|
|
||||||
isOpen={isCandidatePanelOpen}
|
|
||||||
onClose={closeCandidatePanel}
|
|
||||||
title={
|
|
||||||
candidatePanelMode === "add" ? "Add Candidate" : "Edit Candidate"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{candidatePanelMode === "add" && (
|
|
||||||
<CandidateForm mode="add" threadId={currentThreadId} />
|
|
||||||
)}
|
|
||||||
{candidatePanelMode === "edit" && (
|
|
||||||
<CandidateForm
|
|
||||||
mode="edit"
|
|
||||||
threadId={currentThreadId}
|
|
||||||
candidateId={editingCandidateId}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</SlideOutPanel>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Item Confirm Delete Dialog */}
|
{/* Item Confirm Delete Dialog */}
|
||||||
<ConfirmDialog />
|
<ConfirmDialog />
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ function ThreadDetailPage() {
|
|||||||
const { threadId: threadIdParam } = Route.useParams();
|
const { threadId: threadIdParam } = Route.useParams();
|
||||||
const threadId = Number(threadIdParam);
|
const threadId = Number(threadIdParam);
|
||||||
const { data: thread, isLoading, isError } = useThread(threadId);
|
const { data: thread, isLoading, isError } = useThread(threadId);
|
||||||
const openCandidateAddPanel = useUIStore((s) => s.openCandidateAddPanel);
|
const [_showAddCandidate, setShowAddCandidate] = useState(false);
|
||||||
const candidateViewMode = useUIStore((s) => s.candidateViewMode);
|
const candidateViewMode = useUIStore((s) => s.candidateViewMode);
|
||||||
const setCandidateViewMode = useUIStore((s) => s.setCandidateViewMode);
|
const setCandidateViewMode = useUIStore((s) => s.setCandidateViewMode);
|
||||||
const selectedSetupId = useUIStore((s) => s.selectedSetupId);
|
const selectedSetupId = useUIStore((s) => s.selectedSetupId);
|
||||||
@@ -132,7 +132,7 @@ function ThreadDetailPage() {
|
|||||||
{isActive && (
|
{isActive && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={openCandidateAddPanel}
|
onClick={() => setShowAddCandidate(true)}
|
||||||
className="inline-flex items-center gap-2 px-4 py-2 bg-gray-700 hover:bg-gray-800 text-white text-sm font-medium rounded-lg transition-colors"
|
className="inline-flex items-center gap-2 px-4 py-2 bg-gray-700 hover:bg-gray-800 text-white text-sm font-medium rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@@ -1,25 +1,15 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
|
|
||||||
interface UIState {
|
interface UIState {
|
||||||
// Item panel state
|
// Item delete state
|
||||||
panelMode: "closed" | "add" | "edit";
|
|
||||||
editingItemId: number | null;
|
|
||||||
confirmDeleteItemId: number | null;
|
confirmDeleteItemId: number | null;
|
||||||
|
|
||||||
openAddPanel: () => void;
|
|
||||||
openEditPanel: (itemId: number) => void;
|
|
||||||
closePanel: () => void;
|
|
||||||
openConfirmDelete: (itemId: number) => void;
|
openConfirmDelete: (itemId: number) => void;
|
||||||
closeConfirmDelete: () => void;
|
closeConfirmDelete: () => void;
|
||||||
|
|
||||||
// Candidate panel state
|
// Candidate delete state
|
||||||
candidatePanelMode: "closed" | "add" | "edit";
|
|
||||||
editingCandidateId: number | null;
|
|
||||||
confirmDeleteCandidateId: number | null;
|
confirmDeleteCandidateId: number | null;
|
||||||
|
|
||||||
openCandidateAddPanel: () => void;
|
|
||||||
openCandidateEditPanel: (id: number) => void;
|
|
||||||
closeCandidatePanel: () => void;
|
|
||||||
openConfirmDeleteCandidate: (id: number) => void;
|
openConfirmDeleteCandidate: (id: number) => void;
|
||||||
closeConfirmDeleteCandidate: () => void;
|
closeConfirmDeleteCandidate: () => void;
|
||||||
|
|
||||||
@@ -70,28 +60,15 @@ interface UIState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useUIStore = create<UIState>((set) => ({
|
export const useUIStore = create<UIState>((set) => ({
|
||||||
// Item panel
|
// Item delete
|
||||||
panelMode: "closed",
|
|
||||||
editingItemId: null,
|
|
||||||
confirmDeleteItemId: null,
|
confirmDeleteItemId: null,
|
||||||
|
|
||||||
openAddPanel: () => set({ panelMode: "add", editingItemId: null }),
|
|
||||||
openEditPanel: (itemId) => set({ panelMode: "edit", editingItemId: itemId }),
|
|
||||||
closePanel: () => set({ panelMode: "closed", editingItemId: null }),
|
|
||||||
openConfirmDelete: (itemId) => set({ confirmDeleteItemId: itemId }),
|
openConfirmDelete: (itemId) => set({ confirmDeleteItemId: itemId }),
|
||||||
closeConfirmDelete: () => set({ confirmDeleteItemId: null }),
|
closeConfirmDelete: () => set({ confirmDeleteItemId: null }),
|
||||||
|
|
||||||
// Candidate panel
|
// Candidate delete
|
||||||
candidatePanelMode: "closed",
|
|
||||||
editingCandidateId: null,
|
|
||||||
confirmDeleteCandidateId: null,
|
confirmDeleteCandidateId: null,
|
||||||
|
|
||||||
openCandidateAddPanel: () =>
|
|
||||||
set({ candidatePanelMode: "add", editingCandidateId: null }),
|
|
||||||
openCandidateEditPanel: (id) =>
|
|
||||||
set({ candidatePanelMode: "edit", editingCandidateId: id }),
|
|
||||||
closeCandidatePanel: () =>
|
|
||||||
set({ candidatePanelMode: "closed", editingCandidateId: null }),
|
|
||||||
openConfirmDeleteCandidate: (id) => set({ confirmDeleteCandidateId: id }),
|
openConfirmDeleteCandidate: (id) => set({ confirmDeleteCandidateId: id }),
|
||||||
closeConfirmDeleteCandidate: () => set({ confirmDeleteCandidateId: null }),
|
closeConfirmDeleteCandidate: () => set({ confirmDeleteCandidateId: null }),
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user