Improve analysis UX: algorithm explanations, skeleton loading, auto-expand achieved specs

- Replace terse one-line optimization mode descriptions with clearer multi-sentence
  explanations of how Maximize Count and Priority Order algorithms behave
- Add skeleton loading placeholders on course buttons while analysis is pending
- Auto-expand achieved specializations to show credit breakdown by default
- Add instructional subtitles to Course Selection and Specializations sections
- Make Clear and Clear All buttons more prominent with visible backgrounds
This commit is contained in:
2026-02-28 21:56:06 -05:00
parent 6af24d9270
commit 969d4ff5a9
10 changed files with 201 additions and 17 deletions

View File

@@ -64,19 +64,17 @@ function ElectiveSet({
{!isPinned && hasHighImpact && (
<span style={{ fontSize: '11px', color: '#d97706', marginLeft: '8px', fontWeight: 400 }}>high impact</span>
)}
{!isPinned && loading && !analysis && (
<span style={{ fontSize: '11px', color: '#888', marginLeft: '8px', fontWeight: 400 }}>analyzing...</span>
)}
</h4>
{isPinned && (
<button
onClick={onUnpin}
style={{
fontSize: '11px', border: 'none', background: 'none',
color: '#3b82f6', cursor: 'pointer', padding: '2px 4px',
fontSize: '11px', border: '1px solid #bfdbfe', background: '#eff6ff',
color: '#2563eb', cursor: 'pointer', padding: '3px 10px',
borderRadius: '4px', fontWeight: 500,
}}
>
clear
Clear
</button>
)}
</div>
@@ -89,6 +87,7 @@ function ElectiveSet({
{courses.map((course) => {
const ceiling = ceilingMap.get(course.id);
const reqFor = requiredForSpec[course.id];
const showSkeleton = loading && !analysis;
return (
<button
key={course.id}
@@ -102,7 +101,19 @@ function ElectiveSet({
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '8px' }}>
<span style={{ flex: 1 }}>{course.name}</span>
{ceiling && (
{showSkeleton ? (
<span
style={{
display: 'inline-block',
width: '60px',
height: '14px',
borderRadius: '3px',
background: 'linear-gradient(90deg, #e5e7eb 25%, #f0f0f0 50%, #e5e7eb 75%)',
backgroundSize: '200% 100%',
animation: 'skeleton-pulse 1.5s ease-in-out infinite',
}}
/>
) : ceiling ? (
<span style={{
fontSize: '11px', whiteSpace: 'nowrap', fontWeight: 600,
color: ceiling.ceilingCount >= 3 ? '#16a34a' : ceiling.ceilingCount >= 2 ? '#2563eb' : '#666',
@@ -114,7 +125,7 @@ function ElectiveSet({
</span>
)}
</span>
)}
) : null}
</div>
{reqFor && (
<span style={{ fontSize: '11px', color: '#92400e', marginTop: '2px' }}>
@@ -130,6 +141,8 @@ function ElectiveSet({
);
}
const skeletonStyle = `@keyframes skeleton-pulse { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }`;
export function CourseSelection({ pinnedCourses, treeResults, treeLoading, onPin, onUnpin, onClearAll }: CourseSelectionProps) {
const terms: Term[] = ['Spring', 'Summer', 'Fall'];
const hasPinned = Object.keys(pinnedCourses).length > 0;
@@ -139,14 +152,19 @@ export function CourseSelection({ pinnedCourses, treeResults, treeLoading, onPin
return (
<div>
<style>{skeletonStyle}</style>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '12px' }}>
<h2 style={{ fontSize: '16px', margin: 0 }}>Course Selection</h2>
<div>
<h2 style={{ fontSize: '16px', margin: 0 }}>Course Selection</h2>
<p style={{ fontSize: '12px', color: '#888', margin: '2px 0 0' }}>Select one course per elective slot. Analysis shows how each choice affects your specializations.</p>
</div>
{hasPinned && (
<button
onClick={onClearAll}
style={{
fontSize: '12px', border: 'none', background: 'none',
color: '#ef4444', cursor: 'pointer', padding: '2px 6px',
fontSize: '12px', border: '1px solid #fecaca', background: '#fef2f2',
color: '#dc2626', cursor: 'pointer', padding: '3px 10px',
borderRadius: '4px', fontWeight: 500,
}}
>
Clear All