diff --git a/components/StatusDashboard/QuickActionsPanel.tsx b/components/StatusDashboard/QuickActionsPanel.tsx index 3353834..6b7f873 100644 --- a/components/StatusDashboard/QuickActionsPanel.tsx +++ b/components/StatusDashboard/QuickActionsPanel.tsx @@ -1,58 +1,167 @@ -import { BaseNoteStatusService } from "@/core/noteStatusService"; -import settingsService from "@/core/settingsService"; +export type DashboardAction = + | "refresh" + | "open-grouped-view" + | "find-unassigned" + | "change-status" + | "insert-metadata" + | "cycle-status" + | "clear-status" + | "copy-status" + | "paste-status" + | "search-by-status" + | "toggle-multiple-mode" + | "set-quick-status"; interface QuickActionsPanelProps { - onRefresh: () => void; + hasCurrentFile: boolean; + useMultipleStatuses: boolean; + quickStatusCommands: Array<{ name: string; command: string }>; + availableStatuses: Array<{ name: string }>; + onAction: (action: DashboardAction, value?: string) => void; } -export const QuickActionsPanel = ({ onRefresh }: QuickActionsPanelProps) => { +export const QuickActionsPanel = ({ + hasCurrentFile, + useMultipleStatuses, + quickStatusCommands, + availableStatuses, + onAction, +}: QuickActionsPanelProps) => { return (

Quick Actions

- - + +
- console.log( - `Found ${filesWithoutStatus.length} notes without status:`, - filesWithoutStatus.map((f) => f.path), - ); - }} - > - 🔍 Find Notes Without Status - - + {/* Quick Status Group - Only show when not in multi-status mode and has quick statuses */} + {!useMultipleStatuses && quickStatusCommands.length > 0 && ( +
+
+ ⚡ Quick Status +
+ {quickStatusCommands.map((status) => ( + + ))} +
+ )} + + {/* Current Note Group */} +
+
+ 📝 Current Note +
+ + + + +
+ + {/* Clipboard Group */} +
+
+ 📋 Clipboard +
+ + +
+ + {/* Tools Group */} +
+
⚙️ Tools
+ + + +
); diff --git a/components/StatusDashboard/StatusDashboard.tsx b/components/StatusDashboard/StatusDashboard.tsx index b72ffb4..442e473 100644 --- a/components/StatusDashboard/StatusDashboard.tsx +++ b/components/StatusDashboard/StatusDashboard.tsx @@ -4,11 +4,20 @@ import eventBus from "@/core/eventBus"; import { VaultStatsCard } from "./VaultStatsCard"; import { CurrentNoteSection } from "./CurrentNoteSection"; import { StatusDistributionChart } from "./StatusDistributionChart"; -import { QuickActionsPanel } from "./QuickActionsPanel"; +import { QuickActionsPanel, DashboardAction } from "./QuickActionsPanel"; import { useVaultStats } from "./useVaultStats"; import { useCurrentNote } from "./useCurrentNote"; +import { PluginSettings } from "@/types/pluginSettings"; -export const StatusDashboard = () => { +interface StatusDashboardProps { + onAction: (action: DashboardAction, value?: string) => void; + settings: PluginSettings; +} + +export const StatusDashboard = ({ + onAction, + settings, +}: StatusDashboardProps) => { const [isLoading, setIsLoading] = useState(true); const { vaultStats, updateVaultStats } = useVaultStats(); const { currentNote, updateCurrentNote } = useCurrentNote(); @@ -23,6 +32,17 @@ export const StatusDashboard = () => { } }, [updateVaultStats, updateCurrentNote]); + const handleAction = useCallback( + (action: DashboardAction, value?: string) => { + if (action === "refresh") { + loadData(); + } else { + onAction(action, value); + } + }, + [loadData, onAction], + ); + useEffect(() => { loadData(); @@ -90,7 +110,18 @@ export const StatusDashboard = () => { - + ({ + name, + command: `note-status:set-status-${name}`, + }), + )} + availableStatuses={BaseNoteStatusService.getAllAvailableStatuses()} + onAction={handleAction} + /> ); diff --git a/components/atoms/Input.tsx b/components/atoms/Input.tsx index 832691e..184af6e 100644 --- a/components/atoms/Input.tsx +++ b/components/atoms/Input.tsx @@ -9,6 +9,8 @@ interface BaseInputProps { placeholder?: string; className?: string; style?: React.CSSProperties; + onFocus?: () => void; + onBlur?: () => void; } interface TextInputProps extends BaseInputProps { @@ -57,7 +59,19 @@ const getInputStyles = (variant: InputVariant): React.CSSProperties => { }; export const Input = React.forwardRef( - ({ variant, value, onChange, placeholder, className, style }, ref) => { + ( + { + variant, + value, + onChange, + placeholder, + className, + style, + onFocus, + onBlur, + }, + ref, + ) => { const inputStyles = getInputStyles(variant); const combinedStyles = { ...inputStyles, ...style }; @@ -70,6 +84,8 @@ export const Input = React.forwardRef( placeholder={placeholder} className={className} style={combinedStyles} + onFocus={onFocus} + onBlur={onBlur} /> ); }, diff --git a/integrations/views/status-dashboard-view.tsx b/integrations/views/status-dashboard-view.tsx index 381d9e2..acd880a 100644 --- a/integrations/views/status-dashboard-view.tsx +++ b/integrations/views/status-dashboard-view.tsx @@ -1,6 +1,15 @@ -import { ItemView, WorkspaceLeaf } from "obsidian"; +import { ItemView, WorkspaceLeaf, Notice, App } from "obsidian"; import { Root, createRoot } from "react-dom/client"; import { StatusDashboard } from "@/components/StatusDashboard/StatusDashboard"; +import { DashboardAction } from "@/components/StatusDashboard/QuickActionsPanel"; +import { BaseNoteStatusService } from "@/core/noteStatusService"; +import settingsService from "@/core/settingsService"; + +interface AppWithCommands extends App { + commands: { + executeCommandById(commandId: string): boolean; + }; +} export const VIEW_TYPE_STATUS_DASHBOARD = "status-dashboard-view"; @@ -23,13 +32,105 @@ export class StatusDashboardView extends ItemView { return "bar-chart-2"; } + private handleAction = (action: DashboardAction, value?: string) => { + const appWithCommands = this.app as AppWithCommands; + + switch (action) { + case "open-grouped-view": + this.openGroupedView(); + break; + case "find-unassigned": + this.findUnassignedNotes(); + break; + case "change-status": + appWithCommands.commands.executeCommandById( + "note-status:change-status", + ); + break; + case "insert-metadata": + appWithCommands.commands.executeCommandById( + "note-status:insert-status-metadata", + ); + break; + case "cycle-status": + appWithCommands.commands.executeCommandById( + "note-status:cycle-status", + ); + break; + case "clear-status": + appWithCommands.commands.executeCommandById( + "note-status:clear-status", + ); + break; + case "copy-status": + appWithCommands.commands.executeCommandById( + "note-status:copy-status", + ); + break; + case "paste-status": + appWithCommands.commands.executeCommandById( + "note-status:paste-status", + ); + break; + case "search-by-status": + appWithCommands.commands.executeCommandById( + "note-status:search-by-status", + ); + break; + case "toggle-multiple-mode": + appWithCommands.commands.executeCommandById( + "note-status:toggle-multiple-statuses", + ); + break; + case "set-quick-status": + if (value) { + const commandId = `note-status:set-status-${value}`; + const result = + appWithCommands.commands.executeCommandById(commandId); + if (!result) { + new Notice( + `Quick status command failed: ${value}. Make sure the status exists and multiple statuses mode is disabled.`, + ); + } + } + break; + } + }; + + private openGroupedView() { + const leaf = BaseNoteStatusService.app.workspace.getLeaf(); + leaf.setViewState({ type: "grouped-status-view", active: true }); + } + + private findUnassignedNotes() { + const files = BaseNoteStatusService.app.vault.getMarkdownFiles(); + const filesWithoutStatus = files.filter((file) => { + const cachedMetadata = + BaseNoteStatusService.app.metadataCache.getFileCache(file); + const frontmatter = cachedMetadata?.frontmatter; + return ( + !frontmatter || !frontmatter[settingsService.settings.tagPrefix] + ); + }); + new Notice(`Found ${filesWithoutStatus.length} notes without status`); + console.log( + "Notes without status:", + filesWithoutStatus.map((f) => f.path), + ); + } + async onOpen() { const container = this.containerEl.children[1]; container.empty(); container.addClass("status-dashboard-view-container"); this.root = createRoot(container); - this.root.render(); + this.root.render( + , + ); } async onClose() { diff --git a/styles/components/dashboard.css b/styles/components/dashboard.css index 968002a..8135634 100644 --- a/styles/components/dashboard.css +++ b/styles/components/dashboard.css @@ -229,14 +229,30 @@ /* Quick Actions */ .quick-actions { padding: var(--size-4-3); + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: var(--size-4-3); +} + +.quick-actions-group { display: flex; - flex-wrap: wrap; - gap: var(--size-4-2); + flex-direction: column; + gap: var(--size-2-2); +} + +.quick-actions-group-title { + font-size: var(--font-ui-small); + font-weight: var(--font-semibold); + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.05em; + padding-bottom: var(--size-2-1); + border-bottom: 1px solid var(--background-modifier-border); + margin-bottom: var(--size-2-1); } .quick-action-btn { - flex: 1; - min-width: 200px; + width: 100%; padding: var(--size-2-3) var(--size-4-2); border: 1px solid var(--background-modifier-border); border-radius: var(--radius-m); @@ -245,16 +261,33 @@ cursor: pointer; font-size: var(--font-ui-medium); font-weight: var(--font-medium); - text-align: center; + text-align: left; + display: flex; + align-items: center; + gap: var(--size-2-2); transition: all var(--anim-duration-fast) ease; } -.quick-action-btn:hover { +.quick-action-btn:hover:not(:disabled) { background: var(--interactive-hover); transform: translateY(-1px); box-shadow: var(--shadow-s); } +.quick-action-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + background: var(--background-modifier-border); + color: var(--text-faint); +} + +/* Mobile responsive */ +@media (max-width: 768px) { + .quick-actions { + grid-template-columns: 1fr; + } +} + /* States */ .status-dashboard-loading { display: flex;