diff --git a/components/GroupedStatusView/GroupedStatusView.tsx b/components/GroupedStatusView/GroupedStatusView.tsx index fc1f797..891528c 100644 --- a/components/GroupedStatusView/GroupedStatusView.tsx +++ b/components/GroupedStatusView/GroupedStatusView.tsx @@ -6,6 +6,7 @@ import { GroupedDataProvider, useGroupedDataContext, } from "./context/GroupedDataProvider"; +import { NoteStatus } from "@/types/noteStatus"; export type FileItem = { id: string; @@ -33,6 +34,7 @@ export type GroupedStatusViewProps = { onFileClick: (file: FileItem) => void; subscribeToEvents: (onDataChange: () => void) => () => void; getAvailableStatuses: () => StatusItem[]; + getAvailableStatusesWithTemplateInfo: () => NoteStatus[]; }; const GroupedStatusViewContent = () => { @@ -44,7 +46,7 @@ const GroupedStatusViewContent = () => { toggleGroup, toggleFiles, onFileClick, - getAvailableStatuses, + getAvailableStatusesWithTemplateInfo, getLoadedCount, loadMoreItems, handleScroll, @@ -57,14 +59,26 @@ const GroupedStatusViewContent = () => { [onFileClick], ); - const availableStatuses = useMemo( - () => getAvailableStatuses(), - [getAvailableStatuses], - ); - const statusMap = useMemo( - () => new Map(availableStatuses.map((s) => [s.name, s])), - [availableStatuses], - ); + const statusMap = useMemo(() => { + const map = new Map(); + const noteStatuses = getAvailableStatusesWithTemplateInfo(); + + // Build map using scoped identifiers as keys + noteStatuses.forEach((noteStatus) => { + const statusItem: StatusItem = { + name: noteStatus.name, + color: noteStatus.color || "white", + icon: noteStatus.icon, + }; + + const scopedIdentifier = noteStatus.templateId + ? `${noteStatus.templateId}:${noteStatus.name}` + : noteStatus.name; + map.set(scopedIdentifier, statusItem); + }); + + return map; + }, [getAvailableStatusesWithTemplateInfo]); if (isLoading) { return ; @@ -107,9 +121,22 @@ export const GroupedStatusView = ({ onFileClick, subscribeToEvents, getAvailableStatuses, + getAvailableStatusesWithTemplateInfo, }: GroupedStatusViewProps) => { const [searchFilter, setSearchFilter] = useState(""); const [noteNameFilter, setNoteNameFilter] = useState(""); + const [templateFilter, setTemplateFilter] = useState(""); + + // Get available templates from the statuses + const availableTemplates = useMemo(() => { + const templates = new Set(); + getAvailableStatusesWithTemplateInfo().forEach((status) => { + if (status.templateId) { + templates.add(status.templateId); + } + }); + return Array.from(templates).sort(); + }, [getAvailableStatusesWithTemplateInfo]); return (
diff --git a/components/GroupedStatusView/components/FilterSection.tsx b/components/GroupedStatusView/components/FilterSection.tsx index d350fbd..fadc861 100644 --- a/components/GroupedStatusView/components/FilterSection.tsx +++ b/components/GroupedStatusView/components/FilterSection.tsx @@ -5,15 +5,21 @@ import { Input } from "@/components/atoms/Input"; interface FilterSectionProps { searchFilter: string; noteNameFilter: string; + templateFilter: string; + availableTemplates: string[]; onSearchFilterChange: (value: string) => void; onNoteNameFilterChange: (value: string) => void; + onTemplateFilterChange: (value: string) => void; } export const FilterSection = ({ searchFilter, noteNameFilter, + templateFilter, + availableTemplates, onSearchFilterChange, onNoteNameFilterChange, + onTemplateFilterChange, }: FilterSectionProps) => { return (
@@ -23,15 +29,29 @@ export const FilterSection = ({ value={searchFilter} onFilterChange={onSearchFilterChange} /> -
+
+
+ +
); diff --git a/components/GroupedStatusView/components/StatusGroup.tsx b/components/GroupedStatusView/components/StatusGroup.tsx index b398053..58b6af0 100644 --- a/components/GroupedStatusView/components/StatusGroup.tsx +++ b/components/GroupedStatusView/components/StatusGroup.tsx @@ -36,6 +36,11 @@ export const StatusGroup = ({ }: StatusGroupProps) => { const groupKey = `${tag}-${statusName}`; + // Extract template ID from scoped identifier + const templateId = statusName.includes(":") + ? statusName.split(":", 2)[0] + : null; + const handleToggle = useCallback(() => { onToggle(); }, [onToggle]); @@ -43,12 +48,19 @@ export const StatusGroup = ({ return (
- +
+ + {templateId && ( + + {templateId} + + )} +
void; getAvailableStatuses: () => StatusItem[]; + getAvailableStatusesWithTemplateInfo: () => NoteStatus[]; }; const GroupedDataContext = createContext( @@ -52,8 +54,10 @@ type GroupedDataProviderProps = { onFileClick: (file: FileItem) => void; subscribeToEvents: (onDataChange: () => void) => () => void; getAvailableStatuses: () => StatusItem[]; + getAvailableStatusesWithTemplateInfo: () => NoteStatus[]; searchFilter: string; noteNameFilter: string; + templateFilter: string; }; export const GroupedDataProvider = ({ @@ -63,8 +67,10 @@ export const GroupedDataProvider = ({ onFileClick, subscribeToEvents, getAvailableStatuses, + getAvailableStatusesWithTemplateInfo, searchFilter, noteNameFilter, + templateFilter, }: GroupedDataProviderProps) => { const { groupedData, filteredData, isLoading, loadData } = useGroupedData({ getAllFiles, @@ -72,6 +78,7 @@ export const GroupedDataProvider = ({ subscribeToEvents, searchFilter, noteNameFilter, + templateFilter, }); const { getLoadedCount, loadMoreItems, handleScroll } = usePagination(); @@ -93,6 +100,7 @@ export const GroupedDataProvider = ({ toggleFiles, onFileClick, getAvailableStatuses, + getAvailableStatusesWithTemplateInfo, }; return ( diff --git a/components/GroupedStatusView/hooks/useGroupedData.ts b/components/GroupedStatusView/hooks/useGroupedData.ts index 689070f..3978b6a 100644 --- a/components/GroupedStatusView/hooks/useGroupedData.ts +++ b/components/GroupedStatusView/hooks/useGroupedData.ts @@ -7,6 +7,7 @@ export type UseGroupedDataProps = { subscribeToEvents: (onDataChange: () => void) => () => void; searchFilter: string; noteNameFilter: string; + templateFilter: string; }; export const useGroupedData = ({ @@ -15,6 +16,7 @@ export const useGroupedData = ({ subscribeToEvents, searchFilter, noteNameFilter, + templateFilter, }: UseGroupedDataProps) => { const [groupedData, setGroupedData] = useState({}); const [isLoading, setIsLoading] = useState(true); @@ -43,7 +45,12 @@ export const useGroupedData = ({ }, [loadData, subscribeToEvents]); const filteredData = useMemo(() => { - if (!searchFilter.trim() && !noteNameFilter.trim()) return groupedData; + if ( + !searchFilter.trim() && + !noteNameFilter.trim() && + !templateFilter.trim() + ) + return groupedData; const filtered: GroupedByStatus = {}; const searchLower = searchFilter.toLowerCase(); @@ -52,6 +59,18 @@ export const useGroupedData = ({ Object.entries(groupedData).forEach(([tag, statusGroups]) => { filtered[tag] = {}; Object.entries(statusGroups).forEach(([statusName, files]) => { + // Filter by template if templateFilter is provided + if (templateFilter.trim()) { + // Check if statusName contains the template (scoped: "template:status") + const statusTemplate = statusName.includes(":") + ? statusName.split(":", 2)[0] + : ""; + + if (statusTemplate !== templateFilter) { + return; // Skip this status group if it doesn't match the template filter + } + } + let matchingFiles = files; // Filter by status/tag if searchFilter is provided @@ -79,7 +98,7 @@ export const useGroupedData = ({ }); return filtered; - }, [groupedData, searchFilter, noteNameFilter]); + }, [groupedData, searchFilter, noteNameFilter, templateFilter]); return { groupedData, diff --git a/integrations/views/grouped-dashboard-view.tsx b/integrations/views/grouped-dashboard-view.tsx index d82a11b..335f432 100644 --- a/integrations/views/grouped-dashboard-view.tsx +++ b/integrations/views/grouped-dashboard-view.tsx @@ -137,6 +137,10 @@ export class GroupedDashboardView extends ItemView { return statuses.map(this.convertStatusToStatusItem); }; + private getAvailableStatusesWithTemplateInfo = () => { + return BaseNoteStatusService.getAllAvailableStatuses(); + }; + private handleFileClick = (file: FileItem) => { const tFile = BaseNoteStatusService.app.vault.getAbstractFileByPath( file.path, @@ -201,6 +205,9 @@ export class GroupedDashboardView extends ItemView { onFileClick={this.handleFileClick} subscribeToEvents={this.subscribeToEvents} getAvailableStatuses={this.getAvailableStatuses} + getAvailableStatusesWithTemplateInfo={ + this.getAvailableStatusesWithTemplateInfo + } />, ); } diff --git a/integrations/views/grouped-status-view.tsx b/integrations/views/grouped-status-view.tsx index 274cb5d..df267dc 100644 --- a/integrations/views/grouped-status-view.tsx +++ b/integrations/views/grouped-status-view.tsx @@ -122,6 +122,10 @@ export class GroupedStatusView extends ItemView { return statuses.map(this.convertStatusToStatusItem); }; + private getAvailableStatusesWithTemplateInfo = () => { + return BaseNoteStatusService.getAllAvailableStatuses(); + }; + private handleFileClick = (file: FileItem) => { const tFile = BaseNoteStatusService.app.vault.getAbstractFileByPath( file.path, @@ -186,6 +190,9 @@ export class GroupedStatusView extends ItemView { onFileClick={this.handleFileClick} subscribeToEvents={this.subscribeToEvents} getAvailableStatuses={this.getAvailableStatuses} + getAvailableStatusesWithTemplateInfo={ + this.getAvailableStatusesWithTemplateInfo + } />, ); } diff --git a/styles/components/grouped-view.css b/styles/components/grouped-view.css index 2b2f16b..e8c5b0e 100644 --- a/styles/components/grouped-view.css +++ b/styles/components/grouped-view.css @@ -194,3 +194,67 @@ .grouped-status-files-list::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-active-bg); } + +/* Template Filter (BEM) */ +.grouped-status-filters__template { + display: flex; + flex-direction: column; + gap: var(--size-2-1); +} + +.grouped-status-filters__template-select { + width: 100%; + padding: var(--size-2-2) var(--size-2-3); + border: 1px solid var(--background-modifier-border); + border-radius: var(--radius-s); + background: var(--background-primary); + color: var(--text-normal); + font-size: var(--font-ui-small); + cursor: pointer; +} + +.grouped-status-filters__template-select:focus { + outline: none; + border-color: var(--interactive-accent); + box-shadow: 0 0 0 2px var(--background-modifier-accent); +} + +.grouped-status-filters__note { + display: flex; + flex-direction: column; + gap: var(--size-2-1); +} + +.grouped-status-filters__note-input { + width: 100%; + padding: var(--size-2-2) var(--size-2-3); + border: 1px solid var(--background-modifier-border); + border-radius: var(--radius-s); + background: var(--background-primary); + color: var(--text-normal); + font-size: var(--font-ui-small); +} + +.grouped-status-filters__note-input:focus { + outline: none; + border-color: var(--interactive-accent); + box-shadow: 0 0 0 2px var(--background-modifier-accent); +} + +/* Template Badge (BEM) */ +.grouped-status-group__status { + display: flex; + align-items: center; + gap: var(--size-2-2); +} + +.grouped-status-group__template-badge { + background: var(--background-modifier-border); + color: var(--text-muted); + padding: var(--size-2-1) var(--size-2-2); + border-radius: var(--radius-s); + font-size: var(--font-ui-smaller); + font-weight: var(--font-medium); + text-transform: capitalize; + white-space: nowrap; +}