@extends('admin.layouts.app') @php $locale = app()->getLocale(); $nameField = $locale === 'ar' ? 'name_ar' : 'name_en'; $dash = __('admin.startups_admin.unknown') ?: '—'; $optName = function ($option) use ($nameField, $dash) { if (! $option) return $dash; return $option->{$nameField} ?: ($option->name_en ?? $dash); }; $sar = function ($v) use ($dash) { if ($v === null || $v === '' || (is_numeric($v) && (float) $v === 0.0)) return $dash; return number_format((float) $v, 2) . ' SAR'; }; $sarCompact = function ($v) use ($dash) { $n = (float) ($v ?? 0); if ($n <= 0) return $dash; if ($n >= 1_000_000_000) return rtrim(rtrim(number_format($n / 1_000_000_000, 1), '0'), '.') . 'B'; if ($n >= 1_000_000) return rtrim(rtrim(number_format($n / 1_000_000, 1), '0'), '.') . 'M'; if ($n >= 1_000) return rtrim(rtrim(number_format($n / 1_000, 1), '0'), '.') . 'K'; return number_format($n, 0); }; $int = fn ($v) => ($v === null || $v === '') ? $dash : number_format((int) $v); $pct = function ($v) use ($dash) { if ($v === null || $v === '') return $dash; return rtrim(rtrim(number_format((float) $v, 2), '0'), '.') . '%'; }; $text = fn ($v) => ($v === null || $v === '') ? $dash : $v; $has = fn ($v) => $v !== null && $v !== '' && (! is_numeric($v) || (float) $v !== 0.0); /** * Valuation comes from Monday's formula column — it could be: * - a plain numeric string like "12000000.00" * - a numeric-with-suffix like "12M" (already compact) * - free text the user typed in Monday * We strip everything except digits + the first decimal point, then * if it parses to a positive number we compact it (`12M`, `1.5M`, …). * Otherwise return the original string verbatim so a hand-typed * valuation note still renders unchanged. */ $valuationCompact = function ($v) use ($dash) { if ($v === null || $v === '') return $dash; $clean = preg_replace('/[^0-9.]/', '', (string) $v); // Collapse multiple dots; keep only the first. if (substr_count((string) $clean, '.') > 1) { $parts = explode('.', (string) $clean, 2); $clean = $parts[0] . '.' . str_replace('.', '', $parts[1]); } if ($clean === '' || $clean === '.') return $v; $n = (float) $clean; if ($n <= 0) return $v; if ($n >= 1_000_000_000) return rtrim(rtrim(number_format($n / 1_000_000_000, 1), '0'), '.') . 'B'; if ($n >= 1_000_000) return rtrim(rtrim(number_format($n / 1_000_000, 1), '0'), '.') . 'M'; if ($n >= 1_000) return rtrim(rtrim(number_format($n / 1_000, 1), '0'), '.') . 'K'; return number_format($n, 0); }; /** * Post-Money Valuation with lazy computation (added 2026-06-15). * * 1. If `valuation` is filled — return it verbatim (admin may have * written a manual override that the formula can't reproduce, * e.g. a Monday-imported display string like "$10M-$50M"). * 2. Otherwise compute `asking_fund_sar / (offered_equity_pct / 100)` * whenever both inputs are present and positive. This is the SAME * formula the model's saving() hook + StartupResource use, so the * view, the API surface, and the next-save persistence all agree. * 3. Returns null when neither path resolves — let the caller decide * whether to hide the field or render an em-dash. */ $postMoneyValuation = function () use ($startup): ?string { if ($startup->valuation !== null && $startup->valuation !== '') { return (string) $startup->valuation; } $ask = (float) $startup->asking_fund_sar; $eq = (float) $startup->offered_equity_pct; if ($ask > 0 && $eq > 0) { return number_format($ask / ($eq / 100.0), 2, '.', ''); } return null; }; $bytes = function ($n) { if (! $n) return '—'; $units = ['B','KB','MB','GB']; $i = 0; $n = (float) $n; while ($n >= 1024 && $i < count($units) - 1) { $n /= 1024; $i++; } return number_format($n, $n < 10 ? 1 : 0) . ' ' . $units[$i]; }; $initials = collect(explode(' ', trim((string) $startup->display_name))) ->filter()->take(2) ->map(fn ($p) => mb_strtoupper(mb_substr($p, 0, 1))) ->implode(''); if ($initials === '') $initials = '—'; // Hero avatar: prefer the file explicitly tagged slot='logo' so an // "other"-slot image never gets mistaken for the company logo. Falls // back to any image file for legacy rows whose slot was never set // (pre-2026-06-17 signups / Monday imports — the backfill migration // also repairs most of those). $logoFile = $startup->files->firstWhere('slot', 'logo') ?? $startup->files->firstWhere('file_type', \App\Models\File::TYPE_IMAGE); $logoUrl = $logoFile ? route('admin.files.show', $logoFile) : null; // Generic Files section = signup-time uploads (logo, pitch deck) + // any miscellaneous `other` + Monday-synced rows that pre-date the // slot column (slot=null). Initial DD files (slot=initial_dd) are // partitioned off to the dedicated section below so a single file // never appears in both lanes. // // 2026-06-17: `slot=null` rows are now included. The rendering code // already treated them as "other" via `$slot = $file->slot ?: 'other'` // — but the upstream filter was dropping them, which silently hid // Monday-imported files on the show page. $files = $startup->files ->filter(fn ($f) => $f->slot === null || in_array($f->slot, ['logo', 'pitch_deck', 'other'], true)) ->values(); $countryName = $startup->country?->{$nameField} ?: $startup->country?->name_en ?: $dash; $pitchBlocks = array_filter([ 'startup_brief' => $startup->startup_brief, 'problem' => $startup->problem, 'solution' => $startup->solution, 'investment_opportunity' => $startup->investment_opportunity, 'kpi_targets' => $startup->kpi_targets, ], fn ($v) => filled($v)); $fundUse = [ 'fund_use_operation_sar' => $startup->fund_use_operation_sar, 'fund_use_marketing_sar' => $startup->fund_use_marketing_sar, 'fund_use_development_sar' => $startup->fund_use_development_sar, 'fund_use_salaries_sar' => $startup->fund_use_salaries_sar, ]; $totalFundUse = array_sum(array_map(fn ($v) => (float) $v, array_filter($fundUse, fn ($v) => $v !== null && $v !== ''))); @endphp @section('title', $startup->display_name . ' — ' . __('admin.startups_admin.show_title')) @section('page_title', __('admin.startups_admin.show_title')) @push('styles') @endpush @section('content') {{-- AI activity strip — renders nothing if Claude hasn't touched this startup yet. Mirrors the investor show-page placement. --}} @include('admin.ai._entity-badge', ['subject' => $startup]) {{-- {{ __('admin.startups_admin.back_to_list') }} --}} @include('admin.partials.flash')
{{-- ───────────────── HERO ───────────────── --}}
{{-- Logo with edit-mode camera overlay (same flow as the investor avatar). --}}
@if ($startup->sector || $startup->country) {{ $optName($startup->sector) }} @if ($startup->country) · {{ $countryName }} @endif @endif

{{ $startup->display_name }}

{{-- Group — inline-editable. Reuses the EXISTING group-change workflow: the editable-label control PATCHes {field: 'monday_group_id', value} to admin.startups.update-label, which routes to StartupController::updateGroup() (validates active startup groups, writes monday_group_id + monday_group_title + group_id atomically, and records the change via ActivityLogger::recordFieldChange). No custom update path. --}} @php $startupGroupOptions = \App\Models\Group::query() ->ofType(\App\Models\Group::TYPE_STARTUP) ->active() ->whereNotNull('monday_group_id') ->orderBy('name') ->pluck('name', 'monday_group_id') ->all(); @endphp {{ __('admin.startups_admin.fields.group') }}: {{-- Status — shown beside the group, with a distinct (indigo) pill so it never reads as the group. --}} @if ($startup->statusOption) @php $so = $startup->statusOption; $soName = app()->getLocale() === 'ar' ? ($so->name_ar ?: $so->name_en) : $so->name_en; @endphp {{ __('admin.startups_admin.fields.status_option_id') }}: {{ $soName }} @endif {{-- Action badge intentionally removed from the hero badges row: the change-action button in the hero-actions strip below replaces it. --}}
{{-- Active/Inactive badge removed per UX spec. --}}
{{-- ───── Change-action button (first/innermost) ───── Sits closest to the name in the RTL hero strip — admins reach for it before everything else. PATCHes /admin/startups/{id}/label so the side-effect listener (cancel-on-label rejection, notification fan-out, auto-move-to-group) kicks in immediately. --}} @php $actionOpts = $labelOptions['action_option_id'] ?? []; $currentAct = $startup->action; $currentName = $currentAct ? ($optName($currentAct)) : __('admin.startups_admin.row_action_clear'); @endphp
{{-- ───── Favorite star toggle (SECOND icon) ───── Per-admin favorite. Same component as the investor show page — POSTs /admin/favorites/toggle with type=startup. Sits AFTER the action picker per UX spec — the action is the primary affordance, the star is secondary. --}} {{-- Contact icons render ALWAYS — empty fields show a dimmed placeholder so the action strip stays consistent across all startups. --}} @if ($startup->email) @else @endif @if ($startup->phone_number) {{-- WhatsApp shortcut. Mirrors the investor show page (see Phone::whatsAppUrl). Opens wa.me in a new tab; existing copy-to-clipboard preserves. --}} @php $waUrl = \App\Support\Phone::whatsAppUrl($startup->phone_number); @endphp @if ($waUrl) @endif @else @endif @if ($startup->website) @else @endif @if ($startup->linkedin_url) @else @endif {{-- iOS + Android render ALWAYS too, matching email / phone / web / linkedin above. An empty slot reads as "no link on file" via the title tooltip so admins don't wonder whether the icon is missing because of a bug or because no URL was captured. --}} @if ($startup->ios_url) @else @endif @if ($startup->android_url) @else @endif
{{-- Bio lives on its own row spanning the full hero width. The hero grid template puts it on row 2 with `grid-column: 1 / -1` so it stretches under the avatar, body and actions columns — no more half-width wrap. --}} @if ($startup->startup_brief)

{{ \Illuminate\Support\Str::limit($startup->startup_brief, 240) }}

@endif
{{-- ───────────────── DUPLICATE BANNER ───────────────── --}} @if ($startup->is_duplicate) @endif {{-- ───────────────── KPI STRIP ───────────────── --}}
{{ __('admin.startups_admin.fields.asking_fund_sar') }} @php $asking = $sarCompact($startup->asking_fund_sar); @endphp {{ $asking }}@if ($asking !== $dash)SAR@endif
{{ __('admin.startups_admin.fields.offered_equity_pct') }} {{ $pct($startup->offered_equity_pct) }}
{{ __('admin.startups_admin.fields.runway_months') }} {{ $int($startup->runway_months) }}
{{ __('admin.startups_admin.fields.total_raised_sar') }} @php $totalRaised = $sarCompact($startup->total_raised_sar); @endphp {{ $totalRaised }}@if ($totalRaised !== $dash)SAR@endif
{{ __('admin.startups_admin.fields.valuation') }} {{-- Hero stat tile: compact display (e.g. "5M") and the un-compacted SAR value on hover. Read through $postMoneyValuation() so the formula kicks in when the stored column is empty but asking + equity are present. --}} @php $postMoney = $postMoneyValuation(); @endphp {{ $valuationCompact($postMoney) }}
{{-- Rejection-reason card (renders only when the startup has a stored reason — sits right above the section nav). --}} @include('admin.partials.rejection-reason-display', ['subject' => $startup]) {{-- Requested-information card — renders only when the startup has stored request_info (from the "Request More Info" action). --}} @include('admin.partials.request-info-display', ['subject' => $startup]) {{-- ───────────────── SECTION NAV ───────────────── --}}
{{-- ───────────────── IDENTITY — inline section editing ───────────────── --}}
{{-- 2026-06-17: identity rows expanded to surface the new applicant_full_name + the renamed name_en alongside the existing name. Each uses the `rd-info-row--labeled` modifier so label sits above value (cleaner than a long inline "label: value" string that breaks per-character in the narrow grid cell). first_name_* are shown side-by-side; the pair is auto-filled from applicant_full_name by Claude translit. --}} @if ($startup->name_en)
{{ __('admin.startups_admin.fields.name_en') }} {{ $text($startup->name_en) }}
@endif @if ($startup->applicant_full_name)
{{ __('admin.startups_admin.fields.applicant_full_name') }} {{ $text($startup->applicant_full_name) }}
@endif @if ($startup->first_name_en || $startup->first_name_ar)
{{ __('admin.startups_admin.fields.first_name_en') }} / {{ __('admin.startups_admin.fields.first_name_ar') }} @if ($startup->first_name_en){{ $text($startup->first_name_en) }}@endif @if ($startup->first_name_en && $startup->first_name_ar) · @endif @if ($startup->first_name_ar){{ $text($startup->first_name_ar) }}@endif
@endif @if ($startup->email) @endif @if ($startup->phone_number) @php $waUrl = \App\Support\Phone::whatsAppUrl($startup->phone_number); @endphp
{{ $startup->phone_number }} @if ($waUrl) @endif
@endif
{{ $countryName }}
@if ($startup->website) {{-- `#icon-link` symbol was never declared in the sprite block at the top of this view, so the row was rendering with a blank gutter glyph. Switched 2026-06-14 to `#icon-globe` — the same brand icon the hero strip's website button uses, keeping the visual language consistent across the page. --}} @endif @if ($startup->linkedin_url) {{-- LinkedIn row uses an icon-link instead of the bare URL (2026-06-14 per ops spec). Same hover-revealed copy affordance as email/phone rows above. The icon link opens the profile in a new tab; the trailing copy button puts the raw URL on the clipboard. --}}
{{-- Inline LinkedIn glyph removed 2026-06-14: the row already has the LinkedIn icon in the left gutter (rd-info-row__icon), so duplicating it inside the link read as two adjacent icons. Plain text label keeps the link affordance clear. --}} LinkedIn
@endif {{-- first_name_ar + first_name_en row hidden from Contact Info (2026-06-14). Columns still exist + observer still auto-fills them, because the notification template engine resolves `{{first_name_ar}}` / `{{first_name_en}}` placeholders against these fields. Edits stay available via the dedicated first-names admin editor partial. --}}
{{-- ───────────────── PITCH — inline editable ───────────────── --}} {{-- Always rendered (even when all fields are empty) so admins can populate the pitch fields without needing them seeded from Monday first. --}} @if (! empty($pitchBlocks)) @foreach ($pitchBlocks as $key => $val)

{{ __('admin.startups_admin.fields.' . $key) }}

{{ $val }}

@endforeach @else

{{ __('admin.section_edit.empty_hint') }}

@endif
{{-- ───────────────── TEAM — inline CRUD editor ───────────────── Self-contained partial with its own Alpine factory; supports add/edit/remove per row + position multiselect via chip toggles. See admin.partials.editable-members. --}} @include('admin.partials.editable-members', [ 'startup' => $startup, 'members' => $members, ]) {{-- ───────────────── BUSINESS MODEL — inline editable ───────────────── --}} {{-- Field order locked 2026-06-15 per ops spec: Sector → Product Stage → Traction → Revenue Model → Business Model (renamed from Customer Focus). Removed from this card on the same date: • Previously Received Funding • business_model body block • Status / Action (already hidden — still editable via the hero action-picker). Investment Stage moved out (later on 2026-06-15) to the Investment Round card per ops request — the round-level taxonomy now lives together. --}}
{{ __('admin.startups_admin.fields.sector_option_id') }} @if ($startup->sector) {{ $optName($startup->sector) }} @else — @endif
{{ __('admin.startups_admin.fields.product_stage_option_id') }} {{ $optName($startup->productStage) }}
{{ __('admin.startups_admin.fields.traction_option_id') }} {{ $optName($startup->traction) }}
{{ __('admin.startups_admin.fields.revenue_model_option_id') }} {{ $optName($startup->revenueModel) }}
{{ __('admin.startups_admin.fields.customer_focus_option_id') }} {{ $optName($startup->customerFocus) }}
{{-- ───────────────── INVESTMENT ROUND — inline editable ───────────────── --}} {{-- Field order locked 2026-06-15 per ops spec: Investment Stage → Round Type → Asking → Min Ticket → Equity % → Post-Money Valuation → Runway → Total Raised. Investment Stage moved here (later on 2026-06-15) from the Business Model card per ops request ("انقل Investment Stage الي سيكشن Investment Round"). Cap and Floor + Previous Funding Amount remain display-only (columns + Monday-sync kept, not surfaced in the card UI). --}}
{{ __('admin.startups_admin.fields.investment_stage_option_id') }} {{ $optName($startup->investmentStage) }}
{{ __('admin.startups_admin.fields.round_type_option_id') }} {{ $optName($startup->roundType) }}
{{ __('admin.startups_admin.fields.asking_fund_sar') }} {{ $sar($startup->asking_fund_sar) }}
{{ __('admin.startups_admin.fields.min_ticket_size_sar') }} {{ $sar($startup->min_ticket_size_sar) }}
{{ __('admin.startups_admin.fields.offered_equity_pct') }} {{ $pct($startup->offered_equity_pct) }}
{{-- Post-Money Valuation rd-field. Reads through $postMoneyValuation() so when the stored `valuation` column is empty but asking + equity are present, we still surface the computed figure (asking / (equity / 100)) — matching the model's saving() hook and the API surface. (Added 2026-06-15.) --}} @php $postMoneyRow = $postMoneyValuation(); @endphp @if ($postMoneyRow)
{{ __('admin.startups_admin.fields.valuation') }} {{-- Compact display + full value on hover. --}} {{ $valuationCompact($postMoneyRow) }}
@endif
{{ __('admin.startups_admin.fields.runway_months') }} {{ $int($startup->runway_months) }}
{{ __('admin.startups_admin.fields.total_raised_sar') }} {{ $sar($startup->total_raised_sar) }}
{{-- ───────────────── HIDDEN SECTIONS — Item 13 (2026-06-14) ───────────────── Use of Funds, Market Size, and Geographic Focus sections were removed from the visible profile per ops spec. The underlying DB columns + pivot table + Monday-sync mapping ALL stay in place: • fund_use_* ← config/monday_columns.php (fund___operation_, fund__marketing_, fund_development_, fund__salaries_) • market_size_* ← config/monday_columns.php (dup__of_market_size, text34, text6) • startup_geographic_focuses pivot ← StartupItemMapper A future PR can drop the columns AFTER the Monday mapping is rewired to ignore them. Until then, hiding the UI is the safe play. --}} {{-- Files section moved to the very end of the stack per UX spec. --}} {{-- ───────────────── SOURCE & NOTES — inline editable ───────────────── --}}
{{ __('admin.startups_admin.fields.referral_source_option_id') }} {{ $optName($startup->referralSource) }}
{{-- Note Type row hidden 2026-06-14 per Item 13 ops spec. Column `note_type_option_id` + Monday sync mapping (`status_16` → note_type_option_id) stay intact; dropping the column would break the sync writer. --}}
@if ($startup->rejection_reason)

{{ __('admin.startups_admin.fields.rejection_reason') }}

{{ $text($startup->rejection_reason) }}

@endif
{{-- ───────────────── FILES (last section per UX spec) ───────────────── Always rendered (since 2026-06-17). The previous `@if ($files->isNotEmpty())` guard made the whole card disappear when a startup had no logo/pitch_deck/other uploads — which also orphaned the sticky section-nav pill (`#sec-files`) so clicking it scrolled to nothing. The Initial DD card below already handled the empty case gracefully; we mirror that pattern here. --}}
{{-- 2026-06-17 redesign (PM pass): split the Files body into TWO zones to make the editing flow intuitive. 1. Primary-slot lane: Logo + Pitch Deck. Each renders as a stable tile — either showing the current file with Replace + Remove buttons, OR an "empty slot" placeholder with a single Upload button. Admins can tell at a glance which required uploads are missing. 2. Supporting-files lane: any other / null-slot files in a grid, plus a single "+ Upload file" button (slot fixed to 'other' — no dropdown indirection needed). All editing controls are gated by [data-edit-affordance], so the read-only view (when Edit Mode is off) shows just the existing files without any of the buttons. --}} @php $logoFile = $files->firstWhere('slot', 'logo'); $pitchDeckFile = $files->firstWhere('slot', 'pitch_deck'); // "Supporting" = anything that isn't logo OR pitch_deck. // Includes slot='other' AND legacy null-slot rows // (Monday-synced files pre-dating the slot column). $supportingFiles = $files->filter(fn ($f) => ! in_array($f->slot, ['logo', 'pitch_deck'], true) )->values(); $slotIconRef = ['logo' => '#icon-camera', 'pitch_deck' => '#icon-file-text']; // 2026-06-17 PM design pass: replace the legacy // emoji-icon approach with color-coded "chip" // badges driven by file extension. Mirrors the // visual language of Google Drive / Dropbox so // admins can scan a long file list at a glance. $fileChip = function ($file) { $ext = strtolower((string) pathinfo((string) $file->file_name, PATHINFO_EXTENSION)); if ($ext === 'pdf') return ['pdf', 'PDF']; if (in_array($ext, ['jpg','jpeg','png','gif','webp','svg','bmp'])) return ['image', 'IMG']; if (in_array($ext, ['xls','xlsx','csv'])) return ['sheet', 'XLS']; if (in_array($ext, ['ppt','pptx'])) return ['slide', 'PPT']; if (in_array($ext, ['doc','docx','txt','rtf'])) return ['doc', 'DOC']; return ['other', 'FILE']; }; // Candidate detector — used by the friendly "this // looks like a logo / pitch deck" hint banner + // per-card "Set as Logo / Pitch Deck" actions. $isImage = fn ($f) => in_array( strtolower((string) pathinfo((string) $f->file_name, PATHINFO_EXTENSION)), ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp'], true ); $isPdf = fn ($f) => strtolower((string) pathinfo((string) $f->file_name, PATHINFO_EXTENSION)) === 'pdf'; $logoCandidate = ! $logoFile ? $supportingFiles->first(fn ($f) => $isImage($f)) : null; $pitchCandidate = ! $pitchDeckFile ? $supportingFiles->first(fn ($f) => $isPdf($f)) : null; @endphp {{-- ── Auto-detect hint banners ── Surface "Set as Logo / Pitch Deck" suggestions ABOVE the tile grid so they're visible without scrolling into a tile. Each appears only when (a) the target slot is empty AND (b) a candidate file of the right type sits in supporting files. --}} @if ($logoCandidate)
{{ __('admin.startups_admin.files.move_hint_logo') }} {{ $logoCandidate->file_name }}
@endif @if ($pitchCandidate)
{{ __('admin.startups_admin.files.move_hint_deck') }} {{ $pitchCandidate->file_name }}
@endif {{-- ── Tile row: Logo + Pitch Deck + Other Files ── All three sit in the same .rd-file-slots grid so they render side-by-side on wide screens and wrap gracefully on narrower viewports. The first two are single-file slots (filled OR empty placeholder); the third is a list-style tile holding the supporting files + a single "+ Add file" button at the bottom. --}}
@foreach (['logo', 'pitch_deck'] as $slotKey) @php $slotFile = $slotKey === 'logo' ? $logoFile : $pitchDeckFile; @endphp
{{ __('admin.startups_admin.files.' . $slotKey) }} @if ($slotFile) {{ __('admin.startups_admin.files.uploaded') }} @else {{ __('admin.startups_admin.files.not_uploaded') }} @endif
@if ($slotFile) @php [$chipClass, $chipLabel] = $fileChip($slotFile); @endphp {{ $slotFile->file_name }} {{ $slotFile->file_type ?: 'file' }} · {{ $bytes($slotFile->size_bytes) }}
@else
{{ __('admin.startups_admin.files.hint_' . $slotKey) }}
@endif
@endforeach {{-- ── Third tile: Other / Supporting Files ── List-style variant of the slot tile — same outer chrome, but the body holds a scrollable list of compact file rows instead of a single preview. Always renders (even when empty) so the 3-tile row stays visually balanced. --}}
{{ __('admin.startups_admin.files.other') }} {{ $supportingFiles->count() }}
@if ($supportingFiles->isEmpty())
{{ __('admin.startups_admin.files.empty_other') }}
@else
@foreach ($supportingFiles as $file) @php [$chipClass, $chipLabel] = $fileChip($file); @endphp @endforeach
@endif
@php // Pending-files banner (Monday assets mid-download). $hasPending = $files->contains(fn ($f) => in_array($f->downloadState(), ['downloading', 'retrying'], true)); @endphp @if ($hasPending)
⏳ {{ __('admin.files.pending_notice') ?? 'بعض الملفات قيد التحميل من Monday — الصفحة ستحدّث تلقائياً عند اكتمال التنزيل.' }}
@endif {{-- Single hidden file picker for the whole section. All three triggers (slot upload, replace, supporting upload) reuse it; state on the Alpine component (`newSlot`, `_replacingId`) decides what to do when a file is picked. --}}
{{-- ───────────────── INITIAL DD ───────────────── Files captured via the public Startup Initial-DD upload portal (/startup-initial-dd). Grouped by `category` so the legal documents, financial documents, and free-form "other" pile each get their own visual lane. We pull only rows with a `document_type` so this card does NOT duplicate logos / pitch decks already shown above. --}} @php // Source of truth = `slot = initial_dd`. We previously // filtered by `document_type` being filled but that left // DD rows without a typed document invisible. Slot-based // partitioning mirrors the upload portal's contract. $ddFiles = $startup->files ->where('slot', 'initial_dd') ->groupBy('category'); $ddCategoryOrder = [ \App\Models\File::DD_CATEGORY_LEGAL, \App\Models\File::DD_CATEGORY_FINANCIAL, \App\Models\File::DD_CATEGORY_OTHER, ]; $ddCategoryLabels = [ \App\Models\File::DD_CATEGORY_LEGAL => __('admin.startups_admin.initial_dd.legal'), \App\Models\File::DD_CATEGORY_FINANCIAL => __('admin.startups_admin.initial_dd.financial'), \App\Models\File::DD_CATEGORY_OTHER => __('admin.startups_admin.initial_dd.other'), ]; $ddTypeLabels = [ \App\Models\File::DD_TYPE_COMMERCIAL_REGISTRATION => __('admin.startups_admin.initial_dd.types.commercial_registration'), \App\Models\File::DD_TYPE_ARTICLES_OF_ASSOCIATION => __('admin.startups_admin.initial_dd.types.articles_of_association'), \App\Models\File::DD_TYPE_MISA_DOCUMENTS => __('admin.startups_admin.initial_dd.types.misa_documents'), \App\Models\File::DD_TYPE_USES_OF_FUNDS => __('admin.startups_admin.initial_dd.types.uses_of_funds'), \App\Models\File::DD_TYPE_HISTORICAL_PNL => __('admin.startups_admin.initial_dd.types.historical_pnl'), \App\Models\File::DD_TYPE_CAP_TABLE => __('admin.startups_admin.initial_dd.types.cap_table'), \App\Models\File::DD_TYPE_OTHER => __('admin.startups_admin.initial_dd.types.other'), ]; $ddTotal = $ddFiles->flatten(1)->count(); @endphp
{{-- 2026-06-17 redesign (PM pass): each DD category renders as its own always-visible lane (Legal / Financial / Other). Per-category "+ Add document" button opens a popover with the document types relevant to THAT category — admins pick one click → file picker opens. No more "fill 2 dropdowns then click +" dance. --}} @php // Map each category → its document_type whitelist // so the popover only shows relevant types. Pulled // from File::ddCategoryMap() (inverse of type→cat). $catTypeMap = [ \App\Models\File::DD_CATEGORY_LEGAL => [ \App\Models\File::DD_TYPE_COMMERCIAL_REGISTRATION, \App\Models\File::DD_TYPE_ARTICLES_OF_ASSOCIATION, \App\Models\File::DD_TYPE_MISA_DOCUMENTS, ], \App\Models\File::DD_CATEGORY_FINANCIAL => [ \App\Models\File::DD_TYPE_USES_OF_FUNDS, \App\Models\File::DD_TYPE_HISTORICAL_PNL, \App\Models\File::DD_TYPE_CAP_TABLE, ], \App\Models\File::DD_CATEGORY_OTHER => [ \App\Models\File::DD_TYPE_OTHER, ], ]; @endphp {{-- Local file-chip helper (mirrors the one in the Files section) for color-coded type badges inside the Initial DD lists too. --}} @php $ddFileChip = function ($file) { $ext = strtolower((string) pathinfo((string) $file->file_name, PATHINFO_EXTENSION)); if ($ext === 'pdf') return ['pdf', 'PDF']; if (in_array($ext, ['jpg','jpeg','png','gif','webp','svg','bmp'])) return ['image', 'IMG']; if (in_array($ext, ['xls','xlsx','csv'])) return ['sheet', 'XLS']; if (in_array($ext, ['ppt','pptx'])) return ['slide', 'PPT']; if (in_array($ext, ['doc','docx','txt','rtf'])) return ['doc', 'DOC']; return ['other', 'FILE']; }; // Per-category icon class + inline SVG markup. $ddCategoryIcons = [ \App\Models\File::DD_CATEGORY_LEGAL => ['legal', ''], \App\Models\File::DD_CATEGORY_FINANCIAL => ['financial', ''], \App\Models\File::DD_CATEGORY_OTHER => ['other', ''], ]; @endphp {{-- ── DD category tiles row ── Re-uses the .rd-file-slots grid so the 3 DD categories render as a row of tiles matching the Files section above (Logo / Pitch Deck / Other). Each tile carries its own colored category icon, scrollable file list, and an "+ Add document" popover trigger. --}}
@foreach ($ddCategoryOrder as $catKey) @php $group = $ddFiles->get($catKey, collect()); $catLabel = $ddCategoryLabels[$catKey] ?? $catKey; $catTypes = $catTypeMap[$catKey] ?? []; [$catIconClass, $catIconSvg] = $ddCategoryIcons[$catKey] ?? ['other', '']; @endphp
{!! $catIconSvg !!} {{ $catLabel }} {{ $group->count() }}
@if ($group->isEmpty())
{{ __('admin.startups_admin.initial_dd.empty_category', ['category' => $catLabel]) }}
@else
@foreach ($group as $file) @php [$chipClass, $chipLabel] = $ddFileChip($file); $docLabel = $ddTypeLabels[$file->document_type] ?? $file->document_type; @endphp @endforeach
@endif {{-- Per-category "+ Add document" popover. Each category has its own Alpine x-data scope so opening one popover doesn't toggle the others. Popover lists ONLY the document types relevant to this category. --}}
{{ __('admin.startups_admin.files.pick_document') }}
@foreach ($catTypes as $typeKey) @endforeach
@endforeach
{{-- Hidden file picker shared by all category triggers + replace flows. Same pattern as the Files section. --}}
{{-- ───────────────── MEETINGS ───────────────── Mirror of the investor show meetings section. Lists the startup's MS Bookings appointments newest-first with the same tz-aware date/time, attendance pill, status pill for cancellations, and admin notes when present. --}} @if ($meetings->isNotEmpty()) @php $attendancePillStyle = [ 'attended' => ['fg' => '#065f46', 'bg' => '#d1fae5'], 'not_attended' => ['fg' => '#991b1b', 'bg' => '#fee2e2'], 'unknown' => ['fg' => '#475569', 'bg' => '#f1f5f9'], ]; @endphp
    @foreach ($meetings as $m) @php $att = $attendancePillStyle[$m->attendance_status] ?? $attendancePillStyle['unknown']; $isCxl = $m->status === 'cancelled'; @endphp
  • {{ optional($m->meeting_date)->format('Y-m-d') ?: $dash }}
    @if ($m->start_time)
    {{ optional($m->localStart)->format('H:i') }}@if ($m->end_time) – {{ optional($m->localEnd)->format('H:i') }}@endif
    @endif @if ($m->created_at)
    {{ $m->created_at->format('Y-m-d') }}
    @endif
    {{ __('admin.meetings_admin.attendances.' . $m->attendance_status) }} @if ($isCxl) {{ __('admin.meetings_admin.statuses.cancelled') }} @endif @if ($m->staff_name) {{ $m->staff_name }} @endif
    @if ($m->meeting_notes)

    {{ $m->meeting_notes }}

    @endif
  • @endforeach
@endif {{-- ───────────────── ENTITY NOTES ───────────────── Free-form admin notes attached to this startup. Reusable component shared with the investor show page. --}} @include('admin.partials.entity-notes', [ 'subjectType' => 'startup', 'subjectId' => $startup->id, 'notes' => $notes, 'sectionTitle' => __('admin.notes.section_title'), ]) @include('admin.partials.entity-notes-assets')
{{-- Shared CSS + Alpine factory for the inline section-edit feature. --}} @include('admin.partials.editable-section-assets') {{-- Rejection confirmation modal (shared with the startup index). --}} @include('admin.partials.rejection-modal') {{-- Request More Info modal — reuses the rejection modal's CSS, so it MUST be included after it. --}} @include('admin.partials.request-info-modal') @endsection {{-- Toast element used by the copy-to-clipboard feedback. --}}
{{ __('admin.actions.copied') ?: 'Copied' }}
@push('scripts') @endpush