initial commit - code moved to separate repo
This commit is contained in:
58
components/location/LocationCard.js
Normal file
58
components/location/LocationCard.js
Normal file
@ -0,0 +1,58 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import axiosInstance from '../../src/axiosSecure';
|
||||
|
||||
import { TrashIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
|
||||
|
||||
export default function LocationCard({ location }) {
|
||||
|
||||
const router = useRouter();
|
||||
const [isCardVisible, setIsCardVisible] = useState(true);
|
||||
const handleDelete = async (id) => {
|
||||
try {
|
||||
console.log("card: deleting location = ", id, "url: ", `/locations/${id}`);
|
||||
const response = await axiosInstance.delete(`/api/data/locations/${id}`);
|
||||
if (response.status === 200) {
|
||||
document.getElementById(`location-card-${id}`).classList.add('cardFadeOut');
|
||||
setTimeout(() => setIsCardVisible(false), 300);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(JSON.stringify(error));
|
||||
}
|
||||
};
|
||||
|
||||
return isCardVisible ? (
|
||||
<>
|
||||
<div
|
||||
id={`location-card-${location.id}`}
|
||||
className={`relative block p-6 max-w-sm rounded-lg border border-gray-200 shadow-md hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700 mb-3 cursor-pointer ${location.isactive ? 'text-gray-900 dark:text-white font-bold' : 'text-gray-400 dark:text-gray-600'}`}
|
||||
onClick={() => router.push(`/cart/locations/edit/${location.id}`)}
|
||||
>
|
||||
<h5 className={`mb-2 text-2xl tracking-tight`}>
|
||||
{location.name} ({location.isactive ? "active" : "inactive"})
|
||||
</h5>
|
||||
<p className="font-normal text-gray-700 dark:text-gray-200">
|
||||
{location.address}
|
||||
</p>
|
||||
<div
|
||||
onClick={(e) => {
|
||||
e.stopPropagation(); // This should now work as expected
|
||||
handleDelete(location.id);
|
||||
}}
|
||||
className="absolute bottom-2 right-2 z-20"
|
||||
>
|
||||
<button
|
||||
aria-label="Delete location"
|
||||
className="text-red-600 bg-transparent hover:bg-red-100 p-1 hover:border-red-700 rounded"
|
||||
>
|
||||
<TrashIcon className="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : null;
|
||||
|
||||
}
|
272
components/location/LocationForm.js
Normal file
272
components/location/LocationForm.js
Normal file
@ -0,0 +1,272 @@
|
||||
import axiosInstance from '../../src/axiosSecure';
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import toast from "react-hot-toast";
|
||||
import Link from "next/link";
|
||||
import DayOfWeek from "../DayOfWeek";
|
||||
import TextEditor from "../TextEditor";
|
||||
import FileUploadWithPreview from 'components/FileUploadWithPreview ';
|
||||
|
||||
import ProtectedRoute, { serverSideAuth } from "../..//components/protectedRoute";
|
||||
import { UserRole } from "@prisma/client";
|
||||
|
||||
const common = require('src/helpers/common');
|
||||
|
||||
// ------------------ LocationForm ------------------
|
||||
// This component is used to create and edit locations
|
||||
// location model:
|
||||
// model Location {
|
||||
// id Int @id @default(autoincrement())
|
||||
// name String
|
||||
// address String
|
||||
// isactive Boolean @default(true)
|
||||
// content String? @db.Text
|
||||
// cartEvents CartEvent[]
|
||||
// reports Report[]
|
||||
|
||||
// backupLocationId Int?
|
||||
// backupLocation Location? @relation("BackupLocation", fields: [backupLocationId], references: [id])
|
||||
// BackupForLocations Location[] @relation("BackupLocation")
|
||||
// }
|
||||
|
||||
export default function LocationForm() {
|
||||
const [uploadedImages, setUploadedImages] = useState([]);
|
||||
|
||||
const [isPreviewMode, setIsPreviewMode] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUploadedImages = async () => {
|
||||
try {
|
||||
const response = await axiosInstance.get('/uploaded-images');
|
||||
setUploadedImages(response.data.imageUrls);
|
||||
} catch (error) {
|
||||
console.error('Error fetching uploaded images:', error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchUploadedImages();
|
||||
}, []);
|
||||
|
||||
const quillRef = useRef(null);
|
||||
const handleImageSelect = (e) => {
|
||||
const imageUrl = e.target.value;
|
||||
if (imageUrl && quillRef.current) {
|
||||
const editor = quillRef.getQuill();
|
||||
const range = editor.getSelection(true);
|
||||
if (range) {
|
||||
editor.insertEmbed(range.index, 'image', imageUrl);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const [content, setContent] = useState("");
|
||||
|
||||
const [location, set] = useState({
|
||||
name: "",
|
||||
address: "",
|
||||
isactive: true,
|
||||
});
|
||||
|
||||
// const [isEdit, setIsEdit] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLocation = async (id) => {
|
||||
try {
|
||||
console.log("fetching location " + router.query.id);
|
||||
const { data } = await axiosInstance.get("/api/data/locations/" + id);
|
||||
set(data);
|
||||
setContent(data.content);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
if (router.query?.id) {
|
||||
fetchLocation(parseInt(router.query.id.toString()));
|
||||
}
|
||||
console.log("called");
|
||||
}, [router.query.id]);
|
||||
|
||||
|
||||
const [locations, setLocations] = useState([]);
|
||||
useEffect(() => {
|
||||
const fetchLocations = async () => {
|
||||
try {
|
||||
console.log("fetching locations");
|
||||
const { data } = await axiosInstance.get("/api/data/locations");
|
||||
setLocations(data);
|
||||
console.log(data);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
if (!locations.length) {
|
||||
fetchLocations();
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
const handleChange = ({ target }) => {
|
||||
if (target.type === "checkbox") {
|
||||
set({ ...location, [target.name]: target.checked });
|
||||
} else if (target.type === "number") {
|
||||
set({ ...location, [target.name]: parseInt(target.value) });
|
||||
} else {
|
||||
set({ ...location, [target.name]: target.value });
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const dataToSend = {
|
||||
...location,
|
||||
name: location.name.trim(),
|
||||
content: content,
|
||||
};
|
||||
|
||||
if (router.query?.id) { // UPDATE
|
||||
//connect backup location
|
||||
delete dataToSend.id;
|
||||
dataToSend.backupLocationId = parseInt(dataToSend.backupLocationId);
|
||||
// dataToSend.backupLocation = { connect: { id: location.backupLocationId } };
|
||||
// delete dataToSend.backupLocationId;
|
||||
await axiosInstance.put("/api/data/locations/" + router.query.id, {
|
||||
...dataToSend,
|
||||
});
|
||||
toast.success("Task Updated", {
|
||||
position: "bottom-center",
|
||||
});
|
||||
} else { // CREATE
|
||||
await axiosInstance.post("/api/data/locations", dataToSend);
|
||||
toast.success("Task Saved", {
|
||||
position: "bottom-center",
|
||||
});
|
||||
}
|
||||
|
||||
router.push("/cart/locations");
|
||||
} catch (error) {
|
||||
//toast.error(error.response.data.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<ProtectedRoute allowedRoles={[UserRole.ADMIN]} deniedMessage="">
|
||||
<div className="w-full max-lg">
|
||||
<form className="bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4" onSubmit={handleSubmit} >
|
||||
<div className="mb-4">
|
||||
<label className="label" htmlFor="name">Location Name</label>
|
||||
<input className="textbox" placeholder="name" id="name" name="name" onChange={handleChange} value={location.name} autoComplete="off" />
|
||||
</div>
|
||||
{/* Location.address */}
|
||||
<div className="mb-4">
|
||||
<label className="label" htmlFor="address"> Address</label>
|
||||
<input className="textbox"
|
||||
placeholder="address" id="address" name="address" onChange={handleChange} value={location.address} autoComplete="off" />
|
||||
</div>
|
||||
{/* UI for Location.isactive */}
|
||||
<div className="mb-4">
|
||||
<div className="form-check">
|
||||
<input className="checkbox form-input" type="checkbox" id="isactive" name="isactive" onChange={handleChange} checked={location.isactive} autoComplete="off" />
|
||||
<label className="label" htmlFor="isactive">Активна</label>
|
||||
</div>
|
||||
</div>
|
||||
{/* backupLocation */}
|
||||
<div className="mb-4">
|
||||
<label className="label" htmlFor="backupLocation">При дъжд и лошо време</label>
|
||||
{locations && (
|
||||
<select name="backupLocationId" id="backupLocationId" onChange={handleChange} value={location.backupLocationId} placeholder="Избери локация...">
|
||||
|
||||
<option>Избери локация...</option>
|
||||
{locations.map((loc) => (
|
||||
<option key={loc.id} value={loc.id} type="number">{loc.name}</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Location.content */}
|
||||
<div className="mb-4">
|
||||
{/* <select onChange={handleImageSelect}>
|
||||
<option>Select an image</option>
|
||||
{uploadedImages.map((imageUrl, index) => (
|
||||
<option key={index} value={imageUrl}>{imageUrl}</option>
|
||||
))}
|
||||
</select> */}
|
||||
{router.query.id && (
|
||||
<>
|
||||
<div className="flex space-x-4">
|
||||
<FileUploadWithPreview
|
||||
name="picture1"
|
||||
label="Снимка 1"
|
||||
value={location.picture1}
|
||||
prefix={`location-${router.query.id}-picture1`}
|
||||
onUpload={(name, imageUrl) => {
|
||||
console.log('Uploaded image URL:', imageUrl);
|
||||
set(location => ({ ...location, [name]: imageUrl }));
|
||||
}}
|
||||
/>
|
||||
|
||||
<FileUploadWithPreview
|
||||
name="picture2"
|
||||
label="Снимка 2"
|
||||
value={location.picture2}
|
||||
prefix={`location-${router.query.id}-picture2`}
|
||||
onUpload={(name, imageUrl) => {
|
||||
console.log('Uploaded image URL:', imageUrl);
|
||||
set(location => ({ ...location, [name]: imageUrl }));
|
||||
}}
|
||||
/>
|
||||
<FileUploadWithPreview
|
||||
name="picture3"
|
||||
label="Снимка 3"
|
||||
value={location.picture3}
|
||||
prefix={`location-${router.query.id}-picture3`}
|
||||
onUpload={(name, imageUrl) => {
|
||||
console.log('Uploaded image URL:', imageUrl);
|
||||
set(location => ({ ...location, [name]: imageUrl }));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="label" htmlFor="content">Content</label>
|
||||
|
||||
<TextEditor
|
||||
ref={quillRef}
|
||||
value={content}
|
||||
onChange={setContent}
|
||||
placeholder="Описание на локацията. Снимки"
|
||||
prefix={`location-${router.query.id}`} />
|
||||
</>)}
|
||||
</div>
|
||||
<div className="panel-actions pt-12">
|
||||
<Link href="/cart/locations" className="action-button"> обратно </Link>
|
||||
<button className="button bg-blue-500 hover:bg-blue-700 focus:outline-none focus:shadow-outline" type="submit">
|
||||
{router.query?.id ? "Update" : "Save"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<button className='button' onClick={() => setIsPreviewMode(!isPreviewMode)}>
|
||||
{isPreviewMode ? 'Скрий потребителския изглед' : 'Виж какво ще се покаже на потребителите'}
|
||||
</button>
|
||||
</ProtectedRoute>
|
||||
<ProtectedRoute allowedRoles={[UserRole.USER]} deniedMessage=" " bypass={isPreviewMode}>
|
||||
<label className="label" htmlFor="backupLocation">При дъжд и лошо време</label>
|
||||
{location.name}
|
||||
{location.address}
|
||||
{location.backupLocationName}
|
||||
<div className="border-2 border-blue-500 p-5 my-5 rounded-lg" dangerouslySetInnerHTML={{ __html: content }}></div>
|
||||
</ProtectedRoute>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user