Changed all image/video URLs from ads.dine360ads.com/api/ to backend.dine360ads.com/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
626 lines
19 KiB
JavaScript
626 lines
19 KiB
JavaScript
import { useEffect, useState, useMemo } from "react";
|
||
import { useNavigate, useParams } from "react-router-dom";
|
||
import { motion } from "framer-motion";
|
||
import { FaArrowLeft, FaTrash, FaUpload } from "react-icons/fa";
|
||
import { api } from "../API/api";
|
||
import { FaPlus, FaCircle } from "react-icons/fa";
|
||
import Select from 'react-select';
|
||
import canada_cities from '../assets/canada_cities.json';
|
||
import { useLoading } from "../Context/LoadingContext";
|
||
import { FiPlus } from "react-icons/fi";
|
||
|
||
const ManagePartner = () => {
|
||
const { id } = useParams();
|
||
const [files, setFiles] = useState([]);
|
||
const [partners, setPartners] = useState([]);
|
||
const [screens, setScreens] = useState([]);
|
||
const [uploading, setUploading] = useState(false);
|
||
const navigate = useNavigate();
|
||
const [isEnabled, setIsEnabled] = useState(true);
|
||
const { setLoading } = useLoading();
|
||
const [formData, setFormData] = useState({
|
||
transid: "",
|
||
name: "",
|
||
logo_url: "",
|
||
open_time: "",
|
||
close_time: "",
|
||
address: "",
|
||
city: "",
|
||
state: "",
|
||
pincode: "",
|
||
screens: "",
|
||
yt: "",
|
||
scrolltext: ""
|
||
});
|
||
|
||
const [selectedProvince, setSelectedProvince] = useState(null);
|
||
const [selectedCity, setSelectedCity] = useState(null);
|
||
|
||
const provinceOptions = useMemo(
|
||
() =>
|
||
Object.keys(canada_cities).map((province) => ({
|
||
value: province,
|
||
label: province,
|
||
})),
|
||
[canada_cities]
|
||
);
|
||
|
||
const cityOptions = useMemo(
|
||
() =>
|
||
selectedProvince
|
||
? canada_cities[selectedProvince.value].map((city) => ({
|
||
value: city,
|
||
label: city,
|
||
}))
|
||
: [],
|
||
[selectedProvince, canada_cities]
|
||
);
|
||
|
||
useEffect(() => {
|
||
fetchPartners();
|
||
fetchScreens();
|
||
}, [id]);
|
||
|
||
const fetchFiles = async () => {
|
||
try {
|
||
const response = await api.get(`/client/files/${id}`);
|
||
setFiles(response);
|
||
} catch (error) {
|
||
console.error("Error fetching files:", error);
|
||
}
|
||
};
|
||
|
||
const fetchPartners = async () => {
|
||
try {
|
||
|
||
const response = await api.get(`/admin/get-partner/${id}`);
|
||
setPartners(response);
|
||
setFormData(response[0]);
|
||
if (response[0]) {
|
||
setSelectedProvince({ value: response[0].state, label: response[0].state });
|
||
setSelectedCity({ value: response[0].city, label: response[0].city });
|
||
}
|
||
} catch (error) {
|
||
console.error("Error fetching files:", error);
|
||
}
|
||
};
|
||
|
||
const fetchScreens = async () => {
|
||
try {
|
||
const response = await api.get(`/admin/get-screen/${id}`);
|
||
console.log(response)
|
||
setScreens(response);
|
||
console.log(response)
|
||
} catch (error) {
|
||
console.error("Error fetching files:", error);
|
||
}
|
||
};
|
||
|
||
const handleFileUpload = async (file) => {
|
||
if (!file) return;
|
||
|
||
setUploading(true);
|
||
const formData = new FormData();
|
||
formData.append("file", file);
|
||
formData.append("file_name", file.name);
|
||
formData.append("client_id", id); // Attach client ID
|
||
|
||
try {
|
||
setLoading(true)
|
||
await api.post("/files/update-partner-logo", formData, {
|
||
headers: { "Content-Type": "multipart/form-data" },
|
||
});
|
||
navigate("/partners");
|
||
|
||
// Refresh file list after each successful upload
|
||
fetchFiles();
|
||
} catch (error) {
|
||
console.error(`File upload failed for ${file.name}:`, error);
|
||
// Optionally, you could add some user feedback here for individual file failures
|
||
} finally {
|
||
setUploading(false); // Keep this false, as we're handling individual uploads
|
||
setLoading(false)
|
||
}
|
||
};
|
||
|
||
const handleFileChange = async (e) => {
|
||
console.log("oiouyt")
|
||
await handleFileUpload(e.target.files[0]);
|
||
setFormData({ ...formData, image: e.target.files[0] });
|
||
};
|
||
|
||
const handleChange = (e) => {
|
||
const { name, value } = e.target;
|
||
setFormData({ ...formData, [name]: value });
|
||
};
|
||
|
||
const handleProvinceChange = (selectedOption) => {
|
||
setSelectedProvince(selectedOption);
|
||
setSelectedCity(null);
|
||
setFormData({ ...formData, state: selectedOption?.value || "" });
|
||
};
|
||
|
||
const handleCityChange = (selectedOption) => {
|
||
setSelectedCity(selectedOption);
|
||
setFormData({ ...formData, city: selectedOption?.value || "" });
|
||
};
|
||
|
||
const handleEdit = () => {
|
||
setIsEnabled(false);
|
||
};
|
||
|
||
const handleUpdate = async (e) => {
|
||
|
||
e.preventDefault();
|
||
try {
|
||
console.log(formData)
|
||
|
||
await api.post("/admin/update-partner", formData);
|
||
navigate("/partners");
|
||
|
||
} catch (error) {
|
||
console.error("Error updating Partner:", error);
|
||
}
|
||
};
|
||
|
||
const AddScreen = async () => {
|
||
|
||
try {
|
||
console.log({ partnerid: id })
|
||
setLoading(true);
|
||
var data = await api.post("/admin/add-screen", { partnerid: id });
|
||
console.log(data)
|
||
|
||
fetchScreens();
|
||
} catch (error) {
|
||
console.error("Error adding Screen:", error);
|
||
}
|
||
setLoading(false);
|
||
}
|
||
|
||
|
||
const DeleteScreen = async (id) => {
|
||
|
||
try {
|
||
|
||
setLoading(true);
|
||
var data = await api.post("/admin/delete-screen", { screenid: id });
|
||
console.log(data)
|
||
|
||
fetchScreens();
|
||
} catch (error) {
|
||
console.error("Error adding Screen:", error);
|
||
}
|
||
setLoading(false);
|
||
}
|
||
const handleToggleYouTube = async (screenid, sts, idx) => {
|
||
try {
|
||
setLoading(true);
|
||
// call your API – adjust the endpoint & payload as needed
|
||
await api.post("/admin/update-screen-youtube", {
|
||
screenid,
|
||
sts: sts ? 1 : 0,
|
||
});
|
||
|
||
fetchScreens()
|
||
} catch (err) {
|
||
console.error("Error toggling YouTube:", err);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
|
||
|
||
return (
|
||
<div className="min-h-screen bg-gray-900 text-white p-8">
|
||
{/* Back button */}
|
||
<motion.button
|
||
className="mb-4 flex items-center text-gray-400 hover:text-white"
|
||
whileHover={{ scale: 1.1 }}
|
||
onClick={() => navigate("/Partners")}
|
||
>
|
||
<FaArrowLeft className="mr-2" /> Back
|
||
</motion.button>
|
||
|
||
{/* Header */}
|
||
<motion.h1
|
||
className="text-3xl font-bold mb-6"
|
||
initial={{ opacity: 0, y: -20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
>
|
||
Manage Partners
|
||
</motion.h1>
|
||
|
||
{/* Form */}
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
{/* Partner Name */}
|
||
<div>
|
||
<label htmlFor="name" className="block text-sm font-medium text-gray-300">
|
||
Partner Name
|
||
</label>
|
||
<input
|
||
type="text"
|
||
name="name"
|
||
id="name"
|
||
value={formData.name || ""}
|
||
onChange={handleChange}
|
||
disabled={isEnabled}
|
||
className="mt-1 block w-full bg-gray-700 p-2 rounded-lg"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
{/* Logo */}
|
||
|
||
|
||
{/* Open Time */}
|
||
<div>
|
||
<label htmlFor="open_time" className="block text-sm font-medium text-gray-300">
|
||
Open Time
|
||
</label>
|
||
<input
|
||
type="time"
|
||
name="open_time"
|
||
id="open_time"
|
||
value={formData.open_time || ""}
|
||
onChange={handleChange}
|
||
disabled={isEnabled}
|
||
className="mt-1 block w-full bg-gray-700 p-2 rounded-lg"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
{/* Close Time */}
|
||
<div>
|
||
<label htmlFor="close_time" className="block text-sm font-medium text-gray-300">
|
||
Close Time
|
||
</label>
|
||
<input
|
||
type="time"
|
||
name="close_time"
|
||
id="close_time"
|
||
value={formData.close_time || ""}
|
||
onChange={handleChange}
|
||
disabled={isEnabled}
|
||
className="mt-1 block w-full bg-gray-700 p-2 rounded-lg"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
{/* Address */}
|
||
<div>
|
||
<label htmlFor="address" className="block text-sm font-medium text-gray-300">
|
||
Address
|
||
</label>
|
||
<input
|
||
type="text"
|
||
name="address"
|
||
id="address"
|
||
value={formData.address || ""}
|
||
onChange={handleChange}
|
||
disabled={isEnabled}
|
||
className="mt-1 block w-full bg-gray-700 p-2 rounded-lg"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
{/* Province */}
|
||
<div>
|
||
<label htmlFor="state" className="block text-sm font-medium text-gray-300">
|
||
Province
|
||
</label>
|
||
<Select
|
||
options={provinceOptions}
|
||
value={selectedProvince}
|
||
onChange={handleProvinceChange}
|
||
isDisabled={isEnabled}
|
||
className="mt-1"
|
||
styles={{ /* your existing styles */ }}
|
||
placeholder="Select Province"
|
||
/>
|
||
</div>
|
||
|
||
{/* City */}
|
||
<div>
|
||
<label htmlFor="city" className="block text-sm font-medium text-gray-300">
|
||
City
|
||
</label>
|
||
<Select
|
||
options={cityOptions}
|
||
value={selectedCity}
|
||
onChange={handleCityChange}
|
||
isDisabled={isEnabled || !selectedProvince}
|
||
className="mt-1"
|
||
styles={{ /* your existing styles */ }}
|
||
placeholder="Select City"
|
||
/>
|
||
</div>
|
||
|
||
{/* Pincode */}
|
||
<div>
|
||
<label htmlFor="pincode" className="block text-sm font-medium text-gray-300">
|
||
Pincode
|
||
</label>
|
||
<input
|
||
type="text"
|
||
name="pincode"
|
||
id="pincode"
|
||
value={formData.pincode || ""}
|
||
onChange={handleChange}
|
||
disabled={isEnabled}
|
||
className="mt-1 block w-full bg-gray-700 p-2 rounded-lg"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
{/* No. of Screens */}
|
||
<div>
|
||
<label htmlFor="screens" className="block text-sm font-medium text-gray-300">
|
||
No. of Screens
|
||
</label>
|
||
<input
|
||
type="number"
|
||
name="screens"
|
||
id="screens"
|
||
value={formData.screens || ""}
|
||
onChange={handleChange}
|
||
disabled={isEnabled}
|
||
className="mt-1 block w-full bg-gray-700 p-2 rounded-lg"
|
||
required
|
||
min="1"
|
||
/>
|
||
</div>
|
||
<div>
|
||
|
||
<label htmlFor="yt" className="block text-sm font-medium text-gray-700">
|
||
Youtube Links
|
||
</label>
|
||
<textarea
|
||
name="yt"
|
||
id="yt"
|
||
placeholder="Enter YouTube links, separated by commas"
|
||
onChange={handleChange}
|
||
value={formData.yt || ""}
|
||
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
|
||
rows={4}
|
||
required
|
||
disabled={isEnabled}
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label htmlFor="scrolltext" className="block text-sm font-medium text-gray-700">
|
||
Scroll Text
|
||
</label>
|
||
<textarea
|
||
type="text"
|
||
name="scrolltext"
|
||
id="scrolltext"
|
||
placeholder="Scroll Text"
|
||
onChange={handleChange}
|
||
value={formData.scrolltext || ""}
|
||
className="block w-full bg-gray-700 p-2 rounded-lg mb-2"
|
||
required
|
||
disabled={isEnabled}
|
||
></textarea>
|
||
</div>
|
||
<div>
|
||
<label htmlFor="logo_url" className="block text-sm font-medium text-gray-300">
|
||
Logo
|
||
</label>
|
||
<img
|
||
src={`https://backend.dine360ads.com/${formData.logo_url || ""}`}
|
||
alt="Logo"
|
||
className="mt-1 w-[60%] rounded-lg"
|
||
/>
|
||
<input
|
||
type="file"
|
||
name="logo_url"
|
||
id="logo_url"
|
||
accept="image/*"
|
||
onChange={handleFileChange}
|
||
disabled={isEnabled}
|
||
className="mt-2 block w-full bg-gray-700 p-2 rounded-lg"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Edit / Update button */}
|
||
<div className="mt-6">
|
||
{isEnabled ? (
|
||
<motion.button
|
||
type="button"
|
||
onClick={handleEdit}
|
||
className="bg-green-500 px-4 py-2 rounded-lg"
|
||
whileHover={{ scale: 1.05 }}
|
||
>
|
||
Edit
|
||
</motion.button>
|
||
) : (
|
||
<motion.button
|
||
type="button"
|
||
className="bg-blue-500 px-4 py-2 rounded-lg"
|
||
whileHover={{ scale: 1.05 }}
|
||
onClick={handleUpdate}
|
||
>
|
||
Update
|
||
</motion.button>
|
||
)}
|
||
</div>
|
||
|
||
|
||
{/* Screens Preview */}
|
||
{formData.screens && (
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mt-8">
|
||
<div className="bg-gray-800 p-6 rounded-xl shadow-xl flex justify-center items-center ">
|
||
<div className="">
|
||
<motion.button
|
||
type="button"
|
||
onClick={AddScreen}
|
||
className={`
|
||
inline-flex items-center
|
||
bg-gradient-to-r from-blue-500 to-teal-400
|
||
hover:from-blue-600 hover:to-teal-500
|
||
text-white font-semibold
|
||
px-6 py-3 rounded-lg
|
||
shadow-lg focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-300
|
||
transition-transform
|
||
`}
|
||
whileHover={{ scale: 1.05 }}
|
||
whileTap={{ scale: 0.95 }}
|
||
>
|
||
<FiPlus className="w-5 h-5 mr-2" />
|
||
Add New Screen
|
||
</motion.button>
|
||
</div>
|
||
</div>
|
||
|
||
{screens.map((screen, index) => (
|
||
|
||
<div key={index} className="bg-gray-800 p-4 rounded-lg shadow-md relative">
|
||
{/* Delete Button - Top Right of Card */}
|
||
<motion.button
|
||
type="button"
|
||
className="absolute top-3 right-3 text-red-500 hover:text-red-700 bg-white bg-opacity-10 rounded-full p-2 transition z-10"
|
||
whileHover={{ scale: 1.2, rotate: 15 }}
|
||
onClick={() => {
|
||
if (window.confirm("Are you sure you want to delete this screen?")) {
|
||
DeleteScreen(screen.transid);
|
||
}
|
||
}}
|
||
title="Delete this screen"
|
||
>
|
||
<FaTrash />
|
||
</motion.button>
|
||
|
||
<h3 className="text-lg font-semibold mb-2">
|
||
Screen {index + 1} ({id.substring(0, 2) + 'S' + (index + 1)})
|
||
</h3>
|
||
|
||
<div className="flex flex-wrap items-center gap-3 relative">
|
||
{/* Preview Button */}
|
||
<motion.button
|
||
type="button"
|
||
className="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-lg shadow transition"
|
||
whileHover={{ scale: 1.07 }}
|
||
onClick={() => {
|
||
const prefix = id.substring(0, 5);
|
||
const screenName = `Screen${index + 1}`.replace(/\s+/g, '');
|
||
window.open(`${window.location.origin}/ads/${prefix}/${screenName}`, '_blank');
|
||
}}
|
||
>
|
||
Preview
|
||
</motion.button>
|
||
|
||
{/* Copy Link Button */}
|
||
<motion.button
|
||
type="button"
|
||
className="bg-green-600 hover:bg-green-700 px-4 py-2 rounded-lg shadow transition"
|
||
whileHover={{ scale: 1.07 }}
|
||
onClick={() => {
|
||
const prefix = id.substring(0, 5);
|
||
const screenName = `Screen${index + 1}`.replace(/\s+/g, '');
|
||
navigator.clipboard.writeText(`${window.location.origin}/ads/${prefix}/${screenName}`);
|
||
alert('Link copied to clipboard!');
|
||
}}
|
||
>
|
||
Copy Link
|
||
</motion.button>
|
||
|
||
{/* YouTube Toggle Switch */}
|
||
<motion.div
|
||
className="flex items-center gap-2"
|
||
whileHover={{ scale: 1.05 }}
|
||
title={screen.isyoutube === 1 ? "YouTube is enabled for this screen" : "YouTube is disabled for this screen"}
|
||
>
|
||
<label className="relative inline-flex items-center cursor-pointer">
|
||
<input
|
||
type="checkbox"
|
||
checked={screen.isyoutube === 1}
|
||
onChange={() => handleToggleYouTube(screen.transid, screen.isyoutube !== 1, index)}
|
||
className="sr-only peer"
|
||
/>
|
||
<div className={`w-14 h-7 bg-gray-400 rounded-full peer peer-checked:bg-amber-400 peer-focus:ring-2 peer-focus:ring-amber-300 transition-all duration-300`}>
|
||
<div
|
||
className={`absolute left-0 top-0 w-7 h-7 rounded-full bg-white shadow-md transform transition-all duration-300 ${screen.isyoutube === 1 ? 'translate-x-7' : ''}`}
|
||
></div>
|
||
</div>
|
||
<span className={`ml-4 text-sm font-medium ${screen.isyoutube === 1 ? 'text-amber-300' : 'text-fuchsia-300'}`}>
|
||
{screen.isyoutube === 1 ? "YouTube ON" : "YouTube OFF"}
|
||
</span>
|
||
</label>
|
||
</motion.div>
|
||
|
||
|
||
<motion.div
|
||
className="ml-2"
|
||
whileHover={{ scale: 1.05 }}
|
||
title="Change Screen Type"
|
||
>
|
||
<select
|
||
value={screen.screentype || 1}
|
||
onChange={async (e) => {
|
||
const newType = Number(e.target.value);
|
||
try {
|
||
console.log(screen.screentype)
|
||
setLoading(true);
|
||
await api.post("/admin/update-screen-type", {
|
||
screenid: screen.transid,
|
||
sts: newType,
|
||
});
|
||
fetchScreens();
|
||
} catch (err) {
|
||
console.error("Error updating screen type:", err);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}}
|
||
className="bg-gray-700 text-white rounded-lg w-full px-3 py-1 focus:outline-none focus:ring-2 focus:ring-blue-400"
|
||
>
|
||
<option value={1}>Screen Type 1 (Normal)</option>
|
||
{/* <option value={2}>Screen Type 2 (InHouse)</option> */}
|
||
<option value={3}>Screen Type 3 (In-house and Scroll Text)</option>
|
||
</select>
|
||
</motion.div>
|
||
|
||
|
||
{/* Status Indicator */}
|
||
<div className="flex items-center ml-auto">
|
||
<FaCircle
|
||
className={`mr-2 ${screen.isactive === 1
|
||
? "text-green-400"
|
||
: screen.isactive === 2
|
||
? "text-orange-400"
|
||
: "text-red-400"
|
||
}`}
|
||
/>
|
||
<span className="uppercase font-semibold tracking-wide">
|
||
{screen.isactive === 1 ? "ACTIVE" : screen.isactive === 2 ? "IDLE" : "INACTIVE"}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<br />
|
||
<div>
|
||
<br />
|
||
{screen.status}
|
||
<br />
|
||
{screen.stsupdat && (
|
||
<div>
|
||
{new Date(screen.stsupdat).toLocaleDateString('en-GB')}{' '}
|
||
{new Date(screen.stsupdat).toLocaleTimeString()}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
))}
|
||
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
|
||
};
|
||
|
||
export default ManagePartner; |