|
1 | 1 | <script> |
2 | | - import { page } from '$app/stores'; |
3 | | - import { goto } from '$app/navigation'; |
4 | | - import { onMount } from 'svelte'; |
5 | | - import { invalidateAll } from '$app/navigation'; |
6 | 2 | import { |
7 | 3 | ArrowLeft, |
8 | 4 | Edit, |
9 | 5 | Lock, |
10 | 6 | Unlock, |
11 | | - Trash2, |
12 | 7 | Users, |
13 | 8 | Target, |
14 | 9 | DollarSign, |
|
18 | 13 | Phone, |
19 | 14 | Mail, |
20 | 15 | Globe, |
21 | | - Building, |
22 | 16 | MapPin, |
23 | | - Calendar, |
24 | 17 | MessageSquare, |
25 | 18 | CheckSquare, |
26 | 19 | FolderOpen, |
|
31 | 24 | export let data; |
32 | 25 | /** @type {any} */ |
33 | 26 | export let form; |
34 | | - let users = Array.isArray(data.users) ? data.users : []; |
35 | 27 |
|
36 | | - const { account, contacts, opportunities = [], quotes, tasks, cases } = data; |
| 28 | + const { account, contacts, opportunities = [], tasks, cases } = data; |
37 | 29 | let comments = data.comments; |
38 | 30 |
|
39 | 31 | // Form state |
40 | 32 | let showCloseModal = false; |
41 | 33 | let closureReason = ''; |
42 | 34 | let closeError = ''; |
43 | | - let isClosing = false; |
44 | 35 |
|
45 | 36 | // Comment functionality |
46 | 37 | let newComment = ''; |
|
74 | 65 | const data = await res.json().catch(() => ({})); |
75 | 66 | commentError = data?.error || data?.message || 'Failed to add comment.'; |
76 | 67 | } |
77 | | - } catch (err) { |
| 68 | + } catch { |
78 | 69 | commentError = 'Failed to add comment.'; |
79 | 70 | } finally { |
80 | 71 | isSubmittingComment = false; |
81 | 72 | } |
82 | 73 | } |
83 | 74 |
|
84 | | - // Format date string |
| 75 | + /** |
| 76 | + * Format date string |
| 77 | + * @param {string | Date | null | undefined} dateStr |
| 78 | + */ |
85 | 79 | function formatDate(dateStr) { |
86 | 80 | if (!dateStr) return 'N/A'; |
87 | 81 | return new Date(dateStr).toLocaleDateString('en-US', { |
|
91 | 85 | }); |
92 | 86 | } |
93 | 87 |
|
94 | | - // Format currency |
| 88 | + /** |
| 89 | + * Format currency |
| 90 | + * @param {number | null | undefined} value |
| 91 | + */ |
95 | 92 | function formatCurrency(value) { |
96 | 93 | if (!value) return '$0'; |
97 | 94 | return new Intl.NumberFormat('en-US', { |
|
101 | 98 | }).format(value); |
102 | 99 | } |
103 | 100 |
|
104 | | - // Badge color functions |
| 101 | + /** |
| 102 | + * Badge color functions |
| 103 | + * @param {string | null | undefined} stage |
| 104 | + */ |
105 | 105 | function getStageBadgeColor(stage) { |
106 | 106 | switch (stage?.toLowerCase()) { |
107 | 107 | case 'prospecting': return 'bg-blue-100 text-blue-800 dark:bg-blue-900/20 dark:text-blue-400'; |
|
114 | 114 | } |
115 | 115 | } |
116 | 116 |
|
| 117 | + /** |
| 118 | + * @param {string | null | undefined} status |
| 119 | + */ |
117 | 120 | function getCaseStatusBadgeColor(status) { |
118 | 121 | switch (status?.toLowerCase()) { |
119 | 122 | case 'open': return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-400'; |
|
123 | 126 | } |
124 | 127 | } |
125 | 128 |
|
126 | | - function getQuoteStatusBadgeColor(status) { |
127 | | - switch (status?.toLowerCase()) { |
128 | | - case 'draft': return 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300'; |
129 | | - case 'needs_review': return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-400'; |
130 | | - case 'in_review': return 'bg-blue-100 text-blue-800 dark:bg-blue-900/20 dark:text-blue-400'; |
131 | | - case 'approved': return 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400'; |
132 | | - case 'rejected': return 'bg-red-100 text-red-800 dark:bg-red-900/20 dark:text-red-400'; |
133 | | - case 'presented': return 'bg-purple-100 text-purple-800 dark:bg-purple-900/20 dark:text-purple-400'; |
134 | | - case 'accepted': return 'bg-indigo-100 text-indigo-800 dark:bg-indigo-900/20 dark:text-indigo-400'; |
135 | | - default: return 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300'; |
136 | | - } |
137 | | - } |
138 | | -
|
139 | 129 | // Handle form submission errors |
140 | 130 | $: { |
141 | 131 | if (form?.success === false) { |
|
431 | 421 | </tr> |
432 | 422 | </thead> |
433 | 423 | <tbody class="divide-y divide-gray-200 dark:divide-gray-700"> |
434 | | - {#each contacts as contact} |
| 424 | + {#each contacts as contact (contact.id)} |
435 | 425 | <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50"> |
436 | 426 | <td class="py-4 font-medium text-gray-900 dark:text-white"> |
437 | 427 | <a href="/app/contacts/{contact.id}" class="hover:text-blue-600 dark:hover:text-blue-400 hover:underline"> |
|
501 | 491 | </tr> |
502 | 492 | </thead> |
503 | 493 | <tbody class="divide-y divide-gray-200 dark:divide-gray-700"> |
504 | | - {#each opportunities as opportunity} |
| 494 | + {#each opportunities as opportunity (opportunity.id)} |
505 | 495 | <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50"> |
506 | 496 | <td class="py-4 font-medium text-gray-900 dark:text-white"> |
507 | 497 | <a href="/app/opportunities/{opportunity.id}" class="hover:text-blue-600 dark:hover:text-blue-400 hover:underline"> |
|
554 | 544 | </tr> |
555 | 545 | </thead> |
556 | 546 | <tbody class="divide-y divide-gray-200 dark:divide-gray-700"> |
557 | | - {#each tasks as task} |
| 547 | + {#each tasks as task (task.id)} |
558 | 548 | <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50"> |
559 | 549 | <td class="py-4 font-medium text-gray-900 dark:text-white"> |
560 | 550 | <a href="/app/tasks/{task.id}" class="hover:text-blue-600 dark:hover:text-blue-400 hover:underline"> |
|
619 | 609 | </tr> |
620 | 610 | </thead> |
621 | 611 | <tbody class="divide-y divide-gray-200 dark:divide-gray-700"> |
622 | | - {#each cases as caseItem} |
| 612 | + {#each cases as caseItem (caseItem.id)} |
623 | 613 | <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50"> |
624 | 614 | <td class="py-4 font-medium text-gray-900 dark:text-white"> |
625 | 615 | <a href="/app/cases/{caseItem.id}" class="hover:text-blue-600 dark:hover:text-blue-400 hover:underline"> |
|
688 | 678 | </div> |
689 | 679 | {:else} |
690 | 680 | <div class="space-y-4"> |
691 | | - {#each comments as comment} |
| 681 | + {#each comments as comment (comment.id)} |
692 | 682 | <div class="bg-white dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600 p-4"> |
693 | 683 | <div class="flex justify-between items-start mb-2"> |
694 | 684 | <div class="flex items-center space-x-2"> |
|
742 | 732 | <div> |
743 | 733 | <p class="text-sm font-medium text-gray-500 dark:text-gray-400">Pipeline Value</p> |
744 | 734 | <p class="text-2xl font-bold text-gray-900 dark:text-white"> |
745 | | - {formatCurrency(opportunities.reduce((sum, opp) => sum + (opp.amount || 0), 0))} |
| 735 | + {formatCurrency(opportunities.reduce(/** @param {number} sum @param {any} opp */ (sum, opp) => sum + (opp.amount || 0), 0))} |
746 | 736 | </p> |
747 | 737 | </div> |
748 | 738 | <DollarSign class="w-8 h-8 text-yellow-500" /> |
|
752 | 742 | <div> |
753 | 743 | <p class="text-sm font-medium text-gray-500 dark:text-gray-400">Open Cases</p> |
754 | 744 | <p class="text-2xl font-bold text-gray-900 dark:text-white"> |
755 | | - {cases.filter(c => c.status !== 'CLOSED').length} |
| 745 | + {cases.filter(/** @param {any} c */ (c) => c.status !== 'CLOSED').length} |
756 | 746 | </p> |
757 | 747 | </div> |
758 | 748 | <AlertTriangle class="w-8 h-8 text-red-500" /> |
|
0 commit comments