memos/web/src/hooks/useMemoSorting.ts
Steven 1d7efb1580 refactor(web): unify memo stats/filters with context-aware MainLayout
Create unified architecture for memo statistics, filters, and sorting
across all pages (Home, Explore, Archived, Profile) with proper
visibility filtering and consistent data flow.

Key changes:
- Rename HomeLayout → MainLayout to reflect broader usage
- Create useFilteredMemoStats hook for unified stats computation
- Create useMemoFilters/useMemoSorting hooks to eliminate duplication
- Refactor all pages to use unified hooks (~147 lines removed)
- Move Explore route under MainLayout (was sibling before)
- Fix masonry column calculation threshold (1024px → 688px+)

Architecture improvements:
- MainLayout computes filter/stats per route context
- Stats/tags based on same filter as memo list (consistency)
- Proper visibility filtering (PUBLIC/PROTECTED) on Explore
- MemoExplorer/StatisticsView accept stats as required props
- Eliminated optional fallbacks and redundant data fetching

Benefits:
- Single source of truth for stats computation
- Stats remain static (don't change with user filters)
- Reduced code duplication across 4 pages
- Better maintainability and type safety
- Proper security (no private memo leakage on Explore)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 08:46:52 +08:00

85 lines
2.5 KiB
TypeScript

import dayjs from "dayjs";
import { useMemo } from "react";
import { viewStore } from "@/store";
import { State } from "@/types/proto/api/v1/common";
import { Memo } from "@/types/proto/api/v1/memo_service";
export interface UseMemoSortingOptions {
/**
* Whether to sort pinned memos first
* Default: false
*/
pinnedFirst?: boolean;
/**
* State to filter memos by (NORMAL, ARCHIVED, etc.)
* Default: State.NORMAL
*/
state?: State;
}
export interface UseMemoSortingResult {
/**
* Sort function to pass to PagedMemoList's listSort prop
*/
listSort: (memos: Memo[]) => Memo[];
/**
* Order by string to pass to PagedMemoList's orderBy prop
*/
orderBy: string;
}
/**
* Hook to generate memo sorting logic based on options.
*
* This hook consolidates sorting logic that was previously duplicated
* across Home, Explore, Archived, and UserProfile pages.
*
* @param options - Configuration for sorting
* @returns Object with listSort function and orderBy string
*
* @example
* // Home page - pinned first, then by time
* const { listSort, orderBy } = useMemoSorting({
* pinnedFirst: true,
* state: State.NORMAL
* });
*
* @example
* // Explore page - only by time
* const { listSort, orderBy } = useMemoSorting({
* pinnedFirst: false,
* state: State.NORMAL
* });
*/
export const useMemoSorting = (options: UseMemoSortingOptions = {}): UseMemoSortingResult => {
const { pinnedFirst = false, state = State.NORMAL } = options;
// Generate orderBy string for API
const orderBy = useMemo(() => {
const timeOrder = viewStore.state.orderByTimeAsc ? "display_time asc" : "display_time desc";
return pinnedFirst ? `pinned desc, ${timeOrder}` : timeOrder;
}, [pinnedFirst, viewStore.state.orderByTimeAsc]);
// Generate listSort function for client-side sorting
const listSort = useMemo(() => {
return (memos: Memo[]): Memo[] => {
return memos
.filter((memo) => memo.state === state)
.sort((a, b) => {
// First, sort by pinned status if enabled
if (pinnedFirst && a.pinned !== b.pinned) {
return b.pinned ? 1 : -1;
}
// Then sort by display time
return viewStore.state.orderByTimeAsc
? dayjs(a.displayTime).unix() - dayjs(b.displayTime).unix()
: dayjs(b.displayTime).unix() - dayjs(a.displayTime).unix();
});
};
}, [pinnedFirst, state, viewStore.state.orderByTimeAsc]);
return { listSort, orderBy };
};