Conversation
bbarni2020
commented
Dec 29, 2025
There was a problem hiding this comment.
Pull request overview
This PR adds a visual progress bar component to the project details page that displays the current status of a project through a multi-step progress indicator. The component shows different visualizations for rejected projects versus active projects progressing through various stages.
Key changes:
- Added project metadata display (creation date, update date, and time spent)
- Implemented a multi-step progress bar showing project status through 5 stages: Submitted → On print queue → Being printed → Printed → Payout
- Added special visualization for rejected projects with a red-themed progress bar
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <div class="h-2 bg-gradient-to-r from-primary-400 to-primary-600 transition-all duration-300" style="width: calc((({[ | ||
| 'submitted', | ||
| 't1_approved', | ||
| 'printing', | ||
| 'printed', | ||
| 'finalized' | ||
| ].indexOf(data.project.status)}) / 4) * 100%)"></div> | ||
| </div> | ||
| {#each [ | ||
| { key: 'submitted', label: 'Submitted', color: 'bg-indigo-400' }, | ||
| { key: 't1_approved', label: 'On print queue', color: 'bg-yellow-400' }, | ||
| { key: 'printing', label: 'Being printed', color: 'bg-emerald-400' }, | ||
| { key: 'printed', label: 'Printed', color: 'bg-blue-400' }, | ||
| { key: 'finalized', label: 'Payout', color: 'bg-green-200' } | ||
| ] as step, i (step.key)} | ||
| <div class="flex-1 flex flex-col items-center min-w-0 relative z-10" style="position: absolute; left: calc(5% + (90% * {i} / 4)); top: 50%; transform: translate(-50%, -50%);"> | ||
| <div | ||
| class="w-7 h-7 rounded-full border-4 shadow-md transition-all duration-300" | ||
| class:bg-primary-500={[ | ||
| 'submitted', | ||
| 't1_approved', | ||
| 'printing', | ||
| 'printed', | ||
| 'finalized' | ||
| ].indexOf(data.project.status) >= i} | ||
| class:bg-gray-200={[ | ||
| 'submitted', | ||
| 't1_approved', | ||
| 'printing', | ||
| 'printed', | ||
| 'finalized' | ||
| ].indexOf(data.project.status) < i} | ||
| class:border-primary-500={[ | ||
| 'submitted', | ||
| 't1_approved', | ||
| 'printing', | ||
| 'printed', | ||
| 'finalized' | ||
| ].indexOf(data.project.status) >= i} | ||
| class:border-gray-300={[ | ||
| 'submitted', | ||
| 't1_approved', | ||
| 'printing', | ||
| 'printed', | ||
| 'finalized' | ||
| ].indexOf(data.project.status) < i} | ||
| class:{step.color}={true} | ||
| style="box-shadow: 0 2px 8px 0 rgba(0,0,0,0.07);" | ||
| ></div> | ||
| </div> | ||
| {/each} | ||
| </div> | ||
| <div class="relative w-full mt-3" style="height: 22px;"> | ||
| {#each [ | ||
| { key: 'submitted', label: 'Submitted' }, | ||
| { key: 't1_approved', label: 'On print queue' }, | ||
| { key: 'printing', label: 'Being printed' }, | ||
| { key: 'printed', label: 'Printed' }, | ||
| { key: 'finalized', label: 'Payout' } | ||
| ] as step, i (step.key)} | ||
| <div class="absolute w-max max-w-[20%] text-center left-0" style="left: calc(5% + (90% * {i} / 4)); transform: translate(-50%, 0);"> | ||
| <span class="text-xs font-semibold whitespace-nowrap" | ||
| class:text-primary-700={[ | ||
| 'submitted', | ||
| 't1_approved', | ||
| 'printing', | ||
| 'printed', | ||
| 'finalized' | ||
| ].indexOf(data.project.status) >= i} | ||
| class:text-gray-400={[ | ||
| 'submitted', | ||
| 't1_approved', | ||
| 'printing', | ||
| 'printed', | ||
| 'finalized' | ||
| ].indexOf(data.project.status) < i} |
There was a problem hiding this comment.
The same array ['submitted', 't1_approved', 'printing', 'printed', 'finalized'] is repeated multiple times throughout the code. This creates significant code duplication and makes maintenance difficult. Consider extracting this array into a constant at the component level to improve maintainability and reduce the risk of inconsistencies.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| <div class="absolute left-0 top-0 h-4 rounded-full bg-gradient-to-r from-red-500 to-red-400 animate-pulse" style="width: 22%"></div> | ||
| </div> | ||
| <span class="ml-2 text-red-700 font-bold text-base tracking-wide flex items-center gap-1 whitespace-nowrap"> | ||
| <svg width="18" height="18" fill="none" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="#f87171"/><path d="M12 7v5m0 4h.01" stroke="#fff" stroke-width="2" stroke-linecap="round"/></svg> |
There was a problem hiding this comment.
The rejected status icon SVG is missing accessible text for screen readers. Consider adding an aria-label or title element to describe the icon's meaning for users who rely on assistive technologies.
| <svg width="18" height="18" fill="none" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="#f87171"/><path d="M12 7v5m0 4h.01" stroke="#fff" stroke-width="2" stroke-linecap="round"/></svg> | |
| <svg width="18" height="18" fill="none" viewBox="0 0 24 24" role="img" aria-label="Rejected status"><circle cx="12" cy="12" r="10" fill="#f87171"/><path d="M12 7v5m0 4h.01" stroke="#fff" stroke-width="2" stroke-linecap="round"/></svg> |
| {#if data.project.status === 'rejected' || data.project.status === 'rejected_locked'} | ||
| <div class="flex items-center gap-3 w-full"> | ||
| <div class="flex-1 h-4 rounded-full bg-gradient-to-r from-red-200 to-red-100 relative overflow-hidden w-full"> | ||
| <div class="absolute left-0 top-0 h-4 rounded-full bg-gradient-to-r from-red-500 to-red-400 animate-pulse" style="width: 22%"></div> |
There was a problem hiding this comment.
The rejected status progress bar shows a hardcoded width of 22% which appears to be an arbitrary magic number. This value should either be documented with a comment explaining its significance or extracted as a named constant to improve code clarity.
| { key: 'submitted', label: 'Submitted', color: 'bg-indigo-400' }, | ||
| { key: 't1_approved', label: 'On print queue', color: 'bg-yellow-400' }, | ||
| { key: 'printing', label: 'Being printed', color: 'bg-emerald-400' }, | ||
| { key: 'printed', label: 'Printed', color: 'bg-blue-400' }, | ||
| { key: 'finalized', label: 'Payout', color: 'bg-green-200' } | ||
| ] as step, i (step.key)} | ||
| <div class="flex-1 flex flex-col items-center min-w-0 relative z-10" style="position: absolute; left: calc(5% + (90% * {i} / 4)); top: 50%; transform: translate(-50%, -50%);"> | ||
| <div | ||
| class="w-7 h-7 rounded-full border-4 shadow-md transition-all duration-300" | ||
| class:bg-primary-500={[ | ||
| 'submitted', | ||
| 't1_approved', | ||
| 'printing', | ||
| 'printed', | ||
| 'finalized' | ||
| ].indexOf(data.project.status) >= i} | ||
| class:bg-gray-200={[ | ||
| 'submitted', | ||
| 't1_approved', | ||
| 'printing', | ||
| 'printed', | ||
| 'finalized' | ||
| ].indexOf(data.project.status) < i} | ||
| class:border-primary-500={[ | ||
| 'submitted', | ||
| 't1_approved', | ||
| 'printing', | ||
| 'printed', | ||
| 'finalized' | ||
| ].indexOf(data.project.status) >= i} | ||
| class:border-gray-300={[ | ||
| 'submitted', | ||
| 't1_approved', | ||
| 'printing', | ||
| 'printed', | ||
| 'finalized' | ||
| ].indexOf(data.project.status) < i} | ||
| class:{step.color}={true} |
There was a problem hiding this comment.
The step color property defined in the loop (line 86-90) is not being used effectively. Line 123 sets the color with class:{step.color}={true}, but since the circles are already conditionally styled with bg-primary-500 or bg-gray-200 based on the status progression, the step.color classes (bg-indigo-400, bg-yellow-400, etc.) will be overridden and have no visible effect. This creates confusion about the intended styling.
| <div class="h-2 bg-gradient-to-r from-primary-400 to-primary-600 transition-all duration-300" style="width: calc((({[ | ||
| 'submitted', | ||
| 't1_approved', | ||
| 'printing', | ||
| 'printed', | ||
| 'finalized' | ||
| ].indexOf(data.project.status)}) / 4) * 100%)"></div> |
There was a problem hiding this comment.
The progress bar width calculation uses a hardcoded divisor of 4, which assumes there are always 5 steps (0-indexed). If the status list changes in the future, this calculation will break. Consider using a dynamic calculation based on the actual array length minus 1, such as (index / (steps.length - 1)) * 100%.
| { key: 'printed', label: 'Printed', color: 'bg-blue-400' }, | ||
| { key: 'finalized', label: 'Payout', color: 'bg-green-200' } | ||
| ] as step, i (step.key)} | ||
| <div class="flex-1 flex flex-col items-center min-w-0 relative z-10" style="position: absolute; left: calc(5% + (90% * {i} / 4)); top: 50%; transform: translate(-50%, -50%);"> |
There was a problem hiding this comment.
The hardcoded divisor of 4 in the positioning calculation assumes there are always 5 steps. This creates the same maintainability issue as the progress bar width calculation. If steps are added or removed, the positioning will be incorrect.
| { key: 'printed', label: 'Printed' }, | ||
| { key: 'finalized', label: 'Payout' } | ||
| ] as step, i (step.key)} | ||
| <div class="absolute w-max max-w-[20%] text-center left-0" style="left: calc(5% + (90% * {i} / 4)); transform: translate(-50%, 0);"> |
There was a problem hiding this comment.
The same hardcoded divisor of 4 is used here for label positioning, which will break if the number of steps changes. This should be calculated dynamically based on the array length.
| <div class="mb-8 w-full px-0 sm:px-2 md:px-4 lg:px-8 xl:px-16 py-6"> | ||
| {#key data.project.status} | ||
| {#if data.project.status === 'rejected' || data.project.status === 'rejected_locked'} | ||
| <div class="flex items-center gap-3 w-full"> | ||
| <div class="flex-1 h-4 rounded-full bg-gradient-to-r from-red-200 to-red-100 relative overflow-hidden w-full"> | ||
| <div class="absolute left-0 top-0 h-4 rounded-full bg-gradient-to-r from-red-500 to-red-400 animate-pulse" style="width: 22%"></div> | ||
| </div> | ||
| <span class="ml-2 text-red-700 font-bold text-base tracking-wide flex items-center gap-1 whitespace-nowrap"> | ||
| <svg width="18" height="18" fill="none" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="#f87171"/><path d="M12 7v5m0 4h.01" stroke="#fff" stroke-width="2" stroke-linecap="round"/></svg> | ||
| Rejected | ||
| </span> | ||
| </div> | ||
| {:else} | ||
| <div class="w-full flex flex-col items-stretch"> | ||
| <div class="relative w-full flex items-center" style="height: 44px;"> | ||
| <div class="absolute z-0 top-1/2 -translate-y-1/2 left-[5%] right-[5%] h-2 bg-gray-200 rounded-full overflow-hidden"> | ||
| <div class="h-2 bg-gradient-to-r from-primary-400 to-primary-600 transition-all duration-300" style="width: calc((({[ | ||
| 'submitted', | ||
| 't1_approved', | ||
| 'printing', | ||
| 'printed', | ||
| 'finalized' | ||
| ].indexOf(data.project.status)}) / 4) * 100%)"></div> | ||
| </div> | ||
| {#each [ | ||
| { key: 'submitted', label: 'Submitted', color: 'bg-indigo-400' }, | ||
| { key: 't1_approved', label: 'On print queue', color: 'bg-yellow-400' }, | ||
| { key: 'printing', label: 'Being printed', color: 'bg-emerald-400' }, | ||
| { key: 'printed', label: 'Printed', color: 'bg-blue-400' }, | ||
| { key: 'finalized', label: 'Payout', color: 'bg-green-200' } | ||
| ] as step, i (step.key)} | ||
| <div class="flex-1 flex flex-col items-center min-w-0 relative z-10" style="position: absolute; left: calc(5% + (90% * {i} / 4)); top: 50%; transform: translate(-50%, -50%);"> | ||
| <div | ||
| class="w-7 h-7 rounded-full border-4 shadow-md transition-all duration-300" | ||
| class:bg-primary-500={[ | ||
| 'submitted', | ||
| 't1_approved', | ||
| 'printing', | ||
| 'printed', | ||
| 'finalized' | ||
| ].indexOf(data.project.status) >= i} | ||
| class:bg-gray-200={[ | ||
| 'submitted', | ||
| 't1_approved', | ||
| 'printing', | ||
| 'printed', | ||
| 'finalized' | ||
| ].indexOf(data.project.status) < i} | ||
| class:border-primary-500={[ | ||
| 'submitted', | ||
| 't1_approved', | ||
| 'printing', | ||
| 'printed', | ||
| 'finalized' | ||
| ].indexOf(data.project.status) >= i} | ||
| class:border-gray-300={[ | ||
| 'submitted', | ||
| 't1_approved', | ||
| 'printing', | ||
| 'printed', | ||
| 'finalized' | ||
| ].indexOf(data.project.status) < i} | ||
| class:{step.color}={true} | ||
| style="box-shadow: 0 2px 8px 0 rgba(0,0,0,0.07);" | ||
| ></div> | ||
| </div> | ||
| {/each} | ||
| </div> | ||
| <div class="relative w-full mt-3" style="height: 22px;"> | ||
| {#each [ | ||
| { key: 'submitted', label: 'Submitted' }, | ||
| { key: 't1_approved', label: 'On print queue' }, | ||
| { key: 'printing', label: 'Being printed' }, | ||
| { key: 'printed', label: 'Printed' }, | ||
| { key: 'finalized', label: 'Payout' } | ||
| ] as step, i (step.key)} | ||
| <div class="absolute w-max max-w-[20%] text-center left-0" style="left: calc(5% + (90% * {i} / 4)); transform: translate(-50%, 0);"> | ||
| <span class="text-xs font-semibold whitespace-nowrap" | ||
| class:text-primary-700={[ | ||
| 'submitted', | ||
| 't1_approved', | ||
| 'printing', | ||
| 'printed', | ||
| 'finalized' | ||
| ].indexOf(data.project.status) >= i} | ||
| class:text-gray-400={[ | ||
| 'submitted', | ||
| 't1_approved', | ||
| 'printing', | ||
| 'printed', | ||
| 'finalized' | ||
| ].indexOf(data.project.status) < i} | ||
| >{step.label}</span> | ||
| </div> | ||
| {/each} | ||
| </div> | ||
| </div> | ||
| {/if} | ||
| {/key} | ||
| </div> |
There was a problem hiding this comment.
The progress indicator circles lack proper ARIA attributes for screen reader users. Consider adding role="progressbar", aria-valuenow, aria-valuemin, aria-valuemax, and aria-label attributes to make the progress bar accessible to users with disabilities.
| <div class="h-2 bg-gradient-to-r from-primary-400 to-primary-600 transition-all duration-300" style="width: calc((({[ | ||
| 'submitted', | ||
| 't1_approved', | ||
| 'printing', | ||
| 'printed', | ||
| 'finalized' | ||
| ].indexOf(data.project.status)}) / 4) * 100%)"></div> |
There was a problem hiding this comment.
The progress calculation using indexOf() will return -1 if the project status is not in the expected list, resulting in a negative width value. While this may not crash the UI, it could lead to unexpected visual behavior. Consider adding validation to handle unexpected status values gracefully.