Dine360-Ads-Frontend/src/Pages/ManagePartner.jsx
MOHAN 6e8694ba96 Update resource URL to backend.dine360ads.com
Changed all image/video URLs from ads.dine360ads.com/api/ to backend.dine360ads.com/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 15:32:57 +05:30

626 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;