feat: enhance certificate management and project task features

- Updated CertificateForm to improve loading state UI with skeletons.
- Enhanced CertificateResult with better loading indicators and structure.
- Refactored ProjectForumTab to include a skeleton loader and improved error handling.
- Introduced ProjectTaskTab for managing project tasks with modals for creating and editing tasks.
- Improved ProjectsPage with a skeleton loader during project data fetching.
- Refined AddInternsToProjects, ArchiveProjectDialog, CompleteProjectDialog, CreateProjectDialog, CreateTaskDialog, and EditTaskDialog components to handle modal states more effectively.
- Updated EligibleInternTable to enhance loading states and formatting of dates.
- Modified StatusSubmissionTable to simplify status display.
- Improved certificate generation utility functions for better date formatting and layout management.
- Removed deprecated certificate assets and added a new template for certificates.
- Updated types for certificates and projects to include start and end dates.
This commit is contained in:
Kemas M. Aryadary Rasyad
2026-07-15 11:42:22 +07:00
parent c2aacb505f
commit 5689898f33
36 changed files with 390 additions and 240 deletions
@@ -16,7 +16,7 @@ export default function SertificateHistory() {
{loading && (
<div className="flex items-center justify-center py-8 text-gray-400 gap-2">
<Loader2 className="w-5 h-5 animate-spin" />
<span className="text-sm">Loading certificates...</span>
<span className="text-sm">memuat sertifikat...</span>
</div>
)}
@@ -30,7 +30,7 @@ export default function TaskCard({ task }: { task: InternTaskItem }) {
: task.submission_status;
return (
<div
className="border border-card-outline rounded-xl px-6 py-4 mb-3 bg-white flex items-start justify-between gap-3 cursor-pointer hover:bg-gray-50 transition-colors"
className="border border-card-outline rounded-xl px-6 py-4 mb-3 bg-box-primary flex items-start justify-between gap-3 cursor-pointer hover:bg-gray-50 transition-colors"
onClick={() => navigate(`/intern/projects/${task.project_slug}/${task.slug}?type=${task.submission_type}`)}
>
<div className="flex-1 min-w-0">
@@ -8,7 +8,7 @@ export default function ProjectTimeline({ tasks }: { tasks: InternTaskItem[] })
today.setHours(0, 0, 0, 0);
return (
<div>
<div className="flex items-center gap-1.5 mb-0 md:mb-4 justify-between">
<div className="flex items-center gap-1.5 mb-1 md:mb-4 justify-between">
<span className="font-semibold text-md text-font-shade">Timeline</span>
</div>
<ul className="relative pl-4">
@@ -21,7 +21,7 @@ export default function ProjectTimeline({ tasks }: { tasks: InternTaskItem[] })
return (
<li key={task.id} className="relative mb-2 list-none">
{index < tasks.length - 1 && (
<div className="absolute -left-[1.0625rem] top-3 w-px h-full bg-[#ddd]" />
<div className="absolute -left-[1.0625rem] top-3 w-px h-full bg-font-shade/30" />
)}
{/* Dot */}
<div
@@ -43,7 +43,6 @@ export default function SubmitStatusTable({
const rows = [
{ label: "Submission status", value: "Submitted for grading" },
{ label: "Grading status", value: "Not graded" },
{ label: "Time Remaining", value: timeLabel },
];
@@ -98,7 +98,7 @@ export default function TaskFormLink({
return (
<form onSubmit={handleSubmit} className="space-y-5">
<div className="space-y-1.5">
<label className="text-[11px] font-bold text-font-shade tracking-wide block">
<label className="text-[15px] font-bold text-font-shade tracking-wide block">
{isEditing ? "Update Submission Link" : "Submission Link"}
</label>
<div className="relative flex items-center">
@@ -107,14 +107,14 @@ export default function TaskFormLink({
</span>
<input
type="url" required
placeholder="https://example.com/your-pdf-link"
placeholder="https://example.com/"
value={inputLink}
onChange={(e) => setInputLink(e.target.value)}
className="w-full pl-10 pr-4 py-2.5 bg-box-primary/50 border border-box-border rounded-lg text-sm placeholder-font text-font focus:outline-none focus:bg-white focus:border-red-600 focus:ring-1 focus:ring-red-600 font-medium"
/>
</div>
<p className="text-[11px] text-font font-medium pt-0.5">
Ensure the link is accessible to the mentor (e.g., Google Drive, Dropbox).
<p className="text-[13px] text-font font-medium pt-0.5">
Pastikan link yang disubmit bisa diakses oleh publik.
</p>
</div>
<button
@@ -22,7 +22,7 @@ const TaskTimeline = () => {
if (error) return <p className="text-red-500 text-sm">{error}</p>;
return (
<div className=''>
<div>
<h2 className="font-semibold text-font-shade mb-5">Timeline Tugas</h2>
{tasks.length === 0 ? (
@@ -31,7 +31,7 @@ const TaskTimeline = () => {
<p className="text-sm text-gray-400">Tidak ada tugas saat ini</p>
</div>
) : (
<div className="relative overflow-y-auto max-h-[190px]">
<div className="relative overflow-y-auto max-h-[140px] px-3 md:px-6">
<ul className="flex flex-col gap-5">
{tasks.map((item: InternTaskItem, index) => {
const overdue = isOverdue(item.deadline_at);
@@ -41,7 +41,7 @@ const TaskTimeline = () => {
return (
<li key={item.id} className="relative flex gap-4 pl-0">
{!isLast && (
<div className="absolute left-[3px] top-3 w-px h-full bg-[#ddd]" />
<div className="absolute left-[3px] top-3 w-px h-3/2 bg-font-shade/30" />
)}
<div className="relative flex-shrink-0 mt-1">