Skip to content

Commit e22b11f

Browse files
authored
Merge pull request #26 from asepindrak/dev
Dev
2 parents 3e4563c + 5c3ac47 commit e22b11f

File tree

5 files changed

+129
-24
lines changed

5 files changed

+129
-24
lines changed

frontend/package-lock.json

Lines changed: 17 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React from "react";
2+
import { X, Download } from "lucide-react";
3+
4+
interface MediaModalProps {
5+
isOpen: boolean;
6+
url: string;
7+
type: "image" | "video";
8+
onClose: () => void;
9+
}
10+
11+
export default function MediaModal({
12+
isOpen,
13+
url,
14+
type,
15+
onClose,
16+
}: MediaModalProps) {
17+
if (!isOpen) return null;
18+
19+
return (
20+
<div
21+
className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/90 backdrop-blur-sm"
22+
onClick={onClose}
23+
>
24+
<button
25+
onClick={onClose}
26+
className="absolute top-4 right-4 z-[101] p-2 rounded-full bg-white/10 hover:bg-white/20 text-white transition-colors"
27+
aria-label="Close media viewer"
28+
>
29+
<X size={24} />
30+
</button>
31+
32+
<div
33+
className="relative max-w-7xl max-h-[90vh] w-full h-full flex items-center justify-center"
34+
onClick={(e) => e.stopPropagation()}
35+
>
36+
{type === "image" ? (
37+
<img
38+
src={url}
39+
alt="Full size preview"
40+
className="max-w-full max-h-full object-contain rounded-lg shadow-2xl"
41+
/>
42+
) : (
43+
<video
44+
src={url}
45+
controls
46+
autoPlay
47+
className="max-w-full max-h-full rounded-lg shadow-2xl"
48+
/>
49+
)}
50+
</div>
51+
52+
{/* <a
53+
href={url}
54+
download
55+
className="absolute bottom-4 right-4 z-[101] flex items-center gap-2 px-4 py-2 rounded-lg bg-white/10 hover:bg-white/20 text-white transition-colors"
56+
onClick={(e) => e.stopPropagation()}
57+
>
58+
<Download size={18} />
59+
<span>Download</span>
60+
</a> */}
61+
</div>
62+
);
63+
}

frontend/src/components/TaskModal.tsx

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
Send,
1515
Trash,
1616
X,
17+
Download,
1718
} from "lucide-react";
1819
import Swal from "sweetalert2";
1920
import type { Task, Attachment, TeamMember } from "../types";
@@ -22,6 +23,7 @@ import { PrioritySelect } from "./PrioritySelect";
2223
import { AssigneeSelect } from "./AssigneeSelect";
2324
import { handleWhatsapp, handleWhatsappTask } from "../utils/sendWhatsapp";
2425
import WhatsappIcon from "./WhatsappIcon";
26+
import MediaModal from "./MediaModal";
2527
import { useAuthStore } from "../utils/store";
2628
import { FaComment } from "react-icons/fa";
2729

@@ -57,6 +59,11 @@ export default function TaskModal({
5759
const [files, setFiles] = useState<File[]>([]);
5860
const [isLoading, setIsLoading] = useState(false);
5961
const [uploading, setUploading] = useState(false);
62+
const [mediaViewerOpen, setMediaViewerOpen] = useState(false);
63+
const [mediaViewerUrl, setMediaViewerUrl] = useState<string>("");
64+
const [mediaViewerType, setMediaViewerType] = useState<"image" | "video">(
65+
"image"
66+
);
6067
const fileInputRef = useRef<HTMLInputElement | null>(null);
6168
const currentAssignee = team.find((t) => t.id === task.assigneeId);
6269
const assigneePhone = currentAssignee?.phone || "";
@@ -383,6 +390,17 @@ export default function TaskModal({
383390
return s.length === 0;
384391
}
385392

393+
const openMediaViewer = (url: string, type: "image" | "video") => {
394+
setMediaViewerUrl(url);
395+
setMediaViewerType(type);
396+
setMediaViewerOpen(true);
397+
};
398+
399+
const closeMediaViewer = () => {
400+
setMediaViewerOpen(false);
401+
setMediaViewerUrl("");
402+
};
403+
386404
return (
387405
<div className="fixed inset-0 z-50 flex items-center justify-center p-6">
388406
{/* Backdrop */}
@@ -603,16 +621,33 @@ export default function TaskModal({
603621
<img
604622
src={a.url}
605623
alt={a.name}
606-
className="max-h-60 rounded-lg object-cover"
624+
onClick={() =>
625+
openMediaViewer(a.url, "image")
626+
}
627+
className="max-h-60 rounded-lg object-cover cursor-pointer hover:opacity-80 transition-opacity"
628+
title="Click to view full size"
607629
/>
608630
)}
609631

610632
{isVideo && (
611-
<video
612-
src={a.url}
613-
controls
614-
className="max-h-60 rounded-lg"
615-
/>
633+
<div className="relative">
634+
<video
635+
src={a.url}
636+
className="max-h-60 rounded-lg cursor-pointer"
637+
onClick={() =>
638+
openMediaViewer(a.url, "video")
639+
}
640+
title="Click to view in modal"
641+
/>
642+
<div
643+
className="absolute inset-0 flex items-center justify-center pointer-events-none"
644+
aria-hidden="true"
645+
>
646+
<div className="w-16 h-16 rounded-full bg-black/50 flex items-center justify-center">
647+
<div className="w-0 h-0 border-t-8 border-t-transparent border-l-12 border-l-white border-b-8 border-b-transparent ml-1"></div>
648+
</div>
649+
</div>
650+
</div>
616651
)}
617652
</>
618653
)}
@@ -625,23 +660,6 @@ export default function TaskModal({
625660
({Math.round((a.size || 0) / 1024)} KB)
626661
</span>
627662
</div>
628-
629-
<div>
630-
{a.url ? (
631-
<a
632-
href={a.url}
633-
target="_blank"
634-
rel="noreferrer"
635-
className="text-sm underline"
636-
>
637-
Open
638-
</a>
639-
) : (
640-
<span className="text-xs text-gray-400">
641-
(no url)
642-
</span>
643-
)}
644-
</div>
645663
</div>
646664
</div>
647665
);
@@ -748,6 +766,14 @@ export default function TaskModal({
748766
</div>
749767
</div>
750768
</div>
769+
770+
{/* Media Viewer Modal */}
771+
<MediaModal
772+
isOpen={mediaViewerOpen}
773+
url={mediaViewerUrl}
774+
type={mediaViewerType}
775+
onClose={closeMediaViewer}
776+
/>
751777
</div>
752778
);
753779
}

scripts/build.dev.sh

100644100755
File mode changed.

scripts/run.dev.sh

100644100755
File mode changed.

0 commit comments

Comments
 (0)