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 = () => (
Anda
Project Manager
Kelola dan pantau progress pekerjaan tim Anda dengan mudah.
Mensinkronisasi dengan Database Google Sheets...
Tugas Keseluruhan
Sedang Dikerjakan
Tugas Diselesaikan
Penyelesaian Proyek
4 Anggota • Online
{msg.sender} {msg.time}
}{msg.attachment.name}
{msg.attachment.size}
{msg.text}
{msg.reactions && msg.reactions.length > 0 && ({msg.time}
}{desc}
Bagikan tautan ini kepada anggota tim.