Skip to content

Commit

Permalink
feat(frontend): added delete guard when deleting projects, added moda…
Browse files Browse the repository at this point in the history
…l styling in workspace area, implenmented rename file when uploading a file [2024-12-15]
  • Loading branch information
CHRISCARLON committed Dec 15, 2024
1 parent 1c7c6be commit 57e466b
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ export interface LayerUpload {
workspace_id?: string;
}

const defaultBaseLayer: BaseEditNav = {
id: "light",
title: "Light Mode",
icon: "light",
description: "Light blue base map style"
};

const MAP_STYLES = {
light: "/OS_VTS_3857_Light.json",
dark: "/OS_VTS_3857_Dark.json",
Expand All @@ -35,22 +42,22 @@ interface MapClientProps {
export function MapClient({ apiUrl }: MapClientProps) {
// UI States
const [selectedItem, setSelectedItem] = useState<MainMapNav | null>(null);
const [selectedEditItem, setSelectedEditItem] = useState<MapEditNav | null>(
null,
);
const [selectedBaseItem, setSelectedBaseItem] = useState<BaseEditNav | null>(
null,
);
const [selectedEditItem, setSelectedEditItem] = useState<MapEditNav | null>(null);
const [selectedBaseItem, setSelectedBaseItem] = useState<BaseEditNav>(defaultBaseLayer); // Initialize with defaultBaseLayer
const [isModalOpen, setIsModalOpen] = useState(false);
const [currentStyle, setCurrentStyle] = useState<string>(MAP_STYLES.light);

// Layer Management States
const [layers, setLayers] = useState<LayerUpload[]>([]);
const [uploadProgress, setUploadProgress] = useState<number>(0);
const [isUploading, setIsUploading] = useState(false);
const [uploadError, setUploadError] = useState<string | null>(null);
const [uploadSuccess, setUploadSuccess] = useState(false);

// File Upload States
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [fileName, setFileName] = useState("");

// Map Initialisation
const { mapContainer, mapError } = useMapInit({
...INITIAL_MAP_CONFIG,
Expand All @@ -61,17 +68,36 @@ export function MapClient({ apiUrl }: MapClientProps) {
// File Upload Hook Integration
const { uploadFile } = useFileUploader();

// File Selection Handler
const handleFileSelection = useCallback((file: File) => {
setSelectedFile(file);
setUploadError(null);
}, []);

// File Upload Handler
const handleUpload = useCallback(
async (file: File) => {
async (fileToUpload: File) => {
if (!fileToUpload || !fileName.trim()) {
setUploadError("Please provide a valid file and name");
return;
}

setIsUploading(true);
setUploadError(null);
setUploadSuccess(false);
setUploadProgress(0);

try {
// Create a new File object with the custom name while preserving the extension
const extension = fileToUpload.name.split('.').pop();
const renamedFile = new File(
[fileToUpload],
`${fileName}${extension ? `.${extension}` : ''}`,
{ type: fileToUpload.type }
);

await uploadFile(
file,
renamedFile,
"",
(progress) => {
setUploadProgress(progress);
Expand All @@ -91,10 +117,12 @@ export function MapClient({ apiUrl }: MapClientProps) {

setUploadSuccess(true);
setIsUploading(false);
setSelectedFile(null);
setFileName("");

setTimeout(() => {
setUploadSuccess(false);
}, 1000);
}, 2000);
}
},
(error) => {
Expand All @@ -105,15 +133,23 @@ export function MapClient({ apiUrl }: MapClientProps) {
setIsUploading(false);
}
},
[uploadFile],
[uploadFile, fileName],
);

// Abort Upload Handler
const handleAbortUpload = useCallback(() => {
// Implement abort logic here if needed
setIsUploading(false);
setUploadProgress(0);
setUploadError("Upload cancelled");
setSelectedFile(null);
setFileName("");
}, []);

// Cancel File Selection
const handleCancelSelection = useCallback(() => {
setSelectedFile(null);
setFileName("");
setUploadError(null);
}, []);

// Layer Management
Expand Down Expand Up @@ -142,8 +178,10 @@ export function MapClient({ apiUrl }: MapClientProps) {
const handleModalClose = useCallback(() => {
setIsModalOpen(false);
setSelectedItem(null);
setUploadSuccess(false);
setUploadError(null);
setUploadSuccess(false);
setUploadError(null);
setSelectedFile(null);
setFileName("");
}, []);

return (
Expand Down Expand Up @@ -173,11 +211,16 @@ export function MapClient({ apiUrl }: MapClientProps) {
uploadProgress={uploadProgress}
onAbortUpload={handleAbortUpload}
layers={layers}
selectedFile={selectedFile}
fileName={fileName}
onFileSelection={handleFileSelection}
onFileNameChange={setFileName}
onCancelSelection={handleCancelSelection}
/>
<BaseLayerNavigation
onBaseItemClick={handleBaseItemClick}
selectedBaseItem={selectedBaseItem}
/>
</div>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { ModalProps, MainMapNav } from "./types";
import { useRouter } from "next/navigation";

// Loading dots for file upload
const LoadingDots = () => (
<div className="flex gap-1">
<div className="h-2 w-2 rounded-full bg-blue-500 animate-[bounce_1s_ease-in-out_infinite]"></div>
Expand All @@ -19,6 +20,7 @@ const LoadingDots = () => (
</div>
);

// Modals for uploading files, viewing layers etc
const MapModal: React.FC<ModalProps> = ({
isOpen,
onClose,
Expand All @@ -28,14 +30,16 @@ const MapModal: React.FC<ModalProps> = ({
isUploading,
error,
uploadSuccess,
uploadProgress
uploadProgress,
selectedFile,
fileName,
onFileSelection,
onFileNameChange,
onCancelSelection
}) => {
const router = useRouter();

// Add a key state that changes when upload is successful
const [uploadKey, setUploadKey] = React.useState(0);

// Reset the key when upload is successful
React.useEffect(() => {
if (uploadSuccess) {
setUploadKey(prev => prev + 1);
Expand All @@ -61,7 +65,13 @@ const MapModal: React.FC<ModalProps> = ({
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
onLayerUpload(file);
onFileSelection(file);
}
};

const handleUploadClick = () => {
if (selectedFile) {
onLayerUpload(selectedFile);
}
};

Expand All @@ -86,24 +96,61 @@ const MapModal: React.FC<ModalProps> = ({
<h2 className="text-xl font-bold mb-4">Upload</h2>

{/* Upload Section */}
<div className="mb-6">
<label
key={uploadKey}
className="flex flex-col items-center px-4 py-6 bg-white rounded-lg shadow-lg border-2 border-dashed border-gray-300 hover:border-blue-400 cursor-pointer transition-colors"
>
<Upload className="w-8 h-8 text-gray-400 mb-2" />
<span className="text-sm text-gray-500">
Click to upload a file
</span>
<input
type="file"
className="hidden"
onChange={handleFileChange}
accept=".geojson,.json,.gpkg"
disabled={isUploading}
/>
</label>
</div>
{!selectedFile ? (
<div className="mb-6">
<label
key={uploadKey}
className="flex flex-col items-center px-4 py-6 bg-white rounded-lg shadow-lg border-2 border-dashed border-gray-300 hover:border-blue-400 cursor-pointer transition-colors"
>
<Upload className="w-8 h-8 text-gray-400 mb-2" />
<span className="text-sm text-gray-500">
Click to upload a file
</span>
<input
type="file"
className="hidden"
onChange={handleFileChange}
accept=".geojson,.json,.gpkg"
disabled={isUploading}
/>
</label>
</div>
) : (
<div className="mb-6 space-y-4">
<div className="p-4 bg-gray-50 rounded-lg">
<p className="text-sm text-gray-600 mb-2">
Selected file: {selectedFile.name}
</p>
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700">
Enter layer name:
</label>
<input
type="text"
value={fileName}
onChange={(e) => onFileNameChange(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Enter layer name"
/>
</div>
<div className="mt-4 flex space-x-2">
<button
onClick={handleUploadClick}
disabled={!fileName.trim() || isUploading}
className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"
>
Upload
</button>
<button
onClick={onCancelSelection}
className="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300"
>
Cancel
</button>
</div>
</div>
</div>
)}

{isUploading && (
<div className="flex items-center justify-center space-x-2 text-blue-500 mb-4">
Expand Down Expand Up @@ -211,4 +258,4 @@ const MapModal: React.FC<ModalProps> = ({
);
};

export default MapModal;
export default MapModal;
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export interface ModalProps {
uploadSuccess: boolean;
uploadProgress: number;
onAbortUpload: () => void;
selectedFile: File | null;
fileName: string;
onFileSelection: (file: File) => void;
onFileNameChange: (name: string) => void;
onCancelSelection: () => void;
}

/* Map Edit Items */
Expand Down
21 changes: 19 additions & 2 deletions gridwalk-ui/src/app/workspace/[workspaceId]/projectModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,21 @@ export const DeleteProjectModal: React.FC<DeleteProjectModalProps> = ({
}) => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [confirmText, setConfirmText] = useState("");

const isDeleteEnabled = confirmText === "DELETE";

const handleDelete = async (e: React.FormEvent) => {
e.preventDefault();
if (!isDeleteEnabled) return;

setIsLoading(true);
setError(null);

try {
await onConfirm();
onClose();
setConfirmText("");
} catch (err) {
setError(err instanceof Error ? err.message : "An error occurred");
} finally {
Expand Down Expand Up @@ -153,10 +159,21 @@ export const DeleteProjectModal: React.FC<DeleteProjectModalProps> = ({
Are you sure you want to delete{" "}
<span className="font-semibold">{projectName}</span>?
</p>
<p className="text-gray-600 text-sm">
<p className="text-gray-600 text-sm mb-4">
This action cannot be undone. All project data will be permanently
removed.
</p>
<p className="text-gray-700 text-sm mb-2">
Type <span className="font-mono font-bold">DELETE</span> to confirm:
</p>
<input
type="text"
value={confirmText}
onChange={(e) => setConfirmText(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md text-gray-900 placeholder:text-gray-500 focus:outline-none focus:ring-2 focus:ring-red-500"
placeholder="Enter Text..."
disabled={isLoading}
/>
</div>

<form onSubmit={handleDelete}>
Expand All @@ -171,7 +188,7 @@ export const DeleteProjectModal: React.FC<DeleteProjectModalProps> = ({
</button>
<button
type="submit"
disabled={isLoading}
disabled={isLoading || !isDeleteEnabled}
className="px-4 py-2 bg-red-500 text-white rounded-md hover:bg-red-600 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
>
{isLoading ? (
Expand Down
Loading

0 comments on commit 57e466b

Please sign in to comment.