summaryrefslogtreecommitdiff
path: root/apps/web/src/app/projects/store.ts
diff options
context:
space:
mode:
authorMaze <167211895+mazeincoding@users.noreply.github.com>2026-01-31 00:20:04 +0100
committerGitHub <noreply@github.com>2026-01-31 00:20:04 +0100
commit7bf0984698e43b9aa2dd879f65951318ed410390 (patch)
treeccf2cd590a47d550152afc07129cb55c71a9ec35 /apps/web/src/app/projects/store.ts
parent0173db99448d1c4c6415dcf15272e17551b48b04 (diff)
downloadopencut-7bf0984698e43b9aa2dd879f65951318ed410390.tar.gz
codebase overhaul (#697)
Diffstat (limited to 'apps/web/src/app/projects/store.ts')
-rw-r--r--apps/web/src/app/projects/store.ts129
1 files changed, 129 insertions, 0 deletions
diff --git a/apps/web/src/app/projects/store.ts b/apps/web/src/app/projects/store.ts
new file mode 100644
index 00000000..eaf8f947
--- /dev/null
+++ b/apps/web/src/app/projects/store.ts
@@ -0,0 +1,129 @@
+import { create } from "zustand";
+import { persist } from "zustand/middleware";
+import type { TProjectSortKey } from "@/types/project";
+
+export type ProjectsViewMode = "grid" | "list";
+
+interface ProjectsState {
+ searchQuery: string;
+ sortKey: TProjectSortKey;
+ sortOrder: "asc" | "desc";
+ viewMode: ProjectsViewMode;
+ selectedProjectIds: string[];
+ lastSelectedProjectId: string | null;
+ isHydrated: boolean;
+ setIsHydrated: ({ isHydrated }: { isHydrated: boolean }) => void;
+ setSearchQuery: ({ query }: { query: string }) => void;
+ setSortKey: ({ sortKey }: { sortKey: TProjectSortKey }) => void;
+ setSortOrder: ({ sortOrder }: { sortOrder: "asc" | "desc" }) => void;
+ toggleSortOrder: () => void;
+ setViewMode: ({ viewMode }: { viewMode: ProjectsViewMode }) => void;
+ setSelectedProjects: ({ projectIds }: { projectIds: string[] }) => void;
+ clearSelectedProjects: () => void;
+ setProjectSelected: ({
+ projectId,
+ isSelected,
+ }: {
+ projectId: string;
+ isSelected: boolean;
+ }) => void;
+ selectProjectRange: ({
+ projectId,
+ allProjectIds,
+ }: {
+ projectId: string;
+ allProjectIds: string[];
+ }) => void;
+}
+
+const getNextSelectedProjectIds = ({
+ selectedProjectIds,
+ projectId,
+ isSelected,
+}: {
+ selectedProjectIds: string[];
+ projectId: string;
+ isSelected: boolean;
+}): string[] => {
+ const selectedProjectIdSet = new Set(selectedProjectIds);
+
+ if (isSelected) {
+ selectedProjectIdSet.add(projectId);
+ return Array.from(selectedProjectIdSet);
+ }
+
+ selectedProjectIdSet.delete(projectId);
+ return Array.from(selectedProjectIdSet);
+};
+
+export const useProjectsStore = create<ProjectsState>()(
+ persist(
+ (set) => ({
+ searchQuery: "",
+ sortKey: "createdAt",
+ sortOrder: "desc",
+ viewMode: "grid",
+ selectedProjectIds: [],
+ lastSelectedProjectId: null,
+ isHydrated: false,
+ setIsHydrated: ({ isHydrated }) => set({ isHydrated }),
+ setSearchQuery: ({ query }) => set({ searchQuery: query }),
+ setSortKey: ({ sortKey }) => set({ sortKey }),
+ setSortOrder: ({ sortOrder }) => set({ sortOrder }),
+ toggleSortOrder: () =>
+ set((state) => ({
+ sortOrder: state.sortOrder === "asc" ? "desc" : "asc",
+ })),
+ setViewMode: ({ viewMode }) => set({ viewMode }),
+ setSelectedProjects: ({ projectIds }) =>
+ set({ selectedProjectIds: projectIds }),
+ clearSelectedProjects: () =>
+ set({ selectedProjectIds: [], lastSelectedProjectId: null }),
+ setProjectSelected: ({ projectId, isSelected }) =>
+ set((state) => ({
+ selectedProjectIds: getNextSelectedProjectIds({
+ selectedProjectIds: state.selectedProjectIds,
+ projectId,
+ isSelected,
+ }),
+ lastSelectedProjectId: isSelected ? projectId : state.lastSelectedProjectId,
+ })),
+ selectProjectRange: ({ projectId, allProjectIds }) =>
+ set((state) => {
+ const anchorId = state.lastSelectedProjectId;
+ if (!anchorId) {
+ return {
+ selectedProjectIds: [projectId],
+ lastSelectedProjectId: projectId,
+ };
+ }
+
+ const anchorIndex = allProjectIds.indexOf(anchorId);
+ const targetIndex = allProjectIds.indexOf(projectId);
+
+ if (anchorIndex === -1 || targetIndex === -1) {
+ return {
+ selectedProjectIds: [projectId],
+ lastSelectedProjectId: projectId,
+ };
+ }
+
+ const startIndex = Math.min(anchorIndex, targetIndex);
+ const endIndex = Math.max(anchorIndex, targetIndex);
+ const rangeIds = allProjectIds.slice(startIndex, endIndex + 1);
+
+ const merged = new Set([...state.selectedProjectIds, ...rangeIds]);
+ return {
+ selectedProjectIds: Array.from(merged),
+ };
+ }),
+ }),
+ {
+ name: "projects-view-mode",
+ partialize: (state) => ({ viewMode: state.viewMode }),
+ onRehydrateStorage: () => (state) => {
+ state?.setIsHydrated({ isHydrated: true });
+ },
+ },
+ ),
+);