1717 CheckCircle ,
1818 XCircle ,
1919 Clock ,
20- Target
20+ Target ,
21+ X ,
22+ AlertTriangle
2123 } from ' @lucide/svelte' ;
2224 import { goto } from ' $app/navigation' ;
25+ import { enhance } from ' $app/forms' ;
26+ import { page } from ' $app/stores' ;
2327
24- /** @type {{ data: import('./$types').PageData }} */
25- let { data } = $props ();
28+ /** @type {{ data: import('./$types').PageData, form?: any }} */
29+ let { data, form } = $props ();
2630
2731 let searchTerm = $state (' ' );
2832 let selectedStage = $state (' all' );
2933 let sortField = $state (' createdAt' );
3034 let sortDirection = $state (' desc' );
3135 let showFilters = $state (false );
36+ let showDeleteModal = $state (false );
37+ let opportunityToDelete = $state (null );
38+ let deleteLoading = $state (false );
3239
3340 // Stage configurations
3441 const stageConfig = {
101108 sortDirection = ' asc' ;
102109 }
103110 }
111+
112+ function openDeleteModal (opportunity ) {
113+ opportunityToDelete = opportunity;
114+ showDeleteModal = true ;
115+ }
116+
117+ function closeDeleteModal () {
118+ showDeleteModal = false ;
119+ opportunityToDelete = null ;
120+ deleteLoading = false ;
121+ }
104122< / script>
105123
106124< svelte: head>
107125 < title> Opportunities - BottleCRM< / title>
108126< / svelte: head>
109127
110128< div class = " min-h-screen bg-gray-50 dark:bg-gray-900" >
129+ <!-- Success/ Error Messages -->
130+ {#if form? .success }
131+ < div class = " fixed top-4 right-4 z-50 max-w-md" >
132+ < div class = " bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative" role= " alert" >
133+ < strong class = " font-bold" > Success! < / strong>
134+ < span class = " block sm:inline" > {form .message || ' Opportunity deleted successfully.' }< / span>
135+ < / div>
136+ < / div>
137+ {/ if }
138+
139+ {#if form? .message && ! form? .success }
140+ < div class = " fixed top-4 right-4 z-50 max-w-md" >
141+ < div class = " bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role= " alert" >
142+ < strong class = " font-bold" > Error ! < / strong>
143+ < span class = " block sm:inline" > {form .message }< / span>
144+ < / div>
145+ < / div>
146+ {/ if }
147+
111148 <!-- Header -->
112149 < div class = " bg-white dark:bg-gray-800 shadow" >
113150 < div class = " px-4 sm:px-6 lg:px-8" >
342379 < / td>
343380 < td class = " px-6 py-4 whitespace-nowrap" >
344381 < span class = " inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {config.color}" >
345- < svelte: component this = {config .icon } class = " mr-1 h-3 w-3" / >
382+ {#if config .icon }
383+ {@const IconComponent = config .icon }
384+ < IconComponent class = " mr-1 h-3 w-3" / >
385+ {/ if }
346386 {config .label }
347387 < / span>
348388 < / td>
386426 < / td>
387427 < td class = " px-6 py-4 whitespace-nowrap text-right text-sm font-medium" >
388428 < div class = " flex items-center justify-end space-x-2" >
389- < button
390- type = " button "
429+ < a
430+ href = " /app/opportunities/{opportunity.id} "
391431 class = " text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300"
392432 title= " View"
393433 >
394434 < Eye class = " h-4 w-4" / >
395- < / button >
435+ < / a >
396436 < a
397437 href= " /app/opportunities/{opportunity.id}/edit"
398438 class = " text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300"
402442 < / a>
403443 < button
404444 type= " button"
445+ onclick= {() => openDeleteModal (opportunity)}
405446 class = " text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300"
406447 title= " Delete"
407448 >
439480 < / div>
440481
441482
442- < / div>
483+ < / div>
484+
485+ <!-- Delete Confirmation Modal -->
486+ {#if showDeleteModal && opportunityToDelete}
487+ < div
488+ class = " fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"
489+ role= " dialog"
490+ aria- modal= " true"
491+ aria- labelledby= " modal-title"
492+ tabindex= " -1"
493+ onclick= {closeDeleteModal}
494+ onkeydown= {(e ) => e .key === ' Escape' && closeDeleteModal ()}
495+ >
496+ < div
497+ class = " relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white dark:bg-gray-800"
498+ role= " button"
499+ tabindex= " 0"
500+ onkeydown= {(e ) => e .key === ' Escape' && closeDeleteModal ()}
501+ onclick= {(e ) => e .stopPropagation ()}
502+ >
503+ < div class = " mt-3" >
504+ < div class = " flex items-center justify-between mb-4" >
505+ < div class = " flex items-center" >
506+ < div class = " mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 dark:bg-red-900" >
507+ < AlertTriangle class = " h-6 w-6 text-red-600 dark:text-red-400" / >
508+ < / div>
509+ < div class = " ml-4" >
510+ < h3 id= " modal-title" class = " text-lg leading-6 font-medium text-gray-900 dark:text-white" > Delete Opportunity< / h3>
511+ < / div>
512+ < / div>
513+ < button
514+ type= " button"
515+ onclick= {closeDeleteModal}
516+ class = " text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
517+ >
518+ < X class = " h-5 w-5" / >
519+ < / button>
520+ < / div>
521+
522+ < div class = " mt-2" >
523+ < p class = " text-sm text-gray-500 dark:text-gray-400" >
524+ Are you sure you want to delete the opportunity < strong> " {opportunityToDelete?.name || 'Unknown'}" < / strong> ?
525+ This action cannot be undone and will also delete all associated tasks, events, and comments.
526+ < / p>
527+ < / div>
528+
529+ < div class = " mt-6 flex justify-end space-x-3" >
530+ < button
531+ type= " button"
532+ onclick= {closeDeleteModal}
533+ disabled= {deleteLoading}
534+ class = " px-4 py-2 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
535+ >
536+ Cancel
537+ < / button>
538+
539+ < form method= " POST" action= " ?/delete" use: enhance= {({ formElement, formData }) => {
540+ deleteLoading = true ;
541+
542+ return async ({ result }) => {
543+ deleteLoading = false ;
544+
545+ if (result .type === ' success' ) {
546+ closeDeleteModal ();
547+ // Use goto with replaceState and invalidateAll for a clean refresh
548+ await goto ($page .url .pathname , {
549+ replaceState: true ,
550+ invalidateAll: true
551+ });
552+ } else if (result .type === ' failure' ) {
553+ console .error (' Delete failed:' , result .data ? .message );
554+ alert (' Failed to delete opportunity: ' + (result .data ? .message || ' Unknown error' ));
555+ } else if (result .type === ' error' ) {
556+ console .error (' Delete error:' , result .error );
557+ alert (' An error occurred while deleting the opportunity.' );
558+ }
559+ };
560+ }}>
561+ < input type= " hidden" name= " opportunityId" value= {opportunityToDelete? .id || ' ' } / >
562+ < button
563+ type= " submit"
564+ disabled= {deleteLoading}
565+ class = " px-4 py-2 bg-red-600 border border-transparent rounded-md text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 disabled:opacity-50 disabled:cursor-not-allowed"
566+ >
567+ {deleteLoading ? ' Deleting...' : ' Delete' }
568+ < / button>
569+ < / form>
570+ < / div>
571+ < / div>
572+ < / div>
573+ < / div>
574+ {/ if }
0 commit comments