feat: implement profile settings and comprehensive mentor-intern project management system

This commit is contained in:
Kemas M. Aryadary Rasyad
2026-07-10 10:01:25 +07:00
parent 88f457258d
commit 17e3928d35
30 changed files with 302 additions and 278 deletions
+2 -4
View File
@@ -12,19 +12,17 @@ export default function ForumTab({ project }: { project: Project }) {
<HeroProject title={project.project_name} description={project.description} />
{/* Loading skeleton */}
{/* {loading && (
{loading && (
<div className="flex flex-col gap-3 mt-5">
{[...Array(3)].map((_, i) => (
<div key={i} className="h-24 bg-gray-100 rounded-xl animate-pulse" />
))}
</div>
)} */}
)}
{/* Error */}
{error && <p className="text-red-500 text-sm mt-5">{error}</p>}
{/* Timeline + Task list — hanya tampil jika ada task */}
{/* {!loading && tasks.length === 0 ? ( */}
{tasks.length === 0 ? (
<p className="text-md text-font-shade mt-5">Belum ada tugas yang dapat dikerjakan.</p>
) : (
@@ -3,12 +3,16 @@ import { FileText, Info } from "lucide-react";
import TaskFormFile from "./components/TaskForm-file";
import TaskFormLink from "./components/TaskForm-link";
import { useTaskDetail } from "../../../hooks/useTasks";
import { useProjectDetail } from "../../../hooks/useProjects";
export default function TaskSubmission() {
const [searchParams] = useSearchParams();
const { slug, taskSlug } = useParams<{ slug: string; taskSlug: string }>();
const submissionType = (searchParams.get("type") ?? "file") as "file_upload" | "url_link";
const { task, loading, error } = useTaskDetail(taskSlug!, slug);
const { project } = useProjectDetail(slug!);
const isProjectActive = project?.status === "active";
const formatDeadline = (dateStr: string) =>
new Date(dateStr).toLocaleDateString("id-ID", { day: "numeric", month: "long", year: "numeric" });
@@ -53,6 +57,7 @@ export default function TaskSubmission() {
projectId={String(task.project.id)}
deadline={new Date(task.deadline_at)}
initialSubmission={task.my_submission}
isProjectActive={isProjectActive}
/>
) : (
<TaskFormLink
@@ -60,6 +65,7 @@ export default function TaskSubmission() {
projectId={String(task.project.id)}
deadline={new Date(task.deadline_at)}
initialSubmission={task.my_submission}
isProjectActive={isProjectActive}
/>
)}
@@ -7,7 +7,7 @@ const HomeHeader = () => {
useEffect(() => {
api.get("/auth-api/me")
.then((res) => {
setName(res.data.data.full_name || res.data.data.nama_depan);
setName(res.data.data.full_name || (res.data.data.nama_depan + " " + res.data.data.nama_belakang));
})
.catch((err) => {
console.error("Failed to fetch user:", err);
@@ -8,7 +8,7 @@ export default function ProjectTimeline({ tasks }: { tasks: InternTaskItem[] })
today.setHours(0, 0, 0, 0);
return (
<div className="">
<div className="flex items-center gap-1.5 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">
@@ -20,7 +20,6 @@ export default function ProjectTimeline({ tasks }: { tasks: InternTaskItem[] })
const active = isToday || isPast;
return (
<li key={task.id} className="relative mb-6 list-none">
{/* Vertical line ke bawah, kecuali item terakhir */}
{index < tasks.length - 1 && (
<div className="absolute -left-[1.0625rem] top-3 w-px h-full bg-[#ddd]" />
)}
@@ -30,10 +29,10 @@ export default function ProjectTimeline({ tasks }: { tasks: InternTaskItem[] })
active ? 'bg-red' : 'bg-gray-400'
} ${isToday ? 'shadow-md' : ''}`}
/>
<div className={`text-[11px] font-semibold ${isToday ? 'text-[#C0392B]' : 'text-[#888]'}`}>
<div className={`text-[11px] font-semibold ${isToday ? 'text-red' : 'text-font'}`}>
{isToday ? 'Hari ini' : formatDate(task.deadline_at)}
</div>
<div className="text-xs text-[#555] mt-px leading-[1.3]">
<div className="text-xs text-font mt-px leading-[1.3]">
{task.title}
</div>
</li>
@@ -10,6 +10,7 @@ interface SubmitStatusTableProps {
deadline?: Date;
onEdit: () => void;
onDelete: () => void;
disabled: boolean;
}
function getTimeEarly(submittedAt: Date, deadline: Date): string {
@@ -32,6 +33,7 @@ export default function SubmitStatusTable({
deadline,
onEdit,
onDelete,
disabled = false,
}: SubmitStatusTableProps) {
const [confirmDelete, setConfirmDelete] = useState(false);
@@ -86,13 +88,15 @@ export default function SubmitStatusTable({
<div className="flex gap-2">
<button
onClick={onEdit}
className="text-xs font-medium px-4 py-1.5 rounded-lg bg-white border border-gray-300 text-gray-700 hover:bg-gray-50 transition-colors"
disabled={disabled}
className="text-xs font-medium px-4 py-1.5 rounded-lg bg-white border border-gray-300 text-gray-700 hover:bg-gray-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-white"
>
Edit Submission
</button>
<button
onClick={() => setConfirmDelete(true)}
className="text-xs font-medium px-4 py-1.5 rounded-lg bg-white border border-gray-300 text-gray-700 hover:text-red-700 hover:bg-red-50 hover:border-red-200 transition-colors"
disabled={disabled}
className="text-xs font-medium px-4 py-1.5 rounded-lg bg-white border border-gray-300 text-gray-700 hover:text-red-700 hover:bg-red-50 hover:border-red-200 transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-white"
>
Delete Submission
</button>
@@ -11,9 +11,10 @@ interface TaskFormFileProps {
projectId?: string;
deadline?: Date;
initialSubmission?: TaskSubmissionData | null;
isProjectActive?: boolean;
}
export default function TaskFormFile({ taskId, projectId, deadline, initialSubmission }: TaskFormFileProps) {
export default function TaskFormFile({ taskId, projectId, deadline, initialSubmission, isProjectActive = true }: TaskFormFileProps) {
const { submission, loading, submitFile, updateFile, remove } = useSubmission(taskId, initialSubmission, projectId);
const [files, setFiles] = useState<File[]>([]);
const [isDragActive, setIsDragActive] = useState(false);
@@ -119,6 +120,7 @@ export default function TaskFormFile({ taskId, projectId, deadline, initialSubmi
deadline={deadline}
onEdit={() => setIsEditing(true)}
onDelete={handleDelete}
disabled={!isProjectActive}
/>
);
}
@@ -188,8 +190,8 @@ export default function TaskFormFile({ taskId, projectId, deadline, initialSubmi
</button>
)}
<button
type="submit" disabled={loading}
className="w-full bg-[#B30000] hover:bg-[#990000] disabled:opacity-60 text-white font-bold py-3 rounded-xl transition-colors shadow-md flex items-center justify-center gap-2 tracking-wide cursor-pointer"
type="submit" disabled={loading || !isProjectActive}
className="w-full bg-[#B30000] hover:bg-[#990000] disabled:opacity-60 text-white font-bold py-3 rounded-xl transition-colors shadow-md flex items-center justify-center gap-2 tracking-wide cursor-pointer disabled:cursor-not-allowed disabled:hover:bg-[#990000]"
>
{loading ? "Mengirim..." : isEditing ? "Update Submission" : "Save Changes"}
<LuSendHorizontal className="w-4 h-4 stroke-[2.5]" />
@@ -11,13 +11,15 @@ interface TaskFormLinkProps {
projectId?: string;
deadline?: Date;
initialSubmission?: TaskSubmissionData | null;
isProjectActive?: boolean;
}
export default function TaskFormLink({
taskId,
projectId,
deadline,
initialSubmission
initialSubmission,
isProjectActive = true,
}: TaskFormLinkProps) {
const { submission, loading, submitLink, updateLink, remove } = useSubmission(taskId, initialSubmission, projectId);
const [inputLink, setInputLink] = useState(initialSubmission?.url_link ?? "");
@@ -88,6 +90,7 @@ export default function TaskFormLink({
deadline={deadline}
onEdit={() => setIsEditing(true)}
onDelete={handleDelete}
disabled={!isProjectActive}
/>
);
}
@@ -115,8 +118,8 @@ export default function TaskFormLink({
</p>
</div>
<button
type="submit" disabled={loading}
className="w-full bg-[#B30000] hover:bg-[#990000] disabled:opacity-60 text-white font-bold py-3 rounded-xl transition-colors shadow-md flex items-center justify-center gap-2 tracking-wide cursor-pointer"
type="submit" disabled={loading || !isProjectActive}
className="w-full bg-[#B30000] hover:bg-[#990000] disabled:opacity-60 text-white font-bold py-3 rounded-xl transition-colors shadow-md flex items-center justify-center gap-2 tracking-wide cursor-pointer disabled:cursor-not-allowed disabled:hover:bg-[#990000]"
>
{loading ? "Mengirim..." : isEditing ? "Update Submission" : "Submit Task"}
<LuSendHorizontal className="w-4 h-4 stroke-[2.5]" />