Duffer Derek
'use client';
import React, { useState, ChangeEvent, FormEvent, useEffect } from 'react';
import mediaService from '../../services/mediaService';
import { X, ChevronRight, Folder } from 'lucide-react';
interface GenericFormProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
initialProperties: { [key: string]: any };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onSave: (properties: { [key: string]: any }) => void;
}
const GenericForm: React.FC<GenericFormProps> = ({ initialProperties, onSave }) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [properties, setProperties] = useState<{ [key: string]: any }>(initialProperties);
const [isMediaModalOpen, setIsMediaModalOpen] = useState(false);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [mediaItems, setMediaItems] = useState<any[]>([]);
const [selectedImageKey, setSelectedImageKey] = useState<string | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isFolderModalOpen, setIsFolderModalOpen] = useState(false);
const [breadcrumb, setBreadcrumb] = useState<{ id: string | null, name: string }[]>([{ id: null, name: 'Root' }]);
const fileInputRef = React.useRef<HTMLInputElement>(null);
const [folderName, setFolderName] = useState('');
const [fileName, setFileName] = useState('');
const [currentFolder, setCurrentFolder] = useState<string | null>(null);
useEffect(() => {
if (isMediaModalOpen) {
fetchMedia();
}
}, [isMediaModalOpen]);
const fetchMedia = async (folderId: string | null = null) => {
try {
const media = await mediaService.fetchMedia(folderId);
setMediaItems(media);
} catch (error) {
console.error('Failed to fetch media', error);
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const handleChange = (key: string, value: any) => {
setProperties({ ...properties, [key]: value });
};
const handleNestedChange = (key: string, index: number, nestedKey: string, value: string) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updatedArray = properties[key].map((item: any, i: number) =>
i === index ? { ...item, [nestedKey]: value } : item
);
setProperties({ ...properties, [key]: updatedArray });
};
const handleAddItem = (key: string) => {
const newItem = properties[key].length > 0 ? { ...properties[key][0] } : { text: "", href: "" };
setProperties({ ...properties, [key]: [...(properties[key] || []), newItem] });
};
const handleDeleteItem = (key: string, index: number) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updatedArray = properties[key].filter((_: any, i: number) => i !== index);
setProperties({ ...properties, [key]: updatedArray });
};
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
onSave(properties);
};
const handleImageClick = (key: string) => {
setSelectedImageKey(key);
setIsMediaModalOpen(true);
};
const handleImageSelect = (url: string, id: string) => {
if (selectedImageKey) {
handleChange(selectedImageKey, { url, id });
}
setIsMediaModalOpen(false);
};
const handleAddMediaClick = () => {
setIsModalOpen(true);
};
const handleCreateFolderClick = () => {
setIsFolderModalOpen(true);
};
const handleModalClose = () => {
setIsModalOpen(false);
};
const handleFolderModalClose = () => {
setIsFolderModalOpen(false);
};
const handleDrop = (event: React.DragEvent) => {
event.preventDefault();
const files = event.dataTransfer.files;
if (fileInputRef.current) {
fileInputRef.current.files = files;
}
};
const handleSave = async () => {
if (fileInputRef.current && fileInputRef.current.files && fileInputRef.current.files.length > 0) {
try {
await mediaService.uploadMedia(currentFolder, fileInputRef.current.files[0], fileName);
fetchMedia(currentFolder);
} catch (error) {
console.error('Failed to upload media', error);
}
}
setIsModalOpen(false);
};
const handleCreateFolder = async () => {
try {
await mediaService.createFolder(folderName, currentFolder);
fetchMedia(currentFolder);
} catch (error) {
console.error('Failed to create folder', error);
}
setFolderName('');
setIsFolderModalOpen(false);
};
const handleFolderClick = async (folderId: string | null, folderName: string) => {
setCurrentFolder(folderId);
await fetchMedia(folderId);
setBreadcrumb(prev => {
const existingIndex = prev.findIndex(crumb => crumb.id === folderId);
if (existingIndex !== -1) {
return prev.slice(0, existingIndex + 1);
}
return [...prev, { id: folderId, name: folderName }];
});
};
const handleBreadcrumbClick = async (index: number, event: React.MouseEvent) => {
event.preventDefault();
const newBreadcrumb = breadcrumb.slice(0, index + 1);
const folderId = newBreadcrumb[newBreadcrumb.length - 1].id;
setBreadcrumb(newBreadcrumb);
setCurrentFolder(folderId);
await fetchMedia(folderId);
};
const handleRemoveImage = (key: string) => {
handleChange(key, { url: null, id: null });
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const renderMediaItems = (items: any[], size: 'small' | 'large' = 'large') => {
const folders = items.filter(media => media.type === 'Folder');
const files = items.filter(media => media.type !== 'Folder');
return (
<>
{folders.map(media => (
<div key={media.id} className="mb-4">
<div
onClick={() => handleFolderClick(media.id, media.name)}
className="p-2 border border-gray-300 rounded mt-2 cursor-pointer flex items-center"
>
<Folder size={20} className="mr-2" />
{media.name}
</div>
</div>
))}
<div className="flex flex-wrap">
{files.map(media => (
<div key={media.id} className="mb-4 p-2 cursor-pointer" style={{ width: size === 'small' ? '10%' : '100%' }} onClick={() => handleImageSelect(media.urlInfos[0]?.url, media.id)}>
<div className="border border-gray-300 rounded mt-2 cursor-pointer">
{ // eslint-disable-next-line @typescript-eslint/no-explicit-any
media.urlInfos.map((urlInfo: any, index: number) => (
<img
key={index}
src={urlInfo.url}
alt="Media"
className={`w-${size === 'small' ? '48' : 'full'} h-${size === 'small' ? '48' : 'auto'}`}
/>
))}
<div className="text-center">{media.name}</div>
</div>
</div>
))}
</div>
</>
);
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
{Object.keys(properties).map((key) => (
<div key={key} className="mb-4">
<label className="block text-sm font-medium text-gray-700">{key}</label>
{Array.isArray(properties[key]) ? (
<>
{ // eslint-disable-next-line @typescript-eslint/no-explicit-any
properties[key].map((item: any, index: number) => (
<div key={index} className="mb-2">
{Object.keys(item).map((nestedKey) => (
<div key={nestedKey} className="mb-2">
<label className="block text-xs font-medium text-gray-500">{nestedKey}</label>
<input
type="text"
value={item[nestedKey] || ""}
onChange={(e: ChangeEvent<HTMLInputElement>) => handleNestedChange(key, index, nestedKey, e.target.value)}
className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
</div>
))}
<button type="button" onClick={() => handleDeleteItem(key, index)} className="mt-2 text-red-500">- Remove Item</button>
</div>
))}
<button type="button" onClick={() => handleAddItem(key)} className="mt-2 text-blue-500">+ Add Item</button>
</>
) : (
<>
{key === 'image' ? (
<>
<button type="button" onClick={() => handleImageClick(key)} className="mt-2 text-blue-500">Select Image</button>
{properties[key]?.url && (
<>
<img src={properties[key].url} alt="Selected" className="mt-2 w-full h-[100] w-[100]" />
<button type="button" onClick={() => handleRemoveImage(key)} className="mt-2 text-red-500">Remove Image</button>
</>
)}
</>
) : (
<input
type="text"
value={properties[key] || ""}
onChange={(e: ChangeEvent<HTMLInputElement>) => handleChange(key, e.target.value)}
className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
)}
</>
)}
</div>
))}
<div className="flex justify-end">
<button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">Save</button>
</div>
{isMediaModalOpen && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-[9999]">
<div className="bg-white p-4 rounded w-[90%] h-[90%] overflow-y-auto mt-[55px]">
<div className="flex items-center justify-between mb-4">
<h2 className="font-medium">Media</h2>
{breadcrumb.length > 0 && (
<div className="mb-4 flex items-center">
{breadcrumb.map((crumb, index) => (
<React.Fragment key={crumb.id}>
<button
onClick={(event) => handleBreadcrumbClick(index, event)}
className="text-blue-500 hover:underline"
>
{crumb.name}
</button>
{index < breadcrumb.length - 1 && (
<ChevronRight size={16} className="mx-2 text-gray-500" />
)}
</React.Fragment>
))}
</div>
)}
<button
onClick={() => setIsMediaModalOpen(false)}
className="p-1 hover:bg-gray-200 rounded-full transition-colors"
>
<X size={20} className="text-gray-500" />
</button>
</div>
<button
onClick={handleAddMediaClick}
className="mb-4 p-2 bg-blue-500 text-white rounded mr-1"
>
Add Media
</button>
<button
onClick={handleCreateFolderClick}
className="mb-4 p-2 bg-green-500 text-white rounded"
>
Create Folder
</button>
{renderMediaItems(mediaItems, 'small')}
</div>
</div>
)}
{/* Modal for Adding Media */}
{isModalOpen && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-[9999]">
<div className="bg-white p-4 rounded">
<h2 className="font-medium mb-4">Add Media</h2>
<div
onDrop={handleDrop}
onDragOver={(event) => event.preventDefault()}
className="border-dashed border-2 border-gray-300 p-4 mb-4"
>
Drag and drop files here
</div>
<input
type="file"
ref={fileInputRef}
className="mb-4"
/>
<input
type="text"
value={fileName}
onChange={(e) => setFileName(e.target.value)}
placeholder="File Name"
className="mb-4 p-2 border border-gray-300 rounded w-full"
/>
<div className="flex justify-end space-x-2">
<button
onClick={handleModalClose}
className="p-2 bg-gray-500 text-white rounded"
>
Close
</button>
<button
onClick={handleSave}
className="p-2 bg-blue-500 text-white rounded"
>
Save
</button>
</div>
</div>
</div>
)}
{/* Modal for Creating Folder */}
{isFolderModalOpen && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-[9999]">
<div className="bg-white p-4 rounded">
<h2 className="font-medium mb-4">Create Folder</h2>
<div className="mb-4">
<input
type="text"
value={folderName}
onChange={(e) => setFolderName(e.target.value)}
placeholder="Folder Name"
className="p-2 border border-gray-300 rounded w-full"
/>
<button
onClick={handleCreateFolder}
className="mt-2 p-2 bg-green-500 text-white rounded w-full"
>
Create Folder
</button>
</div>
<div className="flex justify-end space-x-2">
<button
onClick={handleFolderModalClose}
className="p-2 bg-gray-500 text-white rounded"
>
Close
</button>
</div>
</div>
</div>
)}
</form>
);
};
export default GenericForm;
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists