feat: add workflow actions, admin enhancements, and comprehensive test coverage

This commit is contained in:
2026-03-09 02:14:08 -05:00
parent 5169107a04
commit 7073f5aa33
71 changed files with 12902 additions and 541 deletions
@@ -1,5 +1,5 @@
<script lang="ts">
import { onDestroy, onMount } from "svelte";
import { createEventDispatcher, onDestroy, onMount } from "svelte";
import { PUBLIC_API_URL } from "$env/static/public";
import { io, type Socket } from "socket.io-client";
import {
@@ -14,11 +14,17 @@
export let opportunityId: string = "";
export let quotePreviewPdfUrl: string | null = null;
export let initialQuotes: CommittedQuote[] = [];
export let initialQuoteId: string | null = null;
export let permissions: PermissionMap = {} as PermissionMap;
export let isClosedOpportunity: boolean = false;
const dispatch = createEventDispatcher<{ quotesChanged: CommittedQuote[] }>();
// ── Permission helpers ──
$: canFetchQuotes = permissions["sales.opportunity.quote.fetch"] !== false;
$: canCommitQuote = permissions["sales.opportunity.quote.commit"] === true;
$: canCommitQuote =
!isClosedOpportunity &&
permissions["sales.opportunity.quote.commit"] === true;
$: canPreviewQuote = permissions["sales.opportunity.quote.preview"] === true;
$: canDownloadQuote =
permissions["sales.opportunity.quote.download"] === true;
@@ -29,8 +35,35 @@
let quotes: CommittedQuote[] = initialQuotes;
let quotesLoading = false;
let quotesError = "";
// Determine initial selection: prefer initialQuoteId match, fall back to first quote
const initialMatch = initialQuoteId
? initialQuotes.find(
(q) => q.id === initialQuoteId || q.quoteFileName === initialQuoteId,
)
: null;
console.log(
"[QuotesTab] initialQuoteId:",
initialQuoteId,
"quotes:",
initialQuotes.map((q) => ({ id: q.id, fileName: q.quoteFileName })),
"match:",
initialMatch?.id,
);
let selectedQuote: CommittedQuote | null =
initialQuotes.length > 0 ? initialQuotes[0] : null;
initialMatch ?? (initialQuotes.length > 0 ? initialQuotes[0] : null);
// Auto-select quote by ID when navigating from activity tab (for post-mount updates)
$: if (initialQuoteId && quotes.length > 0) {
const match = quotes.find(
(q) => q.id === initialQuoteId || q.quoteFileName === initialQuoteId,
);
if (match) {
selectedQuote = match;
viewMode = "list";
loadQuotePreview(match.id);
}
}
// ── Detail data (lazy-loaded with regen data & params) ──
let detailQuotes: Map<string, CommittedQuote> = new Map();
@@ -145,6 +178,7 @@
try {
const result = await sales.fetchQuotes(accessToken, opportunityId);
quotes = result.data ?? [];
dispatch("quotesChanged", quotes);
if (quotes.length > 0 && !selectedQuote) {
selectedQuote = quotes[0];
}