Gonçalo Rodrigues 6ed848a001 feat(finance): org management scaffold — Phase 1
Adds multi-tenant organisation support inside the existing finance namespace.
Users can create organisations, invite others via a copy-paste token link,
and manage teams/members with RBAC (admin, finance, member, viewer).
Fiscal year lifecycle is gated: activation requires all planned events to
be approved first. All org data lives in `org_`-prefixed MongoDB collections.

New files:
- models_org.go      — domain types (Org, OrgTeam, OrgMember, OrgInvite,
                       FiscalYear, OrgEvent, BudgetLine, EventComment,
                       TxRequest with full StatusLog audit trail, etc.)
- store_org.go       — MongoDB store methods for all org collections
- handler_org.go     — HTTP handlers + RegisterOrgRoutes(); join invite
                       route lives at /join/{token} to avoid ServeMux
                       conflict with /orgs/{slug}/... wildcard routes
- templates/org_*.html — list, create, home, teams, members, invite, join

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 12:43:48 +01:00

141 lines
6.2 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{{define "content"}}
{{$d := .}}
<!-- Breadcrumb + actions -->
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:24px; flex-wrap:wrap; gap:12px;">
<div>
<div style="font-size:12px; color:var(--text3); margin-bottom:4px;"><a href="/orgs" style="color:var(--text3); text-decoration:none;">Organisations</a> /</div>
<h1>{{$d.Org.Name}}</h1>
</div>
<div style="display:flex; gap:8px; flex-wrap:wrap;">
<a href="/orgs/{{$d.Org.Slug}}/teams" class="btn btn-outline btn-sm">Teams</a>
<a href="/orgs/{{$d.Org.Slug}}/members" class="btn btn-outline btn-sm">Members</a>
{{if eq $d.MyRole "admin"}}
<a href="/orgs/{{$d.Org.Slug}}/invite" class="btn btn-primary btn-sm">+ Invite</a>
{{end}}
</div>
</div>
<!-- Stats row -->
<div class="grid" style="margin-bottom:24px;">
<div class="card value-card animate-on-scroll">
<h2>Teams</h2>
<div class="value" style="color:var(--text);">{{len $d.Teams}}</div>
</div>
<div class="card value-card animate-on-scroll">
<h2>Members</h2>
<div class="value" style="color:var(--text);">{{len $d.Members}}</div>
</div>
<div class="card value-card animate-on-scroll">
<h2>Fiscal years</h2>
<div class="value" style="color:var(--text);">{{len $d.FiscalYears}}</div>
</div>
</div>
<!-- Active fiscal year banner -->
{{if $d.ActiveYear}}
<div class="card animate-on-scroll" style="margin-bottom:24px; border-color:var(--accent); background:linear-gradient(135deg, var(--bg2), var(--bg3));">
<div style="display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:12px;">
<div>
<div style="font-size:11px; font-weight:700; letter-spacing:.07em; color:var(--accent); margin-bottom:4px;">ACTIVE YEAR</div>
<div style="font-size:20px; font-weight:700;">{{$d.ActiveYear.Label}}</div>
<div style="font-size:12px; color:var(--text3); margin-top:4px;">
{{dateShort $d.ActiveYear.StartDate}} — {{dateShort $d.ActiveYear.EndDate}}
</div>
</div>
<div style="display:flex; gap:8px;">
<a href="/orgs/{{$d.Org.Slug}}/years/{{$d.ActiveYear.ID}}/events" class="btn btn-outline btn-sm">Events</a>
<a href="/orgs/{{$d.Org.Slug}}/years/{{$d.ActiveYear.ID}}/requests" class="btn btn-outline btn-sm">Requests</a>
<a href="/orgs/{{$d.Org.Slug}}/years/{{$d.ActiveYear.ID}}/ledger" class="btn btn-outline btn-sm">Ledger</a>
</div>
</div>
</div>
{{end}}
<!-- Fiscal years list -->
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:12px;">
<h2>Fiscal years</h2>
{{if eq $d.MyRole "admin"}}
<button onclick="document.getElementById('new-year-modal').style.display='flex'" class="btn btn-outline btn-sm">+ New year</button>
{{end}}
</div>
{{if $d.FiscalYears}}
<div class="card animate-on-scroll" style="padding:0; overflow:hidden;">
<table>
<thead>
<tr>
<th>Label</th>
<th>Period</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
{{range $d.FiscalYears}}
<tr>
<td style="font-weight:600;">{{.Label}}</td>
<td style="color:var(--text2); font-size:13px;">{{dateShort .StartDate}} — {{dateShort .EndDate}}</td>
<td>
{{if eq (print .Status) "active"}}
<span style="font-size:11px; font-weight:700; padding:3px 9px; border-radius:20px; background:rgba(74,222,128,0.12); color:var(--green);">active</span>
{{else if eq (print .Status) "closed"}}
<span style="font-size:11px; font-weight:700; padding:3px 9px; border-radius:20px; background:var(--bg3); color:var(--text3);">closed</span>
{{else}}
<span style="font-size:11px; font-weight:700; padding:3px 9px; border-radius:20px; background:rgba(251,191,36,0.1); color:#fbbf24;">draft</span>
{{end}}
</td>
<td style="text-align:right;">
<a href="/orgs/{{$d.Org.Slug}}/years/{{.ID}}/events" class="btn btn-outline btn-sm">View</a>
{{if and (eq (print .Status) "draft") (eq (print $d.MyRole) "admin")}}
<form method="post" action="/orgs/{{$d.Org.Slug}}/years/{{.ID}}/activate" style="display:inline;" onsubmit="return confirm('Activate this fiscal year? All events must be approved first.')">
<button class="btn btn-primary btn-sm">Activate</button>
</form>
{{end}}
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{else}}
<div class="card empty-state animate-on-scroll">
<div style="font-size:36px; margin-bottom:12px;">📅</div>
<h3>No fiscal years yet</h3>
<p style="margin-bottom:16px;">Create a fiscal year to start planning events and budgets.</p>
{{if eq $d.MyRole "admin"}}
<button onclick="document.getElementById('new-year-modal').style.display='flex'" class="btn btn-primary">Create fiscal year</button>
{{end}}
</div>
{{end}}
<!-- New fiscal year modal -->
{{if eq $d.MyRole "admin"}}
<div id="new-year-modal" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.6); z-index:500; align-items:center; justify-content:center;">
<div class="card" style="width:100%; max-width:420px; margin:16px;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
<h2>New fiscal year</h2>
<button onclick="document.getElementById('new-year-modal').style.display='none'" style="background:none; border:none; color:var(--text3); font-size:20px; cursor:pointer;">×</button>
</div>
<form method="post" action="/orgs/{{$d.Org.Slug}}/years">
<div style="margin-bottom:14px;">
<label class="form-label">Label</label>
<input class="form-input" type="text" name="label" placeholder="2025" required>
</div>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px; margin-bottom:20px;">
<div>
<label class="form-label">Start date</label>
<input class="form-input" type="date" name="start_date" required>
</div>
<div>
<label class="form-label">End date</label>
<input class="form-input" type="date" name="end_date" required>
</div>
</div>
<button type="submit" class="btn btn-primary" style="width:100%;">Create</button>
</form>
</div>
</div>
{{end}}
{{end}}