Files
TasGrid/src/views/HelpView.tsx
T
tcardoza 47257aa007
CI / build (push) Has been skipped
CI / deploy (push) Successful in 1m36s
Improved Help and help chatbot
2026-03-25 18:07:51 -05:00

207 lines
26 KiB
TypeScript

import { type Component, For, Show, onCleanup, onMount } from "solid-js";
import { ArrowLeft, BookOpen, Boxes, ExternalLink, HelpCircle, LayoutDashboard, Link2, ListTodo, NotebookText, PencilLine, RefreshCcw, Search, Settings2, Share2, ShieldCheck, Sparkles, WandSparkles, Workflow } from "lucide-solid";
import { Button } from "@/components/ui/button";
interface HelpViewProps { onBack?: () => void; }
interface AccessCard { title: string; content: string; detail: string; href?: string; }
interface QuickAction { title: string; steps: string[]; target: string; }
interface RelatedLink { label: string; target: string; }
interface Section { id: string; title: string; icon: any; intro: string; body: string; steps: string[]; reminder: string; warning: string; related: RelatedLink[]; }
interface DeepSection { id: string; title: string; icon: any; intro: string; bullets: string[]; why: string; related: RelatedLink[]; }
const accessCards: AccessCard[] = [
{ title: "Website", content: "tasgrid.ccllc.pro", href: "https://tasgrid.ccllc.pro", detail: "Open TasGrid in your browser or install it as an app on mobile." },
{ title: "Login", content: "Use your Cardoza Construction email", detail: "Your account uses the same email and password you use for Outlook." },
{ title: "What This Guide Is For", content: "Refresh first, then go deeper", detail: "This guide supports the in-person training you already received." }
];
const quickActions: QuickAction[] = [
{ title: "Create a task", target: "create-tasks", steps: ["Open Quick Entry with + or Ctrl/Cmd+K.", "Enter Title, Priority, and Urgency.", "Create the task and let TasGrid keep the active context."] },
{ title: "Edit a task", target: "manage-tasks", steps: ["Click the task to open it.", "Edit status, dates, tags, and notes.", "Use the status circle to update progress or completion."] },
{ title: "Share a task", target: "share-tasks", steps: ["Open the task.", "Add an @person, @bucket, or #note tag.", "Choose Collaborative or Handoff if prompted."] },
{ title: "Move work into a bucket", target: "buckets-supervision", steps: ["Add the bucket's @tag to the task.", "Or create the task while inside that bucket.", "Pin the bucket in Settings if you want it in the switcher."] },
{ title: "Switch to notes", target: "notes", steps: ["Use the Tasks/Notes toggle.", "Open or create a note.", "Work inside it like a project document, not just a comment box."] },
{ title: "Link a task to a note", target: "notes", steps: ["Open the note's Linked Tasks panel.", "Link an existing task or create a new one.", "TasGrid connects it through the note's #tag."] },
{ title: "Find filtered work", target: "finding-work", steps: ["Use search and filters first.", "Switch views if you need a different ranking lens.", "Save the filter if you will reuse it."] },
{ title: "Recover from trash", target: "recovery-trash", steps: ["Open Settings.", "Expand Trash.", "Recover the item before its 7-day window expires."] }
];
const coreSections: Section[] = [
{ id: "create-tasks", title: "Create Tasks", icon: ListTodo, intro: "Use this when you remember the concept but forgot the exact task flow.", body: "Quick Entry is the main capture flow. It keeps scheduling and context consistent for you.", steps: ["Open Quick Entry with the floating plus button or Ctrl/Cmd+K.", "Enter Title, Priority, and Urgency. Add Size, Due Date, tags, and rich details if needed.", "Create the task. TasGrid automatically applies the current context, like a bucket, supervised user, or active note."], reminder: "Urgency and Due Date stay linked, and new tasks inherit the place you are currently working from.", warning: "If you create work inside a bucket, supervised context, or note, TasGrid usually adds that context automatically.", related: [{ label: "How contexts and sharing work", target: "contexts-sharing" }, { label: "How to find work later", target: "finding-work" }] },
{ id: "manage-tasks", title: "Edit and Manage Tasks", icon: PencilLine, intro: "Use this when you know the task exists but forgot where a control lives.", body: "The task detail sheet is the main editing surface for title, status, dates, tags, favorites, and task notes.", steps: ["Click a task to open the detail sheet.", "Edit title, status, priority, urgency, size, due date, tags, favorite state, and the rich text body.", "Use the status circle to move through progress states or mark the task complete."], reminder: "Task cards also support quick actions like status updates, duplicate, and favorite.", warning: "Deleting a task sends it to trash first, so check trash before assuming something is gone forever.", related: [{ label: "Recover from trash", target: "recovery-trash" }, { label: "Templates and recurrence", target: "time-savers" }] },
{ id: "notes", title: "Notes", icon: NotebookText, intro: "Use this when you need to refresh how notes behave as workspaces.", body: "Notes are a full mode inside the app. They can hold rich content, linked tasks, favorites, and public or private visibility.", steps: ["Switch to Notes using the Tasks/Notes toggle.", "Create or open a note from the notes sidebar.", "Use Linked Tasks to connect existing work or create a task directly from the note context."], reminder: "Creating a task from a note usually links it automatically through the note's #tag.", warning: "If you rename a note, TasGrid also updates the matching #note tag definition so linked tasks stay aligned.", related: [{ label: "How notes become project workspaces", target: "notes-project-workspaces" }, { label: "How rich editing works", target: "editor-rich-content" }] },
{ id: "share-tasks", title: "Share Tasks", icon: Share2, intro: "Use this if you remember that sharing exists, but not the current model.", body: "Task sharing is tag-driven. You share by attaching a user, bucket, or note context to the task.", steps: ["Open the task and edit its tags.", "Add an @person, @bucket, or #note context through the tag picker.", "Choose Collaborative or Handoff when TasGrid asks for a sharing mode."], reminder: "The old mental model of a separate share dialog is no longer the main workflow.", warning: "Handoff and Collaborative do very different things. Handoff moves responsibility; Collaborative keeps the task visible in both places.", related: [{ label: "How contexts and sharing work", target: "contexts-sharing" }, { label: "Buckets and supervision", target: "buckets-supervision" }] },
{ id: "buckets-supervision", title: "Buckets and Supervision", icon: Boxes, intro: "Use this if you forgot how to move between your work, team buckets, and supervised contexts.", body: "Contexts control where work lives and who can see it. The same switcher handles My Bucket, pinned shared buckets, and supervised personal contexts.", steps: ["Use the context switcher to move between My Bucket, pinned buckets, and supervised contexts.", "Pin buckets in Settings if you want them in the switcher.", "Create tasks from the context you are already inside so TasGrid applies the right tag automatically."], reminder: "Your own personal context tag, the active bucket tag, and some note tags may stay hidden while still remaining active.", warning: "If a task seems to belong to a bucket or person even though you cannot see the tag, hidden context behavior is often the reason.", related: [{ label: "Share tasks", target: "share-tasks" }, { label: "Deep dive on context behavior", target: "contexts-sharing" }] },
{ id: "finding-work", title: "Views and Finding Work", icon: Search, intro: "Use this when the task exists but you cannot remember how to surface it quickly.", body: "TasGrid gives you multiple sort views plus search and filters so you can find the right work without rebuilding lists by hand.", steps: ["Use search for text and the filter controls for tags, ranges, edited today, mine, or starred.", "Switch between Focus, Matrix, Snowball, Dig In, Urgency, Priority, or Progress depending on the lens you need.", "Save filters you reuse often so you do not have to rebuild them every time."], reminder: "Changing views is often faster than changing the task itself.", warning: "If a task seems missing, first check your current context and filters before assuming it was deleted or moved.", related: [{ label: "How task views think", target: "task-views-think" }, { label: "Templates and saved filters", target: "time-savers" }] },
{ id: "recovery-trash", title: "Recovery and Trash", icon: RefreshCcw, intro: "Use this when you are trying to undo something or confirm whether an item is really gone.", body: "Settings contains separate trash sections for tasks and notes. Items stay there for 7 days before permanent deletion.", steps: ["Open Settings and expand Trash.", "Look in the Tasks or Notes list depending on what you lost.", "Recover the item or permanently delete it if you are sure."], reminder: "Deletion is usually reversible during the trash window, and the app also offers an immediate undo action.", warning: "Expired trash is permanently removed when the app loads, so do not rely on trash as long-term storage.", related: [{ label: "Manage tasks", target: "manage-tasks" }, { label: "How settings change behavior", target: "settings-behavior" }] }
];
const deepSections: DeepSection[] = [
{ id: "task-views-think", title: "How Task Views Think", icon: LayoutDashboard, intro: "Use this when you want to understand why the same task appears differently across views.", bullets: ["Focus, Urgency, Priority, Progress, Snowball, and Dig In all sort the same active task pool by different rules.", "Matrix places incomplete tasks on an urgency-versus-priority grid instead of a list.", "Matrix dots are clickable, size-aware, and show U, S, and P values.", "The Matrix time scale can be adjusted to 1, 7, 30, 60, or 90 days."], why: "If users understand the view's mental model, they stop fighting the sort order and start choosing the right view for the moment.", related: [{ label: "Find work refresher", target: "finding-work" }, { label: "Templates and saved filters", target: "time-savers" }] },
{ id: "contexts-sharing", title: "How Contexts and Sharing Work", icon: Share2, intro: "Use this when the app's access behavior feels smarter or stranger than a normal tag list.", bullets: ["TasGrid turns @user, @bucket, and #note tags into structured relationships, not just labels.", "Each context can behave as Collaborative or Handoff.", "Per-user policies, bucket policies, and supervision access all change how work moves and who can see it.", "Some context tags stay hidden in the current view while still remaining active."], why: "Most sharing confusion comes from treating contexts like plain tags when they actually control access and ownership behavior.", related: [{ label: "Share tasks refresher", target: "share-tasks" }, { label: "Buckets and supervision", target: "buckets-supervision" }] },
{ id: "notes-project-workspaces", title: "How Notes Become Project Workspaces", icon: NotebookText, intro: "Use this when you want to go beyond simple note editing and understand note structure.", bullets: ["Root notes can have child tabs that inherit the parent note's privacy.", "Desktop supports drag-and-drop tab ordering.", "Linked tasks live at the note workspace level, and root notes manage those relationships directly.", "Share links open note content through the embed route."], why: "Once users treat notes like a workspace with tabs and linked tasks, notes become much more useful than static text pages.", related: [{ label: "Notes refresher", target: "notes" }, { label: "How the editor handles rich content", target: "editor-rich-content" }] },
{ id: "editor-rich-content", title: "How the Editor Handles Rich Content", icon: WandSparkles, intro: "Use this when someone remembers that the editor is powerful, but forgot what it can do.", bullets: ["Tasks, notes, and templates all use the same editor.", "Typing / opens slash commands for blocks, lists, checklists, uploads, job files, dividers, and tables.", "The editor supports underline, links, highlight, alignment, resizable tables, @user mentions, and #note mentions.", "Images, videos, and files are handled differently and can be pasted or dragged directly into the editor."], why: "Users trust the note and task body more when they know it can hold real working content, not just plain text.", related: [{ label: "Notes refresher", target: "notes" }, { label: "How notes become project workspaces", target: "notes-project-workspaces" }] },
{ id: "time-savers", title: "How Templates, Filters, and Recurrence Save Time", icon: Workflow, intro: "Use this when the user understands the basics and wants to stop repeating setup work.", bullets: ["Task templates can store reusable titles, priority, urgency, tags, and rich descriptions.", "Task filters and note filters can be saved as reusable templates.", "Load All is useful when older history is not currently loaded into the visible list.", "Recurrence supports daily, weekly, and monthly schedules, and completed recurring tasks reopen automatically."], why: "These features remove repeated setup and reduce the amount of remembering users have to do from scratch.", related: [{ label: "Create tasks refresher", target: "create-tasks" }, { label: "Find work refresher", target: "finding-work" }] },
{ id: "settings-behavior", title: "How Settings Change Behavior", icon: Settings2, intro: "Use this when a user needs to understand how the app can be configured, not just used.", bullets: ["Settings controls theme, tag values and colors, bucket creation and pinning, supervision access, import tools, trash recovery, app updates, and PWA reset.", "Shared Buckets defines which team spaces exist and whether they behave as Collaborative or Handoff.", "Tag Sharing defines how user contexts behave and who can supervise your personal context.", "Import Tasks helps convert pasted lists into structured work with bulk defaults."], why: "Most team-level confusion comes from not realizing that sharing, buckets, and maintenance behavior are defined in Settings.", related: [{ label: "Buckets and supervision", target: "buckets-supervision" }, { label: "How contexts and sharing work", target: "contexts-sharing" }] }
];
const jumpLinks = [
{ label: "Get Unstuck Fast", target: "quick-start" },
{ label: "Create Tasks", target: "create-tasks" },
{ label: "Share Tasks", target: "share-tasks" },
{ label: "Notes", target: "notes" },
{ label: "Find Work", target: "finding-work" },
{ label: "Deep Dive", target: "task-views-think" }
];
const RelatedTopics: Component<{ items: RelatedLink[]; onJump: (id: string) => void }> = (props) => (
<div class="pt-3 border-t border-border/40 space-y-2">
<p class="text-[0.625rem] font-black uppercase tracking-widest text-muted-foreground">Related topics</p>
<div class="flex flex-wrap gap-2">
<For each={props.items}>{(item) => <button class="rounded-full border border-border/60 bg-background px-3 py-1.5 text-[0.6875rem] font-medium text-muted-foreground hover:text-foreground hover:border-primary/40 hover:bg-primary/5 transition-colors" onClick={() => props.onJump(item.target)}>{item.label}</button>}</For>
</div>
</div>
);
export const HelpView: Component<HelpViewProps> = (props) => {
const scrollToSection = (id?: string, behavior: ScrollBehavior = "smooth") => {
if (!id) return;
document.getElementById(`help-${id}`)?.scrollIntoView({ behavior, block: "start" });
};
const getSectionFromHash = () => window.location.hash.startsWith("#help/") ? decodeURIComponent(window.location.hash.slice("#help/".length)) : undefined;
const jumpTo = (id: string) => { if (window.location.hash !== `#help/${id}`) window.location.hash = `#help/${id}`; scrollToSection(id); };
onMount(() => {
const syncToHash = () => window.setTimeout(() => scrollToSection(getSectionFromHash(), "auto"), 50);
const handleHelpTarget = (event: Event) => {
const detail = (event as CustomEvent).detail as { sectionId?: string } | undefined;
window.setTimeout(() => scrollToSection(detail?.sectionId || getSectionFromHash()), 50);
};
syncToHash();
window.addEventListener("hashchange", syncToHash);
window.addEventListener("taskgrid:help-target", handleHelpTarget);
onCleanup(() => {
window.removeEventListener("hashchange", syncToHash);
window.removeEventListener("taskgrid:help-target", handleHelpTarget);
});
});
return (
<div class="w-full max-w-5xl mx-auto py-4 sm:py-10 px-4 sm:px-0 animate-in fade-in slide-in-from-bottom-2 duration-500">
<header class="flex items-center gap-4 mb-8 sm:mb-10">
<Show when={props.onBack}><Button variant="ghost" size="icon" onClick={() => props.onBack?.()} class="h-10 w-10 rounded-xl"><ArrowLeft size={20} /></Button></Show>
<div>
<h1 class="text-2xl sm:text-4xl font-black tracking-tighter flex items-center gap-3"><HelpCircle size={32} class="text-primary hidden sm:block" />Help</h1>
<p class="text-[0.625rem] sm:text-xs text-muted-foreground font-mono uppercase tracking-widest opacity-70">Refresh what you forgot, then learn the deeper system</p>
</div>
</header>
<div class="space-y-12 pb-20">
<section class="rounded-3xl border border-primary/20 bg-primary/[0.05] p-5 sm:p-6 space-y-4">
<div class="space-y-2">
<p class="text-[0.625rem] font-black uppercase tracking-widest text-primary/80">Jump to</p>
<h2 class="text-xl font-black tracking-tight">Need the fast version?</h2>
<p class="text-sm text-muted-foreground leading-relaxed">Use these shortcuts when you remember being shown something, but do not remember the exact steps anymore.</p>
</div>
<div class="flex flex-wrap gap-2">
<For each={jumpLinks}>{(item) => <button class="rounded-full border border-primary/20 bg-background/80 px-3 py-1.5 text-[0.6875rem] font-semibold text-foreground hover:bg-primary/10 hover:border-primary/40 transition-colors" onClick={() => jumpTo(item.target)}>{item.label}</button>}</For>
</div>
</section>
<section id="help-quick-start" class="space-y-5">
<div class="space-y-2">
<h2 class="text-xl font-bold border-b border-border pb-2 flex items-center gap-2"><Sparkles size={20} class="text-primary" />Get Unstuck Fast</h2>
<p class="text-sm text-muted-foreground leading-relaxed">These cards answer the questions users most often have after in-person training.</p>
</div>
<div class="grid gap-4 md:grid-cols-2">
<For each={quickActions}>{(action) => (
<div class="rounded-2xl border border-border/50 bg-card/70 shadow-sm p-4 space-y-3">
<div class="space-y-1"><p class="text-[0.625rem] font-black uppercase tracking-widest text-primary/80">I need to...</p><h3 class="text-sm font-bold tracking-tight">{action.title}</h3></div>
<ol class="space-y-2">
<For each={action.steps}>{(step, index) => <li class="flex gap-3 text-sm text-muted-foreground leading-relaxed"><span class="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary text-[0.625rem] font-bold">{index() + 1}</span><span>{step}</span></li>}</For>
</ol>
<Button variant="ghost" size="sm" class="h-8 px-2 text-[0.6875rem] font-bold text-primary hover:bg-primary/10" onClick={() => jumpTo(action.target)}>Open full guide</Button>
</div>
)}</For>
</div>
</section>
<section class="space-y-6 rounded-[2rem] border border-sky-200/40 bg-sky-500/[0.04] p-5 sm:p-6">
<div class="space-y-2">
<h2 class="text-xl font-bold border-b border-border/60 pb-2 flex items-center gap-2"><BookOpen size={20} class="text-sky-600" />Core Workflows</h2>
<p class="text-sm text-muted-foreground leading-relaxed">This layer is for refreshing the workflows you already learned in person.</p>
</div>
<div class="space-y-5">
<For each={coreSections}>{(section) => {
const Icon = section.icon;
return <article id={`help-${section.id}`} class="rounded-3xl border border-border/50 bg-card/80 shadow-sm overflow-hidden">
<div class="p-5 sm:p-6 border-b border-border/40 bg-gradient-to-r from-sky-500/[0.08] via-sky-500/[0.03] to-transparent">
<div class="flex items-start gap-4">
<div class="hidden sm:flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl bg-sky-500/10 text-sky-700"><Icon size={20} /></div>
<div class="space-y-2 min-w-0"><h3 class="text-lg sm:text-xl font-black tracking-tight flex items-center gap-2"><Icon size={18} class="sm:hidden text-sky-700" />{section.title}</h3><p class="text-sm text-muted-foreground leading-relaxed">{section.intro}</p></div>
</div>
</div>
<div class="p-5 sm:p-6 space-y-5">
<div class="space-y-2"><p class="text-[0.625rem] font-black uppercase tracking-widest text-muted-foreground">What it is</p><p class="text-sm leading-relaxed text-foreground">{section.body}</p></div>
<div class="space-y-3"><p class="text-[0.625rem] font-black uppercase tracking-widest text-muted-foreground">How to do it</p><ol class="space-y-2"><For each={section.steps}>{(step, index) => <li class="flex gap-3 text-sm text-muted-foreground leading-relaxed"><span class="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-sky-500/10 text-sky-700 text-[0.625rem] font-bold">{index() + 1}</span><span>{step}</span></li>}</For></ol></div>
<div class="grid gap-4 md:grid-cols-2">
<div class="rounded-2xl border border-amber-300/40 bg-amber-500/[0.06] p-4 space-y-2"><p class="text-[0.625rem] font-black uppercase tracking-widest text-amber-700">What people usually forget</p><p class="text-sm text-muted-foreground leading-relaxed">{section.reminder}</p></div>
<div class="rounded-2xl border border-rose-300/40 bg-rose-500/[0.06] p-4 space-y-2"><p class="text-[0.625rem] font-black uppercase tracking-widest text-rose-700">Watch for this</p><p class="text-sm text-muted-foreground leading-relaxed">{section.warning}</p></div>
</div>
<RelatedTopics items={section.related} onJump={jumpTo} />
</div>
</article>;
}}</For>
</div>
</section>
<section class="space-y-6 rounded-[2rem] border border-violet-200/40 bg-violet-500/[0.04] p-5 sm:p-6">
<div class="space-y-2">
<h2 class="text-xl font-bold border-b border-border/60 pb-2 flex items-center gap-2"><Link2 size={20} class="text-violet-700" />Deep Dive</h2>
<p class="text-sm text-muted-foreground leading-relaxed">This layer is optional enrichment for users who want the mental model behind the app.</p>
</div>
<div class="space-y-5">
<For each={deepSections}>{(section) => {
const Icon = section.icon;
return <article id={`help-${section.id}`} class="rounded-3xl border border-border/50 bg-card/80 shadow-sm overflow-hidden">
<div class="p-5 sm:p-6 border-b border-border/40 bg-gradient-to-r from-violet-500/[0.08] via-violet-500/[0.03] to-transparent">
<div class="flex items-start gap-4">
<div class="hidden sm:flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl bg-violet-500/10 text-violet-700"><Icon size={20} /></div>
<div class="space-y-2 min-w-0"><h3 class="text-lg sm:text-xl font-black tracking-tight flex items-center gap-2"><Icon size={18} class="sm:hidden text-violet-700" />{section.title}</h3><p class="text-sm text-muted-foreground leading-relaxed">{section.intro}</p></div>
</div>
</div>
<div class="p-5 sm:p-6 space-y-5">
<div class="space-y-3"><p class="text-[0.625rem] font-black uppercase tracking-widest text-muted-foreground">Learn more</p><ul class="space-y-2"><For each={section.bullets}>{(item) => <li class="flex gap-3 text-sm text-muted-foreground leading-relaxed"><span class="mt-[0.45rem] h-1.5 w-1.5 shrink-0 rounded-full bg-violet-600/70" /><span>{item}</span></li>}</For></ul></div>
<div class="rounded-2xl border border-violet-300/40 bg-violet-500/[0.06] p-4 space-y-2"><p class="text-[0.625rem] font-black uppercase tracking-widest text-violet-700">Why it matters</p><p class="text-sm text-muted-foreground leading-relaxed">{section.why}</p></div>
<RelatedTopics items={section.related} onJump={jumpTo} />
</div>
</article>;
}}</For>
</div>
</section>
<section class="space-y-5">
<h2 class="text-xl font-bold border-b border-border pb-2 flex items-center gap-2"><ShieldCheck size={20} class="text-primary" />Welcome & Access</h2>
<p class="text-sm text-muted-foreground leading-relaxed">This section is mostly here for helping someone else get into TasGrid or for occasional reference later. It is intentionally placed near the end because most users opening Help already know how to get in.</p>
<div class="grid gap-4 md:grid-cols-3">
<For each={accessCards}>{(card) => (
<div class="rounded-2xl border border-border/50 bg-card/50 p-4 space-y-2 shadow-sm">
<p class="text-[0.625rem] font-black uppercase tracking-widest text-muted-foreground">{card.title}</p>
<Show when={card.href} fallback={<p class="text-sm font-bold tracking-tight">{card.content}</p>}>
<a href={card.href} target="_blank" class="text-sm font-bold tracking-tight text-primary flex items-center gap-1 hover:underline">{card.content}<ExternalLink size={12} /></a>
</Show>
<p class="text-sm text-muted-foreground leading-relaxed">{card.detail}</p>
</div>
)}</For>
</div>
</section>
<footer class="pt-10 border-t border-border flex flex-col items-center text-center space-y-4">
<p class="text-sm text-muted-foreground max-w-2xl leading-relaxed">If the app feels out of sync, a quick <button onClick={() => location.reload()} class="text-primary font-bold hover:underline cursor-pointer bg-transparent border-none p-0 inline-flex items-center gap-1">refresh<RefreshCcw size={12} /></button> is still the best first step. Most changes sync automatically in real time.</p>
<p class="text-sm font-medium">If you still need help after that, contact Timothy Cardoza or IT.</p>
</footer>
</div>
</div>
);
};