the finances skeleton — repo, prefix and permission key, before any feature

Published empty on purpose: identity is the expensive part to change later, the
feature is the cheap part to add. One route, two placeholder panels, no tables and
no sidecar.
This commit is contained in:
2026-08-27 17:28:00 +01:00
commit 6e88d849e7
10 changed files with 222 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
import { useParams } from "react-router";
// The second panel, reading the section from the URL rather than from the panel beside it.
export const FinancesDetail = () => {
const { section } = useParams();
return (
<div className="h-full overflow-auto p-6">
<h2 className="text-lg font-semibold text-duck-dark">Detail</h2>
<p className="mt-1 text-sm text-duck-dark/60">
Section from the URL: <code>{section ?? "(none)"}</code>
</p>
</div>
);
};
+29
View File
@@ -0,0 +1,29 @@
import { useClient } from "hooks/useClient";
import { useQuery } from "@tanstack/react-query";
// Placeholder. It proves the route mounts and the panel renders, and nothing else.
export const FinancesOverview = () => {
const client = useClient();
const { data, isLoading } = useQuery({
queryKey: ["finances", "ping"],
queryFn: () =>
client.get<{ plugin: string; ok: boolean }>("/finances/ping"),
});
return (
<div className="h-full overflow-auto p-6">
<h2 className="text-lg font-semibold text-duck-dark">Finances</h2>
<p className="mt-1 text-sm text-duck-dark/60">
Nothing here yet — the skeleton is up and mounted.
</p>
<div className="mt-4 rounded-md border border-duck-dark/10 bg-duck-dark/[0.02] p-3 font-mono text-xs">
<div className="mb-1 text-duck-dark/50">GET /api/finances/ping</div>
{isLoading ? (
<span className="text-duck-dark/40">…</span>
) : (
<span>{JSON.stringify(data)}</span>
)}
</div>
</div>
);
};
+29
View File
@@ -0,0 +1,29 @@
import type { LayoutNode } from "officerdev";
// The starting arrangement. The shell persists whatever the user makes of it, per plugin.
//
// Every `appType` must be a key from `panels.ts` — a mismatch falls back silently instead of rendering
// what was meant.
export const defaultLayout: LayoutNode = {
type: "group",
id: "finances-root",
direction: "horizontal",
children: [
{
node: {
type: "panel",
id: "finances-overview",
appType: "finances-overview",
},
size: 40,
},
{
node: {
type: "panel",
id: "finances-detail",
appType: "finances-detail",
},
size: 60,
},
],
};
+24
View File
@@ -0,0 +1,24 @@
import { Wallet, ListTree } from "lucide-react";
import type { AppRegistryMeta } from "officerdev";
import { FinancesOverview } from "./FinancesOverview";
import { FinancesDetail } from "./FinancesDetail";
// The panels this plugin contributes. The shell renders the screen around them; this plugin never does.
//
// These keys are stored in a user's saved layout, so renaming one is a data change rather than a rename.
export const appRegistryMetas: AppRegistryMeta[] = [
{
key: "finances-overview",
name: "Overview",
icon: Wallet,
component: FinancesOverview,
availableOnPanel: false,
},
{
key: "finances-detail",
name: "Detail",
icon: ListTree,
component: FinancesDetail,
availableOnPanel: false,
},
];