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

91 lines
3.8 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 := .}}
<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> /
<a href="/orgs/{{$d.Org.Slug}}" style="color:var(--text3); text-decoration:none;">{{$d.Org.Name}}</a> /
</div>
<h1>Teams</h1>
</div>
{{if eq $d.MyRole "admin"}}
<button onclick="document.getElementById('new-team-modal').style.display='flex'" class="btn btn-primary">+ New team</button>
{{end}}
</div>
{{if $d.Teams}}
<div class="card animate-on-scroll" style="padding:0; overflow:hidden;">
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Members</th>
{{if eq $d.MyRole "admin"}}<th></th>{{end}}
</tr>
</thead>
<tbody>
{{range $t := $d.Teams}}
{{$count := 0}}
{{range $d.Members}}{{range .TeamIDs}}{{if eq . $t.ID}}{{$count = add $count 1}}{{end}}{{end}}{{end}}
<tr>
<td style="font-weight:600;">{{$t.Name}}</td>
<td>
{{if eq (print $t.Type) "guest"}}
<span style="font-size:11px; font-weight:600; padding:3px 9px; border-radius:20px; background:rgba(251,191,36,0.1); color:#fbbf24;">guest</span>
{{else}}
<span style="font-size:11px; font-weight:600; padding:3px 9px; border-radius:20px; background:var(--accent-glow); color:var(--accent2);">internal</span>
{{end}}
</td>
<td style="color:var(--text2);">{{$count}}</td>
{{if eq $d.MyRole "admin"}}
<td style="text-align:right;">
<form method="post" action="/orgs/{{$d.Org.Slug}}/teams/{{$t.ID}}/delete" style="display:inline;"
onsubmit="return confirm('Delete team {{$t.Name}}? Members will lose team assignment.')">
<button class="btn btn-outline btn-sm" style="color:var(--red); border-color:var(--red);">Delete</button>
</form>
</td>
{{end}}
</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 teams yet</h3>
<p style="margin-bottom:16px;">Teams group members and own events. Guest teams have scoped visibility.</p>
{{if eq $d.MyRole "admin"}}
<button onclick="document.getElementById('new-team-modal').style.display='flex'" class="btn btn-primary">Create first team</button>
{{end}}
</div>
{{end}}
{{if eq $d.MyRole "admin"}}
<div id="new-team-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:400px; margin:16px;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
<h2>New team</h2>
<button onclick="document.getElementById('new-team-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}}/teams">
<div style="margin-bottom:14px;">
<label class="form-label">Team name</label>
<input class="form-input" type="text" name="name" placeholder="Marketing" required autofocus>
</div>
<div style="margin-bottom:20px;">
<label class="form-label">Type</label>
<select class="form-input" name="type">
<option value="internal">Internal — full org visibility</option>
<option value="guest">Guest — sees own team data only</option>
</select>
</div>
<button type="submit" class="btn btn-primary" style="width:100%;">Create team</button>
</form>
</div>
</div>
{{end}}
{{end}}