530 lines
22 KiB
Plaintext
530 lines
22 KiB
Plaintext
package templates
|
||
|
||
import "dealroom/internal/model"
|
||
import "fmt"
|
||
import "strings"
|
||
|
||
func capitalizeFirst(s string) string {
|
||
if len(s) == 0 {
|
||
return s
|
||
}
|
||
return strings.ToUpper(s[:1]) + s[1:]
|
||
}
|
||
|
||
type AdminStats struct {
|
||
ContactCount int
|
||
DealCount int
|
||
UserCount int
|
||
OrgCount int
|
||
}
|
||
|
||
// --- Admin Dashboard ---
|
||
|
||
templ AdminDashboard(profile *model.Profile, stats AdminStats) {
|
||
@Layout(profile, "admin") {
|
||
<div class="space-y-6">
|
||
<div>
|
||
<h1 class="text-2xl font-bold">Admin</h1>
|
||
<p class="text-sm text-gray-500 mt-1">Manage your organization's data.</p>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||
@adminCard("/admin/contacts", "Contacts", fmt.Sprintf("%d", stats.ContactCount), "Buyers, sellers, and advisors", "teal")
|
||
@adminCard("/admin/deals", "Deals", fmt.Sprintf("%d", stats.DealCount), "Active and archived deals", "amber")
|
||
@adminCard("/admin/users", "Users", fmt.Sprintf("%d", stats.UserCount), "Team members and access", "green")
|
||
@adminCard("/admin/organizations", "Organizations", fmt.Sprintf("%d", stats.OrgCount), "Organization management", "purple")
|
||
</div>
|
||
</div>
|
||
}
|
||
}
|
||
|
||
templ adminCard(href string, title string, count string, desc string, color string) {
|
||
<a href={ templ.SafeURL(href) } class="block bg-gray-900 rounded-lg border border-gray-800 p-5 hover:border-gray-700 transition group">
|
||
<div class="flex items-center justify-between mb-3">
|
||
<h3 class="text-sm font-medium text-gray-400 group-hover:text-gray-200 transition">{ title }</h3>
|
||
<span class={ "text-2xl font-bold",
|
||
templ.KV("text-teal-400", color == "teal"),
|
||
templ.KV("text-amber-400", color == "amber"),
|
||
templ.KV("text-green-400", color == "green"),
|
||
templ.KV("text-purple-400", color == "purple") }>{ count }</span>
|
||
</div>
|
||
<p class="text-xs text-gray-600">{ desc }</p>
|
||
</a>
|
||
}
|
||
|
||
// --- Contacts List ---
|
||
|
||
templ AdminContacts(profile *model.Profile, contacts []*model.Contact, filter string) {
|
||
@Layout(profile, "admin") {
|
||
<div class="space-y-5">
|
||
@adminBreadcrumb("Contacts")
|
||
<div class="flex items-center justify-between">
|
||
<div class="flex items-center gap-3">
|
||
<h1 class="text-2xl font-bold">Contacts</h1>
|
||
<span class="text-sm text-gray-500">{ fmt.Sprintf("%d total", len(contacts)) }</span>
|
||
</div>
|
||
<div class="flex items-center gap-3">
|
||
<div class="flex gap-1">
|
||
@filterPill("/admin/contacts", "All", filter == "")
|
||
@filterPill("/admin/contacts?type=buyer", "Buyers", filter == "buyer")
|
||
@filterPill("/admin/contacts?type=internal", "Internal", filter == "internal")
|
||
@filterPill("/admin/contacts?type=advisor", "Advisors", filter == "advisor")
|
||
</div>
|
||
<a href="/admin/contacts/edit" class="h-9 px-4 rounded-lg bg-teal-500 text-white text-sm font-medium flex items-center gap-1.5 hover:bg-teal-600 transition">
|
||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg>
|
||
New Contact
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bg-gray-900 rounded-lg border border-gray-800">
|
||
<table class="w-full">
|
||
<thead>
|
||
<tr class="border-b border-gray-800">
|
||
<th class="text-left px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
|
||
<th class="text-left px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">Company</th>
|
||
<th class="text-left px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
|
||
<th class="text-left px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider w-20">Type</th>
|
||
<th class="text-left px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">Tags</th>
|
||
<th class="text-right px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider w-24">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-gray-800/50">
|
||
if len(contacts) == 0 {
|
||
<tr><td colspan="6" class="px-4 py-8 text-center text-gray-600 text-sm">No contacts found.</td></tr>
|
||
}
|
||
for _, c := range contacts {
|
||
<tr class="hover:bg-gray-800/30 transition group">
|
||
<td class="px-4 py-3">
|
||
<div class="flex items-center gap-2.5">
|
||
<div class="w-8 h-8 rounded-full bg-teal-500/10 flex items-center justify-center text-xs font-bold text-teal-400">
|
||
{ contactInitials(c.FullName) }
|
||
</div>
|
||
<div>
|
||
<span class="text-sm font-medium">{ c.FullName }</span>
|
||
<p class="text-xs text-gray-500">{ c.Title }</p>
|
||
</div>
|
||
</div>
|
||
</td>
|
||
<td class="px-4 py-3 text-sm text-gray-400">{ c.Company }</td>
|
||
<td class="px-4 py-3 text-xs text-gray-500 font-mono">{ c.Email }</td>
|
||
<td class="px-4 py-3">@ContactTypeBadge(c.ContactType)</td>
|
||
<td class="px-4 py-3">
|
||
<div class="flex gap-1 flex-wrap">
|
||
for _, tag := range splitTags(c.Tags) {
|
||
if tag != "" {
|
||
<span class="text-xs px-1.5 py-0.5 rounded bg-gray-800 text-gray-400">{ tag }</span>
|
||
}
|
||
}
|
||
</div>
|
||
</td>
|
||
<td class="px-4 py-3 text-right">
|
||
@editDeleteActions("/admin/contacts", c.ID)
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
}
|
||
}
|
||
|
||
// --- Contact Form ---
|
||
|
||
templ AdminContactForm(profile *model.Profile, contact *model.Contact) {
|
||
@Layout(profile, "admin") {
|
||
<div class="max-w-2xl space-y-5">
|
||
@adminBreadcrumbSub("Contacts", "/admin/contacts", formTitle("Contact", contact.ID))
|
||
<form method="POST" action="/admin/contacts/save" class="bg-gray-900 rounded-lg border border-gray-800 p-6 space-y-4">
|
||
<input type="hidden" name="id" value={ contact.ID }/>
|
||
@formField("full_name", "Full Name", "text", contact.FullName, true)
|
||
@formField("email", "Email", "email", contact.Email, false)
|
||
@formField("phone", "Phone", "tel", contact.Phone, false)
|
||
@formField("company", "Company", "text", contact.Company, false)
|
||
@formField("title", "Title", "text", contact.Title, false)
|
||
@formSelect("contact_type", "Contact Type", contact.ContactType, []SelectOption{
|
||
{Value: "buyer", Label: "Buyer"},
|
||
{Value: "internal", Label: "Internal"},
|
||
{Value: "advisor", Label: "Advisor"},
|
||
})
|
||
@formField("tags", "Tags", "text", contact.Tags, false)
|
||
@formTextarea("notes", "Notes", contact.Notes)
|
||
@formActions("/admin/contacts")
|
||
</form>
|
||
</div>
|
||
}
|
||
}
|
||
|
||
// --- Deals List ---
|
||
|
||
templ AdminDeals(profile *model.Profile, deals []*model.Deal) {
|
||
@Layout(profile, "admin") {
|
||
<div class="space-y-5">
|
||
@adminBreadcrumb("Deals")
|
||
<div class="flex items-center justify-between">
|
||
<div class="flex items-center gap-3">
|
||
<h1 class="text-2xl font-bold">Deals</h1>
|
||
<span class="text-sm text-gray-500">{ fmt.Sprintf("%d total", len(deals)) }</span>
|
||
</div>
|
||
<a href="/admin/deals/edit" class="h-9 px-4 rounded-lg bg-teal-500 text-white text-sm font-medium flex items-center gap-1.5 hover:bg-teal-600 transition">
|
||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg>
|
||
New Deal
|
||
</a>
|
||
</div>
|
||
|
||
<div class="bg-gray-900 rounded-lg border border-gray-800">
|
||
<table class="w-full">
|
||
<thead>
|
||
<tr class="border-b border-gray-800">
|
||
<th class="text-left px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
|
||
<th class="text-left px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">Target Company</th>
|
||
<th class="text-left px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">Stage</th>
|
||
<th class="text-left px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">Size</th>
|
||
<th class="text-left px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider w-20">Status</th>
|
||
<th class="text-right px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider w-24">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-gray-800/50">
|
||
if len(deals) == 0 {
|
||
<tr><td colspan="6" class="px-4 py-8 text-center text-gray-600 text-sm">No deals found.</td></tr>
|
||
}
|
||
for _, d := range deals {
|
||
<tr class="hover:bg-gray-800/30 transition">
|
||
<td class="px-4 py-3 text-sm font-medium">{ d.Name }</td>
|
||
<td class="px-4 py-3 text-sm text-gray-400">{ d.TargetCompany }</td>
|
||
<td class="px-4 py-3">@StageBadge(d.Stage)</td>
|
||
<td class="px-4 py-3 text-sm text-gray-400">{ formatDealSizeCurrency(d.DealSize, d.Currency) }</td>
|
||
<td class="px-4 py-3">
|
||
if d.IsArchived {
|
||
<span class="text-xs px-1.5 py-0.5 rounded bg-gray-700 text-gray-400">Archived</span>
|
||
} else {
|
||
<span class="text-xs px-1.5 py-0.5 rounded bg-green-500/10 text-green-400">Active</span>
|
||
}
|
||
</td>
|
||
<td class="px-4 py-3 text-right">
|
||
@editDeleteActions("/admin/deals", d.ID)
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
}
|
||
}
|
||
|
||
func formatDealSizeCurrency(size float64, currency string) string {
|
||
if size >= 1000000 {
|
||
return fmt.Sprintf("%s%.1fM", currency+" ", size/1000000)
|
||
}
|
||
if size > 0 {
|
||
return fmt.Sprintf("%s%.0f", currency+" ", size)
|
||
}
|
||
return "—"
|
||
}
|
||
|
||
// --- Deal Form ---
|
||
|
||
templ AdminDealForm(profile *model.Profile, deal *model.Deal) {
|
||
@Layout(profile, "admin") {
|
||
<div class="max-w-2xl space-y-5">
|
||
@adminBreadcrumbSub("Deals", "/admin/deals", formTitle("Deal", deal.ID))
|
||
<form method="POST" action="/admin/deals/save" class="bg-gray-900 rounded-lg border border-gray-800 p-6 space-y-4">
|
||
<input type="hidden" name="id" value={ deal.ID }/>
|
||
@formField("name", "Deal Name", "text", deal.Name, true)
|
||
@formTextarea("description", "Description", deal.Description)
|
||
@formField("target_company", "Target Company", "text", deal.TargetCompany, false)
|
||
@formSelect("stage", "Stage", deal.Stage, []SelectOption{
|
||
{Value: "pipeline", Label: "Pipeline"},
|
||
{Value: "loi", Label: "LOI Stage"},
|
||
{Value: "initial_review", Label: "Initial Review"},
|
||
{Value: "due_diligence", Label: "Due Diligence"},
|
||
{Value: "final_negotiation", Label: "Final Negotiation"},
|
||
{Value: "closed", Label: "Closed"},
|
||
{Value: "dead", Label: "Dead"},
|
||
})
|
||
<div class="grid grid-cols-2 gap-4">
|
||
@formField("deal_size", "Deal Size", "number", fmt.Sprintf("%.0f", deal.DealSize), false)
|
||
@formSelect("currency", "Currency", deal.Currency, []SelectOption{
|
||
{Value: "USD", Label: "USD"},
|
||
{Value: "EUR", Label: "EUR"},
|
||
{Value: "GBP", Label: "GBP"},
|
||
})
|
||
</div>
|
||
<div class="grid grid-cols-2 gap-4">
|
||
@formField("ioi_date", "IOI Date", "date", deal.IOIDate, false)
|
||
@formField("loi_date", "LOI Date", "date", deal.LOIDate, false)
|
||
</div>
|
||
<div class="grid grid-cols-2 gap-4">
|
||
@formField("exclusivity_end", "Exclusivity End", "date", deal.ExclusivityEnd, false)
|
||
@formField("expected_close_date", "Expected Close", "date", deal.ExpectedCloseDate, false)
|
||
</div>
|
||
@formField("close_probability", "Close Probability (%)", "number", fmt.Sprintf("%d", deal.CloseProbability), false)
|
||
@formCheckbox("is_archived", "Archived", deal.IsArchived)
|
||
@formActions("/admin/deals")
|
||
</form>
|
||
</div>
|
||
}
|
||
}
|
||
|
||
// --- Users List ---
|
||
|
||
templ AdminUsers(profile *model.Profile, users []*model.Profile) {
|
||
@Layout(profile, "admin") {
|
||
<div class="space-y-5">
|
||
@adminBreadcrumb("Users")
|
||
<div class="flex items-center justify-between">
|
||
<div class="flex items-center gap-3">
|
||
<h1 class="text-2xl font-bold">Users</h1>
|
||
<span class="text-sm text-gray-500">{ fmt.Sprintf("%d total", len(users)) }</span>
|
||
</div>
|
||
<a href="/admin/users/edit" class="h-9 px-4 rounded-lg bg-teal-500 text-white text-sm font-medium flex items-center gap-1.5 hover:bg-teal-600 transition">
|
||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg>
|
||
New User
|
||
</a>
|
||
</div>
|
||
|
||
<div class="bg-gray-900 rounded-lg border border-gray-800">
|
||
<table class="w-full">
|
||
<thead>
|
||
<tr class="border-b border-gray-800">
|
||
<th class="text-left px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
|
||
<th class="text-left px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
|
||
<th class="text-left px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">Role</th>
|
||
<th class="text-right px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider w-24">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-gray-800/50">
|
||
if len(users) == 0 {
|
||
<tr><td colspan="4" class="px-4 py-8 text-center text-gray-600 text-sm">No users found.</td></tr>
|
||
}
|
||
for _, u := range users {
|
||
<tr class="hover:bg-gray-800/30 transition">
|
||
<td class="px-4 py-3">
|
||
<div class="flex items-center gap-2.5">
|
||
<div class="w-8 h-8 rounded-full bg-teal-500/10 flex items-center justify-center text-xs font-bold text-teal-400">
|
||
{ initials(u.FullName) }
|
||
</div>
|
||
<span class="text-sm font-medium">{ u.FullName }</span>
|
||
</div>
|
||
</td>
|
||
<td class="px-4 py-3 text-xs text-gray-500 font-mono">{ u.Email }</td>
|
||
<td class="px-4 py-3">@roleBadge(u.Role)</td>
|
||
<td class="px-4 py-3 text-right">
|
||
if u.ID != profile.ID {
|
||
@editDeleteActions("/admin/users", u.ID)
|
||
} else {
|
||
<a href={ templ.SafeURL("/admin/users/edit?id=" + u.ID) } class="text-xs text-gray-500 hover:text-teal-400 transition">Edit</a>
|
||
}
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
}
|
||
}
|
||
|
||
templ roleBadge(role string) {
|
||
<span class={ "text-xs px-1.5 py-0.5 rounded font-medium",
|
||
templ.KV("bg-purple-500/10 text-purple-400", role == "owner"),
|
||
templ.KV("bg-teal-500/10 text-teal-400", role == "admin"),
|
||
templ.KV("bg-green-500/10 text-green-400", role == "member"),
|
||
templ.KV("bg-gray-700 text-gray-400", role == "viewer") }>
|
||
{ capitalizeFirst(role) }
|
||
</span>
|
||
}
|
||
|
||
// --- User Form ---
|
||
|
||
templ AdminUserForm(profile *model.Profile, user *model.Profile) {
|
||
@Layout(profile, "admin") {
|
||
<div class="max-w-2xl space-y-5">
|
||
@adminBreadcrumbSub("Users", "/admin/users", formTitle("User", user.ID))
|
||
<form method="POST" action="/admin/users/save" class="bg-gray-900 rounded-lg border border-gray-800 p-6 space-y-4">
|
||
<input type="hidden" name="id" value={ user.ID }/>
|
||
@formField("full_name", "Full Name", "text", user.FullName, true)
|
||
@formField("email", "Email", "email", user.Email, true)
|
||
@formSelect("role", "Role", user.Role, []SelectOption{
|
||
{Value: "owner", Label: "Owner"},
|
||
{Value: "admin", Label: "Admin"},
|
||
{Value: "member", Label: "Member"},
|
||
{Value: "viewer", Label: "Viewer"},
|
||
})
|
||
@formActions("/admin/users")
|
||
</form>
|
||
</div>
|
||
}
|
||
}
|
||
|
||
// --- Organizations List ---
|
||
|
||
templ AdminOrgs(profile *model.Profile, orgs []*model.Organization) {
|
||
@Layout(profile, "admin") {
|
||
<div class="space-y-5">
|
||
@adminBreadcrumb("Organizations")
|
||
<div class="flex items-center justify-between">
|
||
<div class="flex items-center gap-3">
|
||
<h1 class="text-2xl font-bold">Organizations</h1>
|
||
<span class="text-sm text-gray-500">{ fmt.Sprintf("%d total", len(orgs)) }</span>
|
||
</div>
|
||
<a href="/admin/organizations/edit" class="h-9 px-4 rounded-lg bg-teal-500 text-white text-sm font-medium flex items-center gap-1.5 hover:bg-teal-600 transition">
|
||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg>
|
||
New Organization
|
||
</a>
|
||
</div>
|
||
|
||
<div class="bg-gray-900 rounded-lg border border-gray-800">
|
||
<table class="w-full">
|
||
<thead>
|
||
<tr class="border-b border-gray-800">
|
||
<th class="text-left px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
|
||
<th class="text-left px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider">Slug</th>
|
||
<th class="text-right px-4 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider w-24">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-gray-800/50">
|
||
if len(orgs) == 0 {
|
||
<tr><td colspan="3" class="px-4 py-8 text-center text-gray-600 text-sm">No organizations found.</td></tr>
|
||
}
|
||
for _, o := range orgs {
|
||
<tr class="hover:bg-gray-800/30 transition">
|
||
<td class="px-4 py-3 text-sm font-medium">{ o.Name }</td>
|
||
<td class="px-4 py-3 text-xs text-gray-500 font-mono">{ o.Slug }</td>
|
||
<td class="px-4 py-3 text-right">
|
||
@editDeleteActions("/admin/organizations", o.ID)
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
}
|
||
}
|
||
|
||
// --- Organization Form ---
|
||
|
||
templ AdminOrgForm(profile *model.Profile, org *model.Organization) {
|
||
@Layout(profile, "admin") {
|
||
<div class="max-w-2xl space-y-5">
|
||
@adminBreadcrumbSub("Organizations", "/admin/organizations", formTitle("Organization", org.ID))
|
||
<form method="POST" action="/admin/organizations/save" class="bg-gray-900 rounded-lg border border-gray-800 p-6 space-y-4">
|
||
<input type="hidden" name="id" value={ org.ID }/>
|
||
@formField("name", "Name", "text", org.Name, true)
|
||
@formField("slug", "Slug", "text", org.Slug, true)
|
||
@formSelect("org_type", "Organization Type", org.OrgType, []SelectOption{
|
||
{Value: "company", Label: "Company"},
|
||
{Value: "bank", Label: "Investment Bank"},
|
||
{Value: "pe_vc", Label: "PE / VC Firm"},
|
||
})
|
||
@formActions("/admin/organizations")
|
||
</form>
|
||
</div>
|
||
}
|
||
}
|
||
|
||
// --- Shared Components ---
|
||
|
||
type SelectOption struct {
|
||
Value string
|
||
Label string
|
||
}
|
||
|
||
func formTitle(entity string, id string) string {
|
||
if id == "" {
|
||
return "New " + entity
|
||
}
|
||
return "Edit " + entity
|
||
}
|
||
|
||
templ adminBreadcrumb(section string) {
|
||
<nav class="flex items-center gap-2 text-sm">
|
||
<a href="/admin" class="text-gray-500 hover:text-gray-300 transition">Admin</a>
|
||
<span class="text-gray-700">›</span>
|
||
<span class="text-gray-300">{ section }</span>
|
||
</nav>
|
||
}
|
||
|
||
templ adminBreadcrumbSub(section string, sectionHref string, sub string) {
|
||
<nav class="flex items-center gap-2 text-sm">
|
||
<a href="/admin" class="text-gray-500 hover:text-gray-300 transition">Admin</a>
|
||
<span class="text-gray-700">›</span>
|
||
<a href={ templ.SafeURL(sectionHref) } class="text-gray-500 hover:text-gray-300 transition">{ section }</a>
|
||
<span class="text-gray-700">›</span>
|
||
<span class="text-gray-300">{ sub }</span>
|
||
</nav>
|
||
}
|
||
|
||
templ filterPill(href string, label string, active bool) {
|
||
<a href={ templ.SafeURL(href) } class={ "text-xs px-2.5 py-1 rounded-full transition",
|
||
templ.KV("bg-teal-500/10 text-teal-400 font-medium", active),
|
||
templ.KV("text-gray-500 hover:text-gray-300 hover:bg-gray-800", !active) }>
|
||
{ label }
|
||
</a>
|
||
}
|
||
|
||
templ editDeleteActions(basePath string, id string) {
|
||
<div class="flex items-center justify-end gap-2">
|
||
<a href={ templ.SafeURL(basePath + "/edit?id=" + id) } class="text-xs text-gray-500 hover:text-teal-400 transition">Edit</a>
|
||
<a href={ templ.SafeURL(basePath + "/delete?id=" + id) } class="text-xs text-gray-500 hover:text-red-400 transition" onclick="return confirm('Are you sure you want to delete this?')">Delete</a>
|
||
</div>
|
||
}
|
||
|
||
templ formField(name string, label string, fieldType string, value string, required bool) {
|
||
<div>
|
||
<label for={ name } class="block text-sm font-medium text-gray-300 mb-1">{ label }</label>
|
||
if required {
|
||
<input type={ fieldType } id={ name } name={ name } value={ value } required
|
||
class="w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-gray-100 focus:border-teal-500 focus:outline-none transition"/>
|
||
} else {
|
||
<input type={ fieldType } id={ name } name={ name } value={ value }
|
||
class="w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-gray-100 focus:border-teal-500 focus:outline-none transition"/>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
templ formTextarea(name string, label string, value string) {
|
||
<div>
|
||
<label for={ name } class="block text-sm font-medium text-gray-300 mb-1">{ label }</label>
|
||
<textarea id={ name } name={ name } rows="3"
|
||
class="w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-gray-100 focus:border-teal-500 focus:outline-none transition">{ value }</textarea>
|
||
</div>
|
||
}
|
||
|
||
templ formSelect(name string, label string, current string, options []SelectOption) {
|
||
<div>
|
||
<label for={ name } class="block text-sm font-medium text-gray-300 mb-1">{ label }</label>
|
||
<select id={ name } name={ name }
|
||
class="w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-gray-100 focus:border-teal-500 focus:outline-none transition">
|
||
for _, opt := range options {
|
||
if opt.Value == current {
|
||
<option value={ opt.Value } selected>{ opt.Label }</option>
|
||
} else {
|
||
<option value={ opt.Value }>{ opt.Label }</option>
|
||
}
|
||
}
|
||
</select>
|
||
</div>
|
||
}
|
||
|
||
templ formCheckbox(name string, label string, checked bool) {
|
||
<div class="flex items-center gap-2">
|
||
if checked {
|
||
<input type="checkbox" id={ name } name={ name } checked class="rounded bg-gray-800 border-gray-700 text-teal-500 focus:ring-teal-500"/>
|
||
} else {
|
||
<input type="checkbox" id={ name } name={ name } class="rounded bg-gray-800 border-gray-700 text-teal-500 focus:ring-teal-500"/>
|
||
}
|
||
<label for={ name } class="text-sm text-gray-300">{ label }</label>
|
||
</div>
|
||
}
|
||
|
||
templ formActions(cancelHref string) {
|
||
<div class="flex items-center gap-3 pt-2">
|
||
<button type="submit" class="h-9 px-5 rounded-lg bg-teal-500 text-white text-sm font-medium hover:bg-teal-600 transition">Save</button>
|
||
<a href={ templ.SafeURL(cancelHref) } class="h-9 px-5 rounded-lg border border-gray-700 text-gray-400 text-sm font-medium flex items-center hover:bg-gray-800 transition">Cancel</a>
|
||
</div>
|
||
}
|