@@ -277,39 +277,48 @@ const ProgressBar = ({
277277 ) ;
278278} ;
279279
280- type RequirementBreakdownProps = {
280+ type CollapsibleProgressCardProps = {
281281 title : string ;
282- hours : Progress ;
283- requirements : RequirementRule [ ] ;
282+ current : number ;
283+ total : number ;
284+ unit : ProgressLabelUnit ;
284285 colorIndex ?: number ;
286+ children : React . ReactNode ;
285287} ;
286- const RequirementBreakdown = ( props : RequirementBreakdownProps ) => {
287- const { title, hours, requirements, colorIndex = 0 } = props ;
288+
289+ // Shared card shell: colored left border, a collapsible header with title,
290+ // progress bar and summary, plus children rendered only while expanded.
291+ const CollapsibleProgressCard = ( {
292+ title,
293+ current,
294+ total,
295+ unit,
296+ colorIndex = 0 ,
297+ children,
298+ } : CollapsibleProgressCardProps ) => {
288299 const [ isOpen , setIsOpen ] = useState ( true ) ;
289300 const borderColor = CATEGORY_COLORS [ colorIndex % CATEGORY_COLORS . length ] ;
290- const progressUnit = getSharedProgressUnit ( requirements ) ;
291301
292302 return (
293303 < div
294304 className = "w-full bg-background rounded-md border border-gray-200 overflow-hidden border-l-4"
295305 style = { { borderLeftColor : borderColor . tailwind } }
296306 >
297- { /* Main header */ }
298307 < button
299308 className = "w-full p-4 flex items-center justify-between hover:bg-hover-bg transition-colors bg-background"
300309 onClick = { ( ) => setIsOpen ( ! isOpen ) }
301310 >
302311 < VStack gap = { 2 } >
303312 < span className = "font-bold text-base text-text" > { title } </ span >
304313 < ProgressBar
305- current = { hours . current }
306- total = { hours . total }
314+ current = { current }
315+ total = { total }
307316 colorIndex = { colorIndex }
308317 />
309318 </ VStack >
310319 < HStack y = "middle" gap = { 2 } >
311320 < span className = "text-text font-medium text-sm" >
312- { formatProgressSummary ( hours . current , hours . total , progressUnit ) }
321+ { formatProgressSummary ( current , total , unit ) }
313322 </ span >
314323 { isOpen ? (
315324 < CaretUpIcon className = "w-5 h-5 text-text" weight = "bold" />
@@ -318,26 +327,44 @@ const RequirementBreakdown = (props: RequirementBreakdownProps) => {
318327 ) }
319328 </ HStack >
320329 </ button >
321-
322- { /* Expanded content */ }
323- { isOpen && (
324- < div className = "bg-background" >
325- { /* Requirement rows */ }
326- < div className = "px-4 py-4" >
327- { requirements . map ( ( requirement , idx ) => (
328- < RequirementRow
329- key = { `${ requirement . text . slice ( 0 , 20 ) } -${ idx } ` }
330- requirement = { requirement }
331- requirementTitle = { title }
332- />
333- ) ) }
334- </ div >
335- </ div >
336- ) }
330+ { isOpen && children }
337331 </ div >
338332 ) ;
339333} ;
340334
335+ type RequirementBreakdownProps = {
336+ title : string ;
337+ hours : Progress ;
338+ requirements : RequirementRule [ ] ;
339+ colorIndex ?: number ;
340+ } ;
341+ const RequirementBreakdown = ( {
342+ title,
343+ hours,
344+ requirements,
345+ colorIndex = 0 ,
346+ } : RequirementBreakdownProps ) => (
347+ < CollapsibleProgressCard
348+ title = { title }
349+ current = { hours . current }
350+ total = { hours . total }
351+ unit = { getSharedProgressUnit ( requirements ) }
352+ colorIndex = { colorIndex }
353+ >
354+ < div className = "bg-background" >
355+ < div className = "px-4 py-4" >
356+ { requirements . map ( ( requirement , idx ) => (
357+ < RequirementRow
358+ key = { `${ requirement . text . slice ( 0 , 20 ) } -${ idx } ` }
359+ requirement = { requirement }
360+ requirementTitle = { title }
361+ />
362+ ) ) }
363+ </ div >
364+ </ div >
365+ </ CollapsibleProgressCard >
366+ ) ;
367+
341368export default RequirementBreakdown ;
342369
343370type UnifiedDegreeCardSection = {
@@ -355,8 +382,6 @@ export const UnifiedDegreeCard = ({
355382 degreeTitle,
356383 sections,
357384} : UnifiedDegreeCardProps ) => {
358- const [ isOpen , setIsOpen ] = useState ( true ) ;
359-
360385 const totalCurrent = sections . reduce ( ( sum , s ) => sum + s . hours . current , 0 ) ;
361386 const totalTotal = sections . reduce ( ( sum , s ) => sum + s . hours . total , 0 ) ;
362387 const totalProgressUnit = getSharedProgressUnit (
@@ -365,57 +390,32 @@ export const UnifiedDegreeCard = ({
365390 const greenColor = CATEGORY_COLORS [ 5 ] ; // green
366391
367392 return (
368- < div
369- className = "w-full bg-background rounded-md border border-gray-200 overflow-hidden border-l-4"
370- style = { { borderLeftColor : greenColor . tailwind } }
393+ < CollapsibleProgressCard
394+ title = { degreeTitle }
395+ current = { totalCurrent }
396+ total = { totalTotal }
397+ unit = { totalProgressUnit }
398+ colorIndex = { 5 }
371399 >
372- { /* Header */ }
373- < button
374- className = "w-full p-4 flex items-center justify-between hover:bg-hover-bg transition-colors bg-background"
375- onClick = { ( ) => setIsOpen ( ! isOpen ) }
376- >
377- < VStack gap = { 2 } >
378- < span className = "font-bold text-base text-text" > { degreeTitle } </ span >
379- < ProgressBar
380- current = { totalCurrent }
381- total = { totalTotal }
382- colorIndex = { 5 }
383- />
384- </ VStack >
385- < HStack y = "middle" gap = { 2 } >
386- < span className = "text-text font-medium text-sm" >
387- { formatProgressSummary ( totalCurrent , totalTotal , totalProgressUnit ) }
388- </ span >
389- { isOpen ? (
390- < CaretUpIcon className = "w-5 h-5 text-text" weight = "bold" />
391- ) : (
392- < CaretDownIcon className = "w-5 h-5 text-text" weight = "bold" />
393- ) }
394- </ HStack >
395- </ button >
396-
397- { /* Expanded: sections with green labels */ }
398- { isOpen && (
399- < div className = "bg-background px-4 pt-4 pb-4" >
400- { sections . map ( ( section , idx ) => (
401- < div key = { section . title || idx } className = { idx > 0 ? "mt-4" : "" } >
402- < span
403- className = "text-sm font-semibold mb-4 block"
404- style = { { color : greenColor . tailwind } }
405- >
406- { section . title }
407- </ span >
408- { section . requirements . map ( ( requirement , rIdx ) => (
409- < RequirementRow
410- key = { `${ requirement . text . slice ( 0 , 20 ) } -${ rIdx } ` }
411- requirement = { requirement }
412- requirementTitle = { section . title }
413- />
414- ) ) }
415- </ div >
416- ) ) }
417- </ div >
418- ) }
419- </ div >
400+ < div className = "bg-background px-4 pt-4 pb-4" >
401+ { sections . map ( ( section , idx ) => (
402+ < div key = { section . title || idx } className = { idx > 0 ? "mt-4" : "" } >
403+ < span
404+ className = "text-sm font-semibold mb-4 block"
405+ style = { { color : greenColor . tailwind } }
406+ >
407+ { section . title }
408+ </ span >
409+ { section . requirements . map ( ( requirement , rIdx ) => (
410+ < RequirementRow
411+ key = { `${ requirement . text . slice ( 0 , 20 ) } -${ rIdx } ` }
412+ requirement = { requirement }
413+ requirementTitle = { section . title }
414+ />
415+ ) ) }
416+ </ div >
417+ ) ) }
418+ </ div >
419+ </ CollapsibleProgressCard >
420420 ) ;
421421} ;
0 commit comments