feat: implement profile settings and comprehensive mentor-intern project management system
This commit is contained in:
@@ -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]" />
|
||||
|
||||
Reference in New Issue
Block a user