209 lines
9.3 KiB
Plaintext
209 lines
9.3 KiB
Plaintext
package templates
|
|
|
|
import "dealroom/internal/model"
|
|
import "fmt"
|
|
|
|
templ DealRoomDetail(profile *model.Profile, deal *model.Deal, folders []*model.Folder, files []*model.File, requests []*model.DiligenceRequest) {
|
|
@Layout(profile, "deals") {
|
|
<div class="space-y-5">
|
|
<!-- Header -->
|
|
<div>
|
|
<a href="/deals" class="text-sm text-gray-500 hover:text-gray-300 flex items-center gap-1 mb-3">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
|
Back to Deal Rooms
|
|
</a>
|
|
<div class="flex items-center gap-3">
|
|
<h1 class="text-2xl font-bold">{ deal.Name }</h1>
|
|
@StageBadge(deal.Stage)
|
|
</div>
|
|
<p class="text-sm text-gray-500 mt-1">{ deal.TargetCompany } · { deal.Description }</p>
|
|
</div>
|
|
|
|
<!-- Deal Info Cards -->
|
|
<div class="grid grid-cols-4 gap-4">
|
|
<div class="bg-gray-900 rounded-lg border border-gray-800 p-4">
|
|
<div class="text-xs text-gray-500 uppercase tracking-wider mb-1">Deal Size</div>
|
|
<div class="text-lg font-bold">{ formatDealSize(deal.DealSize) }</div>
|
|
</div>
|
|
<div class="bg-gray-900 rounded-lg border border-gray-800 p-4">
|
|
<div class="text-xs text-gray-500 uppercase tracking-wider mb-1">Close Probability</div>
|
|
<div class="text-lg font-bold">{ fmt.Sprintf("%d%%", deal.CloseProbability) }</div>
|
|
</div>
|
|
<div class="bg-gray-900 rounded-lg border border-gray-800 p-4">
|
|
<div class="text-xs text-gray-500 uppercase tracking-wider mb-1">IOI Date</div>
|
|
<div class="text-lg font-bold">
|
|
if deal.IOIDate != "" {
|
|
{ deal.IOIDate }
|
|
} else {
|
|
<span class="text-gray-600">—</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-900 rounded-lg border border-gray-800 p-4">
|
|
<div class="text-xs text-gray-500 uppercase tracking-wider mb-1">Exclusivity Ends</div>
|
|
<div class="text-lg font-bold">
|
|
if deal.ExclusivityEnd != "" {
|
|
{ deal.ExclusivityEnd }
|
|
} else {
|
|
<span class="text-gray-600">—</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tabs -->
|
|
<div x-data="{ tab: 'documents' }">
|
|
<div class="flex gap-1 border-b border-gray-800 mb-4">
|
|
<button onclick="showTab('documents')" id="tab-documents" class="px-4 py-2 text-sm font-medium border-b-2 border-teal-500 text-teal-400">
|
|
Documents ({ fmt.Sprintf("%d", len(files)) })
|
|
</button>
|
|
<button onclick="showTab('requests')" id="tab-requests" class="px-4 py-2 text-sm font-medium border-b-2 border-transparent text-gray-500 hover:text-gray-300">
|
|
Request List ({ fmt.Sprintf("%d", len(requests)) })
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Documents Tab -->
|
|
<div id="panel-documents">
|
|
<div class="grid grid-cols-4 gap-4">
|
|
<!-- Folder Tree -->
|
|
<div class="bg-gray-900 rounded-lg border border-gray-800 p-3">
|
|
<h3 class="text-xs font-medium text-gray-500 uppercase tracking-wider mb-3">Folders</h3>
|
|
for _, folder := range folders {
|
|
if folder.ParentID == "" {
|
|
<div class="mb-1">
|
|
<div class="flex items-center gap-1.5 py-1.5 px-2 rounded hover:bg-gray-800 text-sm cursor-pointer">
|
|
<svg class="w-4 h-4 text-teal-400/70 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"></path></svg>
|
|
<span class="truncate text-gray-300">{ folder.Name }</span>
|
|
</div>
|
|
<!-- Child folders -->
|
|
for _, child := range folders {
|
|
if child.ParentID == folder.ID {
|
|
<div class="ml-4">
|
|
<div class="flex items-center gap-1.5 py-1.5 px-2 rounded hover:bg-gray-800 text-sm cursor-pointer">
|
|
<svg class="w-4 h-4 text-teal-400/70 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"></path></svg>
|
|
<span class="truncate text-gray-300">{ child.Name }</span>
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
|
|
<!-- Files Table -->
|
|
<div class="col-span-3 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-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider">File Name</th>
|
|
<th class="text-left px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider w-24">Size</th>
|
|
<th class="text-left px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider w-24">Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-800/50">
|
|
for _, file := range files {
|
|
<tr class="hover:bg-gray-800/30 transition">
|
|
<td class="px-4 py-2.5">
|
|
<div class="flex items-center gap-2">
|
|
@fileIcon(file.Name)
|
|
<span class="text-sm">{ file.Name }</span>
|
|
</div>
|
|
</td>
|
|
<td class="px-4 py-2.5 text-xs text-gray-500">{ formatFileSize(file.FileSize) }</td>
|
|
<td class="px-4 py-2.5">@FileStatusBadge(file.Status)</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Requests Tab -->
|
|
<div id="panel-requests" style="display:none">
|
|
<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-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider w-16">#</th>
|
|
<th class="text-left px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider w-24">Section</th>
|
|
<th class="text-left px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider">Request Item</th>
|
|
<th class="text-left px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider w-16">Priority</th>
|
|
<th class="text-left px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider w-20">Atlas</th>
|
|
<th class="text-left px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider w-16">Conf.</th>
|
|
<th class="text-left px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider">Buyer</th>
|
|
<th class="text-left px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider">Seller</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-800/50">
|
|
for _, req := range requests {
|
|
<tr class="hover:bg-gray-800/30">
|
|
<td class="px-4 py-2.5 text-xs text-gray-500">{ req.ItemNumber }</td>
|
|
<td class="px-4 py-2.5 text-xs text-gray-400">{ req.Section }</td>
|
|
<td class="px-4 py-2.5 text-sm">{ req.Description }</td>
|
|
<td class="px-4 py-2.5">@PriorityBadge(req.Priority)</td>
|
|
<td class="px-4 py-2.5">@StatusIcon(req.AtlasStatus)</td>
|
|
<td class="px-4 py-2.5 text-xs text-gray-500">
|
|
if req.Confidence > 0 {
|
|
{ fmt.Sprintf("%d%%", req.Confidence) }
|
|
}
|
|
</td>
|
|
<td class="px-4 py-2.5 text-xs text-gray-400 max-w-[140px] truncate">{ req.BuyerComment }</td>
|
|
<td class="px-4 py-2.5 text-xs text-gray-400 max-w-[140px] truncate">{ req.SellerComment }</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function showTab(name) {
|
|
document.getElementById('panel-documents').style.display = name === 'documents' ? '' : 'none';
|
|
document.getElementById('panel-requests').style.display = name === 'requests' ? '' : 'none';
|
|
document.getElementById('tab-documents').className = name === 'documents'
|
|
? 'px-4 py-2 text-sm font-medium border-b-2 border-teal-500 text-teal-400'
|
|
: 'px-4 py-2 text-sm font-medium border-b-2 border-transparent text-gray-500 hover:text-gray-300';
|
|
document.getElementById('tab-requests').className = name === 'requests'
|
|
? 'px-4 py-2 text-sm font-medium border-b-2 border-teal-500 text-teal-400'
|
|
: 'px-4 py-2 text-sm font-medium border-b-2 border-transparent text-gray-500 hover:text-gray-300';
|
|
}
|
|
</script>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
func formatFileSize(bytes int64) string {
|
|
if bytes < 1024 {
|
|
return fmt.Sprintf("%d B", bytes)
|
|
}
|
|
if bytes < 1024*1024 {
|
|
return fmt.Sprintf("%d KB", bytes/1024)
|
|
}
|
|
return fmt.Sprintf("%.1f MB", float64(bytes)/(1024*1024))
|
|
}
|
|
|
|
templ fileIcon(name string) {
|
|
<div class={ "w-7 h-7 rounded flex items-center justify-center text-xs font-semibold text-white",
|
|
templ.KV("bg-red-500", hasSuffix(name, ".pdf")),
|
|
templ.KV("bg-green-600", hasSuffix(name, ".xlsx") || hasSuffix(name, ".csv")),
|
|
templ.KV("bg-blue-500", hasSuffix(name, ".doc") || hasSuffix(name, ".docx")),
|
|
templ.KV("bg-gray-600", !hasSuffix(name, ".pdf") && !hasSuffix(name, ".xlsx") && !hasSuffix(name, ".csv") && !hasSuffix(name, ".doc") && !hasSuffix(name, ".docx")) }>
|
|
if hasSuffix(name, ".pdf") {
|
|
PDF
|
|
} else if hasSuffix(name, ".xlsx") || hasSuffix(name, ".csv") {
|
|
XLS
|
|
} else if hasSuffix(name, ".doc") || hasSuffix(name, ".docx") {
|
|
DOC
|
|
} else {
|
|
FILE
|
|
}
|
|
</div>
|
|
}
|
|
|
|
func hasSuffix(s, suffix string) bool {
|
|
return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
|
|
}
|