import React, { useState, useRef, useEffect } from 'react'; import { LayoutDashboard, KanbanSquare, MessageSquare, Calendar, Settings, Bell, Search, Plus, MoreVertical, Paperclip, Send, CheckCircle2, Clock, ArrowRight, TrendingUp, Users, CheckSquare, Activity, BarChart3, ListTodo, History, PieChart, UserCheck, Hash, Smile, Reply, Video, Phone, FileText, Image as ImageIcon, Mic, MicOff, VideoOff, PhoneOff, MonitorUp, Maximize, UserPlus, Copy, Share2, Mail, Download } from 'lucide-react'; const INITIAL_TASKS = [ { id: 1, title: 'Revisi Copywriting Landing Page', status: 'todo', assignee: 'Budi', priority: 'High', date: '5 Aug', comments: 3, attachments: 1 }, { id: 2, title: 'Integrasi Payment Gateway', status: 'doing', assignee: 'Andi', priority: 'High', date: '6 Aug', comments: 8, attachments: 2 }, { id: 3, title: 'Meeting Mingguan Tim Marketing', status: 'done', assignee: 'Siti', priority: 'Medium', date: '4 Aug', comments: 0, attachments: 0 }, { id: 4, title: 'Setup Database Staging', status: 'todo', assignee: 'Andi', priority: 'Low', date: '8 Aug', comments: 1, attachments: 0 }, { id: 5, title: 'Buat Aset Desain Banner', status: 'doing', assignee: 'Rina', priority: 'Medium', date: '7 Aug', comments: 2, attachments: 4 } ]; const INITIAL_MESSAGES = [ { id: 1, sender: 'Andi (Dev)', text: 'Halo tim, API untuk payment gateway sudah siap di staging ya.', time: '09:00', isMe: false, reactions: ['🔥', '👍'] }, { id: 2, sender: 'Siti (PM)', text: 'Mantap! @Budi tolong siapkan copy untuk email notifikasinya ya.', time: '09:05', isMe: false, reactions: [] }, { id: 3, sender: 'Budi (Copywriter)', text: 'Siap, sedang saya kerjakan. Nanti sore saya push ke Kanban.', time: '09:10', isMe: false, reactions: ['🙌'], replyTo: 'Mantap! @Budi tolong siapkan copy untuk email notifikasinya ya.' }, { id: 4, sender: 'Anda', text: 'Berikut draft awal untuk aset banner bulan depan. Tolong review ya tim.', time: '09:15', isMe: true, attachment: { type: 'image', name: 'draft-banner-v1.jpg', size: '2.4 MB' } } ]; // URL Web App GAS Anda const GAS_API_URL = "https://script.google.com/macros/s/AKfycbwmvOMBpoD94LUTtW3a_WPfNtZ0lQDIGNc4arktm_XIR1JQlerbLGui-w0mio9tFcKG/exec"; export default function KerjaTimApp() { const [activeTab, setActiveTab] = useState('dashboard'); const [tasks, setTasks] = useState([]); const [isLoadingTasks, setIsLoadingTasks] = useState(true); const [messages, setMessages] = useState(INITIAL_MESSAGES); const [newMessage, setNewMessage] = useState(''); // States for Task form & filters const [newTaskTitle, setNewTaskTitle] = useState(''); const [showNewTaskForm, setShowNewTaskForm] = useState(false); const [newTaskAssignee, setNewTaskAssignee] = useState('Anda'); const [newTaskPriority, setNewTaskPriority] = useState('Medium'); const [newTaskDate, setNewTaskDate] = useState(''); const [filterAssignee, setFilterAssignee] = useState('Semua'); const [filterPriority, setFilterPriority] = useState('Semua'); const [draggedTaskId, setDraggedTaskId] = useState(null); // States for Video Call & Invites const [isVideoCallActive, setIsVideoCallActive] = useState(false); const [localStream, setLocalStream] = useState(null); const [isMicMuted, setIsMicMuted] = useState(false); const [isCameraOff, setIsCameraOff] = useState(false); const [hasMediaError, setHasMediaError] = useState(false); const videoRef = useRef(null); const [showInviteModal, setShowInviteModal] = useState(false); const [isCopied, setIsCopied] = useState(false); const dummyMeetingLink = "https://kerjatim.app/meet/proyek-alpha-882"; useEffect(() => { fetch(GAS_API_URL) .then(response => response.json()) .then(data => { if (Array.isArray(data)) { setTasks(data); } else { setTasks(INITIAL_TASKS); } setIsLoadingTasks(false); }) .catch(error => { console.error("Gagal terhubung ke Google Sheets:", error); setTasks(INITIAL_TASKS); setIsLoadingTasks(false); }); }, []); const startVideoCall = async () => { setIsVideoCallActive(true); setHasMediaError(false); try { const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); setLocalStream(stream); setIsMicMuted(false); setIsCameraOff(false); } catch (err) { console.error("Akses kamera/mic ditolak:", err); setHasMediaError(true); } }; const endVideoCall = () => { if (localStream) localStream.getTracks().forEach(track => track.stop()); setLocalStream(null); setIsVideoCallActive(false); }; const toggleMic = () => { if (localStream) { localStream.getAudioTracks().forEach(track => track.enabled = !track.enabled); setIsMicMuted(!localStream.getAudioTracks()[0]?.enabled); } else { setIsMicMuted(!isMicMuted); } }; const toggleCamera = () => { if (localStream) { localStream.getVideoTracks().forEach(track => track.enabled = !track.enabled); setIsCameraOff(!localStream.getVideoTracks()[0]?.enabled); } else { setIsCameraOff(!isCameraOff); } }; const copyMeetingLink = async () => { const fallbackCopyText = (text) => { const textArea = document.createElement("textarea"); textArea.value = text; textArea.style.position = "fixed"; textArea.style.left = "-9999px"; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { document.execCommand('copy'); setIsCopied(true); setTimeout(() => setIsCopied(false), 2000); } catch (err) { console.error('Gagal menyalin tautan (Fallback):', err); } document.body.removeChild(textArea); }; try { if (navigator.clipboard && navigator.clipboard.writeText) { await navigator.clipboard.writeText(dummyMeetingLink); setIsCopied(true); setTimeout(() => setIsCopied(false), 2000); } else { fallbackCopyText(dummyMeetingLink); } } catch (err) { fallbackCopyText(dummyMeetingLink); } }; const shareViaWhatsApp = () => { const text = `Halo tim, mari bergabung ke rapat harian Proyek Alpha sekarang. Klik tautan berikut:\n\n${dummyMeetingLink}`; window.open(`https://wa.me/?text=${encodeURIComponent(text)}`, '_blank'); }; const shareViaEmail = () => { const subject = "Undangan Rapat: Daily Standup Proyek Alpha"; const body = `Halo tim,\n\nMari bergabung ke rapat harian Proyek Alpha sekarang. Klik tautan di bawah ini untuk bergabung:\n\n${dummyMeetingLink}\n\nTerima kasih.`; window.open(`mailto:?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`, '_blank'); }; useEffect(() => { if (videoRef.current && localStream && !isCameraOff) { videoRef.current.srcObject = localStream; } }, [localStream, isVideoCallActive, isCameraOff]); const moveTask = (taskId, newStatus) => { setTasks(tasks.map(t => t.id === taskId ? { ...t, status: newStatus } : t)); }; const addTask = async (e) => { e.preventDefault(); if (!newTaskTitle.trim()) return; const newTask = { id: Date.now(), title: newTaskTitle, status: 'todo', assignee: newTaskAssignee, priority: newTaskPriority, date: newTaskDate ? new Date(newTaskDate).toLocaleDateString('id-ID', {day: 'numeric', month: 'short'}) : 'Hari ini', comments: 0, attachments: 0 }; setTasks([...tasks, newTask]); setNewTaskTitle(''); setNewTaskAssignee('Anda'); setNewTaskPriority('Medium'); setNewTaskDate(''); setShowNewTaskForm(false); try { await fetch(GAS_API_URL, { method: 'POST', body: JSON.stringify(newTask) }); } catch (error) { console.error("Gagal mengirim data ke cloud:", error); } }; const deleteTask = (taskId) => { setTasks(tasks.filter(t => t.id !== taskId)); }; const sendMessage = (e) => { e.preventDefault(); if (!newMessage.trim()) return; const msg = { id: Date.now(), sender: 'Anda', text: newMessage, time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), isMe: true }; setMessages([...messages, msg]); setNewMessage(''); }; const handleDragStart = (e, taskId) => { setDraggedTaskId(taskId); e.dataTransfer.setData('text/plain', taskId); }; const handleDrop = (e, status) => { e.preventDefault(); if (draggedTaskId) { moveTask(draggedTaskId, status); setDraggedTaskId(null); } }; const renderSidebar = () => (
KT
KerjaTim
Menu Utama
); const renderHeader = () => (

{activeTab === 'chat' ? 'Obrolan Proyek Alpha' : activeTab === 'kanban' ? 'Kanban: Proyek Alpha' : activeTab}

A
S
B

Anda

Project Manager

ME
); const renderKanban = () => { const columns = [ { id: 'todo', title: 'To Do (Akan Datang)' }, { id: 'doing', title: 'Doing (Sedang Dikerjakan)' }, { id: 'done', title: 'Done (Selesai)' } ]; const filteredTasks = tasks.filter(t => { if (filterAssignee !== 'Semua' && t.assignee !== filterAssignee) return false; if (filterPriority !== 'Semua' && t.priority !== filterPriority) return false; return true; }); return (

Kelola dan pantau progress pekerjaan tim Anda dengan mudah.

Filter Anggota:
Prioritas:
{showNewTaskForm && (
setNewTaskTitle(e.target.value)} placeholder="Judul Tugas (misal: Buat Laporan Keuangan)" className="w-full border border-slate-200 rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-blue-500 font-medium text-slate-800" autoFocus />
setNewTaskDate(e.target.value)} className="w-full border border-slate-200 rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-blue-500 bg-slate-50 text-slate-700" />
)} {isLoadingTasks ? (

Mensinkronisasi dengan Database Google Sheets...

) : (
{columns.map(col => (

{col.title}

{filteredTasks.filter(t => t.status === col.id).length}
e.preventDefault()} onDrop={(e) => handleDrop(e, col.id)} > {filteredTasks.filter(t => t.status === col.id).map(task => (
handleDragStart(e, task.id)} className="bg-white p-4 rounded-2xl shadow-sm border border-slate-100 hover:shadow-md transition-all cursor-grab active:cursor-grabbing group relative" >
{task.priority}

{task.title}

{task.date}
{task.assignee.charAt(0)}
{task.comments}
{task.attachments}
))}
))}
)}
); }; const renderDashboard = () => { // Analytics Computations const totalTasks = tasks.length; const todoTasks = tasks.filter(t => t.status === 'todo').length; const doingTasks = tasks.filter(t => t.status === 'doing').length; const doneTasks = tasks.filter(t => t.status === 'done').length; const todoPercent = totalTasks === 0 ? 0 : Math.round((todoTasks / totalTasks) * 100); const doingPercent = totalTasks === 0 ? 0 : Math.round((doingTasks / totalTasks) * 100); const donePercent = totalTasks === 0 ? 0 : Math.round((doneTasks / totalTasks) * 100); const progressPercent = totalTasks === 0 ? 0 : Math.round((doneTasks / totalTasks) * 100); const highCount = tasks.filter(t => t.priority === 'High').length; const medCount = tasks.filter(t => t.priority === 'Medium').length; const lowCount = tasks.filter(t => t.priority === 'Low').length; const highPct = totalTasks === 0 ? 0 : Math.round((highCount / totalTasks) * 100); const medPct = totalTasks === 0 ? 0 : Math.round((medCount / totalTasks) * 100); const lowPct = totalTasks === 0 ? 0 : Math.round((lowCount / totalTasks) * 100); // Workload computation const assigneeMap = {}; tasks.filter(t => t.status !== 'done').forEach(t => { assigneeMap[t.assignee] = (assigneeMap[t.assignee] || 0) + 1; }); const workloadData = Object.keys(assigneeMap).map(name => ({ name, count: assigneeMap[name] })).sort((a, b) => b.count - a.count); const maxWorkload = workloadData.length > 0 ? Math.max(...workloadData.map(d => d.count)) : 1; const chartData = [ { day: 'Sen', tasks: 4 }, { day: 'Sel', tasks: 6 }, { day: 'Rab', tasks: 3 }, { day: 'Kam', tasks: 8 }, { day: 'Jum', tasks: 5 } ]; const maxChartTasks = 8; return (

Ringkasan Proyek

{/* KPI Cards */}
Total

{totalTasks}

Tugas Keseluruhan

Aktif

{doingTasks}

Sedang Dikerjakan

Selesai

{doneTasks}

Tugas Diselesaikan

Progress

{progressPercent}%

Penyelesaian Proyek

{/* Charts Section */}

Produktivitas Mingguan

{chartData.map((data, idx) => (
{data.tasks} tugas
{data.day}
))}

Distribusi Status Tugas

To Do
{todoTasks}
Doing
{doingTasks}
Done
{doneTasks}
{/* Extended Analytics */}

Beban Kerja Anggota (Tugas Aktif)

{workloadData.length > 0 ? workloadData.map((data, index) => (
{data.name.charAt(0)}
{data.name} {data.count} Tugas
)) : (
Semua anggota tim sedang tidak ada tugas aktif.
)}

Tingkat Prioritas

{totalTasks} Total
Tinggi {highCount} ({highPct}%)
Sedang {medCount} ({medPct}%)
Rendah {lowCount} ({lowPct}%)
); }; const renderChat = () => (

Obrolan Tim Umum

4 Anggota • Online

{messages.map(msg => (
{!msg.isMe && (
{msg.sender.charAt(0)}
)}
{!msg.isMe &&

{msg.sender} {msg.time}

}
{msg.replyTo && (
Membalas "{msg.replyTo.substring(0, 30)}..."
)} {msg.attachment && (
{msg.attachment.type === 'image' && (
{msg.attachment.name}
)}
{msg.attachment.type === 'image' ? : }

{msg.attachment.name}

{msg.attachment.size}

)}

{msg.text}

{msg.reactions && msg.reactions.length > 0 && (
{msg.reactions.map((emoji, i) => {emoji})}
)}
{msg.isMe &&

{msg.time}

}
))}