fileInputRef.current?.click()}
className={`w-full min-h-[120px] border border-dashed rounded-xl p-4 bg-box-secondary cursor-pointer transition-all ${isDragActive ? "border-[#B30000] bg-red-50/40" : "border-foreground hover:border-gray-500"
}`}
@@ -126,20 +98,12 @@ if (isSubmitted) {
{files.length > 0 ? (
{files.map((file, index) => (
-
e.stopPropagation()}
- >
+
e.stopPropagation()}>
{file.name}
-
@@ -148,19 +112,16 @@ if (isSubmitted) {
) : (
-
- Click to upload or drag & drop
-
+
Click to upload or drag & drop
PDF, DOCX, or ZIP (Max. 10MB)
)}
-
- Save Changes
+ {loading ? "Mengirim..." : isEditing ? "Update Submission" : "Save Changes"}
diff --git a/src/Pages/internify/intern/components/TaskForm-link.tsx b/src/Pages/internify/intern/components/TaskForm-link.tsx
index 478133f..618682d 100644
--- a/src/Pages/internify/intern/components/TaskForm-link.tsx
+++ b/src/Pages/internify/intern/components/TaskForm-link.tsx
@@ -1,80 +1,76 @@
import { useState } from "react";
-import { Link, FileText } from "lucide-react";
+import { Link } from "lucide-react";
import { LuSendHorizontal } from "react-icons/lu";
import SubmitStatusTable from "./SubmissionStatusTable";
+import { useSubmission } from "../../../../hooks/useTasks";
+import type { TaskSubmissionData } from "../../../../types/task.types";
interface TaskFormLinkProps {
- onSubmitSuccess: (link: string) => void;
+ taskId: string;
deadline?: Date;
+ initialSubmission?: TaskSubmissionData | null;
}
-export default function TaskFormLink({ onSubmitSuccess, deadline }: TaskFormLinkProps) {
- const [submissionLink, setSubmissionLink] = useState("");
- const [isSubmitted, setIsSubmitted] = useState(false);
+export default function TaskFormLink({ taskId, deadline, initialSubmission }: TaskFormLinkProps) {
+ const { submission, loading, submitLink, updateLink, remove } = useSubmission(taskId, initialSubmission);
+ const [inputLink, setInputLink] = useState(initialSubmission?.url_link ?? "");
+ const [isEditing, setIsEditing] = useState(false);
- const handleSubmit = (e: React.FormEvent) => {
+ const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
- if (!submissionLink.trim()) {
- // alert("Silakan masukkan link tugas Anda terlebih dahulu.");
- return;
- }
- setIsSubmitted(true); // ini hilang
- onSubmitSuccess(submissionLink);
+ if (!inputLink.trim()) return;
+ const res = isEditing
+ ? await updateLink(submission!.id, inputLink)
+ : await submitLink(inputLink);
+ if (res) { setIsEditing(false); }
};
- const handleEditSubmission = () => {
- setIsSubmitted(false);
- };
+ const handleDelete = async () => {
+ if (!confirm("Yakin ingin menghapus submission ini?")) return;
+ const ok = await remove(submission!.id);
+ if (ok) setInputLink("");
+};
- const handleDeleteSubmission = () => {
- if (confirm("Are you sure you want to delete this submission?")) {
- setSubmissionLink("");
- setIsSubmitted(false);
- }
- };
-
- if (isSubmitted) {
- return (
-
- );
-}
+ if (submission && !isEditing) {
+ return (
+
setIsEditing(true)}
+ onDelete={handleDelete}
+ />
+ );
+ }
return (
diff --git a/src/Pages/internify/intern/components/TaskTimeline.tsx b/src/Pages/internify/intern/components/TaskTimeline.tsx
index 446b50e..5206e25 100644
--- a/src/Pages/internify/intern/components/TaskTimeline.tsx
+++ b/src/Pages/internify/intern/components/TaskTimeline.tsx
@@ -1,8 +1,8 @@
-import { useInternTasks } from "../../../../hooks/useProjects";
-import type { InternTask } from "../../../../types/project.types";
+import { useMyTasks } from "../../../../hooks/useProjects";
+import type { InternTaskItem } from "../../../../types/project.types";
const TaskTimeline = () => {
- const { tasks, loading, error } = useInternTasks();
+ const { tasks, loading, error } = useMyTasks();
const today = new Date();
const isOverdue = (dateStr: string) => new Date(dateStr) < today;
@@ -23,7 +23,7 @@ const TaskTimeline = () => {
- {tasks.map((item: InternTask) => {
+ {tasks.map((item: InternTaskItem) => {
const overdue = isOverdue(item.deadline_at);
const deadlineDate = new Date(item.deadline_at);
return (
diff --git a/src/Pages/internify/layout.tsx b/src/Pages/internify/layout.tsx
index 0379c1c..2c05d3e 100644
--- a/src/Pages/internify/layout.tsx
+++ b/src/Pages/internify/layout.tsx
@@ -17,7 +17,7 @@ export const InternLayout = () => {
{isSidebarOpen && (
)}
@@ -47,7 +47,7 @@ export const MentorLayout = () => {
/>
)}
-
+ {/* */}
diff --git a/src/hooks/useProjectDetail.ts b/src/hooks/useProjectDetail.ts
deleted file mode 100644
index 836a819..0000000
--- a/src/hooks/useProjectDetail.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-// hooks/useProjectDetail.ts
-/**
- * Bagian Detail dari Project: Ketika User sudah masuk ke dalam Project Card
- * Komponen yang masuk: IDProject, ParticipantTab, ForumTab, Submissions
- */
-import { useEffect, useState } from "react";
-import { getProjectById, getProjectInterns, getProjectTasks } from "../services/ProjectService";
-import type { Project, Intern, ProjectTask } from "../types/project.types";
-
-export const useProjectDetail = (id: string) => {
- const [project, setProject] = useState(null);
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState(null);
-
- useEffect(() => {
- if (!id) return;
- setLoading(true);
- getProjectById(id)
- .then(setProject)
- .catch(() => setError("Gagal memuat detail project."))
- .finally(() => setLoading(false));
- }, [id]);
-
- return { project, loading, error };
-};
-
-export const useProjectInterns = (projectId: string) => {
- const [interns, setInterns] = useState([]);
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState(null);
-
- useEffect(() => {
- if (!projectId) return;
- setLoading(true);
- getProjectInterns(projectId)
- .then(setInterns)
- .catch(() => setError("Gagal memuat participants."))
- .finally(() => setLoading(false));
- }, [projectId]);
-
- return { interns, loading, error };
-};
-
-export const useProjectTasks = (projectId: string) => {
- const [tasks, setTasks] = useState([]);
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState(null);
-
- useEffect(() => {
- if (!projectId) return;
- setLoading(true);
- getProjectTasks(projectId)
- .then(setTasks)
- .catch(() => setError("Gagal memuat tasks."))
- .finally(() => setLoading(false));
- }, [projectId]);
-
- return { tasks, loading, error };
-};
\ No newline at end of file
diff --git a/src/hooks/useProjects.ts b/src/hooks/useProjects.ts
index f3cec5e..5cfd188 100644
--- a/src/hooks/useProjects.ts
+++ b/src/hooks/useProjects.ts
@@ -1,34 +1,109 @@
-import { useEffect, useState } from "react";
-// Projects
-import { getMyProjects } from "../services/ProjectService";
-import type { Project } from "../types/project.types";
-// Tasks
-import { getMyTasks } from "../services/ProjectService";
-import type { InternTask } from "../types/project.types";
+// hooks/useProject.ts
+import { useState, useEffect, useCallback } from "react";
+import type {
+ Project,
+ ProjectDetail,
+ InternTaskItem,
+ InternDetail,
+ CreateProjectPayload,
+ UpdateProjectPayload,
+ AssignMemberPayload,
+ ProjectMember
+} from "../types/project.types";
+import {
+ getProjects,
+ getProjectById,
+ getMyProjects,
+ getMyTasks,
+ getMentorProjects,
+ getAllInterns,
+ createProject,
+ updateProject,
+ archiveProject,
+ assignMember,
+ getProjectMembers
+} from "../services/ProjectService";
-export const useProjects = () => {
+// GET /project-api/get
+export const useProjects = (status?: "active" | "completed" | "archived") => {
const [projects, setProjects] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const refetch = useCallback(() => {
+ setLoading(true);
+ getProjects(status)
+ .then(setProjects)
+ .catch(() => setError("Gagal memuat projects."))
+ .finally(() => setLoading(false));
+ }, [status]);
+
+ useEffect(() => { refetch(); }, [refetch]);
+
+ return { projects, loading, error, refetch };
+};
+
+// GET /project-api/get/{id}
+export const useProjectDetail = (id: string) => {
+ const [project, setProject] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const refetch = useCallback(() => {
+ if (!id) return;
+ setLoading(true);
+ getProjectById(id)
+ .then(setProject)
+ .catch(() => setError("Gagal memuat detail project."))
+ .finally(() => setLoading(false));
+ }, [id]);
+
+ useEffect(() => { refetch(); }, [refetch]);
+
+ return { project, loading, error, refetch };
+};
+
+//Khusus Ambil Intern dari Project/{id}
+export const useProjectInterns = (projectId: string) => {
+ const [interns, setInterns] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
+ if (!projectId) return;
setLoading(true);
+ getProjectMembers(projectId)
+ .then(setInterns)
+ .catch(() => setError("Gagal memuat participants."))
+ .finally(() => setLoading(false));
+ }, [projectId]);
+
+ return { interns, loading, error };
+};
+
+// GET /project-api/my-projects
+export const useMyProjects = () => {
+ const [projects, setProjects] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
getMyProjects()
.then(setProjects)
- .catch(() => setError("Gagal memuat project."))
+ .catch(() => setError("Gagal memuat my projects."))
.finally(() => setLoading(false));
}, []);
return { projects, loading, error };
};
-export const useInternTasks = () => {
- const [tasks, setTasks] = useState([]);
- const [loading, setLoading] = useState(false);
+// GET /project-api/my-tasks
+export const useMyTasks = () => {
+ const [tasks, setTasks] = useState([]);
+ const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
- setLoading(true);
getMyTasks()
.then(setTasks)
.catch(() => setError("Gagal memuat tasks."))
@@ -38,3 +113,118 @@ export const useInternTasks = () => {
return { tasks, loading, error };
};
+// GET /project-api/mentor-projects
+export const useMentorProjects = () => {
+ const [projects, setProjects] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ getMentorProjects()
+ .then(setProjects)
+ .catch(() => setError("Gagal memuat mentor projects."))
+ .finally(() => setLoading(false));
+ }, []);
+
+ return { projects, loading, error };
+};
+
+// GET /project-api/interns
+export const useAllInterns = () => {
+ const [interns, setInterns] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ getAllInterns()
+ .then(setInterns)
+ .catch(() => setError("Gagal memuat data interns."))
+ .finally(() => setLoading(false));
+ }, []);
+
+ return { interns, loading, error };
+};
+
+// POST /project-api/add
+export const useCreateProject = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const create = async (payload: CreateProjectPayload) => {
+ setLoading(true);
+ setError(null);
+ try {
+ return await createProject(payload);
+ } catch {
+ setError("Gagal membuat project.");
+ return null;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return { create, loading, error };
+};
+
+// PATCH /project-api/update/{id}
+export const useUpdateProject = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const update = async (id: string | number, payload: UpdateProjectPayload) => {
+ setLoading(true);
+ setError(null);
+ try {
+ return await updateProject(id, payload);
+ } catch {
+ setError("Gagal mengupdate project.");
+ return null;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return { update, loading, error };
+};
+
+// DELETE /project-api/delete/{id}
+export const useArchiveProject = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const archive = async (id: string | number) => {
+ setLoading(true);
+ setError(null);
+ try {
+ return await archiveProject(id);
+ } catch {
+ setError("Gagal mengarsipkan project.");
+ return null;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return { archive, loading, error };
+};
+
+// POST /project-api/assign-member
+export const useAssignMember = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const assign = async (payload: AssignMemberPayload) => {
+ setLoading(true);
+ setError(null);
+ try {
+ return await assignMember(payload);
+ } catch {
+ setError("Gagal assign member.");
+ return null;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return { assign, loading, error };
+};
\ No newline at end of file
diff --git a/src/hooks/useTasks.ts b/src/hooks/useTasks.ts
new file mode 100644
index 0000000..49cc841
--- /dev/null
+++ b/src/hooks/useTasks.ts
@@ -0,0 +1,180 @@
+// hooks/useTasks.ts
+import { useState, useEffect, useCallback } from "react";
+import type { ProjectTask, ProjectTaskDetail, CreateTaskPayload, UpdateTaskPayload, TaskSubmissionData } from "../types/task.types";
+import {
+ getProjectTasks,
+ getTaskById,
+ createTask,
+ updateTask,
+ deleteTask,
+ submitTaskFile,
+ submitTaskLink,
+ updateSubmissionFile,
+ updateSubmissionLink,
+ deleteSubmission,
+} from "../services/TaskService";
+
+// GET /task-api/projects/{id_project}/tasks
+export const useProjectTasks = (projectId: string) => {
+ const [tasks, setTasks] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ if (!projectId) return;
+ setLoading(true);
+ getProjectTasks(projectId)
+ .then(setTasks)
+ .catch(() => setError("Gagal memuat task."))
+ .finally(() => setLoading(false));
+ }, [projectId]);
+
+ return { tasks, loading, error };
+};
+
+// GET /task-api/tasks/{id}
+export const useTaskDetail = (taskId: string, projectId?: string) => {
+ const [task, setTask] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const refetch = useCallback(() => {
+ if (!taskId) return;
+ setLoading(true);
+ getTaskById(taskId, projectId)
+ .then(setTask)
+ .catch(() => setError("Gagal memuat detail task."))
+ .finally(() => setLoading(false));
+ }, [taskId, projectId]);
+
+ useEffect(() => { refetch(); }, [refetch]);
+
+ return { task, loading, error, refetch };
+};
+
+// POST /task-api/projects/{id_project}/tasks
+export const useCreateTask = (projectId: string) => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const create = async (payload: CreateTaskPayload): Promise => {
+ setLoading(true);
+ setError(null);
+ try {
+ return await createTask(projectId, payload);
+ } catch {
+ setError("Gagal membuat task.");
+ return null;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return { create, loading, error };
+};
+
+// PATCH /task-api/tasks/{id}
+export const useUpdateTask = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const update = async (
+ taskId: string | number,
+ payload: UpdateTaskPayload,
+ projectId?: string
+ ) => {
+ setLoading(true);
+ setError(null);
+ try {
+ return await updateTask(taskId, payload, projectId);
+ } catch {
+ setError("Gagal mengupdate task.");
+ return null;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return { update, loading, error };
+};
+
+// DELETE /task-api/tasks/{id}
+export const useDeleteTask = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const remove = async (taskId: string | number, projectId?: string) => {
+ setLoading(true);
+ setError(null);
+ try {
+ await deleteTask(taskId, projectId);
+ return true;
+ } catch {
+ setError("Gagal menghapus task.");
+ return false;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return { remove, loading, error };
+};
+
+// POST + PATCH + DELETE submission — digabung dalam satu hook per task
+export const useSubmission = (taskId: string, initialSubmission?: TaskSubmissionData | null, projectId?: string) => {
+ const [submission, setSubmission] = useState(initialSubmission ?? null);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const submitFile = async (file: File) => {
+ setLoading(true); setError(null);
+ try {
+ const res = await submitTaskFile(taskId, file, projectId);
+ if (res) setSubmission(res);
+ return res;
+ } catch { setError("Gagal submit file."); return null; }
+ finally { setLoading(false); }
+ };
+
+ const submitLink = async (url: string) => {
+ setLoading(true); setError(null);
+ try {
+ const res = await submitTaskLink(taskId, url, projectId);
+ if (res) setSubmission(res);
+ return res;
+ } catch { setError("Gagal submit link."); return null; }
+ finally { setLoading(false); }
+ };
+
+ const updateFile = async (submissionId: number, file: File) => {
+ setLoading(true); setError(null);
+ try {
+ const res = await updateSubmissionFile(submissionId, file);
+ if (res) setSubmission(res);
+ return res;
+ } catch { setError("Gagal update submission."); return null; }
+ finally { setLoading(false); }
+ };
+
+ const updateLink = async (submissionId: number, url: string) => {
+ setLoading(true); setError(null);
+ try {
+ const res = await updateSubmissionLink(submissionId, url);
+ if (res) setSubmission(res);
+ return res;
+ } catch { setError("Gagal update submission."); return null; }
+ finally { setLoading(false); }
+ };
+
+ const remove = async (submissionId: number) => {
+ setLoading(true); setError(null);
+ try {
+ await deleteSubmission(submissionId);
+ setSubmission(null);
+ return true;
+ } catch { setError("Gagal hapus submission."); return false; }
+ finally { setLoading(false); }
+ };
+
+ return { submission, submitFile, submitLink, updateFile, updateLink, remove, loading, error };
+};
\ No newline at end of file
diff --git a/src/index.css b/src/index.css
index ae5add1..a6d3e55 100644
--- a/src/index.css
+++ b/src/index.css
@@ -31,8 +31,8 @@
--color-primary-foreground: #ffffff;
--color-primary-hover: #84281c;
- --color-base: #e82222;
- --color-base-foreground: #fef2f2;
+ --color-red: #e82222;
+ --color-red-foreground: #fef2f2;
--color-border: #a8a9aa;
@@ -43,14 +43,11 @@
}
.container {
- flex: 1;
- min-width: 0; /* penting! mencegah overflow */
+ min-width: 0;
padding-inline: calc(var(--spacing) * 14);
padding-block: calc(var(--spacing) * 12);
background-color: var(--color-background);
- display: flex;
flex-direction: column;
- gap: 13px;
min-height: 100vh;
}
@@ -130,6 +127,12 @@ dialog.custom-dialog::backdrop {
border-bottom: 2px solid #b3b8c0; /* border-gray-100 */
}
+@media (max-width: 1024px) {
+ .container {
+ padding-inline: calc(var(--spacing) * 16);
+ }
+}
+
/* Breakpoint Medium (md:) - Aktif saat lebar layar >= 768px */
@media (min-width: 768px) {
.dialog-header {
@@ -139,4 +142,9 @@ dialog.custom-dialog::backdrop {
padding-top: 1.25rem; /* 20px */
padding-bottom: 1.25rem;/* 20px */
}
+
+ .container {
+ padding-inline: calc(var(--spacing) * 14);
+ padding-block: calc(var(--spacing) * 10);
+ }
}
\ No newline at end of file
diff --git a/src/services/ProjectService.ts b/src/services/ProjectService.ts
index 6f025c9..3192758 100644
--- a/src/services/ProjectService.ts
+++ b/src/services/ProjectService.ts
@@ -1,29 +1,96 @@
+// services/ProjectService.ts
import api from "../lib/api";
-import type { Project, InternTask, Intern, ProjectTask } from "../types/project.types";
+import type {
+ Project,
+ ProjectDetail,
+ InternTaskItem,
+ InternDetail,
+ CreateProjectPayload,
+ UpdateProjectPayload,
+ AssignMemberPayload,
+ MemberAssignment,
+ ProjectMember
+} from "../types/project.types";
-export const getMyProjects = async (): Promise => {
- const res = await api.get("/project-api/my-projects");
+// POST /project-api/add
+export const createProject = async (
+ payload: CreateProjectPayload
+): Promise => {
+ const res = await api.post("/project-api/add", payload);
return res.data.data;
};
-export const getProjectById = async (id: string): Promise => {
- const res = await api.get(`/project-api/get/${id}`);
- return res.data.data;
-};
-
-export const getMyTasks = async (): Promise => {
- const res = await api.get("/project-api/my-tasks");
- return res.data.data;
-};
-
-export const getProjectInterns = async (projectId: string): Promise => {
- const res = await api.get(`/project-api/interns`, {
- params: { projectId }
+// GET /project-api/get
+export const getProjects = async (
+ status?: "active" | "completed" | "archived"
+): Promise => {
+ const res = await api.get("/project-api/get", {
+ params: status ? { status } : undefined,
});
return res.data.data;
};
-export const getProjectTasks = async (projectId: string): Promise => {
- const res = await api.get(`/task-api/projects/${projectId}/tasks`);
+// GET /project-api/get/{id} → ambil members saja
+export const getProjectMembers = async (
+ id: string | number
+): Promise => {
+ const res = await api.get(`/project-api/get/${id}`);
+ return res.data.data.members;
+};
+
+// GET /project-api/get/{id}
+export const getProjectById = async (
+ id: string | number
+): Promise => {
+ const res = await api.get(`/project-api/get/${id}`);
+ return res.data.data;
+};
+
+// PATCH /project-api/update/{id}
+export const updateProject = async (
+ id: string | number,
+ payload: UpdateProjectPayload
+): Promise => {
+ const res = await api.patch(`/project-api/update/${id}`, payload);
+ return res.data.data;
+};
+
+// DELETE /project-api/delete/{id} — soft delete (archive)
+export const archiveProject = async (
+ id: string | number
+): Promise => {
+ const res = await api.delete(`/project-api/delete/${id}`);
+ return res.data.data;
+};
+
+// GET /project-api/my-projects (intern only)
+export const getMyProjects = async (): Promise => {
+ const res = await api.get("/project-api/my-projects");
+ return res.data.data;
+};
+
+// GET /project-api/my-tasks (intern only)
+export const getMyTasks = async (): Promise => {
+ const res = await api.get("/project-api/my-tasks");
+ return res.data.data;
+};
+
+// GET /project-api/mentor-projects (admin/mentor only)
+export const getMentorProjects = async (): Promise => {
+ const res = await api.get("/project-api/mentor-projects");
+ return res.data.data;
+};
+
+// GET /project-api/interns (admin/mentor only)
+export const getAllInterns = async (): Promise => {
+ const res = await api.get("/project-api/interns");
+ return res.data.data;
+};
+
+// POST /project-api/assign-member (admin/mentor only)
+export const assignMember = async (
+ payload: AssignMemberPayload
+): Promise => {
+ const res = await api.post("/project-api/assign-member", payload);
return res.data.data;
};
\ No newline at end of file
diff --git a/src/services/TaskService.ts b/src/services/TaskService.ts
new file mode 100644
index 0000000..3d6b0c1
--- /dev/null
+++ b/src/services/TaskService.ts
@@ -0,0 +1,108 @@
+// services/TaskService.ts
+import api from "../lib/api";
+import type {
+ ProjectTask,
+ ProjectTaskDetail,
+ CreateTaskPayload,
+ UpdateTaskPayload,
+ TaskSubmissionData,
+} from "../types/task.types";
+
+// ── Task ──────────────────────────────────────────────
+
+export const createTask = async (
+ projectId: string | number,
+ payload: CreateTaskPayload
+): Promise => {
+ const res = await api.post(`/task-api/projects/${projectId}/tasks`, payload);
+ return res.data.data;
+};
+
+export const getProjectTasks = async (
+ projectId: string | number
+): Promise => {
+ const res = await api.get(`/task-api/projects/${projectId}/tasks`);
+ return res.data.data;
+};
+
+export const getTaskById = async (
+ taskId: string | number,
+ projectId?: string | number
+): Promise => {
+ const res = await api.get(`/task-api/tasks/${taskId}`, {
+ params: projectId ? { project: projectId } : undefined,
+ });
+ return res.data.data;
+};
+
+export const updateTask = async (
+ taskId: string | number,
+ payload: UpdateTaskPayload,
+ projectId?: string | number
+): Promise => {
+ const res = await api.patch(`/task-api/tasks/${taskId}`, payload, {
+ params: projectId ? { project: projectId } : undefined,
+ });
+ return res.data.data;
+};
+
+export const deleteTask = async (
+ taskId: string | number,
+ projectId?: string | number
+): Promise => {
+ await api.delete(`/task-api/tasks/${taskId}`, {
+ params: projectId ? { project: projectId } : undefined,
+ });
+};
+
+// ── Submission ────────────────────────────────────────
+
+export const submitTaskFile = async (
+ taskId: string | number,
+ file: File,
+ projectId?: string | number
+): Promise => {
+ const formData = new FormData();
+ formData.append("submission_file", file);
+ const res = await api.post(`/task-api/tasks/${taskId}/submissions`, formData, {
+ params: projectId ? { project: projectId } : undefined,
+ });
+ return res.data.data;
+};
+
+export const submitTaskLink = async (
+ taskId: string | number,
+ url_link: string,
+ projectId?: string | number
+): Promise => {
+ const formData = new FormData();
+ formData.append("url_link", url_link);
+ const res = await api.post(`/task-api/tasks/${taskId}/submissions`, formData, {
+ params: projectId ? { project: projectId } : undefined,
+ });
+ return res.data.data;
+};
+
+export const updateSubmissionFile = async (
+ submissionId: number,
+ file: File
+): Promise => {
+ const formData = new FormData();
+ formData.append("submission_file", file);
+ const res = await api.patch(`/task-api/submissions/${submissionId}`, formData);
+ return res.data.data;
+};
+
+export const updateSubmissionLink = async (
+ submissionId: number,
+ url_link: string
+): Promise => {
+ const formData = new FormData();
+ formData.append("url_link", url_link);
+ const res = await api.patch(`/task-api/submissions/${submissionId}`, formData);
+ return res.data.data;
+};
+
+export const deleteSubmission = async (submissionId: number): Promise => {
+ await api.delete(`/task-api/submissions/${submissionId}`);
+};
\ No newline at end of file
diff --git a/src/services/UserService.ts b/src/services/UserService.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/types/project.types.ts b/src/types/project.types.ts
index e363e02..3db67ef 100644
--- a/src/types/project.types.ts
+++ b/src/types/project.types.ts
@@ -1,21 +1,34 @@
-// Admin/Mentor Projects
+// types/project.types.ts
+
+export type ProjectStatus = "active" | "completed" | "archived";
+export type MemberStatus = "active" | "inactive";
+
export interface ProjectAdmin {
id: number;
full_name: string;
email: string;
- profile_picture: string;
- professional_bio: string;
+ profile_picture: string | null;
+ professional_bio: string | null;
}
-// Project General
+export interface ProjectMember {
+ id: number;
+ id_user: number;
+ status?: MemberStatus;
+ created_at?: string;
+ updated_at?: string;
+}
+
+// Response GET /project-api/get dan GET /project-api/get/{id}
export interface Project {
id: number;
+ slug: string;
project_icon: string;
project_name: string;
description: string;
start_date: string;
end_date: string;
- status: "active" | "inactive" | "completed";
+ status: ProjectStatus;
admin: ProjectAdmin;
total_members: number;
total_tasks: number;
@@ -23,31 +36,35 @@ export interface Project {
updated_at: string;
}
-//
-export interface TaskSubmissionDetails {
- id: number;
- file_path: string | null;
- url_link: string | null;
- submitted_at: string;
- updated_at: string;
+// Detail project (includes members array)
+export interface ProjectDetail extends Project {
+ members: ProjectMember[];
}
-// Task General: ALL TASK COMBINED
-export interface InternTask {
+// Response GET /project-api/my-tasks
+export interface InternTaskItem {
id: number;
+ slug: string;
id_project: number;
+ project_slug: string;
project_name: string;
project_icon: string;
title: string;
description: string;
deadline_at: string;
- submission_type: "url_link" | "file";
- submission_status: "submitted" | "pending" | "late";
- submission_details: TaskSubmissionDetails | null;
+ submission_type: "file_upload" | "url_link";
+ submission_status: "pending" | "submitted" | "done" | "overdue";
+ submission_details: {
+ id: number;
+ file_path: string | null;
+ url_link: string | null;
+ submitted_at: string;
+ updated_at: string;
+ } | null;
}
-// Intern
-export interface Intern {
+// Response GET /project-api/interns
+export interface InternDetail {
id: number;
name: string;
email: string;
@@ -57,14 +74,47 @@ export interface Intern {
avatar: string | null;
}
-//Project Task
-export interface ProjectTask {
+// Payload POST /project-api/add
+export interface CreateProjectPayload {
+ project_icon: string;
+ project_name: string;
+ description: string;
+ start_date: string; // "YYYY-MM-DD"
+ end_date: string; // "YYYY-MM-DD"
+ member_emails?: string[];
+}
+
+// Payload PATCH /project-api/update/{id}
+export interface UpdateProjectPayload {
+ project_icon?: string;
+ project_name?: string;
+ description?: string;
+ start_date?: string;
+ end_date?: string;
+ status?: ProjectStatus;
+}
+
+// Payload POST /project-api/assign-member
+export interface AssignMemberPayload {
+ id_project: number;
+ id_user: number;
+}
+
+// Response assign-member
+export interface MemberAssignment {
id: number;
id_project: number;
- title: string;
- description: string;
- deadline_at: string;
- submission_type: "file_upload" | "url_link";
+ id_user: number;
+ status: MemberStatus;
created_at: string;
updated_at: string;
+}
+
+export interface ProjectMember {
+ id: number;
+ full_name: string;
+ email: string;
+ professional_bio: string | null;
+ position: string;
+ kelompok_peminatan: string;
}
\ No newline at end of file
diff --git a/src/types/task.types.ts b/src/types/task.types.ts
index e69de29..4fc6b02 100644
--- a/src/types/task.types.ts
+++ b/src/types/task.types.ts
@@ -0,0 +1,69 @@
+// types/task.types.ts
+
+export type SubmissionType = "file_upload" | "url_link";
+export type DisplayStatus = "pending" | "submitted" | "done" | "overdue";
+
+export interface ProjectTask {
+ id: number;
+ id_project: number;
+ slug: string;
+ title: string;
+ description: string;
+ deadline_at: string;
+ submission_type: SubmissionType;
+ created_at: string;
+ updated_at: string;
+}
+
+export interface TaskSubmissionData {
+ id: number;
+ id_task: number;
+ id_user: number;
+ file_path: string | null;
+ url_link: string | null;
+ submitted_at: string;
+ updated_at: string;
+}
+
+export interface ProjectTaskDetail extends ProjectTask {
+ project: {
+ id: number;
+ project_name: string;
+ project_icon: string;
+ };
+ display_status: DisplayStatus;
+ my_submission: TaskSubmissionData | null;
+}
+
+export interface AdminTaskDetail extends ProjectTask {
+ project: {
+ id: number;
+ project_name: string;
+ project_icon: string;
+ };
+ submission_summary: {
+ total_members: number;
+ total_submitted: number;
+ total_done: number;
+ total_pending: number;
+ total_overdue: number;
+ };
+ submissions: Array<{
+ id_user: number;
+ full_name: string;
+ file_path: string | null;
+ url_link: string | null;
+ submitted_at: string | null;
+ updated_at: string | null;
+ }>;
+}
+
+export interface CreateTaskPayload {
+ title: string;
+ description: string;
+ deadline_date: string; // "YYYY-MM-DD"
+ specific_time: string; // "HH:mm"
+ submission_type: SubmissionType;
+}
+
+export interface UpdateTaskPayload extends Partial {}
\ No newline at end of file
diff --git a/src/types/user.types.ts b/src/types/user.types.ts
new file mode 100644
index 0000000..e69de29