Add Documents, Knowledge Base, and Automation pages
Three new pages in the sidebar navigation: Documents (CRUD with type filtering, inline editing, status lifecycle), Knowledge Base (card grid with category/importance filters, create/delete), and Automation (rule table with trigger/action/priority/enable toggle, create/edit/delete). Each page connects to the corresponding backend service from the previous commit.
This commit is contained in:
1 parent
b5b8f3ba3c
commit
d50f0d8d1f
5 files changed
+421
No files matched your search
@@ -9,6 +9,9 @@ import { ChatPage } from "../pages/ChatPage";
|
||||
import { FilesPage } from "../pages/FilesPage";
|
||||
import { ApprovalsPage } from "../pages/ApprovalsPage";
|
||||
import { SocialPage } from "../pages/SocialPage";
|
||||
import { DocumentsPage } from "../pages/DocumentsPage";
|
||||
import { KnowledgePage } from "../pages/KnowledgePage";
|
||||
import { AutomationPage } from "../pages/AutomationPage";
|
||||
import { SettingsPage } from "../pages/SettingsPage";
|
||||
|
||||
export function App() {
|
||||
@@ -37,6 +40,9 @@ export function App() {
|
||||
work: <WorkPage {...pageProps} />,
|
||||
chat: <ChatPage {...pageProps} />,
|
||||
files: <FilesPage {...pageProps} />,
|
||||
docs: <DocumentsPage {...pageProps} />,
|
||||
knowledge: <KnowledgePage {...pageProps} />,
|
||||
automation: <AutomationPage {...pageProps} />,
|
||||
approvals: <ApprovalsPage {...pageProps} />,
|
||||
social: <SocialPage {...pageProps} />,
|
||||
settings: <SettingsPage {...pageProps} />,
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
import { useMemo, useState, type FormEvent } from "react";
|
||||
import { Button, Field, Icon, IconButton, Pill } from "../shared/ui";
|
||||
import { TopbarActions } from "../shared/TopbarActions";
|
||||
import { Dialog } from "../shared/Dialog";
|
||||
import type { AutomationRuleRecord } from "../../../shared/domain";
|
||||
import type { PageProps } from "./types";
|
||||
import { activeOrgId, formatRelative } from "./types";
|
||||
|
||||
const TRIGGER_EVENTS = [
|
||||
"task_created",
|
||||
"task_status_changed",
|
||||
"agent_hired",
|
||||
"approval_created",
|
||||
"message_sent",
|
||||
"file_uploaded",
|
||||
"goal_created",
|
||||
"project_created",
|
||||
] as const;
|
||||
|
||||
const ACTION_TYPES = [
|
||||
"assign_to_department",
|
||||
"create_task",
|
||||
"send_notification",
|
||||
"update_status",
|
||||
"escalate",
|
||||
] as const;
|
||||
|
||||
function text(form: FormData, key: string): string {
|
||||
return String(form.get(key) ?? "").trim();
|
||||
}
|
||||
|
||||
export function AutomationPage({ snapshot, actions }: PageProps) {
|
||||
const orgId = activeOrgId(snapshot);
|
||||
const rules = useMemo(() => snapshot.automationRules.filter((r) => r.orgId === orgId), [snapshot.automationRules, orgId]);
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
|
||||
const editing = editingId ? rules.find((r) => r.id === editingId) ?? null : null;
|
||||
const enabledCount = rules.filter((r) => r.enabled).length;
|
||||
const totalFires = rules.reduce((sum, r) => sum + r.fireCount, 0);
|
||||
|
||||
const create = (event: FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
const form = new FormData(event.currentTarget);
|
||||
void actions.run(() => window.agentCompany.createAutomationRule({
|
||||
orgId,
|
||||
name: text(form, "name"),
|
||||
triggerEvent: text(form, "triggerEvent"),
|
||||
conditions: {},
|
||||
actionType: text(form, "actionType"),
|
||||
actionConfig: {},
|
||||
priority: Number(text(form, "priority")) || 100,
|
||||
enabled: true,
|
||||
})).then((r) => { if (r) setCreateOpen(false); });
|
||||
};
|
||||
|
||||
const save = (event: FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
if (!editing) return;
|
||||
const form = new FormData(event.currentTarget);
|
||||
void actions.run(() => window.agentCompany.updateAutomationRule({
|
||||
orgId,
|
||||
ruleId: editing.id,
|
||||
name: text(form, "name"),
|
||||
triggerEvent: text(form, "triggerEvent"),
|
||||
conditions: {},
|
||||
actionType: text(form, "actionType"),
|
||||
actionConfig: {},
|
||||
priority: Number(text(form, "priority")) || editing.priority,
|
||||
enabled: form.get("enabled") === "on",
|
||||
})).then((r) => { if (r) setEditingId(null); });
|
||||
};
|
||||
|
||||
const remove = (ruleId: string) => {
|
||||
void actions.run(() => window.agentCompany.deleteAutomationRule({ orgId, ruleId }));
|
||||
setEditingId(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="page">
|
||||
<TopbarActions>
|
||||
<Button tone="primary" onClick={() => setCreateOpen(true)}><Icon name="plus" size={13} /> New rule</Button>
|
||||
</TopbarActions>
|
||||
<h1 className="page-h1">Automation</h1>
|
||||
<p className="page-sub">{enabledCount} active · {totalFires} total fires</p>
|
||||
|
||||
<div className="card" style={{ padding: 0, overflow: "hidden" }}>
|
||||
<div className="table-row head" style={{ gridTemplateColumns: "minmax(0, 2fr) minmax(0, 1.2fr) minmax(0, 1.2fr) 70px 80px 50px" }}>
|
||||
<div>Name</div><div>Trigger</div><div>Action</div><div>Fires</div><div>Status</div><div />
|
||||
</div>
|
||||
{rules.map((rule, index) => (
|
||||
<div
|
||||
key={rule.id}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="table-row clickable"
|
||||
onClick={() => setEditingId(rule.id)}
|
||||
onKeyDown={(e) => { if (e.key === "Enter") setEditingId(rule.id); }}
|
||||
style={{ gridTemplateColumns: "minmax(0, 2fr) minmax(0, 1.2fr) minmax(0, 1.2fr) 70px 80px 50px", borderBottom: index === rules.length - 1 ? "none" : "1px solid var(--border-soft)" }}
|
||||
>
|
||||
<div style={{ fontWeight: 480, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{rule.name}</div>
|
||||
<div><Pill>{humanize(rule.triggerEvent)}</Pill></div>
|
||||
<div><Pill tone="violet">{humanize(rule.actionType)}</Pill></div>
|
||||
<div className="num" style={{ fontSize: 12 }}>{rule.fireCount}</div>
|
||||
<div>{rule.enabled ? <Pill tone="green">Active</Pill> : <Pill>Paused</Pill>}</div>
|
||||
<div style={{ textAlign: "right" }} onClick={(e) => e.stopPropagation()}>
|
||||
<IconButton icon="x" label="Delete" onClick={() => remove(rule.id)} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Dialog title="New automation rule" open={createOpen} submitLabel="Create" onClose={() => setCreateOpen(false)} onSubmit={create}>
|
||||
<Field label="Name"><input className="input" name="name" required /></Field>
|
||||
<Field label="Trigger">
|
||||
<select className="input" name="triggerEvent" defaultValue="task_created">
|
||||
{TRIGGER_EVENTS.map((t) => <option key={t} value={t}>{humanize(t)}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Action">
|
||||
<select className="input" name="actionType" defaultValue="assign_to_department">
|
||||
{ACTION_TYPES.map((a) => <option key={a} value={a}>{humanize(a)}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Priority"><input className="input" name="priority" type="number" defaultValue="100" /></Field>
|
||||
</Dialog>
|
||||
|
||||
{editing && (
|
||||
<Dialog key={editing.id} title={editing.name} open submitLabel="Save" onClose={() => setEditingId(null)} onSubmit={save}>
|
||||
<Field label="Name"><input className="input" name="name" defaultValue={editing.name} required /></Field>
|
||||
<Field label="Trigger">
|
||||
<select className="input" name="triggerEvent" defaultValue={editing.triggerEvent}>
|
||||
{TRIGGER_EVENTS.map((t) => <option key={t} value={t}>{humanize(t)}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Action">
|
||||
<select className="input" name="actionType" defaultValue={editing.actionType}>
|
||||
{ACTION_TYPES.map((a) => <option key={a} value={a}>{humanize(a)}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Priority"><input className="input" name="priority" type="number" defaultValue={editing.priority} /></Field>
|
||||
<Field label="Enabled">
|
||||
<label className="row" style={{ gap: 8 }}>
|
||||
<input type="checkbox" name="enabled" defaultChecked={editing.enabled} />
|
||||
<span style={{ fontSize: 13 }}>Rule is active</span>
|
||||
</label>
|
||||
</Field>
|
||||
{editing.lastFiredAt && <div style={{ fontSize: 12, color: "var(--text-3)" }}>Last fired {formatRelative(editing.lastFiredAt)}</div>}
|
||||
</Dialog>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function humanize(value: string): string {
|
||||
return value.replace(/_/g, " ");
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import { useMemo, useState, type FormEvent } from "react";
|
||||
import { Avatar, Button, Field, Icon, IconButton, Pill } from "../shared/ui";
|
||||
import { TopbarActions } from "../shared/TopbarActions";
|
||||
import { Dialog } from "../shared/Dialog";
|
||||
import type { DocumentRecord } from "../../../shared/domain";
|
||||
import type { PageProps } from "./types";
|
||||
import { activeOrgId, formatRelative } from "./types";
|
||||
|
||||
const DOC_TYPES = ["prd", "spec", "memo", "report", "runbook", "proposal", "policy", "guide", "template", "other"] as const;
|
||||
const DOC_STATUSES = ["draft", "in_review", "approved", "archived"] as const;
|
||||
|
||||
function text(form: FormData, key: string): string {
|
||||
return String(form.get(key) ?? "").trim();
|
||||
}
|
||||
|
||||
function optional(form: FormData, key: string): string | null {
|
||||
return text(form, key) || null;
|
||||
}
|
||||
|
||||
export function DocumentsPage({ snapshot, actions }: PageProps) {
|
||||
const orgId = activeOrgId(snapshot);
|
||||
const documents = useMemo(() => snapshot.documents.filter((d) => d.orgId === orgId), [snapshot.documents, orgId]);
|
||||
const agents = useMemo(() => snapshot.agents.filter((a) => a.orgId === orgId), [snapshot.agents, orgId]);
|
||||
const projects = useMemo(() => snapshot.projects.filter((p) => p.orgId === orgId), [snapshot.projects, orgId]);
|
||||
const goals = useMemo(() => snapshot.goals.filter((g) => g.orgId === orgId), [snapshot.goals, orgId]);
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
const [filter, setFilter] = useState<string>("all");
|
||||
|
||||
const filtered = filter === "all" ? documents : documents.filter((d) => d.docType === filter);
|
||||
const editing = editingId ? documents.find((d) => d.id === editingId) ?? null : null;
|
||||
|
||||
const create = (event: FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
const form = new FormData(event.currentTarget);
|
||||
void actions.run(() => window.agentCompany.createDocument({
|
||||
orgId,
|
||||
title: text(form, "title"),
|
||||
docType: text(form, "docType"),
|
||||
content: text(form, "content"),
|
||||
authorAgentId: optional(form, "authorAgentId"),
|
||||
reviewerAgentId: null,
|
||||
projectId: optional(form, "projectId"),
|
||||
goalId: optional(form, "goalId"),
|
||||
taskId: null,
|
||||
})).then((r) => { if (r) setCreateOpen(false); });
|
||||
};
|
||||
|
||||
const save = (event: FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
if (!editing) return;
|
||||
const form = new FormData(event.currentTarget);
|
||||
void actions.run(() => window.agentCompany.updateDocument({
|
||||
orgId,
|
||||
documentId: editing.id,
|
||||
title: text(form, "title"),
|
||||
content: text(form, "content"),
|
||||
status: text(form, "status"),
|
||||
})).then((r) => { if (r) setEditingId(null); });
|
||||
};
|
||||
|
||||
const remove = (docId: string) => {
|
||||
void actions.run(() => window.agentCompany.deleteDocument({ orgId, documentId: docId }));
|
||||
setEditingId(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="page">
|
||||
<TopbarActions>
|
||||
<select className="button" value={filter} onChange={(e) => setFilter(e.target.value)} aria-label="Filter type">
|
||||
<option value="all">All types</option>
|
||||
{DOC_TYPES.map((t) => <option key={t} value={t}>{t}</option>)}
|
||||
</select>
|
||||
<Button tone="primary" onClick={() => setCreateOpen(true)}><Icon name="plus" size={13} /> New document</Button>
|
||||
</TopbarActions>
|
||||
<h1 className="page-h1">Documents</h1>
|
||||
<p className="page-sub">{documents.length} {documents.length === 1 ? "document" : "documents"}</p>
|
||||
|
||||
<div className="card" style={{ padding: 0, overflow: "hidden" }}>
|
||||
<div className="table-row head" style={{ gridTemplateColumns: "minmax(0, 2.5fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) 60px" }}>
|
||||
<div>Title</div><div>Type</div><div>Status</div><div>Updated</div><div />
|
||||
</div>
|
||||
{filtered.map((doc, index) => {
|
||||
const author = agents.find((a) => a.id === doc.authorAgentId);
|
||||
return (
|
||||
<div
|
||||
key={doc.id}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="table-row clickable"
|
||||
onClick={() => setEditingId(doc.id)}
|
||||
onKeyDown={(e) => { if (e.key === "Enter") setEditingId(doc.id); }}
|
||||
style={{ gridTemplateColumns: "minmax(0, 2.5fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) 60px", borderBottom: index === filtered.length - 1 ? "none" : "1px solid var(--border-soft)" }}
|
||||
>
|
||||
<div className="row" style={{ gap: 8, minWidth: 0 }}>
|
||||
{author && <Avatar name={author.name} size="sm" bot />}
|
||||
<span style={{ fontWeight: 480, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{doc.title}</span>
|
||||
</div>
|
||||
<div><Pill>{doc.docType}</Pill></div>
|
||||
<div><Pill tone={statusTone(doc.status)}>{doc.status}</Pill></div>
|
||||
<div style={{ fontSize: 12, color: "var(--text-3)" }}>{formatRelative(doc.updatedAt)}</div>
|
||||
<div style={{ textAlign: "right" }} onClick={(e) => e.stopPropagation()}>
|
||||
<IconButton icon="x" label="Delete" onClick={() => remove(doc.id)} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<Dialog title="New document" open={createOpen} submitLabel="Create" onClose={() => setCreateOpen(false)} onSubmit={create}>
|
||||
<Field label="Title"><input className="input" name="title" required /></Field>
|
||||
<Field label="Type">
|
||||
<select className="input" name="docType" defaultValue="memo">
|
||||
{DOC_TYPES.map((t) => <option key={t} value={t}>{t}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Content"><textarea className="input" name="content" rows={6} required /></Field>
|
||||
<Field label="Author">
|
||||
<select className="input" name="authorAgentId" defaultValue="">
|
||||
<option value="">None</option>
|
||||
{agents.map((a) => <option key={a.id} value={a.id}>{a.name}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Project">
|
||||
<select className="input" name="projectId" defaultValue="">
|
||||
<option value="">None</option>
|
||||
{projects.map((p) => <option key={p.id} value={p.id}>{p.name}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Goal">
|
||||
<select className="input" name="goalId" defaultValue="">
|
||||
<option value="">None</option>
|
||||
{goals.map((g) => <option key={g.id} value={g.id}>{g.title}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
</Dialog>
|
||||
|
||||
{editing && (
|
||||
<Dialog key={editing.id} title={editing.title} open submitLabel="Save" onClose={() => setEditingId(null)} onSubmit={save}>
|
||||
<Field label="Title"><input className="input" name="title" defaultValue={editing.title} required /></Field>
|
||||
<Field label="Status">
|
||||
<select className="input" name="status" defaultValue={editing.status}>
|
||||
{DOC_STATUSES.map((s) => <option key={s} value={s}>{s}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Content"><textarea className="input" name="content" rows={10} defaultValue={editing.content} required /></Field>
|
||||
</Dialog>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function statusTone(status: string): "amber" | "violet" | "green" | undefined {
|
||||
if (status === "draft") return undefined;
|
||||
if (status === "in_review") return "amber";
|
||||
if (status === "approved") return "green";
|
||||
return "violet";
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { useMemo, useState, type FormEvent } from "react";
|
||||
import { Button, Field, Icon, IconButton, Pill } from "../shared/ui";
|
||||
import { TopbarActions } from "../shared/TopbarActions";
|
||||
import { Dialog } from "../shared/Dialog";
|
||||
import type { PageProps } from "./types";
|
||||
import { activeOrgId, formatRelative } from "./types";
|
||||
|
||||
const CATEGORIES = ["process", "technical", "product", "market", "legal", "financial", "operational", "general"] as const;
|
||||
const IMPORTANCES = ["critical", "high", "medium", "low"] as const;
|
||||
|
||||
function text(form: FormData, key: string): string {
|
||||
return String(form.get(key) ?? "").trim();
|
||||
}
|
||||
|
||||
export function KnowledgePage({ snapshot, actions }: PageProps) {
|
||||
const orgId = activeOrgId(snapshot);
|
||||
const entries = useMemo(() => snapshot.knowledgeEntries.filter((e) => e.orgId === orgId), [snapshot.knowledgeEntries, orgId]);
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
const [filter, setFilter] = useState<string>("all");
|
||||
|
||||
const filtered = filter === "all" ? entries : entries.filter((e) => e.category === filter);
|
||||
|
||||
const create = (event: FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
const form = new FormData(event.currentTarget);
|
||||
void actions.run(() => window.agentCompany.createKnowledgeEntry({
|
||||
orgId,
|
||||
topic: text(form, "topic"),
|
||||
category: text(form, "category"),
|
||||
content: text(form, "content"),
|
||||
importance: text(form, "importance"),
|
||||
sourceType: "manual",
|
||||
sourceId: null,
|
||||
})).then((r) => { if (r) setCreateOpen(false); });
|
||||
};
|
||||
|
||||
const remove = (entryId: string) => {
|
||||
void actions.run(() => window.agentCompany.deleteKnowledgeEntry({ orgId, entryId }));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="page">
|
||||
<TopbarActions>
|
||||
<select className="button" value={filter} onChange={(e) => setFilter(e.target.value)} aria-label="Filter category">
|
||||
<option value="all">All categories</option>
|
||||
{CATEGORIES.map((c) => <option key={c} value={c}>{c}</option>)}
|
||||
</select>
|
||||
<Button tone="primary" onClick={() => setCreateOpen(true)}><Icon name="plus" size={13} /> Add entry</Button>
|
||||
</TopbarActions>
|
||||
<h1 className="page-h1">Knowledge Base</h1>
|
||||
<p className="page-sub">{entries.length} {entries.length === 1 ? "entry" : "entries"}</p>
|
||||
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(320px, 1fr))", gap: 12 }}>
|
||||
{filtered.map((entry) => (
|
||||
<article key={entry.id} className="card" style={{ padding: 16 }}>
|
||||
<div className="row" style={{ marginBottom: 8 }}>
|
||||
<span style={{ fontWeight: 540, flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{entry.topic}</span>
|
||||
<IconButton icon="x" label="Delete" onClick={() => remove(entry.id)} />
|
||||
</div>
|
||||
<div className="row" style={{ gap: 6, marginBottom: 10 }}>
|
||||
<Pill>{entry.category}</Pill>
|
||||
<Pill tone={importanceTone(entry.importance)}>{entry.importance}</Pill>
|
||||
</div>
|
||||
<div style={{ fontSize: 13, color: "var(--text-2)", lineHeight: 1.5, whiteSpace: "pre-wrap", maxHeight: 120, overflow: "hidden" }}>
|
||||
{entry.content}
|
||||
</div>
|
||||
<div style={{ marginTop: 8, fontSize: 11, color: "var(--text-3)" }}>{formatRelative(entry.updatedAt)}</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Dialog title="New knowledge entry" open={createOpen} submitLabel="Add" onClose={() => setCreateOpen(false)} onSubmit={create}>
|
||||
<Field label="Topic"><input className="input" name="topic" required /></Field>
|
||||
<Field label="Category">
|
||||
<select className="input" name="category" defaultValue="general">
|
||||
{CATEGORIES.map((c) => <option key={c} value={c}>{c}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Importance">
|
||||
<select className="input" name="importance" defaultValue="medium">
|
||||
{IMPORTANCES.map((i) => <option key={i} value={i}>{i}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Content"><textarea className="input" name="content" rows={6} required /></Field>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function importanceTone(importance: string): "red" | "amber" | undefined {
|
||||
if (importance === "critical") return "red";
|
||||
if (importance === "high") return "amber";
|
||||
return undefined;
|
||||
}
|
||||
@@ -12,6 +12,9 @@ export type RouteId =
|
||||
| "work"
|
||||
| "chat"
|
||||
| "files"
|
||||
| "docs"
|
||||
| "knowledge"
|
||||
| "automation"
|
||||
| "approvals"
|
||||
| "social"
|
||||
| "settings";
|
||||
@@ -32,6 +35,9 @@ const primaryNav: NavSpec[] = [
|
||||
{ id: "work", label: "Work", icon: "work" },
|
||||
{ id: "chat", label: "Messages", icon: "chat" },
|
||||
{ id: "files", label: "Files", icon: "docs" },
|
||||
{ id: "docs", label: "Documents", icon: "file" },
|
||||
{ id: "knowledge", label: "Knowledge", icon: "sparkle" },
|
||||
{ id: "automation", label: "Automation", icon: "bot" },
|
||||
{ id: "approvals", label: "Approvals", icon: "approve" },
|
||||
{ id: "social", label: "Social", icon: "social" },
|
||||
];
|
||||
|
||||
Reference in new issue
Block a user