@@ -510,7 +510,9 @@ <h1 class="mt-4 text-3xl font-semibold leading-tight sm:text-4xl">
510510 { type : "Events (incl. Conf + HH)" , events : 2 , avgInvited : 330 , avgAttended : 300 , avgOpps : 85 , avgOppInvited : "25%" , avgOppAttended : "28%" , avgClosed : 25 , win : "28%" } ,
511511 { type : "Partner Events" , events : 3 , avgInvited : 280 , avgAttended : 207 , avgOpps : 52 , avgOppInvited : "19%" , avgOppAttended : "25%" , avgClosed : 12 , win : "24%" } ,
512512 ]
513- }
513+ } ,
514+ filterModalOpen : false ,
515+ selectedPartnerTypes : [ ]
514516 } ;
515517 const PARTNER_TYPES = [ "Registered" , "Preferred" , "Premier" , "MSP" , "Technology Partner (ISV)" , "OEM Partner" , "Distributor Partner" ] ;
516518 const TABS = [
@@ -521,6 +523,26 @@ <h1 class="mt-4 text-3xl font-semibold leading-tight sm:text-4xl">
521523 { id : 'margins' , label : 'Margins' , icon : 'dollar-sign' } ,
522524 { id : 'roe' , label : 'Rules of Engagement' , icon : 'gavel' }
523525 ] ;
526+ function initFilter ( ) {
527+ STATE . selectedPartnerTypes = PARTNER_TYPES . slice ( ) ;
528+ if ( typeof localStorage !== 'undefined' ) {
529+ try {
530+ const saved = localStorage . getItem ( 'recastPartnerTypeFilter' ) ;
531+ if ( saved ) {
532+ const parsed = JSON . parse ( saved ) ;
533+ STATE . selectedPartnerTypes = parsed . selectedPartnerTypes . filter ( t => PARTNER_TYPES . includes ( t ) ) ;
534+ }
535+ } catch ( e ) {
536+ // fallback to default
537+ }
538+ }
539+ if ( STATE . selectedPartnerTypes . length === 0 ) {
540+ STATE . selectedPartnerTypes = PARTNER_TYPES . slice ( ) ;
541+ }
542+ if ( ! STATE . selectedPartnerTypes . includes ( STATE . partnerType ) ) {
543+ STATE . partnerType = STATE . selectedPartnerTypes [ 0 ] ;
544+ }
545+ }
524546 function setDefaultBand ( type ) {
525547 if ( type === 'Registered' ) return 'band3' ;
526548 if ( type === 'Preferred' ) return 'band2' ;
@@ -562,6 +584,9 @@ <h1 class="mt-4 text-3xl font-semibold leading-tight sm:text-4xl">
562584 }
563585 }
564586 function render ( ) {
587+ if ( ! STATE . selectedPartnerTypes . includes ( STATE . partnerType ) ) {
588+ STATE . partnerType = STATE . selectedPartnerTypes [ 0 ] || PARTNER_TYPES [ 0 ] ;
589+ }
565590 const data = getPartnerData ( STATE . partnerType ) ;
566591 let html = '<div class="space-y-6">' ;
567592 html += renderPartnerTypeSelector ( ) ;
@@ -589,14 +614,16 @@ <h1 class="mt-4 text-3xl font-semibold leading-tight sm:text-4xl">
589614 break ;
590615 case 'onboarding' :
591616 if ( STATE . partnerType !== 'Registered' ) {
592- html += renderOnboardingContent ( ) ;
617+ html += renderOnboardingContent ( data ) ;
593618 } else {
594619 html += '<p class="text-center text-gray-700 leading-relaxed">Not applicable for Registered partners.</p>' ;
595620 }
596621 break ;
597622 }
598623 html += '</div>' ;
599624 html += '</div>' ;
625+ html += renderFilterTrigger ( ) ;
626+ if ( STATE . filterModalOpen ) html += renderFilterModal ( ) ;
600627 if ( STATE . showBusinessPlanModal ) {
601628 html += renderBusinessPlanModal ( ) ;
602629 }
@@ -611,9 +638,51 @@ <h1 class="mt-4 text-3xl font-semibold leading-tight sm:text-4xl">
611638 lucide . createIcons ( ) ;
612639 addEventListeners ( ) ;
613640 }
641+ function renderFilterTrigger ( ) {
642+ return `
643+ <div class="fixed bottom-4 left-4 z-50">
644+ <input type="checkbox" id="filterTrigger" class="h-4 w-4 rounded accent-[#2563eb] opacity-50 focus:opacity-100" ${ STATE . filterModalOpen ? 'checked' : '' } />
645+ </div>
646+ ` ;
647+ }
648+ function renderFilterModal ( ) {
649+ let checkboxes = '' ;
650+ PARTNER_TYPES . forEach ( t => {
651+ const display = t === 'Registered' ? 'Registered (LAR/Referral)' : t ;
652+ const checked = STATE . selectedPartnerTypes . includes ( t ) ? 'checked' : '' ;
653+ checkboxes += `
654+ <label class="flex items-center gap-2">
655+ <input type="checkbox" class="partner-type-checkbox h-4 w-4 rounded accent-[#2563eb]" data-type="${ t } " ${ checked } />
656+ ${ display }
657+ </label>
658+ ` ;
659+ } ) ;
660+ const allChecked = STATE . selectedPartnerTypes . length === PARTNER_TYPES . length ? 'checked' : '' ;
661+ return `
662+ <div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 px-4" role="dialog" aria-modal="true" id="filterModalBg">
663+ <div class="relative max-w-md w-full bg-white rounded-xl shadow-2xl p-6">
664+ <button class="absolute top-4 right-4 text-gray-600 hover:text-gray-900 focus:outline-none" id="closeFilterModal">
665+ <i data-lucide="x" class="h-5 w-5"></i>
666+ </button>
667+ <h2 class="text-xl font-medium mb-4">Select Partner Types to Show</h2>
668+ <div class="space-y-3">
669+ <label class="flex items-center gap-2 font-medium">
670+ <input type="checkbox" id="selectAll" class="h-4 w-4 rounded accent-[#2563eb]" ${ allChecked } />
671+ Select All
672+ </label>
673+ ${ checkboxes }
674+ </div>
675+ <div class="mt-6 flex justify-end gap-3">
676+ <button id="resetFilter" class="rounded-full border border-gray-300 px-4 py-2 text-sm hover:bg-gray-50 transition-all duration-300">Reset</button>
677+ <button id="saveFilter" class="rounded-full bg-[#2563eb] hover:bg-[#1d4ed8] px-4 py-2 text-sm text-white shadow-sm transition-all duration-300">Save</button>
678+ </div>
679+ </div>
680+ </div>
681+ ` ;
682+ }
614683 function renderPartnerTypeSelector ( ) {
615684 let options = '' ;
616- PARTNER_TYPES . forEach ( ( t ) => {
685+ STATE . selectedPartnerTypes . forEach ( ( t ) => {
617686 const display = t === "Registered" ? "Registered (LAR/Referral)" : t ;
618687 options += `<option value="${ t } " ${ STATE . partnerType === t ? 'selected' : '' } >${ display } </option>` ;
619688 } ) ;
@@ -829,9 +898,7 @@ <h4 class="font-medium mb-2">${r.band} - ${r.upTo}</h4>
829898 return html ;
830899 }
831900 function renderROEContent ( ) {
832- // MSP already has its own ROE block; keep that path
833901 if ( STATE . partnerType === 'MSP' ) {
834- // Add partner/internal toggle even for MSP
835902 return renderROEWithToggle ( renderSimpleROE ( STATE . partnerType ) , renderInternalROE ( ) ) ;
836903 }
837904 return renderROEWithToggle ( renderSimpleROE ( STATE . partnerType ) , renderInternalROE ( ) ) ;
@@ -1026,7 +1093,7 @@ <h3 class="text-lg font-semibold p-5 border-b border-[#E2E8F0] flex items-center
10261093 function renderSalesMotionContent ( ) {
10271094 let html = '<h2 class="text-xl font-medium mb-6 flex items-center gap-3"><i data-lucide="table" class="h-6 w-6 stroke-[#2563eb]"></i>Sales Motion</h2>' ;
10281095 html += '<div class="flex flex-col gap-4 md:flex-row md:items-end md:justify-between mb-4">' ;
1029- html += '<div><p class="text-base text-gray-700 leading-relaxed mt-1">See who owns each stage, who supports it , and what moves the deal forward .</p></div>' ;
1096+ html += '<div><p class="text-base text-gray-700 leading-relaxed mt-1">View the sales cycle matrix showing stages, ownership, support, exit criteria , and deliverables based on the selected band .</p></div>' ;
10301097 if ( STATE . partnerType !== 'Registered' ) {
10311098 html += '<select id="bandSelect" class="w-[280px] rounded-lg border border-[#E2E8F0] bg-white px-4 py-2.5 text-sm focus:border-[#2563eb] focus:ring-2 focus:ring-[#2563eb] focus:ring-opacity-50">' ;
10321099 [ 'band1' , 'band2' , 'band3' ] . forEach ( b => {
@@ -1082,30 +1149,24 @@ <h3 class="text-lg font-semibold p-5 border-b border-[#E2E8F0] flex items-center
10821149 html += '</div>' ;
10831150 return html ;
10841151 }
1085- function renderOnboardingContent ( ) {
1152+ function renderOnboardingContent ( data ) {
1153+ let html = '<h2 class="text-xl font-medium mb-6 flex items-center gap-3"><i data-lucide="rocket" class="h-6 w-6 stroke-[#2563eb]"></i>Onboarding Requirements</h2>' ;
1154+ let certHtml = '<div class="rounded-xl bg-amber-100 border border-amber-200 p-4 mb-6">' ;
1155+ certHtml += '<h3 class="text-lg font-medium mb-2 flex items-center gap-2"><i data-lucide="award" class="h-5 w-5 stroke-[#d97706]"></i>Required Certifications</h3>' ;
1156+ let certText ;
10861157 if ( STATE . partnerType === 'MSP' ) {
1087- let html = '<h2 class="text-xl font-medium mb-6 flex items-center gap-3"><i data-lucide="rocket" class="h-6 w-6 stroke-[#2563eb]"></i>Onboarding</h2>' ;
1088- html += '<h3 class="text-xl font-medium mb-4">Onboarding Steps:</h3>' ;
1089- html += '<ol class="space-y-4 list-decimal pl-6">' ;
1090- STATE . msp . onboarding . steps . forEach ( s => {
1091- html += `<li class="text-gray-700 leading-relaxed">${ s . replace ( / \n / g, '<br>' ) } </li>` ;
1092- } ) ;
1093- html += '</ol>' ;
1094- html += '<h3 class="text-xl font-medium mt-8 mb-4">Resources Provided:</h3>' ;
1095- html += '<ul class="space-y-4">' ;
1096- STATE . msp . onboarding . resources . forEach ( r => {
1097- html += `
1098- <li class="flex items-start gap-3">
1099- <div class="flex-shrink-0 mt-1 bg-[#2563eb] rounded-full p-1">
1100- <i data-lucide="check" class="h-3 w-3 stroke-white"></i>
1101- </div>
1102- <span class="text-gray-700 leading-relaxed">${ r } </span>
1103- </li>
1104- ` ;
1105- } ) ;
1106- html += '</ul>' ;
1107- return html ;
1158+ certText = 'Initial sales and technical certifications for eligible product lines.' ;
1159+ } else if ( STATE . partnerType === 'Preferred' ) {
1160+ certText = '1 Certified Sales resource and 1 Certified Technical resource.' ;
1161+ } else if ( STATE . partnerType === 'Premier' ) {
1162+ certText = 'Certified sales and technical resources (minimum 2 each recommended).' ;
11081163 } else {
1164+ certText = 'Customized onboarding based on GTM motion; must complete GTM certification.' ;
1165+ }
1166+ certHtml += `<p class="text-gray-700 leading-relaxed">For ${ STATE . partnerType } partners: ${ certText } These must be completed during the initial onboarding phase before advancing and accessing full partner benefits, resources, and margin eligibility.</p>` ;
1167+ certHtml += '</div>' ;
1168+ html += certHtml ;
1169+ if ( data . isStandard ) {
11091170 const steps = STATE . model . onboarding . steps ;
11101171 let buttonsHtml = '<div class="flex flex-wrap gap-3 mb-6">' ;
11111172 steps . forEach ( ( s , i ) => {
@@ -1134,12 +1195,31 @@ <h3 class="text-lg font-semibold p-5 border-b border-[#E2E8F0] flex items-center
11341195 ` ;
11351196 } ) ;
11361197 listHtml += '</ul>' ;
1137- return `
1138- <h2 class="text-xl font-medium mb-6 flex items-center gap-3"><i data-lucide="rocket" class="h-6 w-6 stroke-[#2563eb]"></i>Onboarding Requirements</h2>
1139- ${ buttonsHtml }
1140- ${ listHtml }
1141- ` ;
1198+ html += buttonsHtml + listHtml ;
1199+ } else if ( STATE . partnerType === 'MSP' ) {
1200+ html += '<h3 class="text-xl font-medium mb-4">Onboarding Steps:</h3>' ;
1201+ html += '<ol class="space-y-4 list-decimal pl-6">' ;
1202+ STATE . msp . onboarding . steps . forEach ( s => {
1203+ html += `<li class="text-gray-700 leading-relaxed">${ s . replace ( / \n / g, '<br>' ) } </li>` ;
1204+ } ) ;
1205+ html += '</ol>' ;
1206+ html += '<h3 class="text-xl font-medium mt-8 mb-4">Resources Provided:</h3>' ;
1207+ html += '<ul class="space-y-4">' ;
1208+ STATE . msp . onboarding . resources . forEach ( r => {
1209+ html += `
1210+ <li class="flex items-start gap-3">
1211+ <div class="flex-shrink-0 mt-1 bg-[#2563eb] rounded-full p-1">
1212+ <i data-lucide="check" class="h-3 w-3 stroke-white"></i>
1213+ </div>
1214+ <span class="text-gray-700 leading-relaxed">${ r } </span>
1215+ </li>
1216+ ` ;
1217+ } ) ;
1218+ html += '</ul>' ;
1219+ } else {
1220+ // For OEM, ISV, Distributor - already handled in certHtml
11421221 }
1222+ return html ;
11431223 }
11441224 function renderBusinessPlanModal ( ) {
11451225 return `
@@ -1523,10 +1603,81 @@ <h2 class="text-xl font-medium mb-4 flex items-center gap-3"><i data-lucide="upl
15231603 URL . revokeObjectURL ( url ) ;
15241604 } ) ;
15251605 }
1606+ const filterTrigger = document . getElementById ( 'filterTrigger' ) ;
1607+ if ( filterTrigger ) {
1608+ filterTrigger . addEventListener ( 'change' , ( e ) => {
1609+ STATE . filterModalOpen = e . target . checked ;
1610+ render ( ) ;
1611+ } ) ;
1612+ }
1613+ const selectAll = document . getElementById ( 'selectAll' ) ;
1614+ if ( selectAll ) {
1615+ selectAll . addEventListener ( 'change' , ( e ) => {
1616+ if ( e . target . checked ) {
1617+ STATE . selectedPartnerTypes = PARTNER_TYPES . slice ( ) ;
1618+ } else {
1619+ STATE . selectedPartnerTypes = [ ] ;
1620+ }
1621+ render ( ) ;
1622+ } ) ;
1623+ }
1624+ document . querySelectorAll ( '.partner-type-checkbox' ) . forEach ( cb => {
1625+ cb . addEventListener ( 'change' , ( e ) => {
1626+ const type = e . target . dataset . type ;
1627+ if ( e . target . checked ) {
1628+ if ( ! STATE . selectedPartnerTypes . includes ( type ) ) {
1629+ STATE . selectedPartnerTypes . push ( type ) ;
1630+ }
1631+ } else {
1632+ STATE . selectedPartnerTypes = STATE . selectedPartnerTypes . filter ( t => t !== type ) ;
1633+ }
1634+ render ( ) ;
1635+ } ) ;
1636+ } ) ;
1637+ const saveFilter = document . getElementById ( 'saveFilter' ) ;
1638+ if ( saveFilter ) {
1639+ saveFilter . addEventListener ( 'click' , ( ) => {
1640+ if ( STATE . selectedPartnerTypes . length === 0 ) {
1641+ STATE . selectedPartnerTypes = PARTNER_TYPES . slice ( ) ;
1642+ }
1643+ if ( typeof localStorage !== 'undefined' ) {
1644+ localStorage . setItem ( 'recastPartnerTypeFilter' , JSON . stringify ( { selectedPartnerTypes : STATE . selectedPartnerTypes } ) ) ;
1645+ }
1646+ STATE . filterModalOpen = false ;
1647+ render ( ) ;
1648+ } ) ;
1649+ }
1650+ const resetFilter = document . getElementById ( 'resetFilter' ) ;
1651+ if ( resetFilter ) {
1652+ resetFilter . addEventListener ( 'click' , ( ) => {
1653+ STATE . selectedPartnerTypes = PARTNER_TYPES . slice ( ) ;
1654+ if ( typeof localStorage !== 'undefined' ) {
1655+ localStorage . setItem ( 'recastPartnerTypeFilter' , JSON . stringify ( { selectedPartnerTypes : STATE . selectedPartnerTypes } ) ) ;
1656+ }
1657+ STATE . filterModalOpen = false ;
1658+ render ( ) ;
1659+ } ) ;
1660+ }
1661+ const closeFilterModal = document . getElementById ( 'closeFilterModal' ) ;
1662+ if ( closeFilterModal ) {
1663+ closeFilterModal . addEventListener ( 'click' , ( ) => {
1664+ STATE . filterModalOpen = false ;
1665+ render ( ) ;
1666+ } ) ;
1667+ }
1668+ const filterModalBg = document . getElementById ( 'filterModalBg' ) ;
1669+ if ( filterModalBg ) {
1670+ filterModalBg . addEventListener ( 'click' , ( e ) => {
1671+ if ( e . target === filterModalBg ) {
1672+ STATE . filterModalOpen = false ;
1673+ render ( ) ;
1674+ }
1675+ } ) ;
1676+ }
15261677 const modals = document . querySelectorAll ( '#businessPlanModal, #partnerEventsModal, #evaluationModal, #csvModal' ) ;
1527- modals . forEach ( modal => {
1528- modal . addEventListener ( 'click' , ( e ) => {
1529- if ( e . target === modal ) {
1678+ modals . forEach ( m => {
1679+ m . addEventListener ( 'click' , ( e ) => {
1680+ if ( e . target === m ) {
15301681 STATE . showBusinessPlanModal = false ;
15311682 STATE . showPartnerEventsModal = false ;
15321683 STATE . showEvaluationModal = false ;
@@ -1536,16 +1687,20 @@ <h2 class="text-xl font-medium mb-4 flex items-center gap-3"><i data-lucide="upl
15361687 } ) ;
15371688 } ) ;
15381689 document . addEventListener ( 'keydown' , ( e ) => {
1539- if ( e . key === 'Escape' && ( STATE . showBusinessPlanModal || STATE . showPartnerEventsModal || STATE . showEvaluationModal || STATE . showCsvModal ) ) {
1690+ if ( e . key === 'Escape' && ( STATE . showBusinessPlanModal || STATE . showPartnerEventsModal || STATE . showEvaluationModal || STATE . showCsvModal || STATE . filterModalOpen ) ) {
15401691 STATE . showBusinessPlanModal = false ;
15411692 STATE . showPartnerEventsModal = false ;
15421693 STATE . showEvaluationModal = false ;
15431694 STATE . showCsvModal = false ;
1695+ STATE . filterModalOpen = false ;
15441696 render ( ) ;
15451697 }
15461698 } ) ;
15471699 }
1548- document . addEventListener ( 'DOMContentLoaded' , render ) ;
1700+ document . addEventListener ( 'DOMContentLoaded' , ( ) => {
1701+ initFilter ( ) ;
1702+ render ( ) ;
1703+ } ) ;
15491704 </ script >
15501705 </ body >
15511706</ html >
0 commit comments