feat(sales): add quotes tab, PDF viewer, and opportunity sidebar enhancements

This commit is contained in:
2026-03-06 23:49:27 -06:00
parent 762edd8eb7
commit b735981b6b
17 changed files with 4222 additions and 129 deletions
+39 -2
View File
@@ -11,6 +11,7 @@
import ContactsTab from "./components/ContactsTab.svelte";
import ActivityTab from "./components/ActivityTab.svelte";
import ProductsTab from "./components/ProductsTab.svelte";
import QuotesTab from "./components/QuotesTab.svelte";
export let data: PageData;
@@ -19,6 +20,7 @@
$: notes = data.notes;
$: contacts = data.contacts;
$: products = data.products;
$: quotes = data.quotes ?? [];
$: permissions = data.permissions;
let localProductSequence: number[] | null =
data.opportunity?.productSequence ?? null;
@@ -48,6 +50,7 @@
const tabs = [
"Overview",
"Products",
"Quotes",
"Notes",
"Contacts",
"Activity",
@@ -55,6 +58,11 @@
type Tab = (typeof tabs)[number];
let activeTab: Tab = "Overview";
// Hide Quotes tab if user lacks fetch permission
$: visibleTabs = tabs.filter(
(t) => t !== "Quotes" || permissions["sales.opportunity.quote.fetch"] !== false
);
// Track whether ProductsTab is in edit mode
let productsEditing = false;
@@ -126,7 +134,7 @@
<!-- Mobile vertical nav menu -->
{#if isMobile && mobileActiveTab === null}
<div class="mobile-nav-menu">
{#each tabs as tab}
{#each visibleTabs as tab}
<button
class="mobile-nav-item"
on:click={() => selectMobileTab(tab)}
@@ -183,6 +191,22 @@
d="M16 3.13a4 4 0 010 7.75"
/>
</svg>
{:else if tab === "Quotes"}
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
width="20"
height="20"
>
<path
d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"
/>
<polyline points="14 2 14 8 20 8" />
<line x1="8" y1="13" x2="16" y2="13" />
<line x1="8" y1="17" x2="13" y2="17" />
</svg>
{:else}
<svg
viewBox="0 0 24 24"
@@ -206,6 +230,9 @@
{#if tab === "Contacts" && contacts.length > 0}
<span class="mobile-nav-badge">{contacts.length}</span>
{/if}
{#if tab === "Quotes" && quotes.length > 0}
<span class="mobile-nav-badge">{quotes.length}</span>
{/if}
<svg
class="mobile-nav-chevron"
viewBox="0 0 24 24"
@@ -252,7 +279,7 @@
{/if}
<div class="tab-bar" role="tablist">
{#each tabs as tab}
{#each visibleTabs as tab}
<button
class="tab-btn"
class:active={activeTab === tab}
@@ -270,6 +297,9 @@
{#if tab === "Contacts" && contacts.length > 0}
<span class="tab-count-badge">{contacts.length}</span>
{/if}
{#if tab === "Quotes" && quotes.length > 0}
<span class="tab-count-badge">{quotes.length}</span>
{/if}
</button>
{/each}
</div>
@@ -293,6 +323,13 @@
on:sequenceSaved={handleSequenceSaved}
on:productsChanged={handleProductsChanged}
/>
{:else if activeTab === "Quotes"}
<QuotesTab
accessToken={data.accessToken}
opportunityId={data.opportunityId}
initialQuotes={quotes}
{permissions}
/>
{:else if activeTab === "Notes"}
<NotesTab
{notes}