test pertama:
- implementasi seluruh backend pada akun intern dan mentor pada bagian lms - kondisi semua hooks dan types yang dibutuhkan - Pengujian task, project, dan submission pada mentor dan intern next progress: - Implementasi lib eksternal untuk unduh kedalam devices dan kirim sertifikat ke internet (linkedin) - implementasi proses sertifikat pada mentor untuk melakukan generate sertifikat dengan template dan intern pada projek
This commit is contained in:
@@ -59,6 +59,13 @@ export default function CertificateNotAvailable({
|
|||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-400 mt-2 text-left">Semua tugas telah diselesaikan! Silahkan tunggu untuk mendapatkan sertifikat</p>
|
<p className="text-xs text-gray-400 mt-2 text-left">Semua tugas telah diselesaikan! Silahkan tunggu untuk mendapatkan sertifikat</p>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleClaim}
|
||||||
|
disabled={loading}
|
||||||
|
className="mt-0 px-6 py-2.5 bg-green-600 hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed text-white font-semibold rounded-lg transition-colors duration-200"
|
||||||
|
>
|
||||||
|
{loading ? "Memproses..." : "Klaim Sertifikat"}
|
||||||
|
</button>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -84,15 +91,6 @@ export default function CertificateNotAvailable({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={handleClaim}
|
|
||||||
disabled={loading}
|
|
||||||
className="mt-2 px-6 py-2.5 bg-green-600 hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed text-white font-semibold rounded-lg transition-colors duration-200"
|
|
||||||
>
|
|
||||||
{loading ? "Memproses..." : "Klaim Sertifikat"}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{error && <p className="text-red-500 text-sm mt-2">{error}</p>}
|
{error && <p className="text-red-500 text-sm mt-2">{error}</p>}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const HomeProjectList = () => {
|
|||||||
{projects.map((project) => (
|
{projects.map((project) => (
|
||||||
<div
|
<div
|
||||||
key={project.id}
|
key={project.id}
|
||||||
onClick={() => navigate(`/intern/projects/${toSlug(project.project_name)}`)}
|
onClick={() => navigate(`/intern/projects/${toSlug(project.slug)}`)}
|
||||||
className="flex items-center gap-4 border border-card-outline rounded-lg px-4 py-3 hover:bg-gray-50 cursor-pointer transition-colors duration-150"
|
className="flex items-center gap-4 border border-card-outline rounded-lg px-4 py-3 hover:bg-gray-50 cursor-pointer transition-colors duration-150"
|
||||||
>
|
>
|
||||||
<div className="w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0 bg-red-600" />
|
<div className="w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0 bg-red-600" />
|
||||||
|
|||||||
@@ -7,6 +7,16 @@ const formatDate = (dateStr: string) =>
|
|||||||
const formatTime = (dateStr: string) =>
|
const formatTime = (dateStr: string) =>
|
||||||
new Date(dateStr).toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' });
|
new Date(dateStr).toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' });
|
||||||
|
|
||||||
|
const statusStyles: Record<InternTaskItem["submission_status"], string> = {
|
||||||
|
not_submitted: "bg-gray-50 text-gray-700 border-gray-200",
|
||||||
|
submitted: "bg-green-50 text-green-700 border-green-200",
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusLabels: Record<InternTaskItem["submission_status"], string> = {
|
||||||
|
not_submitted: "Pending",
|
||||||
|
submitted: "Submitted",
|
||||||
|
};
|
||||||
|
|
||||||
export default function TaskCard({ task }: { task: InternTaskItem }) {
|
export default function TaskCard({ task }: { task: InternTaskItem }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
return (
|
return (
|
||||||
@@ -29,13 +39,12 @@ export default function TaskCard({ task }: { task: InternTaskItem }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="block md:hidden h-[10px] bg-gray-600"/>
|
<div className="block md:hidden h-[10px] bg-gray-600" />
|
||||||
|
|
||||||
<div className="shrink-0 hidden md:flex items-center">
|
<div className="shrink-0 hidden md:flex items-center border border-card-outline">
|
||||||
<span
|
<span
|
||||||
className={`font-semibold text-xs px-3.5 py-[5px] rounded-md whitespace-nowrap bg-gray-50`}
|
className={`font-semibold text-xs px-3.5 py-[5px] rounded-md whitespace-nowrap border ${statusStyles[task.submission_status]}`}>
|
||||||
>
|
{statusLabels[task.submission_status]}
|
||||||
{task.submission_status}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export default function ManageInternsModal({ isOpen, onClose, projectId, initial
|
|||||||
const [invitedList, setInvitedList] = useState<ProjectMember[]>(initialMembers);
|
const [invitedList, setInvitedList] = useState<ProjectMember[]>(initialMembers);
|
||||||
|
|
||||||
const { mahasiswa, loading: loadingMahasiswa } = useMahasiswa();
|
const { mahasiswa, loading: loadingMahasiswa } = useMahasiswa();
|
||||||
const { assign, loading: assigning } = useAssignMember();
|
const { assign, loading: assigning, error } = useAssignMember();
|
||||||
|
|
||||||
// Sync saat modal dibuka
|
// Sync saat modal dibuka
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -84,8 +84,13 @@ export default function ManageInternsModal({ isOpen, onClose, projectId, initial
|
|||||||
/>
|
/>
|
||||||
<Search className="absolute right-3.5 w-4 h-4 text-gray-400" />
|
<Search className="absolute right-3.5 w-4 h-4 text-gray-400" />
|
||||||
</div>
|
</div>
|
||||||
|
{error &&
|
||||||
|
<div className="p-1 flex justify-center bg-red-foreground border border-red text-sm text-red-500 font-light rounded-md">
|
||||||
|
{error}: Sudah berada di project lain
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div className="overflow-y-auto space-y-3 pr-1 flex-1">
|
<div className="overflow-y-auto space-y-3 pr-1 flex-1 h-[170px]">
|
||||||
{/* Sudah jadi member */}
|
{/* Sudah jadi member */}
|
||||||
{invitedList.map((member) => (
|
{invitedList.map((member) => (
|
||||||
<div key={member.id} className="flex items-center justify-between p-2 rounded-xl hover:bg-gray-50 transition-colors">
|
<div key={member.id} className="flex items-center justify-between p-2 rounded-xl hover:bg-gray-50 transition-colors">
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default function MentorTaskCard({ task }: {task: ProjectTask}) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="border border-[#eee] rounded-[10px] px-5 py-4 mb-3 bg-white shadow-lg flex items-start justify-between gap-3 cursor-pointer hover:border-gray-300 transition-colors"
|
className="border border-card-outline rounded-[10px] px-5 py-4 mb-3 bg-card shadow-lg flex items-start justify-between gap-3 cursor-pointer hover:border-card-outline/50 transition-colors"
|
||||||
onClick={handleCardClick}
|
onClick={handleCardClick}
|
||||||
>
|
>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
|
|||||||
@@ -253,6 +253,7 @@ export const useAssignMember = () => {
|
|||||||
return { assign, loading, error };
|
return { assign, loading, error };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Hapus/Keluarkan Member dari Projek
|
||||||
export const useRemoveMember = () => {
|
export const useRemoveMember = () => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|||||||
@@ -101,8 +101,7 @@ export const useUpdateProfile = () => {
|
|||||||
return { save, loading, error, successMsg, setSuccessMsg, setError };
|
return { save, loading, error, successMsg, setSuccessMsg, setError };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Ambil Seluruh Mahasiswa
|
||||||
|
|
||||||
export const useMahasiswa = () => {
|
export const useMahasiswa = () => {
|
||||||
const [mahasiswa, setMahasiswa] = useState<Mahasiswa[]>([]);
|
const [mahasiswa, setMahasiswa] = useState<Mahasiswa[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export interface InternTaskItem {
|
|||||||
description: string;
|
description: string;
|
||||||
deadline_at: string;
|
deadline_at: string;
|
||||||
submission_type: "file_upload" | "url_link";
|
submission_type: "file_upload" | "url_link";
|
||||||
submission_status: "pending" | "submitted" | "done" | "overdue";
|
submission_status: "not_submitted" | "submitted";
|
||||||
submission_details: {
|
submission_details: {
|
||||||
id: number;
|
id: number;
|
||||||
file_path: string | null;
|
file_path: string | null;
|
||||||
|
|||||||
Reference in New Issue
Block a user