From f506870af7064849c06203f1b50a7db8991ce689 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Wed, 2 Jul 2025 16:55:13 +0700 Subject: [PATCH 001/248] thanh --- src/pages/auth/sign-in.jsx | 113 +++++++++++++++++++++++++++---------- 1 file changed, 82 insertions(+), 31 deletions(-) diff --git a/src/pages/auth/sign-in.jsx b/src/pages/auth/sign-in.jsx index 3b3da41a..8a93c8cc 100644 --- a/src/pages/auth/sign-in.jsx +++ b/src/pages/auth/sign-in.jsx @@ -1,14 +1,61 @@ -import { - Card, - Input, - Checkbox, - Button, - Typography, -} from "@material-tailwind/react"; -import { Link } from "react-router-dom"; +import React, {useRef, useState} from "react"; +import {Card, Input, Checkbox, Button, Typography,} from "@material-tailwind/react"; +import { Link, useNavigate } from "react-router-dom"; +import { data } from "autoprefixer"; + + export function SignIn() { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [emailError, setEmailError] = useState(""); + const [passwordError, setPasswordError] = useState(""); + const navigate = useNavigate(); + + const emailRef = useRef(); + const passwordRef = useRef(); + + const handleLogin = async (e) => { + e.preventDefault(); + + setEmailError(""); + setPasswordError(""); + + if (!email.trim()) { + setEmailError("Vui lòng nhập Email"); + emailRef.current?.focus(); + return; + } + + if (!password.trim()) { + setPasswordError("Vui lòng nhập Password"); + passwordRef.current?.focus(); + return; + } + + try{ + const res = await fetch('https://api-ndolv2.nongdanonline.vn/auth/login', { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({email, password}), + }); + + const data = await res.json(); + + if(res.ok) { + localStorage.setItem("token", data.accessToken); + alert("Đăng nhập thành công!"); + navigate("/dashboard/home"); + } else { + alert(data.message || "Đăng nhập thất bại") + } + }catch (error) { + console.error("Login error:", error); + alert("Không thể kết nối tới máy chủ, thử lại sau"); + } + + } return (
@@ -16,7 +63,7 @@ export function SignIn() { Sign In Enter your email and password to Sign In.
-
+
Your email @@ -24,11 +71,19 @@ export function SignIn() { setEmail(e.target.value)} className=" !border-t-blue-gray-200 focus:!border-t-gray-900" labelProps={{ className: "before:content-none after:content-none", }} + inputRef={emailRef} /> + {emailError && ( + + {emailError} + + )} Password @@ -36,31 +91,22 @@ export function SignIn() { type="password" size="lg" placeholder="********" + value={password} + onChange={(e) => setPassword(e.target.value)} className=" !border-t-blue-gray-200 focus:!border-t-gray-900" labelProps={{ className: "before:content-none after:content-none", }} + inputRef={passwordRef} /> -
- - I agree the  - - Terms and Conditions - + {passwordError && ( + + {passwordError} - } - containerProps={{ className: "-ml-2.5" }} - /> - @@ -78,13 +124,16 @@ export function SignIn() { containerProps={{ className: "-ml-2.5" }} /> - + Forgot Password - + +
+ + Not registered? - Create account + Create account From 80db9db52658f3f11db64c56455441ebe4dc4f67 Mon Sep 17 00:00:00 2001 From: Longvu003 Date: Wed, 2 Jul 2025 17:28:24 +0700 Subject: [PATCH 002/248] up code Accept Farm --- package.json | 5 +- src/data/Farm-data.js | 34 +++++++++++ src/pages/dashboard/AcceptFarm.jsx | 76 +++++++++++++++++++++++ src/pages/dashboard/index.js | 2 +- src/pages/dashboard/notifications.jsx | 88 --------------------------- src/routes.jsx | 8 +-- 6 files changed, 118 insertions(+), 95 deletions(-) create mode 100644 src/data/Farm-data.js create mode 100644 src/pages/dashboard/AcceptFarm.jsx delete mode 100644 src/pages/dashboard/notifications.jsx diff --git a/package.json b/package.json index 44f14fa6..c018e952 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@heroicons/react": "2.0.18", "@material-tailwind/react": "2.1.4", "apexcharts": "3.44.0", + "axios": "^1.10.0", "prop-types": "15.8.1", "react": "18.2.0", "react-apexcharts": "1.4.1", @@ -27,6 +28,6 @@ "prettier": "3.0.3", "prettier-plugin-tailwindcss": "0.5.6", "tailwindcss": "3.3.4", - "vite": "4.5.0" + "vite": "^4.5.0" } -} \ No newline at end of file +} diff --git a/src/data/Farm-data.js b/src/data/Farm-data.js new file mode 100644 index 00000000..f70d59e2 --- /dev/null +++ b/src/data/Farm-data.js @@ -0,0 +1,34 @@ +export const Requiment = [ + { + id: 1, + nameFarm: "Farm Long vu 1", + content: "Đây là farm cá Rô Phi", + price: 20000, + category: "Cá Rô phi", + img: "https://snnptnt.binhdinh.gov.vn/assets/news//upload/files/cho-ca-an.jpg", + }, + { + id: 2, + nameFarm: "Farm Long vu 2", + content: "Đây là farm cá Thác Lác", + price: 30000, + category: "Thác Lác", + img: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR5WOFLHSW-OL3dvWMaiaVRdU7EoeVxraJAcg&s", + }, + { + id: 3, + nameFarm: "Farm Long vu 3", + content: "Đây là farm cá Trê", + price: 40000, + category: "Cá Trê", + img: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSgivFrBB_KFA9qmJVmDtdQ9AEfcMekxmMmsA&s", + }, + { + id: 4, + nameFarm: "Farm Long vu 4", + content: "Đây là farm cá Diêu Hồng", + price: 45000, + category: "Cá Diêu Hồng", + img: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRriBKfDu-1cnpup36PtENhHpBR6_ET-Xzxjg&s", + }, + ]; \ No newline at end of file diff --git a/src/pages/dashboard/AcceptFarm.jsx b/src/pages/dashboard/AcceptFarm.jsx new file mode 100644 index 00000000..27c01090 --- /dev/null +++ b/src/pages/dashboard/AcceptFarm.jsx @@ -0,0 +1,76 @@ +import React, { useEffect } from "react"; +import { InformationCircleIcon } from "@heroicons/react/24/outline"; +import { useState } from "react"; +import { Requiment } from "@/data/Farm-data"; +import axios from "axios"; +export function AcceptFarm() { + const BaseUrl=`https://api-ndolv2.nongdanonline.vn` + const [Accept, setAccept] = React.useState({}); + const [farm,setFarm]=useState([]) + const AcceptFarm = async (id) => { + // setAccept((prev) => ({ ...prev, [id]: true })); + setFarm((prev) => prev.filter((item) => item.id !== id)); + + }; +const getFarm =async ()=>{ +try { +const res= await axios.get(`${BaseUrl}/farms`) +if(res.status===200){ +setFarm(res.data) +}else{ +console.log("Có lỗi khi lấy data ") +} +} catch (error) { + console.log("Lỗi :",error) +} +} +useEffect(()=>{ + getFarm() +},[]) + return ( +
+
+ + { + farm.length===0 ? (
Không có đơn !
):( ( farm.map((item) => ( + +
+ {item.name} + Vị trí: {item.location} + {item.pricePerMonth} Vnđ + Mã số: {item.code} + {/* { + item.farmImages ?( + {item.nameFarm} + + ):( + + ) + } */} + + + {!Accept[item.id] ? ( + + ) : ( + + )} +
)) + )) + } + +
+
+ ); +} + +export default AcceptFarm; diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index 7651895e..881c908c 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,4 +1,4 @@ export * from "@/pages/dashboard/home"; export * from "@/pages/dashboard/profile"; export * from "@/pages/dashboard/tables"; -export * from "@/pages/dashboard/notifications"; +export * from "@/pages/dashboard/AcceptFarm"; diff --git a/src/pages/dashboard/notifications.jsx b/src/pages/dashboard/notifications.jsx deleted file mode 100644 index f4be88b0..00000000 --- a/src/pages/dashboard/notifications.jsx +++ /dev/null @@ -1,88 +0,0 @@ -import React from "react"; -import { - Typography, - Alert, - Card, - CardHeader, - CardBody, -} from "@material-tailwind/react"; -import { InformationCircleIcon } from "@heroicons/react/24/outline"; - -export function Notifications() { - const [showAlerts, setShowAlerts] = React.useState({ - blue: true, - green: true, - orange: true, - red: true, - }); - const [showAlertsWithIcon, setShowAlertsWithIcon] = React.useState({ - blue: true, - green: true, - orange: true, - red: true, - }); - const alerts = ["gray", "green", "orange", "red"]; - - return ( -
- - - - Alerts - - - - {alerts.map((color) => ( - setShowAlerts((current) => ({ ...current, [color]: false }))} - > - A simple {color} alert with an example link. Give - it a click if you like. - - ))} - - - - - - Alerts with Icon - - - - {alerts.map((color) => ( - - } - onClose={() => setShowAlertsWithIcon((current) => ({ - ...current, - [color]: false, - }))} - > - A simple {color} alert with an example link. Give - it a click if you like. - - ))} - - -
- ); -} - -export default Notifications; diff --git a/src/routes.jsx b/src/routes.jsx index 3a5a8da0..7df9af9e 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -6,7 +6,7 @@ import { ServerStackIcon, RectangleStackIcon, } from "@heroicons/react/24/solid"; -import { Home, Profile, Tables, Notifications } from "@/pages/dashboard"; +import { Home, Profile, Tables, AcceptFarm } from "@/pages/dashboard"; import { SignIn, SignUp } from "@/pages/auth"; const icon = { @@ -37,9 +37,9 @@ export const routes = [ }, { icon: , - name: "notifications", - path: "/notifications", - element: , + name: "Accept Farms", + path: "/AcceptFarm", + element: , }, ], }, From d868e740fd7167e885eb636a19525e2f59512e54 Mon Sep 17 00:00:00 2001 From: Longvu003 Date: Wed, 2 Jul 2025 19:17:29 +0700 Subject: [PATCH 003/248] . --- src/App.jsx | 2 +- src/pages/dashboard/AcceptFarm.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 87826600..b84c7349 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -10,5 +10,5 @@ function App() { ); } - + export default App; diff --git a/src/pages/dashboard/AcceptFarm.jsx b/src/pages/dashboard/AcceptFarm.jsx index 27c01090..26b07ca1 100644 --- a/src/pages/dashboard/AcceptFarm.jsx +++ b/src/pages/dashboard/AcceptFarm.jsx @@ -63,7 +63,7 @@ useEffect(()=>{ > Đã duyệt - )} + )} )) )) } From 50be448ae75729b8a2272f0206059852fa5d158f Mon Sep 17 00:00:00 2001 From: Tie902 Date: Wed, 2 Jul 2025 19:24:08 +0700 Subject: [PATCH 004/248] qlyusers --- package.json | 3 +- src/pages/dashboard/index.js | 2 +- src/pages/dashboard/profile.jsx | 221 -------------------------------- src/pages/dashboard/users.jsx | 74 +++++++++++ src/routes.jsx | 12 +- 5 files changed, 83 insertions(+), 229 deletions(-) delete mode 100644 src/pages/dashboard/profile.jsx create mode 100644 src/pages/dashboard/users.jsx diff --git a/package.json b/package.json index 44f14fa6..67c043d5 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@heroicons/react": "2.0.18", "@material-tailwind/react": "2.1.4", "apexcharts": "3.44.0", + "axios": "^1.10.0", "prop-types": "15.8.1", "react": "18.2.0", "react-apexcharts": "1.4.1", @@ -29,4 +30,4 @@ "tailwindcss": "3.3.4", "vite": "4.5.0" } -} \ No newline at end of file +} diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index 7651895e..b5f5911b 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,4 +1,4 @@ export * from "@/pages/dashboard/home"; -export * from "@/pages/dashboard/profile"; +export * from "@/pages/dashboard/users"; export * from "@/pages/dashboard/tables"; export * from "@/pages/dashboard/notifications"; diff --git a/src/pages/dashboard/profile.jsx b/src/pages/dashboard/profile.jsx deleted file mode 100644 index 0d9f0115..00000000 --- a/src/pages/dashboard/profile.jsx +++ /dev/null @@ -1,221 +0,0 @@ -import { - Card, - CardBody, - CardHeader, - CardFooter, - Avatar, - Typography, - Tabs, - TabsHeader, - Tab, - Switch, - Tooltip, - Button, -} from "@material-tailwind/react"; -import { - HomeIcon, - ChatBubbleLeftEllipsisIcon, - Cog6ToothIcon, - PencilIcon, -} from "@heroicons/react/24/solid"; -import { Link } from "react-router-dom"; -import { ProfileInfoCard, MessageCard } from "@/widgets/cards"; -import { platformSettingsData, conversationsData, projectsData } from "@/data"; - -export function Profile() { - return ( - <> -
-
-
- - -
-
- -
- - Richard Davis - - - CEO / Co-Founder - -
-
-
- - - - - App - - - - Message - - - - Settings - - - -
-
-
-
- - Platform Settings - -
- {platformSettingsData.map(({ title, options }) => ( -
- - {title} - -
- {options.map(({ checked, label }) => ( - - ))} -
-
- ))} -
-
- - - - -
- ), - }} - action={ - - - - } - /> -
- - Platform Settings - -
    - {conversationsData.map((props) => ( - - reply - - } - /> - ))} -
-
-
-
- - Projects - - - Architects design houses - -
- {projectsData.map( - ({ img, title, description, tag, route, members }) => ( - - - {title} - - - - {tag} - - - {title} - - - {description} - - - - - - -
- {members.map(({ img, name }, key) => ( - - - - ))} -
-
-
- ) - )} -
-
- - - - ); -} - -export default Profile; diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx new file mode 100644 index 00000000..2c691684 --- /dev/null +++ b/src/pages/dashboard/users.jsx @@ -0,0 +1,74 @@ +import React, { useEffect, useState } from "react"; +import axios from "axios"; +import { + Card, CardHeader, CardBody, CardFooter, + Avatar, Typography, Button, Tooltip +} from "@material-tailwind/react"; +import { Link } from "react-router-dom"; + +export function Users() { + const [users, setUsers] = useState([]); + + useEffect(() => { + axios.get("https://api-ndolv2.nongdanonline.vn/users/my", { + headers: { Authorization: "Bearer YOUR_TOKEN" } + }) + .then(res => setUsers(res.data)) + .catch(err => console.error("Lỗi:", err)); + }, []); + + return ( +
+ + Users Management + + + Danh sách người dùng + +
+ {users.map((user) => ( + + + {user.fullName} + + + + {user.fullName} + + + Email: {user.email} + + + Phone: {user.phone} + + + Role: {user.role} + + + Active: {user.isActive ? "Yes" : "No"} + + + + + + + + + + + + ))} +
+
+ ); +} + +export default Users; diff --git a/src/routes.jsx b/src/routes.jsx index 3a5a8da0..44d87153 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -6,7 +6,7 @@ import { ServerStackIcon, RectangleStackIcon, } from "@heroicons/react/24/solid"; -import { Home, Profile, Tables, Notifications } from "@/pages/dashboard"; +import { Home, Users, Tables, Notifications } from "@/pages/dashboard"; import { SignIn, SignUp } from "@/pages/auth"; const icon = { @@ -24,11 +24,11 @@ export const routes = [ element: , }, { - icon: , - name: "profile", - path: "/profile", - element: , - }, + icon: , + name: "users", + path: "/users", + element: , +}, { icon: , name: "tables", From 88a03840aaec44595a4e145287ef53cd4dac7973 Mon Sep 17 00:00:00 2001 From: Longvu003 Date: Wed, 2 Jul 2025 17:28:24 +0700 Subject: [PATCH 005/248] up code Accept Farm --- package.json | 5 +- src/data/Farm-data.js | 34 +++++++++++ src/pages/dashboard/AcceptFarm.jsx | 76 +++++++++++++++++++++++ src/pages/dashboard/index.js | 2 +- src/pages/dashboard/notifications.jsx | 88 --------------------------- src/routes.jsx | 8 +-- 6 files changed, 118 insertions(+), 95 deletions(-) create mode 100644 src/data/Farm-data.js create mode 100644 src/pages/dashboard/AcceptFarm.jsx delete mode 100644 src/pages/dashboard/notifications.jsx diff --git a/package.json b/package.json index 44f14fa6..c018e952 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@heroicons/react": "2.0.18", "@material-tailwind/react": "2.1.4", "apexcharts": "3.44.0", + "axios": "^1.10.0", "prop-types": "15.8.1", "react": "18.2.0", "react-apexcharts": "1.4.1", @@ -27,6 +28,6 @@ "prettier": "3.0.3", "prettier-plugin-tailwindcss": "0.5.6", "tailwindcss": "3.3.4", - "vite": "4.5.0" + "vite": "^4.5.0" } -} \ No newline at end of file +} diff --git a/src/data/Farm-data.js b/src/data/Farm-data.js new file mode 100644 index 00000000..f70d59e2 --- /dev/null +++ b/src/data/Farm-data.js @@ -0,0 +1,34 @@ +export const Requiment = [ + { + id: 1, + nameFarm: "Farm Long vu 1", + content: "Đây là farm cá Rô Phi", + price: 20000, + category: "Cá Rô phi", + img: "https://snnptnt.binhdinh.gov.vn/assets/news//upload/files/cho-ca-an.jpg", + }, + { + id: 2, + nameFarm: "Farm Long vu 2", + content: "Đây là farm cá Thác Lác", + price: 30000, + category: "Thác Lác", + img: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR5WOFLHSW-OL3dvWMaiaVRdU7EoeVxraJAcg&s", + }, + { + id: 3, + nameFarm: "Farm Long vu 3", + content: "Đây là farm cá Trê", + price: 40000, + category: "Cá Trê", + img: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSgivFrBB_KFA9qmJVmDtdQ9AEfcMekxmMmsA&s", + }, + { + id: 4, + nameFarm: "Farm Long vu 4", + content: "Đây là farm cá Diêu Hồng", + price: 45000, + category: "Cá Diêu Hồng", + img: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRriBKfDu-1cnpup36PtENhHpBR6_ET-Xzxjg&s", + }, + ]; \ No newline at end of file diff --git a/src/pages/dashboard/AcceptFarm.jsx b/src/pages/dashboard/AcceptFarm.jsx new file mode 100644 index 00000000..27c01090 --- /dev/null +++ b/src/pages/dashboard/AcceptFarm.jsx @@ -0,0 +1,76 @@ +import React, { useEffect } from "react"; +import { InformationCircleIcon } from "@heroicons/react/24/outline"; +import { useState } from "react"; +import { Requiment } from "@/data/Farm-data"; +import axios from "axios"; +export function AcceptFarm() { + const BaseUrl=`https://api-ndolv2.nongdanonline.vn` + const [Accept, setAccept] = React.useState({}); + const [farm,setFarm]=useState([]) + const AcceptFarm = async (id) => { + // setAccept((prev) => ({ ...prev, [id]: true })); + setFarm((prev) => prev.filter((item) => item.id !== id)); + + }; +const getFarm =async ()=>{ +try { +const res= await axios.get(`${BaseUrl}/farms`) +if(res.status===200){ +setFarm(res.data) +}else{ +console.log("Có lỗi khi lấy data ") +} +} catch (error) { + console.log("Lỗi :",error) +} +} +useEffect(()=>{ + getFarm() +},[]) + return ( +
+
+ + { + farm.length===0 ? (
Không có đơn !
):( ( farm.map((item) => ( + +
+ {item.name} + Vị trí: {item.location} + {item.pricePerMonth} Vnđ + Mã số: {item.code} + {/* { + item.farmImages ?( + {item.nameFarm} + + ):( + + ) + } */} + + + {!Accept[item.id] ? ( + + ) : ( + + )} +
)) + )) + } + +
+
+ ); +} + +export default AcceptFarm; diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index 7651895e..881c908c 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,4 +1,4 @@ export * from "@/pages/dashboard/home"; export * from "@/pages/dashboard/profile"; export * from "@/pages/dashboard/tables"; -export * from "@/pages/dashboard/notifications"; +export * from "@/pages/dashboard/AcceptFarm"; diff --git a/src/pages/dashboard/notifications.jsx b/src/pages/dashboard/notifications.jsx deleted file mode 100644 index f4be88b0..00000000 --- a/src/pages/dashboard/notifications.jsx +++ /dev/null @@ -1,88 +0,0 @@ -import React from "react"; -import { - Typography, - Alert, - Card, - CardHeader, - CardBody, -} from "@material-tailwind/react"; -import { InformationCircleIcon } from "@heroicons/react/24/outline"; - -export function Notifications() { - const [showAlerts, setShowAlerts] = React.useState({ - blue: true, - green: true, - orange: true, - red: true, - }); - const [showAlertsWithIcon, setShowAlertsWithIcon] = React.useState({ - blue: true, - green: true, - orange: true, - red: true, - }); - const alerts = ["gray", "green", "orange", "red"]; - - return ( -
- - - - Alerts - - - - {alerts.map((color) => ( - setShowAlerts((current) => ({ ...current, [color]: false }))} - > - A simple {color} alert with an example link. Give - it a click if you like. - - ))} - - - - - - Alerts with Icon - - - - {alerts.map((color) => ( - - } - onClose={() => setShowAlertsWithIcon((current) => ({ - ...current, - [color]: false, - }))} - > - A simple {color} alert with an example link. Give - it a click if you like. - - ))} - - -
- ); -} - -export default Notifications; diff --git a/src/routes.jsx b/src/routes.jsx index 3a5a8da0..7df9af9e 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -6,7 +6,7 @@ import { ServerStackIcon, RectangleStackIcon, } from "@heroicons/react/24/solid"; -import { Home, Profile, Tables, Notifications } from "@/pages/dashboard"; +import { Home, Profile, Tables, AcceptFarm } from "@/pages/dashboard"; import { SignIn, SignUp } from "@/pages/auth"; const icon = { @@ -37,9 +37,9 @@ export const routes = [ }, { icon: , - name: "notifications", - path: "/notifications", - element: , + name: "Accept Farms", + path: "/AcceptFarm", + element: , }, ], }, From 41de33a8bbd537d1001c61966397436e58df87a5 Mon Sep 17 00:00:00 2001 From: Longvu003 Date: Wed, 2 Jul 2025 19:17:29 +0700 Subject: [PATCH 006/248] . --- src/App.jsx | 2 +- src/pages/dashboard/AcceptFarm.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 87826600..b84c7349 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -10,5 +10,5 @@ function App() { ); } - + export default App; diff --git a/src/pages/dashboard/AcceptFarm.jsx b/src/pages/dashboard/AcceptFarm.jsx index 27c01090..26b07ca1 100644 --- a/src/pages/dashboard/AcceptFarm.jsx +++ b/src/pages/dashboard/AcceptFarm.jsx @@ -63,7 +63,7 @@ useEffect(()=>{ > Đã duyệt - )} + )} )) )) } From 3997db86ca3b64f548a012c1df14884b218a9b9c Mon Sep 17 00:00:00 2001 From: Longvu003 Date: Wed, 2 Jul 2025 19:36:44 +0700 Subject: [PATCH 007/248] . --- src/pages/auth/sign-in.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/auth/sign-in.jsx b/src/pages/auth/sign-in.jsx index 8a93c8cc..65f973f8 100644 --- a/src/pages/auth/sign-in.jsx +++ b/src/pages/auth/sign-in.jsx @@ -54,7 +54,6 @@ export function SignIn() { console.error("Login error:", error); alert("Không thể kết nối tới máy chủ, thử lại sau"); } - } return (
From 2c5fb488c3efeed0b6fa002dc7fb8d5a0caf2095 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Wed, 2 Jul 2025 20:07:42 +0700 Subject: [PATCH 008/248] qlusers --- src/pages/dashboard/users.jsx | 72 +++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/pages/dashboard/users.jsx diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx new file mode 100644 index 00000000..d54ab3ac --- /dev/null +++ b/src/pages/dashboard/users.jsx @@ -0,0 +1,72 @@ +import React, { useEffect, useState } from "react"; +import axios from "axios"; +import { + Card, CardHeader, CardBody, CardFooter, + Avatar, Typography, Button, Tooltip +} from "@material-tailwind/react"; +import { Link } from "react-router-dom"; +export function Users() { + const [users, setUsers] = useState([]); + useEffect(() => { + axios.get("https://api-ndolv2.nongdanonline.vn/users/my", { + headers: { Authorization: "Bearer YOUR_TOKEN" } + }) + .then(res => setUsers(res.data)) + .catch(err => console.error("Lỗi:", err)); + }, []); + + return ( +
+ + Users Management + + + Danh sách người dùng + +
+ {users.map((user) => ( + + + {user.fullName} + + + + {user.fullName} + + + Email: {user.email} + + + Phone: {user.phone} + + + Role: {user.role} + + + Active: {user.isActive ? "Yes" : "No"} + + + + + + + + + + + + ))} +
+
+ ); +} + +export default Users; From 5b4297f0742e94f2f238a9c684abe6a29a490e3e Mon Sep 17 00:00:00 2001 From: Tie902 Date: Wed, 2 Jul 2025 20:18:16 +0700 Subject: [PATCH 009/248] qlusers --- src/layouts/auth.jsx | 2 +- src/pages/dashboard/users.jsx | 1 + src/routes.jsx | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/layouts/auth.jsx b/src/layouts/auth.jsx index c7a21165..b37dbd71 100644 --- a/src/layouts/auth.jsx +++ b/src/layouts/auth.jsx @@ -16,7 +16,7 @@ export function Auth() { icon: ChartPieIcon, }, { - name: "profile", + name: "Users", path: "/dashboard/home", icon: UserIcon, }, diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index 2c691684..aee018cd 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -17,6 +17,7 @@ export function Users() { .catch(err => console.error("Lỗi:", err)); }, []); + return (
diff --git a/src/routes.jsx b/src/routes.jsx index 58faa4f9..7723f96b 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -6,7 +6,7 @@ import { ServerStackIcon, RectangleStackIcon, } from "@heroicons/react/24/solid"; -import { Home, Users, Tables, Notifications } from "@/pages/dashboard"; +import { Home, Users, Tables, AcceptFarm } from "@/pages/dashboard"; import { SignIn, SignUp } from "@/pages/auth"; const icon = { From cb76c5f9b8b2322da168de1bc0ed77afb513a915 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Wed, 2 Jul 2025 20:19:29 +0700 Subject: [PATCH 010/248] Update routes.jsx --- src/routes.jsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/routes.jsx b/src/routes.jsx index 7723f96b..24f982cf 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -6,7 +6,11 @@ import { ServerStackIcon, RectangleStackIcon, } from "@heroicons/react/24/solid"; +<<<<<<< Updated upstream import { Home, Users, Tables, AcceptFarm } from "@/pages/dashboard"; +======= +import { Home, User , Tables, AcceptFarm } from "@/pages/dashboard"; +>>>>>>> Stashed changes import { SignIn, SignUp } from "@/pages/auth"; const icon = { @@ -24,11 +28,19 @@ export const routes = [ element: , }, { +<<<<<<< Updated upstream icon: , name: "users", path: "/users", element: , }, +======= + icon: , + name: "users", + path: "/users", + element: , + }, +>>>>>>> Stashed changes { icon: , name: "tables", From 9b131ce81b7d0d38a7cf13f8518a9c0716e88f39 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Wed, 2 Jul 2025 20:25:49 +0700 Subject: [PATCH 011/248] chinhlai --- src/routes.jsx | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/routes.jsx b/src/routes.jsx index 24f982cf..b48c72e6 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -6,11 +6,8 @@ import { ServerStackIcon, RectangleStackIcon, } from "@heroicons/react/24/solid"; -<<<<<<< Updated upstream import { Home, Users, Tables, AcceptFarm } from "@/pages/dashboard"; -======= -import { Home, User , Tables, AcceptFarm } from "@/pages/dashboard"; ->>>>>>> Stashed changes + import { SignIn, SignUp } from "@/pages/auth"; const icon = { @@ -28,19 +25,11 @@ export const routes = [ element: , }, { -<<<<<<< Updated upstream - icon: , - name: "users", - path: "/users", - element: , -}, -======= icon: , name: "users", path: "/users", element: , }, ->>>>>>> Stashed changes { icon: , name: "tables", From 7c4eebc778fc1bc7d5ff47a2761883496f76a6c2 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Wed, 2 Jul 2025 20:30:18 +0700 Subject: [PATCH 012/248] sualoicuatien --- src/layouts/auth.jsx | 2 +- src/pages/dashboard/index.js | 2 +- src/pages/dashboard/profile.jsx | 221 -------------------------------- src/routes.jsx | 8 +- 4 files changed, 6 insertions(+), 227 deletions(-) delete mode 100644 src/pages/dashboard/profile.jsx diff --git a/src/layouts/auth.jsx b/src/layouts/auth.jsx index c7a21165..7810f8c8 100644 --- a/src/layouts/auth.jsx +++ b/src/layouts/auth.jsx @@ -16,7 +16,7 @@ export function Auth() { icon: ChartPieIcon, }, { - name: "profile", + name: "User", path: "/dashboard/home", icon: UserIcon, }, diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index 881c908c..5f29542d 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,4 +1,4 @@ export * from "@/pages/dashboard/home"; -export * from "@/pages/dashboard/profile"; +export * from "@/pages/dashboard/users"; export * from "@/pages/dashboard/tables"; export * from "@/pages/dashboard/AcceptFarm"; diff --git a/src/pages/dashboard/profile.jsx b/src/pages/dashboard/profile.jsx deleted file mode 100644 index 0d9f0115..00000000 --- a/src/pages/dashboard/profile.jsx +++ /dev/null @@ -1,221 +0,0 @@ -import { - Card, - CardBody, - CardHeader, - CardFooter, - Avatar, - Typography, - Tabs, - TabsHeader, - Tab, - Switch, - Tooltip, - Button, -} from "@material-tailwind/react"; -import { - HomeIcon, - ChatBubbleLeftEllipsisIcon, - Cog6ToothIcon, - PencilIcon, -} from "@heroicons/react/24/solid"; -import { Link } from "react-router-dom"; -import { ProfileInfoCard, MessageCard } from "@/widgets/cards"; -import { platformSettingsData, conversationsData, projectsData } from "@/data"; - -export function Profile() { - return ( - <> -
-
-
- - -
-
- -
- - Richard Davis - - - CEO / Co-Founder - -
-
-
- - - - - App - - - - Message - - - - Settings - - - -
-
-
-
- - Platform Settings - -
- {platformSettingsData.map(({ title, options }) => ( -
- - {title} - -
- {options.map(({ checked, label }) => ( - - ))} -
-
- ))} -
-
- - - - -
- ), - }} - action={ - - - - } - /> -
- - Platform Settings - -
    - {conversationsData.map((props) => ( - - reply - - } - /> - ))} -
-
-
-
- - Projects - - - Architects design houses - -
- {projectsData.map( - ({ img, title, description, tag, route, members }) => ( - - - {title} - - - - {tag} - - - {title} - - - {description} - - - - - - -
- {members.map(({ img, name }, key) => ( - - - - ))} -
-
-
- ) - )} -
-
- - - - ); -} - -export default Profile; diff --git a/src/routes.jsx b/src/routes.jsx index 7df9af9e..7a8ca4e0 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -6,7 +6,7 @@ import { ServerStackIcon, RectangleStackIcon, } from "@heroicons/react/24/solid"; -import { Home, Profile, Tables, AcceptFarm } from "@/pages/dashboard"; +import { Home, Users , Tables, AcceptFarm } from "@/pages/dashboard"; import { SignIn, SignUp } from "@/pages/auth"; const icon = { @@ -25,9 +25,9 @@ export const routes = [ }, { icon: , - name: "profile", - path: "/profile", - element: , + name: "Users", + path: "/Users", + element: , }, { icon: , From b57030668851147a7b4243e783bd9d78f3580430 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Wed, 2 Jul 2025 21:04:28 +0700 Subject: [PATCH 013/248] thanh --- src/pages/dashboard/fram.jsx | 154 +++++++++++++++++++++++ src/pages/dashboard/index.js | 8 +- src/pages/dashboard/tables.jsx | 221 --------------------------------- src/routes.jsx | 8 +- 4 files changed, 162 insertions(+), 229 deletions(-) create mode 100644 src/pages/dashboard/fram.jsx delete mode 100644 src/pages/dashboard/tables.jsx diff --git a/src/pages/dashboard/fram.jsx b/src/pages/dashboard/fram.jsx new file mode 100644 index 00000000..28af8446 --- /dev/null +++ b/src/pages/dashboard/fram.jsx @@ -0,0 +1,154 @@ +import React from "react"; +import { + Card, + CardHeader, + CardBody, + Typography, + Avatar, + Chip, + Tooltip, + Progress, +} from "@material-tailwind/react"; +import { EllipsisVerticalIcon } from "@heroicons/react/24/outline"; +import { authorsTableData } from "@/data"; // hoặc projectsTableData nếu dùng bảng project + +const Fram = () => { + return ( +
+ {/* Breadcrumb */} +
+ +

All products

+
+ + {/* Search */} +
+
+ +
+ +
+
+
+ + {/* Table */} + + + + + + {["author", "function", "status", "employed", ""].map((el) => ( + + ))} + + + + {authorsTableData.map( + ({ img, name, email, job, online, date }, key) => { + const className = `py-3 px-5 ${ + key === authorsTableData.length - 1 + ? "" + : "border-b border-blue-gray-50" + }`; + + return ( + + + + + + + + ); + } + )} + +
+ + {el} + +
+
+ +
+ + {name} + + + {email} + +
+
+
+ + {job[0]} + + + {job[1]} + + + + + + {date} + + + + Edit + +
+
+
+
+ ); +}; + +export default Fram; diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index 881c908c..f9ede442 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,4 +1,4 @@ -export * from "@/pages/dashboard/home"; -export * from "@/pages/dashboard/profile"; -export * from "@/pages/dashboard/tables"; -export * from "@/pages/dashboard/AcceptFarm"; +export { default as Home } from "@/pages/dashboard/home"; +export { default as Profile } from "@/pages/dashboard/profile"; +export { default as Fram } from "@/pages/dashboard/fram"; +export { default as AcceptFarm } from "@/pages/dashboard/AcceptFarm"; diff --git a/src/pages/dashboard/tables.jsx b/src/pages/dashboard/tables.jsx deleted file mode 100644 index 3d453ed7..00000000 --- a/src/pages/dashboard/tables.jsx +++ /dev/null @@ -1,221 +0,0 @@ -import { - Card, - CardHeader, - CardBody, - Typography, - Avatar, - Chip, - Tooltip, - Progress, -} from "@material-tailwind/react"; -import { EllipsisVerticalIcon } from "@heroicons/react/24/outline"; -import { authorsTableData, projectsTableData } from "@/data"; - -export function Tables() { - return ( -
- - - - Authors Table - - - - - - - {["author", "function", "status", "employed", ""].map((el) => ( - - ))} - - - - {authorsTableData.map( - ({ img, name, email, job, online, date }, key) => { - const className = `py-3 px-5 ${ - key === authorsTableData.length - 1 - ? "" - : "border-b border-blue-gray-50" - }`; - - return ( - - - - - - - - ); - } - )} - -
- - {el} - -
-
- -
- - {name} - - - {email} - -
-
-
- - {job[0]} - - - {job[1]} - - - - - - {date} - - - - Edit - -
-
-
- - - - Projects Table - - - - - - - {["companies", "members", "budget", "completion", ""].map( - (el) => ( - - ) - )} - - - - {projectsTableData.map( - ({ img, name, members, budget, completion }, key) => { - const className = `py-3 px-5 ${ - key === projectsTableData.length - 1 - ? "" - : "border-b border-blue-gray-50" - }`; - - return ( - - - - - - - - ); - } - )} - -
- - {el} - -
-
- - - {name} - -
-
- {members.map(({ img, name }, key) => ( - - - - ))} - - - {budget} - - -
- - {completion}% - - -
-
- - - -
-
-
-
- ); -} - -export default Tables; diff --git a/src/routes.jsx b/src/routes.jsx index 7df9af9e..cd788ede 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -6,7 +6,7 @@ import { ServerStackIcon, RectangleStackIcon, } from "@heroicons/react/24/solid"; -import { Home, Profile, Tables, AcceptFarm } from "@/pages/dashboard"; +import { Home, Profile, Fram, AcceptFarm } from "@/pages/dashboard"; import { SignIn, SignUp } from "@/pages/auth"; const icon = { @@ -31,9 +31,9 @@ export const routes = [ }, { icon: , - name: "tables", - path: "/tables", - element: , + name: "Fram", + path: "/Fram", + element: , }, { icon: , From 9559f69152fd7486e3a9457d8ec1c71ef71fc45d Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Wed, 2 Jul 2025 21:06:42 +0700 Subject: [PATCH 014/248] thanh --- src/pages/dashboard/fram.jsx | 154 ----------------------- src/pages/dashboard/tables.jsx | 221 +++++++++++++++++++++++++++++++++ src/routes.jsx | 6 +- 3 files changed, 224 insertions(+), 157 deletions(-) delete mode 100644 src/pages/dashboard/fram.jsx create mode 100644 src/pages/dashboard/tables.jsx diff --git a/src/pages/dashboard/fram.jsx b/src/pages/dashboard/fram.jsx deleted file mode 100644 index 28af8446..00000000 --- a/src/pages/dashboard/fram.jsx +++ /dev/null @@ -1,154 +0,0 @@ -import React from "react"; -import { - Card, - CardHeader, - CardBody, - Typography, - Avatar, - Chip, - Tooltip, - Progress, -} from "@material-tailwind/react"; -import { EllipsisVerticalIcon } from "@heroicons/react/24/outline"; -import { authorsTableData } from "@/data"; // hoặc projectsTableData nếu dùng bảng project - -const Fram = () => { - return ( -
- {/* Breadcrumb */} -
- -

All products

-
- - {/* Search */} -
-
- -
- -
-
-
- - {/* Table */} - - - - - - {["author", "function", "status", "employed", ""].map((el) => ( - - ))} - - - - {authorsTableData.map( - ({ img, name, email, job, online, date }, key) => { - const className = `py-3 px-5 ${ - key === authorsTableData.length - 1 - ? "" - : "border-b border-blue-gray-50" - }`; - - return ( - - - - - - - - ); - } - )} - -
- - {el} - -
-
- -
- - {name} - - - {email} - -
-
-
- - {job[0]} - - - {job[1]} - - - - - - {date} - - - - Edit - -
-
-
-
- ); -}; - -export default Fram; diff --git a/src/pages/dashboard/tables.jsx b/src/pages/dashboard/tables.jsx new file mode 100644 index 00000000..3d453ed7 --- /dev/null +++ b/src/pages/dashboard/tables.jsx @@ -0,0 +1,221 @@ +import { + Card, + CardHeader, + CardBody, + Typography, + Avatar, + Chip, + Tooltip, + Progress, +} from "@material-tailwind/react"; +import { EllipsisVerticalIcon } from "@heroicons/react/24/outline"; +import { authorsTableData, projectsTableData } from "@/data"; + +export function Tables() { + return ( +
+ + + + Authors Table + + + + + + + {["author", "function", "status", "employed", ""].map((el) => ( + + ))} + + + + {authorsTableData.map( + ({ img, name, email, job, online, date }, key) => { + const className = `py-3 px-5 ${ + key === authorsTableData.length - 1 + ? "" + : "border-b border-blue-gray-50" + }`; + + return ( + + + + + + + + ); + } + )} + +
+ + {el} + +
+
+ +
+ + {name} + + + {email} + +
+
+
+ + {job[0]} + + + {job[1]} + + + + + + {date} + + + + Edit + +
+
+
+ + + + Projects Table + + + + + + + {["companies", "members", "budget", "completion", ""].map( + (el) => ( + + ) + )} + + + + {projectsTableData.map( + ({ img, name, members, budget, completion }, key) => { + const className = `py-3 px-5 ${ + key === projectsTableData.length - 1 + ? "" + : "border-b border-blue-gray-50" + }`; + + return ( + + + + + + + + ); + } + )} + +
+ + {el} + +
+
+ + + {name} + +
+
+ {members.map(({ img, name }, key) => ( + + + + ))} + + + {budget} + + +
+ + {completion}% + + +
+
+ + + +
+
+
+
+ ); +} + +export default Tables; diff --git a/src/routes.jsx b/src/routes.jsx index e6824382..96c3b34d 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -33,9 +33,9 @@ export const routes = [ }, { icon: , - name: "Fram", - path: "/Fram", - element: , + name: "Tables", + path: "/Tables", + element: , }, { icon: , From 60d5f2d144bfc3e4730ced5fa8ed3e23532b96e4 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Wed, 2 Jul 2025 21:15:31 +0700 Subject: [PATCH 015/248] ok --- src/pages/dashboard/users.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index aee018cd..a46cf673 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -10,7 +10,7 @@ export function Users() { const [users, setUsers] = useState([]); useEffect(() => { - axios.get("https://api-ndolv2.nongdanonline.vn/users/my", { + axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { headers: { Authorization: "Bearer YOUR_TOKEN" } }) .then(res => setUsers(res.data)) From 555a505bc782e0896d3cd799641765e60c344f96 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Wed, 2 Jul 2025 21:17:25 +0700 Subject: [PATCH 016/248] thanh --- src/pages/dashboard/tables.jsx | 268 +++++++++------------------------ 1 file changed, 68 insertions(+), 200 deletions(-) diff --git a/src/pages/dashboard/tables.jsx b/src/pages/dashboard/tables.jsx index 3d453ed7..88314800 100644 --- a/src/pages/dashboard/tables.jsx +++ b/src/pages/dashboard/tables.jsx @@ -13,209 +13,77 @@ import { authorsTableData, projectsTableData } from "@/data"; export function Tables() { return ( -
- - - - Authors Table - - - - - - - {["author", "function", "status", "employed", ""].map((el) => ( - +
+
+
+ +

All products

+
+
+
+
+ +
+ +
+ +
+
+ {[...Array(4)].map((_, index) => ( + + + + + ))} -
- - - {authorsTableData.map( - ({ img, name, email, job, online, date }, key) => { - const className = `py-3 px-5 ${ - key === authorsTableData.length - 1 - ? "" - : "border-b border-blue-gray-50" - }`; - - return ( - - - - - - - - ); - } - )} - -
- - {el} - -
-
- -
- - {name} - - - {email} - -
-
-
- - {job[0]} - - - {job[1]} - - - - - - {date} - - - - Edit - -
-
-
- - - - Projects Table - - - - - - - {["companies", "members", "budget", "completion", ""].map( - (el) => ( - - ) - )} - - - - {projectsTableData.map( - ({ img, name, members, budget, completion }, key) => { - const className = `py-3 px-5 ${ - key === projectsTableData.length - 1 - ? "" - : "border-b border-blue-gray-50" - }`; - - return ( - - - - - - - - ); - } - )} - -
- - {el} - -
-
- - - {name} - -
-
- {members.map(({ img, name }, key) => ( - - - - ))} - - - {budget} - - -
- - {completion}% - - -
-
- - - -
-
-
+
+
+ + + + ); + } export default Tables; From 7a54ba04deeffc0e0e15d306ac10e3aad65deb0e Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Wed, 2 Jul 2025 21:30:10 +0700 Subject: [PATCH 017/248] thanh --- src/pages/dashboard/tables.jsx | 270 ++++++++++++++++++++++++--------- 1 file changed, 201 insertions(+), 69 deletions(-) diff --git a/src/pages/dashboard/tables.jsx b/src/pages/dashboard/tables.jsx index 88314800..c66eadd5 100644 --- a/src/pages/dashboard/tables.jsx +++ b/src/pages/dashboard/tables.jsx @@ -13,77 +13,209 @@ import { authorsTableData, projectsTableData } from "@/data"; export function Tables() { return ( -
-
-
- -

All products

-
-
-
-
- -
- -
-
-
-
- {[...Array(4)].map((_, index) => ( - - - - - +
+ + + + Authors Table + + + + + + + {["author", "function", "status", "employed", ""].map((el) => ( + ))} - - - - - - + + + + {authorsTableData.map( + ({ img, name, email, job, online, date }, key) => { + const className = `py-3 px-5 ${ + key === authorsTableData.length - 1 + ? "" + : "border-b border-blue-gray-50" + }`; + + return ( + + + + + + + + ); + } + )} + +
+ + {el} + +
+
+ +
+ + {name} + + + {email} + +
+
+
+ + {job[0]} + + + {job[1]} + + + + + + {date} + + + + Edit + +
+
+
+ + + + Projects Table + + + + + + + {["companies", "members", "budget", "completion", ""].map( + (el) => ( + + ) + )} + + + + {projectsTableData.map( + ({ img, name, members, budget, completion }, key) => { + const className = `py-3 px-5 ${ + key === projectsTableData.length - 1 + ? "" + : "border-b border-blue-gray-50" + }`; + + return ( + + + + + + + + ); + } + )} + +
+ + {el} + +
+
+ + + {name} + +
+
+ {members.map(({ img, name }, key) => ( + + + + ))} + + + {budget} + + +
+ + {completion}% + + +
+
+ + + +
+
+
); - } -export default Tables; +export default Tables; \ No newline at end of file From 0dc3431cee0bc8066664992ef7d7fd33289b42ef Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Wed, 2 Jul 2025 21:36:50 +0700 Subject: [PATCH 018/248] Update tables.jsx --- src/pages/dashboard/tables.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/dashboard/tables.jsx b/src/pages/dashboard/tables.jsx index c66eadd5..7020b7c9 100644 --- a/src/pages/dashboard/tables.jsx +++ b/src/pages/dashboard/tables.jsx @@ -77,7 +77,7 @@ export function Tables() { Date: Wed, 2 Jul 2025 21:48:33 +0700 Subject: [PATCH 019/248] thanh --- src/pages/dashboard/farms.jsx | 181 +++++++++++++++++++++++++++ src/pages/dashboard/index.js | 2 +- src/pages/dashboard/tables.jsx | 221 --------------------------------- src/routes.jsx | 8 +- 4 files changed, 186 insertions(+), 226 deletions(-) create mode 100644 src/pages/dashboard/farms.jsx delete mode 100644 src/pages/dashboard/tables.jsx diff --git a/src/pages/dashboard/farms.jsx b/src/pages/dashboard/farms.jsx new file mode 100644 index 00000000..0fbf23a2 --- /dev/null +++ b/src/pages/dashboard/farms.jsx @@ -0,0 +1,181 @@ +import React, { useState } from "react"; +import { + Card, + CardBody, + Input, + Button, + Typography, + Checkbox, + Chip, +} from "@material-tailwind/react"; +import { + PencilSquareIcon, + TrashIcon, + PlusIcon, +} from "@heroicons/react/24/solid"; + +// Dữ liệu mẫu – bạn có thể thay thế bằng dữ liệu lấy từ API +const products = [ + { + id: "#194556", + name: "Education Dashboard", + category: "Html templates", + technology: "Angular", + description: "Start developing with an open‑source...", + price: "$149", + discount: "No", + }, + { + id: "#623232", + name: "React UI Kit", + category: "Html templates", + technology: "React JS", + description: "Start developing with an open‑source...", + price: "$129", + discount: "10%", + }, + { + id: "#194356", + name: "Education Dashboard", + category: "Html templates", + technology: "Angular", + description: "Start developing with an open‑source...", + price: "$149", + discount: "No", + }, + { + id: "#323323", + name: "React UI Kit", + category: "Html templates", + technology: "React JS", + description: "Start developing with an open‑source...", + price: "$129", + discount: "No", + }, + { + id: "#994856", + name: "Education Dashboard", + category: "Html templates", + technology: "Angular", + description: "Start developing with an open‑source...", + price: "$149", + discount: "25%", + }, + { + id: "#194256", + name: "Education Dashboard", + category: "Html templates", + technology: "Angular", + description: "Start developing with an open‑source...", + price: "$149", + discount: "10%", + }, +]; + +export function Farms() { + const [search, setSearch] = useState(""); + + const filteredProducts = products.filter((p) => + p.name.toLowerCase().includes(search.toLowerCase()) + ); + + return ( + + {/* Tiêu đề + nút thêm mới */} +
+ All products + +
+ + {/* Ô tìm kiếm */} + setSearch(e.target.value)} + className="mb-6" + /> + + {/* Bảng dữ liệu */} + + + + + + {[ + "PRODUCT NAME", + "TECHNOLOGY", + "DESCRIPTION", + "ID", + "PRICE", + "DISCOUNT", + "ACTIONS", + ].map((head) => ( + + ))} + + + + {filteredProducts.map((product, idx) => ( + + + + + + + + + + + ))} + +
+ + + + {head} + +
+ + + + {product.name} + + + {product.category} + + + {product.technology} + + {product.description} + {product.id}{product.price} + {product.discount === "No" ? ( + No + ) : ( + + )} + +
+ + +
+
+
+
+ ); +} +export default Farms; \ No newline at end of file diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index 7651895e..d650d0ab 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,4 +1,4 @@ export * from "@/pages/dashboard/home"; export * from "@/pages/dashboard/profile"; -export * from "@/pages/dashboard/tables"; +export * from "@/pages/dashboard/farms"; export * from "@/pages/dashboard/notifications"; diff --git a/src/pages/dashboard/tables.jsx b/src/pages/dashboard/tables.jsx deleted file mode 100644 index 3d453ed7..00000000 --- a/src/pages/dashboard/tables.jsx +++ /dev/null @@ -1,221 +0,0 @@ -import { - Card, - CardHeader, - CardBody, - Typography, - Avatar, - Chip, - Tooltip, - Progress, -} from "@material-tailwind/react"; -import { EllipsisVerticalIcon } from "@heroicons/react/24/outline"; -import { authorsTableData, projectsTableData } from "@/data"; - -export function Tables() { - return ( -
- - - - Authors Table - - - - - - - {["author", "function", "status", "employed", ""].map((el) => ( - - ))} - - - - {authorsTableData.map( - ({ img, name, email, job, online, date }, key) => { - const className = `py-3 px-5 ${ - key === authorsTableData.length - 1 - ? "" - : "border-b border-blue-gray-50" - }`; - - return ( - - - - - - - - ); - } - )} - -
- - {el} - -
-
- -
- - {name} - - - {email} - -
-
-
- - {job[0]} - - - {job[1]} - - - - - - {date} - - - - Edit - -
-
-
- - - - Projects Table - - - - - - - {["companies", "members", "budget", "completion", ""].map( - (el) => ( - - ) - )} - - - - {projectsTableData.map( - ({ img, name, members, budget, completion }, key) => { - const className = `py-3 px-5 ${ - key === projectsTableData.length - 1 - ? "" - : "border-b border-blue-gray-50" - }`; - - return ( - - - - - - - - ); - } - )} - -
- - {el} - -
-
- - - {name} - -
-
- {members.map(({ img, name }, key) => ( - - - - ))} - - - {budget} - - -
- - {completion}% - - -
-
- - - -
-
-
-
- ); -} - -export default Tables; diff --git a/src/routes.jsx b/src/routes.jsx index 3a5a8da0..728a3e0a 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -6,7 +6,7 @@ import { ServerStackIcon, RectangleStackIcon, } from "@heroicons/react/24/solid"; -import { Home, Profile, Tables, Notifications } from "@/pages/dashboard"; +import { Home, Profile, Farms, Notifications } from "@/pages/dashboard"; import { SignIn, SignUp } from "@/pages/auth"; const icon = { @@ -31,9 +31,9 @@ export const routes = [ }, { icon: , - name: "tables", - path: "/tables", - element: , + name: "Farms", + path: "/Farms", + element: , }, { icon: , From 7d67d776f27b29cf0fd51fb0d11aae2348869bdd Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Wed, 2 Jul 2025 21:50:24 +0700 Subject: [PATCH 020/248] thanh --- src/pages/dashboard/farms.jsx | 181 +++++++++++++++++++++++++++ src/pages/dashboard/index.js | 2 +- src/pages/dashboard/tables.jsx | 221 --------------------------------- src/routes.jsx | 8 +- 4 files changed, 186 insertions(+), 226 deletions(-) create mode 100644 src/pages/dashboard/farms.jsx delete mode 100644 src/pages/dashboard/tables.jsx diff --git a/src/pages/dashboard/farms.jsx b/src/pages/dashboard/farms.jsx new file mode 100644 index 00000000..0fbf23a2 --- /dev/null +++ b/src/pages/dashboard/farms.jsx @@ -0,0 +1,181 @@ +import React, { useState } from "react"; +import { + Card, + CardBody, + Input, + Button, + Typography, + Checkbox, + Chip, +} from "@material-tailwind/react"; +import { + PencilSquareIcon, + TrashIcon, + PlusIcon, +} from "@heroicons/react/24/solid"; + +// Dữ liệu mẫu – bạn có thể thay thế bằng dữ liệu lấy từ API +const products = [ + { + id: "#194556", + name: "Education Dashboard", + category: "Html templates", + technology: "Angular", + description: "Start developing with an open‑source...", + price: "$149", + discount: "No", + }, + { + id: "#623232", + name: "React UI Kit", + category: "Html templates", + technology: "React JS", + description: "Start developing with an open‑source...", + price: "$129", + discount: "10%", + }, + { + id: "#194356", + name: "Education Dashboard", + category: "Html templates", + technology: "Angular", + description: "Start developing with an open‑source...", + price: "$149", + discount: "No", + }, + { + id: "#323323", + name: "React UI Kit", + category: "Html templates", + technology: "React JS", + description: "Start developing with an open‑source...", + price: "$129", + discount: "No", + }, + { + id: "#994856", + name: "Education Dashboard", + category: "Html templates", + technology: "Angular", + description: "Start developing with an open‑source...", + price: "$149", + discount: "25%", + }, + { + id: "#194256", + name: "Education Dashboard", + category: "Html templates", + technology: "Angular", + description: "Start developing with an open‑source...", + price: "$149", + discount: "10%", + }, +]; + +export function Farms() { + const [search, setSearch] = useState(""); + + const filteredProducts = products.filter((p) => + p.name.toLowerCase().includes(search.toLowerCase()) + ); + + return ( + + {/* Tiêu đề + nút thêm mới */} +
+ All products + +
+ + {/* Ô tìm kiếm */} + setSearch(e.target.value)} + className="mb-6" + /> + + {/* Bảng dữ liệu */} + + + + + + {[ + "PRODUCT NAME", + "TECHNOLOGY", + "DESCRIPTION", + "ID", + "PRICE", + "DISCOUNT", + "ACTIONS", + ].map((head) => ( + + ))} + + + + {filteredProducts.map((product, idx) => ( + + + + + + + + + + + ))} + +
+ + + + {head} + +
+ + + + {product.name} + + + {product.category} + + + {product.technology} + + {product.description} + {product.id}{product.price} + {product.discount === "No" ? ( + No + ) : ( + + )} + +
+ + +
+
+
+
+ ); +} +export default Farms; \ No newline at end of file diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index 4ace5393..d1a03cb8 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,6 +1,6 @@ export * from "@/pages/dashboard/home"; export * from "@/pages/dashboard/users"; -export * from "@/pages/dashboard/tables"; +export * from "@/pages/dashboard/farms"; export * from "@/pages/dashboard/AcceptFarm"; diff --git a/src/pages/dashboard/tables.jsx b/src/pages/dashboard/tables.jsx deleted file mode 100644 index 7020b7c9..00000000 --- a/src/pages/dashboard/tables.jsx +++ /dev/null @@ -1,221 +0,0 @@ -import { - Card, - CardHeader, - CardBody, - Typography, - Avatar, - Chip, - Tooltip, - Progress, -} from "@material-tailwind/react"; -import { EllipsisVerticalIcon } from "@heroicons/react/24/outline"; -import { authorsTableData, projectsTableData } from "@/data"; - -export function Tables() { - return ( -
- - - - Authors Table - - - - - - - {["author", "function", "status", "employed", ""].map((el) => ( - - ))} - - - - {authorsTableData.map( - ({ img, name, email, job, online, date }, key) => { - const className = `py-3 px-5 ${ - key === authorsTableData.length - 1 - ? "" - : "border-b border-blue-gray-50" - }`; - - return ( - - - - - - - - ); - } - )} - -
- - {el} - -
-
- -
- - {name} - - - {email} - -
-
-
- - {job[0]} - - - {job[1]} - - - - - - {date} - - - - Edit - -
-
-
- - - - Projects Table - - - - - - - {["companies", "members", "budget", "completion", ""].map( - (el) => ( - - ) - )} - - - - {projectsTableData.map( - ({ img, name, members, budget, completion }, key) => { - const className = `py-3 px-5 ${ - key === projectsTableData.length - 1 - ? "" - : "border-b border-blue-gray-50" - }`; - - return ( - - - - - - - - ); - } - )} - -
- - {el} - -
-
- - - {name} - -
-
- {members.map(({ img, name }, key) => ( - - - - ))} - - - {budget} - - -
- - {completion}% - - -
-
- - - -
-
-
-
- ); -} - -export default Tables; \ No newline at end of file diff --git a/src/routes.jsx b/src/routes.jsx index 96c3b34d..6a53ae78 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -7,7 +7,7 @@ import { RectangleStackIcon, } from "@heroicons/react/24/solid"; -import { Home, Users , Tables, AcceptFarm } from "@/pages/dashboard"; +import { Home, Users , Farms, AcceptFarm } from "@/pages/dashboard"; import { SignIn, SignUp } from "@/pages/auth"; @@ -33,9 +33,9 @@ export const routes = [ }, { icon: , - name: "Tables", - path: "/Tables", - element: , + name: "Farms", + path: "/Farms", + element: , }, { icon: , From af535cf566cce023650ebf08e6fe611fc53e5034 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Wed, 2 Jul 2025 22:54:36 +0700 Subject: [PATCH 021/248] Update users.jsx --- src/pages/dashboard/users.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index a46cf673..aee018cd 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -10,7 +10,7 @@ export function Users() { const [users, setUsers] = useState([]); useEffect(() => { - axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { + axios.get("https://api-ndolv2.nongdanonline.vn/users/my", { headers: { Authorization: "Bearer YOUR_TOKEN" } }) .then(res => setUsers(res.data)) From d23f3438587224caea0b807da8426d2bc03447b1 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Thu, 3 Jul 2025 17:10:07 +0700 Subject: [PATCH 022/248] thanh --- src/context/index.jsx | 8 +++- src/pages/auth/sign-in.jsx | 5 +- src/widgets/layout/dashboard-navbar.jsx | 18 +------ src/widgets/layout/sidenav.jsx | 62 +++++++++++++++++++++++-- 4 files changed, 69 insertions(+), 24 deletions(-) diff --git a/src/context/index.jsx b/src/context/index.jsx index 653a362d..169d1528 100644 --- a/src/context/index.jsx +++ b/src/context/index.jsx @@ -24,6 +24,9 @@ export function reducer(state, action) { case "OPEN_CONFIGURATOR": { return { ...state, openConfigurator: action.value }; } + case "AUTH_STATUS": { + return { ...state, isAuthenticated: action.value} + } default: { throw new Error(`Unhandled action type: ${action.type}`); } @@ -33,11 +36,12 @@ export function reducer(state, action) { export function MaterialTailwindControllerProvider({ children }) { const initialState = { openSidenav: false, - sidenavColor: "dark", + sidenavColor: "blue-gray", sidenavType: "white", transparentNavbar: true, fixedNavbar: false, openConfigurator: false, + isAuthenticated: Boolean(localStorage.getItem("token")), }; const [controller, dispatch] = React.useReducer(reducer, initialState); @@ -83,3 +87,5 @@ export const setFixedNavbar = (dispatch, value) => dispatch({ type: "FIXED_NAVBAR", value }); export const setOpenConfigurator = (dispatch, value) => dispatch({ type: "OPEN_CONFIGURATOR", value }); +export const setAuthStatus = (dispatch, value) => + dispatch({type: "AUTH_STATUS", value}) diff --git a/src/pages/auth/sign-in.jsx b/src/pages/auth/sign-in.jsx index 8a93c8cc..26d56e1d 100644 --- a/src/pages/auth/sign-in.jsx +++ b/src/pages/auth/sign-in.jsx @@ -2,7 +2,7 @@ import React, {useRef, useState} from "react"; import {Card, Input, Checkbox, Button, Typography,} from "@material-tailwind/react"; import { Link, useNavigate } from "react-router-dom"; import { data } from "autoprefixer"; - +import { useMaterialTailwindController, setAuthStatus } from "@/context"; @@ -12,7 +12,7 @@ export function SignIn() { const [emailError, setEmailError] = useState(""); const [passwordError, setPasswordError] = useState(""); const navigate = useNavigate(); - + const [, dispatch] = useMaterialTailwindController(); const emailRef = useRef(); const passwordRef = useRef(); @@ -45,6 +45,7 @@ export function SignIn() { if(res.ok) { localStorage.setItem("token", data.accessToken); + setAuthStatus(dispatch, true); alert("Đăng nhập thành công!"); navigate("/dashboard/home"); } else { diff --git a/src/widgets/layout/dashboard-navbar.jsx b/src/widgets/layout/dashboard-navbar.jsx index d91e23f7..7eef5ddb 100644 --- a/src/widgets/layout/dashboard-navbar.jsx +++ b/src/widgets/layout/dashboard-navbar.jsx @@ -83,23 +83,7 @@ export function DashboardNavbar() { > - - - - - - + diff --git a/src/widgets/layout/sidenav.jsx b/src/widgets/layout/sidenav.jsx index cc7e6ffe..c38a1f65 100644 --- a/src/widgets/layout/sidenav.jsx +++ b/src/widgets/layout/sidenav.jsx @@ -7,17 +7,33 @@ import { IconButton, Typography, } from "@material-tailwind/react"; -import { useMaterialTailwindController, setOpenSidenav } from "@/context"; - +import { + useMaterialTailwindController, + setOpenSidenav, + setAuthStatus, +} from "@/context"; +import { useNavigate } from "react-router-dom"; export function Sidenav({ brandImg, brandName, routes }) { const [controller, dispatch] = useMaterialTailwindController(); - const { sidenavColor, sidenavType, openSidenav } = controller; + const { sidenavColor, sidenavType, openSidenav, isAuthenticated } = controller; + const navigate = useNavigate(); + + const handleLogout = () => { + localStorage.removeItem("token"); + setAuthStatus(dispatch, false); + navigate("/auth/sign-in"); + }; + const sidenavTypes = { dark: "bg-gradient-to-br from-gray-800 to-gray-900", white: "bg-white shadow-sm", transparent: "bg-transparent", }; + const visibleRoutes = isAuthenticated + ? routes.filter(({ title }) => title?.toLowerCase() !== "auth pages") + : routes; + return (
- {routes.map(({ layout, title, pages }, key) => ( + {visibleRoutes.map(({ layout, title, pages }, key) => (
    {title && (
  • @@ -91,6 +107,44 @@ export function Sidenav({ brandImg, brandName, routes }) {
))}
+ + {isAuthenticated && ( +
+ +
+ )} ); } From 1cb211d05454c2908d2f8ed67e431ec891f6ae8f Mon Sep 17 00:00:00 2001 From: Tie902 Date: Thu, 3 Jul 2025 18:28:08 +0700 Subject: [PATCH 023/248] qlusers --- src/pages/dashboard/users.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index aee018cd..a46cf673 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -10,7 +10,7 @@ export function Users() { const [users, setUsers] = useState([]); useEffect(() => { - axios.get("https://api-ndolv2.nongdanonline.vn/users/my", { + axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { headers: { Authorization: "Bearer YOUR_TOKEN" } }) .then(res => setUsers(res.data)) From b1cf7dedcebbe9a79a2c2622daa83867a4e7bda2 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Thu, 3 Jul 2025 20:13:03 +0700 Subject: [PATCH 024/248] qlyusers --- src/pages/dashboard/users.jsx | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index a46cf673..c8b311e5 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react"; import axios from "axios"; import { Card, CardHeader, CardBody, CardFooter, - Avatar, Typography, Button, Tooltip + Typography, Button, Tooltip } from "@material-tailwind/react"; import { Link } from "react-router-dom"; @@ -11,13 +11,17 @@ export function Users() { useEffect(() => { axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { - headers: { Authorization: "Bearer YOUR_TOKEN" } + headers: { + Authorization: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE1NDIyMDgsImV4cCI6MTc1MTgwMTQwOH0.t5Vl4SVsFu5sty27uWWcedZXTI21uPIrzUqsoHHDV5k" + } + }) + .then(res => { + console.log("Response:", res.data); + setUsers(res.data); // res.data là mảng user }) - .then(res => setUsers(res.data)) .catch(err => console.error("Lỗi:", err)); }, []); - return (
@@ -27,11 +31,11 @@ export function Users() { Danh sách người dùng
- {users.map((user) => ( + {Array.isArray(users) && users.map((user) => ( {user.fullName} @@ -44,10 +48,10 @@ export function Users() { Email: {user.email} - Phone: {user.phone} + Phone: {user.phone || "N/A"} - Role: {user.role} + Role: {Array.isArray(user.role) ? user.role.join(", ") : user.role} Active: {user.isActive ? "Yes" : "No"} From 88d49592932b42be18fae868a7cd5dc88cc6ec80 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Thu, 3 Jul 2025 20:14:35 +0700 Subject: [PATCH 025/248] qlusers --- src/pages/dashboard/users.jsx | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index d54ab3ac..c8b311e5 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -2,16 +2,23 @@ import React, { useEffect, useState } from "react"; import axios from "axios"; import { Card, CardHeader, CardBody, CardFooter, - Avatar, Typography, Button, Tooltip + Typography, Button, Tooltip } from "@material-tailwind/react"; import { Link } from "react-router-dom"; + export function Users() { const [users, setUsers] = useState([]); + useEffect(() => { - axios.get("https://api-ndolv2.nongdanonline.vn/users/my", { - headers: { Authorization: "Bearer YOUR_TOKEN" } + axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { + headers: { + Authorization: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE1NDIyMDgsImV4cCI6MTc1MTgwMTQwOH0.t5Vl4SVsFu5sty27uWWcedZXTI21uPIrzUqsoHHDV5k" + } + }) + .then(res => { + console.log("Response:", res.data); + setUsers(res.data); // res.data là mảng user }) - .then(res => setUsers(res.data)) .catch(err => console.error("Lỗi:", err)); }, []); @@ -24,11 +31,11 @@ export function Users() { Danh sách người dùng
- {users.map((user) => ( + {Array.isArray(users) && users.map((user) => ( {user.fullName} @@ -41,10 +48,10 @@ export function Users() { Email: {user.email} - Phone: {user.phone} + Phone: {user.phone || "N/A"} - Role: {user.role} + Role: {Array.isArray(user.role) ? user.role.join(", ") : user.role} Active: {user.isActive ? "Yes" : "No"} From ad65ba7d885035ded8fcaa093be0b830368f0add Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Thu, 3 Jul 2025 20:40:22 +0700 Subject: [PATCH 026/248] thanh --- package.json | 3 +- src/pages/dashboard/farms.jsx | 255 ++++++++++++++-------------------- 2 files changed, 104 insertions(+), 154 deletions(-) diff --git a/package.json b/package.json index 44f14fa6..67c043d5 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@heroicons/react": "2.0.18", "@material-tailwind/react": "2.1.4", "apexcharts": "3.44.0", + "axios": "^1.10.0", "prop-types": "15.8.1", "react": "18.2.0", "react-apexcharts": "1.4.1", @@ -29,4 +30,4 @@ "tailwindcss": "3.3.4", "vite": "4.5.0" } -} \ No newline at end of file +} diff --git a/src/pages/dashboard/farms.jsx b/src/pages/dashboard/farms.jsx index 0fbf23a2..b4a3fe52 100644 --- a/src/pages/dashboard/farms.jsx +++ b/src/pages/dashboard/farms.jsx @@ -1,181 +1,130 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; +import axios from "axios"; import { - Card, - CardBody, - Input, - Button, - Typography, - Checkbox, - Chip, + Card, CardBody, Input, Button, Typography, Checkbox, Chip, } from "@material-tailwind/react"; import { - PencilSquareIcon, - TrashIcon, - PlusIcon, + PencilSquareIcon, TrashIcon, PlusIcon, } from "@heroicons/react/24/solid"; -// Dữ liệu mẫu – bạn có thể thay thế bằng dữ liệu lấy từ API -const products = [ - { - id: "#194556", - name: "Education Dashboard", - category: "Html templates", - technology: "Angular", - description: "Start developing with an open‑source...", - price: "$149", - discount: "No", - }, - { - id: "#623232", - name: "React UI Kit", - category: "Html templates", - technology: "React JS", - description: "Start developing with an open‑source...", - price: "$129", - discount: "10%", - }, - { - id: "#194356", - name: "Education Dashboard", - category: "Html templates", - technology: "Angular", - description: "Start developing with an open‑source...", - price: "$149", - discount: "No", - }, - { - id: "#323323", - name: "React UI Kit", - category: "Html templates", - technology: "React JS", - description: "Start developing with an open‑source...", - price: "$129", - discount: "No", - }, - { - id: "#994856", - name: "Education Dashboard", - category: "Html templates", - technology: "Angular", - description: "Start developing with an open‑source...", - price: "$149", - discount: "25%", - }, - { - id: "#194256", - name: "Education Dashboard", - category: "Html templates", - technology: "Angular", - description: "Start developing with an open‑source...", - price: "$149", - discount: "10%", - }, -]; - export function Farms() { + const [farms, setFarms] = useState([]); const [search, setSearch] = useState(""); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchFarms = async () => { + try { + const token = localStorage.getItem("token"); // hoặc lấy từ context + const res = await axios.get("https://api-ndolv2.nongdanonline.vn/adminfarms", { + headers: { Authorization: `Bearer ${token}` }, + }); + setFarms(res.data); + } catch (err) { + setError(err.response?.data?.message || err.message); + } finally { + setLoading(false); + } + }; - const filteredProducts = products.filter((p) => - p.name.toLowerCase().includes(search.toLowerCase()) + fetchFarms(); + }, []); + + const filteredFarms = farms.filter((farm) => + farm.name?.toLowerCase().includes(search.toLowerCase()) ); return ( - {/* Tiêu đề + nút thêm mới */}
- All products + Danh sách nông trại
- {/* Ô tìm kiếm */} setSearch(e.target.value)} className="mb-6" /> - {/* Bảng dữ liệu */} - - - - - - {[ - "PRODUCT NAME", - "TECHNOLOGY", - "DESCRIPTION", - "ID", - "PRICE", - "DISCOUNT", - "ACTIONS", - ].map((head) => ( - - ))} - - - - {filteredProducts.map((product, idx) => ( - - + {filteredFarms.map((farm, idx) => ( + + + + + + + + + + + + + ))} + +
- - - - {head} - -
+ {loading && Đang tải dữ liệu...} + {error && Lỗi: {error}} + + {!loading && !error && ( + + + + + - - - - - - + + {[ + "TÊN FARM", + "MÃ FARM", + "CHỦ SỞ HỮU", + "SỐ ĐIỆN THOẠI", + "ĐỊA CHỈ", + "DIỆN TÍCH", + "GIÁ / THÁNG", + "TRẠNG THÁI", + "THAO TÁC", + ].map((head) => ( + + ))} - ))} - -
- - - - {product.name} - - - {product.category} - - - {product.technology} - - {product.description} - {product.id}{product.price} - {product.discount === "No" ? ( - No - ) : ( - - )} - -
- - -
-
+ + {head} + +
-
+ +
+ + {farm.name}{farm.code}{farm.ownerInfo?.name || "—"}{farm.ownerInfo?.phone || "—"}{farm.location}{farm.area || 0} m² + {farm.pricePerMonth > 0 + ? farm.pricePerMonth.toLocaleString("vi-VN") + " đ" + : "Miễn phí"} + + + +
+ + +
+
+
+ )}
); } -export default Farms; \ No newline at end of file +export default Farms \ No newline at end of file From 9d56415fadacd46588e06c8b7b14f6506658d4a8 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Fri, 4 Jul 2025 14:03:51 +0700 Subject: [PATCH 027/248] thanh --- src/pages/dashboard/FarmForm.jsx | 116 +++++++++++++ src/pages/dashboard/farms.jsx | 277 +++++++++++++++++++------------ 2 files changed, 291 insertions(+), 102 deletions(-) create mode 100644 src/pages/dashboard/FarmForm.jsx diff --git a/src/pages/dashboard/FarmForm.jsx b/src/pages/dashboard/FarmForm.jsx new file mode 100644 index 00000000..5fc69bb9 --- /dev/null +++ b/src/pages/dashboard/FarmForm.jsx @@ -0,0 +1,116 @@ +import React, { useState, useEffect } from "react"; +import { + Dialog, + DialogHeader, + DialogBody, + DialogFooter, + Input, + Button, +} from "@material-tailwind/react"; + +const FarmForm = ({ open, onClose, initialData, onSubmit }) => { + const [formData, setFormData] = useState({ + name: "", + code: "", + location: "", + area: "", + pricePerMonth: "", + status: "pending", + ownerName: "", + ownerPhone: "", + }); + + useEffect(() => { + if (initialData) { + setFormData({ + name: initialData.name || "", + code: initialData.code || "", + location: initialData.location || "", + area: initialData.area?.toString() || "", + pricePerMonth: initialData.pricePerMonth?.toString() || "", + status: initialData.status || "pending", + ownerName: initialData.ownerInfo?.name || "", + ownerPhone: initialData.ownerInfo?.phone || "", + }); + } else { + setFormData({ + name: "", + code: "", + location: "", + area: "", + pricePerMonth: "", + status: "pending", + ownerName: "", + ownerPhone: "", + }); + } + }, [initialData]); + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData((prev) => ({ + ...prev, + [name]: value, + })); + }; + + const handleSubmit = () => { + if (!formData.name || !formData.code) { + return alert("Vui lòng nhập đầy đủ thông tin."); + } + + const parsedData = { + name: formData.name, + code: formData.code, + location: formData.location, + area: formData.area === "" ? 0 : parseFloat(formData.area), + pricePerMonth: + formData.pricePerMonth === "" ? 0 : parseFloat(formData.pricePerMonth), + status: formData.status || "pending", + + ownerInfo: { + name: formData.ownerName || "Chưa rõ", + phone: formData.ownerPhone || "", + email: "", // Optional, bạn có thể thêm input nếu muốn + }, + coordinates: { + lat: 0, + lng: 0, + }, + features: [], + phone: "", + zalo: "", + operationTime: "", + defaultImage: "", + services: [], + }; + + console.log("parsedData gửi về:", parsedData); + + onSubmit(parsedData); + onClose(); + }; + + return ( + + {initialData ? "Chỉnh sửa" : "Thêm"} nông trại + + + + + + + + + + + + + + + ); +}; + +export default FarmForm \ No newline at end of file diff --git a/src/pages/dashboard/farms.jsx b/src/pages/dashboard/farms.jsx index b4a3fe52..fb170430 100644 --- a/src/pages/dashboard/farms.jsx +++ b/src/pages/dashboard/farms.jsx @@ -6,125 +6,198 @@ import { import { PencilSquareIcon, TrashIcon, PlusIcon, } from "@heroicons/react/24/solid"; +import FarmForm from "./farmForm"; export function Farms() { const [farms, setFarms] = useState([]); const [search, setSearch] = useState(""); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const [openForm, setOpenForm] = useState(false); + const [editingFarm, setEditingFarm] = useState(null); - useEffect(() => { - const fetchFarms = async () => { - try { - const token = localStorage.getItem("token"); // hoặc lấy từ context - const res = await axios.get("https://api-ndolv2.nongdanonline.vn/adminfarms", { - headers: { Authorization: `Bearer ${token}` }, - }); - setFarms(res.data); - } catch (err) { - setError(err.response?.data?.message || err.message); - } finally { - setLoading(false); - } - }; + const fetchFarms = async () => { + try { + const token = localStorage.getItem("token"); + const res = await axios.get("https://api-ndolv2.nongdanonline.vn/adminfarms", { + headers: { Authorization: `Bearer ${token}` }, + }); + setFarms(res.data); + } catch (err) { + setError(err.response?.data?.message || err.message); + } finally { + setLoading(false); + } + }; + useEffect(() => { fetchFarms(); }, []); + const addFarm = async (newFarm) => { + try { + const token = localStorage.getItem("token"); + + const completedFarm = { + ...newFarm, + ownerInfo: { + name: newFarm.ownerInfo?.name || "Chưa rõ", + phone: newFarm.ownerInfo?.phone || "", + email: newFarm.ownerInfo?.email || "", + }, + coordinates: { + lat: newFarm.coordinates?.lat || 0, + lng: newFarm.coordinates?.lng || 0, + }, + features: newFarm.features || [], + phone: newFarm.phone || "", + zalo: newFarm.zalo || "", + operationTime: newFarm.operationTime || "", + defaultImage: newFarm.defaultImage || "", + services: newFarm.services || [], + }; + + console.log("📦 Dữ liệu gửi đi:", completedFarm); + + await axios.post("https://api-ndolv2.nongdanonline.vn/adminfarms", completedFarm, { + headers: { Authorization: `Bearer ${token}` }, + }); + + alert(" Tạo farm thành công!"); + fetchFarms(); + } catch (err) { + console.error(" Lỗi tạo farm:", { + status: err.response?.status, + data: err.response?.data, + }); + alert("Lỗi thêm: " + (err.response?.data?.message || err.message)); + } + }; + + const editFarm = async (id, updatedFarm) => { + try { + const token = localStorage.getItem("token"); + await axios.put(`https://api-ndolv2.nongdanonline.vn/adminfarms/${id}`, updatedFarm, { + headers: { Authorization: `Bearer ${token}` }, + }); + fetchFarms(); + } catch (err) { + alert("Lỗi sửa: " + (err.response?.data?.message || err.message)); + } + }; + + const deleteFarm = async (id) => { + if (!window.confirm("Bạn có chắc chắn muốn xoá không?")) return; + console.log(" Xoá farm với id:", id); + try { + const token = localStorage.getItem("token"); + await axios.delete(`https://api-ndolv2.nongdanonline.vn/adminfarms/${id}`, { + headers: { Authorization: `Bearer ${token}` }, + }); + setFarms((prevFarms) => prevFarms.filter((farm) => farm._id !== id)); + } catch (err) { + alert("Lỗi xoá: " + (err.response?.data?.message || err.message)); + } + }; + + const handleAddClick = () => { + setEditingFarm(null); + setOpenForm(true); + }; + + const handleEditClick = (farm) => { + setEditingFarm(farm); + setOpenForm(true); + }; + const filteredFarms = farms.filter((farm) => farm.name?.toLowerCase().includes(search.toLowerCase()) ); return ( - -
- Danh sách nông trại - -
- - setSearch(e.target.value)} - className="mb-6" - /> - - {loading && Đang tải dữ liệu...} - {error && Lỗi: {error}} - - {!loading && !error && ( - - - - - - {[ - "TÊN FARM", - "MÃ FARM", - "CHỦ SỞ HỮU", - "SỐ ĐIỆN THOẠI", - "ĐỊA CHỈ", - "DIỆN TÍCH", - "GIÁ / THÁNG", - "TRẠNG THÁI", - "THAO TÁC", - ].map((head) => ( - - ))} - - - - {filteredFarms.map((farm, idx) => ( - - - - - - - - - - - + <> + +
+ Danh sách nông trại + +
+ setSearch(e.target.value)} + className="mb-6" + /> + {loading ? ( + Đang tải dữ liệu... + ) : error ? ( + Lỗi: {error} + ) : ( + +
- - - - {head} - -
- - {farm.name}{farm.code}{farm.ownerInfo?.name || "—"}{farm.ownerInfo?.phone || "—"}{farm.location}{farm.area || 0} m² - {farm.pricePerMonth > 0 - ? farm.pricePerMonth.toLocaleString("vi-VN") + " đ" - : "Miễn phí"} - - - -
- - -
-
+ + + + {["Tên", "Mã", "Chủ sở hữu", "SĐT", "Địa chỉ", "Diện tích", "Giá", "Trạng thái", "Thao tác"].map((head) => ( + + ))} - ))} - -
{head}
-
- )} -
+ + + {filteredFarms.map((farm) => ( + + + {farm.name} + {farm.code} + {farm.ownerInfo?.name || "—"} + {farm.ownerInfo?.phone || "—"} + {farm.location} + {farm.area} m² + {farm.pricePerMonth?.toLocaleString("vi-VN") || "0"} đ + + + + +
+ + +
+ + + ))} + + + + )} +
+ + setOpenForm(false)} + initialData={editingFarm} + onSubmit={(data) => { + editingFarm ? editFarm(editingFarm._id, data) : addFarm(data); + }} + /> + ); } + export default Farms \ No newline at end of file From 733b7816634dfc5ca34eece0984180d877bfb65b Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Fri, 4 Jul 2025 14:22:26 +0700 Subject: [PATCH 028/248] thanh --- src/pages/dashboard/AcceptFarm.jsx | 1 + src/pages/dashboard/farms.jsx | 277 +++++++++++------------------ 2 files changed, 103 insertions(+), 175 deletions(-) diff --git a/src/pages/dashboard/AcceptFarm.jsx b/src/pages/dashboard/AcceptFarm.jsx index 26b07ca1..8e2421e4 100644 --- a/src/pages/dashboard/AcceptFarm.jsx +++ b/src/pages/dashboard/AcceptFarm.jsx @@ -7,6 +7,7 @@ export function AcceptFarm() { const BaseUrl=`https://api-ndolv2.nongdanonline.vn` const [Accept, setAccept] = React.useState({}); const [farm,setFarm]=useState([]) + const AcceptFarm = async (id) => { // setAccept((prev) => ({ ...prev, [id]: true })); setFarm((prev) => prev.filter((item) => item.id !== id)); diff --git a/src/pages/dashboard/farms.jsx b/src/pages/dashboard/farms.jsx index fb170430..b4a3fe52 100644 --- a/src/pages/dashboard/farms.jsx +++ b/src/pages/dashboard/farms.jsx @@ -6,198 +6,125 @@ import { import { PencilSquareIcon, TrashIcon, PlusIcon, } from "@heroicons/react/24/solid"; -import FarmForm from "./farmForm"; export function Farms() { const [farms, setFarms] = useState([]); const [search, setSearch] = useState(""); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const [openForm, setOpenForm] = useState(false); - const [editingFarm, setEditingFarm] = useState(null); - - const fetchFarms = async () => { - try { - const token = localStorage.getItem("token"); - const res = await axios.get("https://api-ndolv2.nongdanonline.vn/adminfarms", { - headers: { Authorization: `Bearer ${token}` }, - }); - setFarms(res.data); - } catch (err) { - setError(err.response?.data?.message || err.message); - } finally { - setLoading(false); - } - }; useEffect(() => { + const fetchFarms = async () => { + try { + const token = localStorage.getItem("token"); // hoặc lấy từ context + const res = await axios.get("https://api-ndolv2.nongdanonline.vn/adminfarms", { + headers: { Authorization: `Bearer ${token}` }, + }); + setFarms(res.data); + } catch (err) { + setError(err.response?.data?.message || err.message); + } finally { + setLoading(false); + } + }; + fetchFarms(); }, []); - const addFarm = async (newFarm) => { - try { - const token = localStorage.getItem("token"); - - const completedFarm = { - ...newFarm, - ownerInfo: { - name: newFarm.ownerInfo?.name || "Chưa rõ", - phone: newFarm.ownerInfo?.phone || "", - email: newFarm.ownerInfo?.email || "", - }, - coordinates: { - lat: newFarm.coordinates?.lat || 0, - lng: newFarm.coordinates?.lng || 0, - }, - features: newFarm.features || [], - phone: newFarm.phone || "", - zalo: newFarm.zalo || "", - operationTime: newFarm.operationTime || "", - defaultImage: newFarm.defaultImage || "", - services: newFarm.services || [], - }; - - console.log("📦 Dữ liệu gửi đi:", completedFarm); - - await axios.post("https://api-ndolv2.nongdanonline.vn/adminfarms", completedFarm, { - headers: { Authorization: `Bearer ${token}` }, - }); - - alert(" Tạo farm thành công!"); - fetchFarms(); - } catch (err) { - console.error(" Lỗi tạo farm:", { - status: err.response?.status, - data: err.response?.data, - }); - alert("Lỗi thêm: " + (err.response?.data?.message || err.message)); - } - }; - - const editFarm = async (id, updatedFarm) => { - try { - const token = localStorage.getItem("token"); - await axios.put(`https://api-ndolv2.nongdanonline.vn/adminfarms/${id}`, updatedFarm, { - headers: { Authorization: `Bearer ${token}` }, - }); - fetchFarms(); - } catch (err) { - alert("Lỗi sửa: " + (err.response?.data?.message || err.message)); - } - }; - - const deleteFarm = async (id) => { - if (!window.confirm("Bạn có chắc chắn muốn xoá không?")) return; - console.log(" Xoá farm với id:", id); - try { - const token = localStorage.getItem("token"); - await axios.delete(`https://api-ndolv2.nongdanonline.vn/adminfarms/${id}`, { - headers: { Authorization: `Bearer ${token}` }, - }); - setFarms((prevFarms) => prevFarms.filter((farm) => farm._id !== id)); - } catch (err) { - alert("Lỗi xoá: " + (err.response?.data?.message || err.message)); - } - }; - - const handleAddClick = () => { - setEditingFarm(null); - setOpenForm(true); - }; - - const handleEditClick = (farm) => { - setEditingFarm(farm); - setOpenForm(true); - }; - const filteredFarms = farms.filter((farm) => farm.name?.toLowerCase().includes(search.toLowerCase()) ); return ( - <> - -
- Danh sách nông trại - -
- setSearch(e.target.value)} - className="mb-6" - /> - {loading ? ( - Đang tải dữ liệu... - ) : error ? ( - Lỗi: {error} - ) : ( - - - - - - {["Tên", "Mã", "Chủ sở hữu", "SĐT", "Địa chỉ", "Diện tích", "Giá", "Trạng thái", "Thao tác"].map((head) => ( - - ))} - - - - {filteredFarms.map((farm) => ( - - - - - - - - - - - - - ))} - -
{head}
{farm.name}{farm.code}{farm.ownerInfo?.name || "—"}{farm.ownerInfo?.phone || "—"}{farm.location}{farm.area} m²{farm.pricePerMonth?.toLocaleString("vi-VN") || "0"} đ - - -
- - -
-
-
- )} -
- - setOpenForm(false)} - initialData={editingFarm} - onSubmit={(data) => { - editingFarm ? editFarm(editingFarm._id, data) : addFarm(data); - }} + +
+ Danh sách nông trại + +
+ + setSearch(e.target.value)} + className="mb-6" /> - + + {loading && Đang tải dữ liệu...} + {error && Lỗi: {error}} + + {!loading && !error && ( + + + + + + {[ + "TÊN FARM", + "MÃ FARM", + "CHỦ SỞ HỮU", + "SỐ ĐIỆN THOẠI", + "ĐỊA CHỈ", + "DIỆN TÍCH", + "GIÁ / THÁNG", + "TRẠNG THÁI", + "THAO TÁC", + ].map((head) => ( + + ))} + + + + {filteredFarms.map((farm, idx) => ( + + + + + + + + + + + + + ))} + +
+ + + + {head} + +
+ + {farm.name}{farm.code}{farm.ownerInfo?.name || "—"}{farm.ownerInfo?.phone || "—"}{farm.location}{farm.area || 0} m² + {farm.pricePerMonth > 0 + ? farm.pricePerMonth.toLocaleString("vi-VN") + " đ" + : "Miễn phí"} + + + +
+ + +
+
+
+ )} +
); } - export default Farms \ No newline at end of file From f2d8db2ed3f85c31fc4d9be28716135cde8361b8 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Fri, 4 Jul 2025 14:23:08 +0700 Subject: [PATCH 029/248] Update farms.jsx --- src/pages/dashboard/farms.jsx | 277 +++++++++++++++++++++------------- 1 file changed, 175 insertions(+), 102 deletions(-) diff --git a/src/pages/dashboard/farms.jsx b/src/pages/dashboard/farms.jsx index b4a3fe52..9c75323d 100644 --- a/src/pages/dashboard/farms.jsx +++ b/src/pages/dashboard/farms.jsx @@ -6,125 +6,198 @@ import { import { PencilSquareIcon, TrashIcon, PlusIcon, } from "@heroicons/react/24/solid"; +import FarmForm from "./farmForm"; export function Farms() { const [farms, setFarms] = useState([]); const [search, setSearch] = useState(""); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const [openForm, setOpenForm] = useState(false); + const [editingFarm, setEditingFarm] = useState(null); - useEffect(() => { - const fetchFarms = async () => { - try { - const token = localStorage.getItem("token"); // hoặc lấy từ context - const res = await axios.get("https://api-ndolv2.nongdanonline.vn/adminfarms", { - headers: { Authorization: `Bearer ${token}` }, - }); - setFarms(res.data); - } catch (err) { - setError(err.response?.data?.message || err.message); - } finally { - setLoading(false); - } - }; + const fetchFarms = async () => { + try { + const token = localStorage.getItem("token"); + const res = await axios.get("https://api-ndolv2.nongdanonline.vn/adminfarms", { + headers: { Authorization: `Bearer ${token}` }, + }); + setFarms(res.data); + } catch (err) { + setError(err.response?.data?.message || err.message); + } finally { + setLoading(false); + } + }; + useEffect(() => { fetchFarms(); }, []); + const addFarm = async (newFarm) => { + try { + const token = localStorage.getItem("token"); + + const completedFarm = { + ...newFarm, + ownerInfo: { + name: newFarm.ownerInfo?.name || "Chưa rõ", + phone: newFarm.ownerInfo?.phone || "", + email: newFarm.ownerInfo?.email || "", + }, + coordinates: { + lat: newFarm.coordinates?.lat || 0, + lng: newFarm.coordinates?.lng || 0, + }, + features: newFarm.features || [], + phone: newFarm.phone || "", + zalo: newFarm.zalo || "", + operationTime: newFarm.operationTime || "", + defaultImage: newFarm.defaultImage || "", + services: newFarm.services || [], + }; + + console.log("📦 Dữ liệu gửi đi:", completedFarm); + + await axios.post("https://api-ndolv2.nongdanonline.vn/adminfarms", completedFarm, { + headers: { Authorization: `Bearer ${token}` }, + }); + + alert(" Tạo farm thành công!"); + fetchFarms(); + } catch (err) { + console.error(" Lỗi tạo farm:", { + status: err.response?.status, + data: err.response?.data, + }); + alert("Lỗi thêm: " + (err.response?.data?.message || err.message)); + } + }; + + const editFarm = async (id, updatedFarm) => { + try { + const token = localStorage.getItem("token"); + await axios.put(`https://api-ndolv2.nongdanonline.vn/adminfarms/${id}`, updatedFarm, { + headers: { Authorization: `Bearer ${token}` }, + }); + fetchFarms(); + } catch (err) { + alert("Lỗi sửa: " + (err.response?.data?.message || err.message)); + } + }; + + const deleteFarm = async (id) => { + if (!window.confirm("Bạn có chắc chắn muốn xoá không?")) return; + console.log(" Xoá farm với id:", id); + try { + const token = localStorage.getItem("token"); +await axios.delete(`https://api-ndolv2.nongdanonline.vn/adminfarms/${id}`, { + headers: { Authorization: `Bearer ${token}` }, + }); + setFarms((prevFarms) => prevFarms.filter((farm) => farm._id !== id)); + } catch (err) { + alert("Lỗi xoá: " + (err.response?.data?.message || err.message)); + } + }; + + const handleAddClick = () => { + setEditingFarm(null); + setOpenForm(true); + }; + + const handleEditClick = (farm) => { + setEditingFarm(farm); + setOpenForm(true); + }; + const filteredFarms = farms.filter((farm) => farm.name?.toLowerCase().includes(search.toLowerCase()) ); return ( - -
- Danh sách nông trại - -
- - setSearch(e.target.value)} - className="mb-6" - /> - - {loading && Đang tải dữ liệu...} - {error && Lỗi: {error}} - - {!loading && !error && ( - - - - - - {[ - "TÊN FARM", - "MÃ FARM", - "CHỦ SỞ HỮU", - "SỐ ĐIỆN THOẠI", - "ĐỊA CHỈ", - "DIỆN TÍCH", - "GIÁ / THÁNG", - "TRẠNG THÁI", - "THAO TÁC", - ].map((head) => ( - - ))} - - - - {filteredFarms.map((farm, idx) => ( - - - - - - - - - - - + <> + +
+ Danh sách nông trại + +
+ setSearch(e.target.value)} + className="mb-6" + /> + {loading ? ( + Đang tải dữ liệu... + ) : error ? ( + Lỗi: {error} + ) : ( + +
- - - - {head} - -
- - {farm.name}{farm.code}{farm.ownerInfo?.name || "—"}{farm.ownerInfo?.phone || "—"}{farm.location}{farm.area || 0} m² - {farm.pricePerMonth > 0 - ? farm.pricePerMonth.toLocaleString("vi-VN") + " đ" - : "Miễn phí"} - - - -
- - -
-
+ + + + {["Tên", "Mã", "Chủ sở hữu", "SĐT", "Địa chỉ", "Diện tích", "Giá", "Trạng thái", "Thao tác"].map((head) => ( + + ))} - ))} - -
{head}
-
- )} -
+ + + {filteredFarms.map((farm) => ( + + + {farm.name} + {farm.code} + {farm.ownerInfo?.name || "—"} + {farm.ownerInfo?.phone || "—"} + {farm.location} + {farm.area} m² + {farm.pricePerMonth?.toLocaleString("vi-VN") || "0"} đ + + + + +
+ + +
+ + + ))} + + + + )} + + + setOpenForm(false)} + initialData={editingFarm} + onSubmit={(data) => { + editingFarm ? editFarm(editingFarm._id, data) : addFarm(data); + }} + /> + ); } + export default Farms \ No newline at end of file From bcad6a0a90b341691dce3f7634724bcfb6a8357e Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Fri, 4 Jul 2025 15:51:55 +0700 Subject: [PATCH 030/248] thanh --- src/pages/dashboard/farms.jsx | 204 +++++++++++++++++++++------------- src/widgets/layout/footer.jsx | 30 +---- 2 files changed, 130 insertions(+), 104 deletions(-) diff --git a/src/pages/dashboard/farms.jsx b/src/pages/dashboard/farms.jsx index fb170430..2cd9c167 100644 --- a/src/pages/dashboard/farms.jsx +++ b/src/pages/dashboard/farms.jsx @@ -1,27 +1,32 @@ import React, { useState, useEffect } from "react"; import axios from "axios"; import { - Card, CardBody, Input, Button, Typography, Checkbox, Chip, + Card, CardBody, Input, Button, Typography, Checkbox, Chip, Tabs, TabsHeader, Tab } from "@material-tailwind/react"; import { PencilSquareIcon, TrashIcon, PlusIcon, } from "@heroicons/react/24/solid"; import FarmForm from "./farmForm"; +const BASE_URL = "https://api-ndolv2.nongdanonline.vn"; +const getOpts = () => ({ + headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }, +}); + export function Farms() { const [farms, setFarms] = useState([]); - const [search, setSearch] = useState(""); - const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const [loading, setLoading] = useState(true); + + const [tab, setTab] = useState("all") + const [search, setSearch] = useState(""); + const [openForm, setOpenForm] = useState(false); const [editingFarm, setEditingFarm] = useState(null); const fetchFarms = async () => { try { - const token = localStorage.getItem("token"); - const res = await axios.get("https://api-ndolv2.nongdanonline.vn/adminfarms", { - headers: { Authorization: `Bearer ${token}` }, - }); + const res = await axios.get(`${BASE_URL}/adminfarms`, getOpts()); setFarms(res.data); } catch (err) { setError(err.response?.data?.message || err.message); @@ -30,57 +35,20 @@ export function Farms() { } }; - useEffect(() => { - fetchFarms(); - }, []); - - const addFarm = async (newFarm) => { + const addFarm = async (data) => { try { - const token = localStorage.getItem("token"); - - const completedFarm = { - ...newFarm, - ownerInfo: { - name: newFarm.ownerInfo?.name || "Chưa rõ", - phone: newFarm.ownerInfo?.phone || "", - email: newFarm.ownerInfo?.email || "", - }, - coordinates: { - lat: newFarm.coordinates?.lat || 0, - lng: newFarm.coordinates?.lng || 0, - }, - features: newFarm.features || [], - phone: newFarm.phone || "", - zalo: newFarm.zalo || "", - operationTime: newFarm.operationTime || "", - defaultImage: newFarm.defaultImage || "", - services: newFarm.services || [], - }; - - console.log("📦 Dữ liệu gửi đi:", completedFarm); - - await axios.post("https://api-ndolv2.nongdanonline.vn/adminfarms", completedFarm, { - headers: { Authorization: `Bearer ${token}` }, - }); - + await axios.post(`${BASE_URL}/adminfarms`, data, getOpts()); + await fetchFarms(); alert(" Tạo farm thành công!"); - fetchFarms(); } catch (err) { - console.error(" Lỗi tạo farm:", { - status: err.response?.status, - data: err.response?.data, - }); - alert("Lỗi thêm: " + (err.response?.data?.message || err.message)); + alert("Lỗi thêm: " + (err.response?.data?.message || err.message)); } }; - const editFarm = async (id, updatedFarm) => { + const editFarm = async (id, data) => { try { - const token = localStorage.getItem("token"); - await axios.put(`https://api-ndolv2.nongdanonline.vn/adminfarms/${id}`, updatedFarm, { - headers: { Authorization: `Bearer ${token}` }, - }); - fetchFarms(); + await axios.put(`${BASE_URL}/adminfarms/${id}`, data, getOpts()); + await fetchFarms(); } catch (err) { alert("Lỗi sửa: " + (err.response?.data?.message || err.message)); } @@ -90,45 +58,87 @@ export function Farms() { if (!window.confirm("Bạn có chắc chắn muốn xoá không?")) return; console.log(" Xoá farm với id:", id); try { - const token = localStorage.getItem("token"); - await axios.delete(`https://api-ndolv2.nongdanonline.vn/adminfarms/${id}`, { - headers: { Authorization: `Bearer ${token}` }, - }); + await axios.delete(`${BASE_URL}/adminfarms/${id}`, getOpts()); setFarms((prevFarms) => prevFarms.filter((farm) => farm._id !== id)); } catch (err) { alert("Lỗi xoá: " + (err.response?.data?.message || err.message)); } }; - const handleAddClick = () => { - setEditingFarm(null); - setOpenForm(true); - }; + const activateFarm = async (id) => { + try { + await axios.patch(`${BASE_URL}/adminfarms/${id}/activate`, null, getOpts()); + await fetchFarms(); + }catch (err) { + alert("Lỗi kích hoạt: " + (err.response?.data?.message || err.message)); + } + } - const handleEditClick = (farm) => { - setEditingFarm(farm); - setOpenForm(true); - }; + const deactivateFarm = async (id) => { + try { + await axios.patch(`${BASE_URL}/adminfarms/${id}/deactivate`, null, getOpts()); + await fetchFarms(); + }catch (err) { + alert("Lỗi kích hoạt: " + (err.response?.data?.message || err.message)); + } + } + + useEffect(() => { + fetchFarms(); + }, []); - const filteredFarms = farms.filter((farm) => + const displayedFarms = farms + .filter((farm) => { + if (tab === "all") return true; + return farm.status === tab; + }) + .filter((farm) => farm.name?.toLowerCase().includes(search.toLowerCase()) ); + const approveFarm = (id) => activateFarm(id); + const rejectFarm = (id) => deactivateFarm(id); return ( <>
- Danh sách nông trại -
+ setSearch(e.target.value)} className="mb-6" /> + {loading ? ( Đang tải dữ liệu... ) : error ? ( @@ -145,7 +155,7 @@ export function Farms() { - {filteredFarms.map((farm) => ( + {displayedFarms.map((farm) => ( {farm.name} @@ -157,27 +167,71 @@ export function Farms() { {farm.pricePerMonth?.toLocaleString("vi-VN") || "0"} đ + -
+
+ {/* Sửa */} + + {/* Xoá */} + + {/* Theo trạng thái */} + {farm.status === "pending" && ( + <> + + + + )} + + {farm.status === "inactive" && ( + + )} + + {farm.status === "active" && ( + + )}
diff --git a/src/widgets/layout/footer.jsx b/src/widgets/layout/footer.jsx index 1ea98e53..ad0cf531 100644 --- a/src/widgets/layout/footer.jsx +++ b/src/widgets/layout/footer.jsx @@ -7,35 +7,7 @@ export function Footer({ brandName, brandLink, routes }) { return (
-
- - © {year}, made with{" "} - by{" "} - - {brandName} - {" "} - for a better web. - -
    - {routes.map(({ name, path }) => ( -
  • - - {name} - -
  • - ))} -
-
+
); } From 2b501f84bdd8b5013a604513adb147dee2bcdccb Mon Sep 17 00:00:00 2001 From: Longvu003 Date: Fri, 4 Jul 2025 16:55:04 +0700 Subject: [PATCH 031/248] upcode layout questions --- jsconfig.json | 4 +- package.json | 1 + src/App.jsx | 7 +- src/ipconfig.tsx | 1 + src/pages/dashboard/AcceptFarm.jsx | 92 +++++++-------- src/pages/dashboard/FarmDetail.tsx | 46 ++++++++ src/pages/dashboard/Questions/Questions.tsx | 108 ++++++++++++++++++ .../dashboard/Questions/UpdateQuestion.tsx | 58 ++++++++++ src/pages/dashboard/index.js | 1 + src/pages/dashboard/notifications.jsx | 0 src/pages/dashboard/users.jsx | 5 +- src/routes.jsx | 8 +- 12 files changed, 279 insertions(+), 52 deletions(-) create mode 100644 src/ipconfig.tsx create mode 100644 src/pages/dashboard/FarmDetail.tsx create mode 100644 src/pages/dashboard/Questions/Questions.tsx create mode 100644 src/pages/dashboard/Questions/UpdateQuestion.tsx create mode 100644 src/pages/dashboard/notifications.jsx diff --git a/jsconfig.json b/jsconfig.json index b30b1c0f..680d82bb 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,8 +1,10 @@ { "compilerOptions": { + "jsx": "react-jsx", + "baseUrl": ".", "paths": { "@/*": ["src/*"], }, }, -} \ No newline at end of file +} diff --git a/package.json b/package.json index c018e952..cb7c7fd9 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "react": "18.2.0", "react-apexcharts": "1.4.1", "react-dom": "18.2.0", + "react-loader-spinner": "^6.1.6", "react-router-dom": "6.17.0" }, "devDependencies": { diff --git a/src/App.jsx b/src/App.jsx index b84c7349..0c1c83bc 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,12 +1,15 @@ import { Routes, Route, Navigate } from "react-router-dom"; import { Dashboard, Auth } from "@/layouts"; - +import FarmDetail from "./pages/dashboard/FarmDetail"; +import UpdateQuestion from "./pages/dashboard/Questions/UpdateQuestion"; function App() { return ( } /> } /> - } /> + } /> +} /> + } /> ); } diff --git a/src/ipconfig.tsx b/src/ipconfig.tsx new file mode 100644 index 00000000..51cd1b17 --- /dev/null +++ b/src/ipconfig.tsx @@ -0,0 +1 @@ +export const BaseUrl=`https://api-ndolv2.nongdanonline.vn` \ No newline at end of file diff --git a/src/pages/dashboard/AcceptFarm.jsx b/src/pages/dashboard/AcceptFarm.jsx index 26b07ca1..950fce6b 100644 --- a/src/pages/dashboard/AcceptFarm.jsx +++ b/src/pages/dashboard/AcceptFarm.jsx @@ -3,71 +3,71 @@ import { InformationCircleIcon } from "@heroicons/react/24/outline"; import { useState } from "react"; import { Requiment } from "@/data/Farm-data"; import axios from "axios"; +import { useNavigate } from "react-router-dom"; +import { BaseUrl } from "@/ipconfig"; export function AcceptFarm() { - const BaseUrl=`https://api-ndolv2.nongdanonline.vn` - const [Accept, setAccept] = React.useState({}); + const navigate = useNavigate(); const [farm,setFarm]=useState([]) const AcceptFarm = async (id) => { - // setAccept((prev) => ({ ...prev, [id]: true })); setFarm((prev) => prev.filter((item) => item.id !== id)); - }; + const tokenUser= localStorage.getItem("token") const getFarm =async ()=>{ try { -const res= await axios.get(`${BaseUrl}/farms`) +const res= await axios.get(`${BaseUrl}/adminfarms`,{ headers: { + Authorization: `Bearer ${tokenUser}`, } + + },) if(res.status===200){ setFarm(res.data) }else{ console.log("Có lỗi khi lấy data ") } } catch (error) { - console.log("Lỗi :",error) + console.log("Lỗi server :",error) } } + +const getDetailFarm =async(id)=>{ +navigate(`/FarmDetail/${id}`) +} useEffect(()=>{ getFarm() },[]) return ( -
-
- - { - farm.length===0 ? (
Không có đơn !
):( ( farm.map((item) => ( - -
- {item.name} - Vị trí: {item.location} - {item.pricePerMonth} Vnđ - Mã số: {item.code} - {/* { - item.farmImages ?( - {item.nameFarm} - - ):( - - ) - } */} - - - {!Accept[item.id] ? ( - - ) : ( - - )} -
)) - )) - } - +
+
+ {farm.length === 0 ? ( +
Không có đơn!
+ ) : ( + farm.map((item) => ( +
getDetailFarm(item.id)} + className="border rounded-2xl shadow-lg p-6 flex flex-col md:flex-row items-center gap-6 bg-white transition hover:shadow-2xl cursor-pointer" + key={item.id} + > + {item.name} +
+ {item.name} + Vị trí: {item.location} + {item.pricePerMonth} Vnđ + Mã số: {item.code} +
+
e.stopPropagation()}> + +
+
+ )) + )}
); diff --git a/src/pages/dashboard/FarmDetail.tsx b/src/pages/dashboard/FarmDetail.tsx new file mode 100644 index 00000000..10f6e334 --- /dev/null +++ b/src/pages/dashboard/FarmDetail.tsx @@ -0,0 +1,46 @@ +import React from "react"; +import axios from "axios"; +import { useEffect } from "react"; +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { useParams } from "react-router-dom"; +import { BaseUrl } from "@/ipconfig"; +export function FarmDetail(){ + const { id } = useParams(); + const tokenUser= localStorage.getItem("token") + const [product, setProduct] = useState(null) +const getDetail=async()=>{ +try { +const res= await axios(`${BaseUrl}/adminfarms/${id}` +,{ headers: { + Authorization: `Bearer ${tokenUser}`, } + }, +) +if(res.status===200){ + setProduct(res.data) +} } catch (error) { + console.log("Lỗi server",error) + } +} +useEffect(()=>{ +getDetail() +},[]) +console.log("data nè :",product) +return ( +
+ {product ? ( +
+ {product.location} + {/* Hiển thị các thông tin khác nếu cần */} +
+ ) : ( +
+ Không có data +
+ )} +
+) + +} + +export default FarmDetail \ No newline at end of file diff --git a/src/pages/dashboard/Questions/Questions.tsx b/src/pages/dashboard/Questions/Questions.tsx new file mode 100644 index 00000000..bfbd5977 --- /dev/null +++ b/src/pages/dashboard/Questions/Questions.tsx @@ -0,0 +1,108 @@ +import React, { useEffect } from 'react' +import axios from 'axios' +import { useState } from 'react' +import { BaseUrl } from '@/ipconfig' +import { useNavigate } from 'react-router-dom' +import { Oval } from 'react-loader-spinner'; + +export const Questions = () => { + const [loading, setLoading] = useState(true); + const tokenUser= localStorage.getItem("token") +const [questions,setQuestions]=useState([]) +const navigate=useNavigate() +const getData=async()=>{ +try { +const res= await axios.get(`${BaseUrl}/admin-questions`,{ + headers:{Authorization:`Bearer ${tokenUser}` } +}) + +if(res.status===200){ +setQuestions(res.data) +setLoading(false) +} +console.log("Có lỗi trong lúc lấy data") +} catch (error) { + console.log("Lỗi nè: ",error) +} + +} + +const gotoUpdate =async(id)=>{ +navigate(`/UpdateQuestion/${id}`) +} +console.log(questions) +useEffect(()=>{ +getData() +},[]) + return ( +
+ { + loading ? + ( ):( + questions.map((item) => ( +
+
+
{item.text}
+
+ + +
+
+
+ {Array.isArray(item.options) && item.options.length > 0 ? ( + item.options.map((opt: string, idx: number) => ( + + )) + ) : item.type === "image" ? ( + + ) : item.type === "link" ? ( + + ) : ( + + )} +
+
+ )) + ) + } +
+ ) +} + +export default Questions \ No newline at end of file diff --git a/src/pages/dashboard/Questions/UpdateQuestion.tsx b/src/pages/dashboard/Questions/UpdateQuestion.tsx new file mode 100644 index 00000000..9c4d0143 --- /dev/null +++ b/src/pages/dashboard/Questions/UpdateQuestion.tsx @@ -0,0 +1,58 @@ +import React, { useEffect } from 'react' +import { useParams } from 'react-router-dom' +import axios from 'axios' +import { BaseUrl } from '@/ipconfig' +import { useState } from 'react' + + export const UpdateQuestion = () => { + const tokenUser= localStorage.getItem("token") + const [questionOptions,setQuestionOptions]=useState([]) + const [questionText,setQuestionText]=useState() + const [questionDetail,setQuestionDetail]=useState() + const {id}=useParams() + +const getQuestionByid =async()=>{ +try { + const res= await axios.get(`${BaseUrl}/admin-questions/${id}`,{headers:{Authorization:`Bearer ${tokenUser}`}}) +if(res.status===200){ + setQuestionText(res.data.text||"") + setQuestionOptions(res.data.options||[]) +} +console.log("Có lỗi khi lấy data") +} catch (error) { + console.log("Lỗi nè:",error) +} + +} + + const handleText=(item)=>{ + setQuestionText(item.target.value) + } + +const handleTitleChange = (e: React.ChangeEvent) => { + // setQuestionText(e.target.value) + } + +console.log(questionDetail) + useEffect(()=>{ +getQuestionByid() + },[]) + return ( +
+
+ handleTitleChange} + placeholder='Nhấn vào đây'/> +
+ + { + questionOptions.map((item,index)=>( + + + )) + } +
+ ) +} + +export default UpdateQuestion \ No newline at end of file diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index d1a03cb8..d0354d41 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -3,4 +3,5 @@ export * from "@/pages/dashboard/home"; export * from "@/pages/dashboard/users"; export * from "@/pages/dashboard/farms"; export * from "@/pages/dashboard/AcceptFarm"; +export * from "@/pages/dashboard/Questions/Questions"; diff --git a/src/pages/dashboard/notifications.jsx b/src/pages/dashboard/notifications.jsx new file mode 100644 index 00000000..e69de29b diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index d54ab3ac..25247a4e 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -7,9 +7,10 @@ import { import { Link } from "react-router-dom"; export function Users() { const [users, setUsers] = useState([]); + const tokenUser= localStorage.getItem("token") useEffect(() => { - axios.get("https://api-ndolv2.nongdanonline.vn/users/my", { - headers: { Authorization: "Bearer YOUR_TOKEN" } + axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { + headers: { Authorization: `Bearer ${tokenUser}` } }) .then(res => setUsers(res.data)) .catch(err => console.error("Lỗi:", err)); diff --git a/src/routes.jsx b/src/routes.jsx index 6a53ae78..43f97dca 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -7,7 +7,7 @@ import { RectangleStackIcon, } from "@heroicons/react/24/solid"; -import { Home, Users , Farms, AcceptFarm } from "@/pages/dashboard"; +import { Home, Users , Farms, AcceptFarm,Questions } from "@/pages/dashboard"; import { SignIn, SignUp } from "@/pages/auth"; @@ -43,6 +43,12 @@ export const routes = [ path: "/AcceptFarm", element: , }, + { + icon: , + name: "Questions", + path: "/Questions", + element: , + }, ], }, { From f6a0a43429e22969a042bbeafa6352f91ad6572f Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Fri, 4 Jul 2025 17:21:27 +0700 Subject: [PATCH 032/248] Update farms.jsx --- src/pages/dashboard/farms.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/dashboard/farms.jsx b/src/pages/dashboard/farms.jsx index 2cd9c167..f5fc8bbc 100644 --- a/src/pages/dashboard/farms.jsx +++ b/src/pages/dashboard/farms.jsx @@ -6,7 +6,7 @@ import { import { PencilSquareIcon, TrashIcon, PlusIcon, } from "@heroicons/react/24/solid"; -import FarmForm from "./farmForm"; +import FarmForm from "./FarmForm"; const BASE_URL = "https://api-ndolv2.nongdanonline.vn"; const getOpts = () => ({ From 51a004b9c6e82ac51f83f827e522e065eec90758 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Fri, 4 Jul 2025 17:28:52 +0700 Subject: [PATCH 033/248] thanh --- src/App.jsx | 3 +- src/pages/dashboard/AcceptFarm.jsx | 77 --------------------------- src/pages/dashboard/index.js | 2 - src/pages/dashboard/notifications.jsx | 0 src/routes.jsx | 9 +--- 5 files changed, 3 insertions(+), 88 deletions(-) delete mode 100644 src/pages/dashboard/AcceptFarm.jsx delete mode 100644 src/pages/dashboard/notifications.jsx diff --git a/src/App.jsx b/src/App.jsx index 0c1c83bc..181d1b01 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -7,8 +7,7 @@ function App() { } /> } /> - } /> -} /> + } /> } /> ); diff --git a/src/pages/dashboard/AcceptFarm.jsx b/src/pages/dashboard/AcceptFarm.jsx deleted file mode 100644 index 54706d01..00000000 --- a/src/pages/dashboard/AcceptFarm.jsx +++ /dev/null @@ -1,77 +0,0 @@ -import React, { useEffect } from "react"; -import { InformationCircleIcon } from "@heroicons/react/24/outline"; -import { useState } from "react"; -import { Requiment } from "@/data/Farm-data"; -import axios from "axios"; -import { useNavigate } from "react-router-dom"; -import { BaseUrl } from "@/ipconfig"; -export function AcceptFarm() { - const navigate = useNavigate(); - const [farm,setFarm]=useState([]) - - const AcceptFarm = async (id) => { - setFarm((prev) => prev.filter((item) => item.id !== id)); - }; - const tokenUser= localStorage.getItem("token") -const getFarm =async ()=>{ -try { -const res= await axios.get(`${BaseUrl}/adminfarms`,{ headers: { - Authorization: `Bearer ${tokenUser}`, } - - },) -if(res.status===200){ -setFarm(res.data) -}else{ -console.log("Có lỗi khi lấy data ") -} -} catch (error) { - console.log("Lỗi server :",error) -} -} - -const getDetailFarm =async(id)=>{ -navigate(`/FarmDetail/${id}`) -} -useEffect(()=>{ - getFarm() -},[]) - return ( -
-
- {farm.length === 0 ? ( -
Không có đơn!
- ) : ( - farm.map((item) => ( -
getDetailFarm(item.id)} - className="border rounded-2xl shadow-lg p-6 flex flex-col md:flex-row items-center gap-6 bg-white transition hover:shadow-2xl cursor-pointer" - key={item.id} - > - {item.name} -
- {item.name} - Vị trí: {item.location} - {item.pricePerMonth} Vnđ - Mã số: {item.code} -
-
e.stopPropagation()}> - -
-
- )) - )} -
-
- ); -} - -export default AcceptFarm; diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index d0354d41..230ec3c5 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,7 +1,5 @@ - export * from "@/pages/dashboard/home"; export * from "@/pages/dashboard/users"; export * from "@/pages/dashboard/farms"; -export * from "@/pages/dashboard/AcceptFarm"; export * from "@/pages/dashboard/Questions/Questions"; diff --git a/src/pages/dashboard/notifications.jsx b/src/pages/dashboard/notifications.jsx deleted file mode 100644 index e69de29b..00000000 diff --git a/src/routes.jsx b/src/routes.jsx index 9b94721e..ceaaf6cd 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -7,7 +7,7 @@ import { RectangleStackIcon, } from "@heroicons/react/24/solid"; -import { Home, Users , Farms, AcceptFarm,Questions } from "@/pages/dashboard"; +import { Home, Users , Farms,Questions } from "@/pages/dashboard"; import { SignIn, SignUp } from "@/pages/auth"; @@ -38,12 +38,7 @@ export const routes = [ path: "/Farms", element: , }, - { - icon: , - name: "Accept Farms", - path: "/AcceptFarm", - element: , - }, + { icon: , name: "Questions", From 34b3f8146e7f0b94ebb5e037aaceac7cb788615f Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Fri, 4 Jul 2025 18:18:11 +0700 Subject: [PATCH 034/248] thanh --- src/App.jsx | 1 - src/data/Farm-data.js | 34 ----- src/data/authors-table-data.js | 52 ------- src/data/conversations-data.js | 29 ---- src/data/index.js | 8 -- src/data/orders-overview-data.js | 49 ------- src/data/platform-settings-data.js | 38 ----- src/data/projects-data.js | 60 -------- src/data/projects-table-data.js | 65 --------- src/data/statistics-cards-data.js | 55 -------- src/data/statistics-charts-data.js | 131 ------------------ src/pages/dashboard/FarmDetail.tsx | 46 ------ src/pages/dashboard/Questions/Questions.tsx | 10 +- .../dashboard/Questions/UpdateQuestion.tsx | 4 +- src/pages/dashboard/farms.jsx | 2 +- .../dashboard => widgets/layout}/FarmForm.jsx | 0 16 files changed, 8 insertions(+), 576 deletions(-) delete mode 100644 src/data/Farm-data.js delete mode 100644 src/data/authors-table-data.js delete mode 100644 src/data/conversations-data.js delete mode 100644 src/data/index.js delete mode 100644 src/data/orders-overview-data.js delete mode 100644 src/data/platform-settings-data.js delete mode 100644 src/data/projects-data.js delete mode 100644 src/data/projects-table-data.js delete mode 100644 src/data/statistics-cards-data.js delete mode 100644 src/data/statistics-charts-data.js delete mode 100644 src/pages/dashboard/FarmDetail.tsx rename src/{pages/dashboard => widgets/layout}/FarmForm.jsx (100%) diff --git a/src/App.jsx b/src/App.jsx index 181d1b01..7f415143 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,6 +1,5 @@ import { Routes, Route, Navigate } from "react-router-dom"; import { Dashboard, Auth } from "@/layouts"; -import FarmDetail from "./pages/dashboard/FarmDetail"; import UpdateQuestion from "./pages/dashboard/Questions/UpdateQuestion"; function App() { return ( diff --git a/src/data/Farm-data.js b/src/data/Farm-data.js deleted file mode 100644 index f70d59e2..00000000 --- a/src/data/Farm-data.js +++ /dev/null @@ -1,34 +0,0 @@ -export const Requiment = [ - { - id: 1, - nameFarm: "Farm Long vu 1", - content: "Đây là farm cá Rô Phi", - price: 20000, - category: "Cá Rô phi", - img: "https://snnptnt.binhdinh.gov.vn/assets/news//upload/files/cho-ca-an.jpg", - }, - { - id: 2, - nameFarm: "Farm Long vu 2", - content: "Đây là farm cá Thác Lác", - price: 30000, - category: "Thác Lác", - img: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR5WOFLHSW-OL3dvWMaiaVRdU7EoeVxraJAcg&s", - }, - { - id: 3, - nameFarm: "Farm Long vu 3", - content: "Đây là farm cá Trê", - price: 40000, - category: "Cá Trê", - img: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSgivFrBB_KFA9qmJVmDtdQ9AEfcMekxmMmsA&s", - }, - { - id: 4, - nameFarm: "Farm Long vu 4", - content: "Đây là farm cá Diêu Hồng", - price: 45000, - category: "Cá Diêu Hồng", - img: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRriBKfDu-1cnpup36PtENhHpBR6_ET-Xzxjg&s", - }, - ]; \ No newline at end of file diff --git a/src/data/authors-table-data.js b/src/data/authors-table-data.js deleted file mode 100644 index 7ae9d9cb..00000000 --- a/src/data/authors-table-data.js +++ /dev/null @@ -1,52 +0,0 @@ -export const authorsTableData = [ - { - img: "/img/team-2.jpeg", - name: "John Michael", - email: "john@creative-tim.com", - job: ["Manager", "Organization"], - online: true, - date: "23/04/18", - }, - { - img: "/img/team-1.jpeg", - name: "Alexa Liras", - email: "alexa@creative-tim.com", - job: ["Programator", "Developer"], - online: false, - date: "11/01/19", - }, - { - img: "/img/team-4.jpeg", - name: "Laurent Perrier", - email: "laurent@creative-tim.com", - job: ["Executive", "Projects"], - online: true, - date: "19/09/17", - }, - { - img: "/img/team-3.jpeg", - name: "Michael Levi", - email: "michael@creative-tim.com", - job: ["Programator", "Developer"], - online: true, - date: "24/12/08", - }, - { - img: "/img/bruce-mars.jpeg", - name: "Bruce Mars", - email: "bruce@creative-tim.com", - job: ["Manager", "Executive"], - online: false, - date: "04/10/21", - }, - { - img: "/img/team-2.jpeg", - name: "Alexander", - email: "alexander@creative-tim.com", - job: ["Programator", "Developer"], - online: false, - date: "14/09/20", - }, -]; - -export default authorsTableData; diff --git a/src/data/conversations-data.js b/src/data/conversations-data.js deleted file mode 100644 index 2b488644..00000000 --- a/src/data/conversations-data.js +++ /dev/null @@ -1,29 +0,0 @@ -export const conversationsData = [ - { - img: "/img/team-1.jpeg", - name: "Sophie B.", - message: "Hi! I need more information...", - }, - { - img: "/img/team-2.jpeg", - name: "Alexander", - message: "Awesome work, can you...", - }, - { - img: "/img/team-3.jpeg", - name: "Ivanna", - message: "About files I can...", - }, - { - img: "/img/team-4.jpeg", - name: "Peterson", - message: "Have a great afternoon...", - }, - { - img: "/img/bruce-mars.jpeg", - name: "Bruce Mars", - message: "Hi! I need more information...", - }, -]; - -export default conversationsData; diff --git a/src/data/index.js b/src/data/index.js deleted file mode 100644 index 7ca52a08..00000000 --- a/src/data/index.js +++ /dev/null @@ -1,8 +0,0 @@ -export * from "@/data/statistics-cards-data"; -export * from "@/data/statistics-charts-data"; -export * from "@/data/projects-table-data"; -export * from "@/data/orders-overview-data"; -export * from "@/data/platform-settings-data"; -export * from "@/data/conversations-data"; -export * from "@/data/projects-data"; -export * from "@/data/authors-table-data"; diff --git a/src/data/orders-overview-data.js b/src/data/orders-overview-data.js deleted file mode 100644 index d1ba49d9..00000000 --- a/src/data/orders-overview-data.js +++ /dev/null @@ -1,49 +0,0 @@ -import { - BellIcon, - PlusCircleIcon, - ShoppingCartIcon, - CreditCardIcon, - LockOpenIcon, - BanknotesIcon, -} from "@heroicons/react/24/solid"; - -export const ordersOverviewData = [ - { - icon: BellIcon, - color: "text-blue-gray-300", - title: "$2400, Design changes", - description: "22 DEC 7:20 PM", - }, - { - icon: PlusCircleIcon, - color: "text-blue-gray-300", - title: "New order #1832412", - description: "21 DEC 11 PM", - }, - { - icon: ShoppingCartIcon, - color: "text-blue-gray-300", - title: "Server payments for April", - description: "21 DEC 9:34 PM", - }, - { - icon: CreditCardIcon, - color: "text-blue-gray-300", - title: "New card added for order #4395133", - description: "20 DEC 2:20 AM", - }, - { - icon: LockOpenIcon, - color: "text-blue-gray-300", - title: "Unlock packages for development", - description: "18 DEC 4:54 AM", - }, - { - icon: BanknotesIcon, - color: "text-blue-gray-300", - title: "New order #9583120", - description: "17 DEC", - }, -]; - -export default ordersOverviewData; diff --git a/src/data/platform-settings-data.js b/src/data/platform-settings-data.js deleted file mode 100644 index 140c5dfd..00000000 --- a/src/data/platform-settings-data.js +++ /dev/null @@ -1,38 +0,0 @@ -export const platformSettingsData = [ - { - title: "account", - options: [ - { - checked: true, - label: "Email me when someone follows me", - }, - { - checked: false, - label: "Email me when someone answers on my post", - }, - { - checked: true, - label: "Email me when someone mentions me", - }, - ], - }, - { - title: "application", - options: [ - { - checked: false, - label: "New launches and projects", - }, - { - checked: true, - label: "Monthly product updates", - }, - { - checked: false, - label: "Subscribe to newsletter", - }, - ], - }, -]; - -export default platformSettingsData; diff --git a/src/data/projects-data.js b/src/data/projects-data.js deleted file mode 100644 index 3c83bc08..00000000 --- a/src/data/projects-data.js +++ /dev/null @@ -1,60 +0,0 @@ -export const projectsData = [ - { - img: "/img/home-decor-1.jpeg", - title: "Modern", - tag: "Project #1", - description: - "As Uber works through a huge amount of internal management turmoil.", - route: "/dashboard/profile", - members: [ - { img: "/img/team-1.jpeg", name: "Romina Hadid" }, - { img: "/img/team-2.jpeg", name: "Ryan Tompson" }, - { img: "/img/team-3.jpeg", name: "Jessica Doe" }, - { img: "/img/team-4.jpeg", name: "Alexander Smith" }, - ], - }, - { - img: "/img/home-decor-2.jpeg", - title: "Scandinavian", - tag: "Project #2", - description: - "Music is something that every person has his or her own specific opinion about.", - route: "/dashboard/profile", - members: [ - { img: "/img/team-4.jpeg", name: "Alexander Smith" }, - { img: "/img/team-3.jpeg", name: "Jessica Doe" }, - { img: "/img/team-2.jpeg", name: "Ryan Tompson" }, - { img: "/img/team-1.jpeg", name: "Romina Hadid" }, - ], - }, - { - img: "/img/home-decor-3.jpeg", - title: "Minimalist", - tag: "Project #3", - description: - "Different people have different taste, and various types of music.", - route: "/dashboard/profile", - members: [ - { img: "/img/team-1.jpeg", name: "Romina Hadid" }, - { img: "/img/team-2.jpeg", name: "Ryan Tompson" }, - { img: "/img/team-3.jpeg", name: "Jessica Doe" }, - { img: "/img/team-4.jpeg", name: "Alexander Smith" }, - ], - }, - { - img: "/img/home-decor-4.jpeg", - title: "Gothic", - tag: "Project #4", - description: - "Why would anyone pick blue over pink? Pink is obviously a better color.", - route: "/dashboard/profile", - members: [ - { img: "/img/team-4.jpeg", name: "Alexander Smith" }, - { img: "/img/team-3.jpeg", name: "Jessica Doe" }, - { img: "/img/team-2.jpeg", name: "Ryan Tompson" }, - { img: "/img/team-1.jpeg", name: "Romina Hadid" }, - ], - }, -]; - -export default projectsData; diff --git a/src/data/projects-table-data.js b/src/data/projects-table-data.js deleted file mode 100644 index d37e08ba..00000000 --- a/src/data/projects-table-data.js +++ /dev/null @@ -1,65 +0,0 @@ -export const projectsTableData = [ - { - img: "/img/logo-xd.svg", - name: "Material XD Version", - members: [ - { img: "/img/team-1.jpeg", name: "Romina Hadid" }, - { img: "/img/team-2.jpeg", name: "Ryan Tompson" }, - { img: "/img/team-3.jpeg", name: "Jessica Doe" }, - { img: "/img/team-4.jpeg", name: "Alexander Smith" }, - ], - budget: "$14,000", - completion: 60, - }, - { - img: "/img/logo-atlassian.svg", - name: "Add Progress Track", - members: [ - { img: "/img/team-2.jpeg", name: "Ryan Tompson" }, - { img: "/img/team-4.jpeg", name: "Alexander Smith" }, - ], - budget: "$3,000", - completion: 10, - }, - { - img: "/img/logo-slack.svg", - name: "Fix Platform Errors", - members: [ - { img: "/img/team-3.jpeg", name: "Jessica Doe" }, - { img: "/img/team-1.jpeg", name: "Romina Hadid" }, - ], - budget: "Not set", - completion: 100, - }, - { - img: "/img/logo-spotify.svg", - name: "Launch our Mobile App", - members: [ - { img: "/img/team-4.jpeg", name: "Alexander Smith" }, - { img: "/img/team-3.jpeg", name: "Jessica Doe" }, - { img: "/img/team-2.jpeg", name: "Ryan Tompson" }, - { img: "/img/team-1.jpeg", name: "Romina Hadid" }, - ], - budget: "$20,500", - completion: 100, - }, - { - img: "/img/logo-jira.svg", - name: "Add the New Pricing Page", - members: [{ img: "/img/team-4.jpeg", name: "Alexander Smith" }], - budget: "$500", - completion: 25, - }, - { - img: "/img/logo-invision.svg", - name: "Redesign New Online Shop", - members: [ - { img: "/img/team-1.jpeg", name: "Romina Hadid" }, - { img: "/img/team-4.jpeg", name: "Alexander Smith" }, - ], - budget: "$2,000", - completion: 40, - }, -]; - -export default projectsTableData; diff --git a/src/data/statistics-cards-data.js b/src/data/statistics-cards-data.js deleted file mode 100644 index d470e708..00000000 --- a/src/data/statistics-cards-data.js +++ /dev/null @@ -1,55 +0,0 @@ -import { - BanknotesIcon, - UserPlusIcon, - UsersIcon, - ChartBarIcon, -} from "@heroicons/react/24/solid"; - -export const statisticsCardsData = [ - { - color: "gray", - icon: BanknotesIcon, - title: "Today's Money", - value: "$53k", - footer: { - color: "text-green-500", - value: "+55%", - label: "than last week", - }, - }, - { - color: "gray", - icon: UsersIcon, - title: "Today's Users", - value: "2,300", - footer: { - color: "text-green-500", - value: "+3%", - label: "than last month", - }, - }, - { - color: "gray", - icon: UserPlusIcon, - title: "New Clients", - value: "3,462", - footer: { - color: "text-red-500", - value: "-2%", - label: "than yesterday", - }, - }, - { - color: "gray", - icon: ChartBarIcon, - title: "Sales", - value: "$103,430", - footer: { - color: "text-green-500", - value: "+5%", - label: "than yesterday", - }, - }, -]; - -export default statisticsCardsData; diff --git a/src/data/statistics-charts-data.js b/src/data/statistics-charts-data.js deleted file mode 100644 index 15a36464..00000000 --- a/src/data/statistics-charts-data.js +++ /dev/null @@ -1,131 +0,0 @@ -import { chartsConfig } from "@/configs"; - -const websiteViewsChart = { - type: "bar", - height: 220, - series: [ - { - name: "Views", - data: [50, 20, 10, 22, 50, 10, 40], - }, - ], - options: { - ...chartsConfig, - colors: "#388e3c", - plotOptions: { - bar: { - columnWidth: "16%", - borderRadius: 5, - }, - }, - xaxis: { - ...chartsConfig.xaxis, - categories: ["M", "T", "W", "T", "F", "S", "S"], - }, - }, -}; - -const dailySalesChart = { - type: "line", - height: 220, - series: [ - { - name: "Sales", - data: [50, 40, 300, 320, 500, 350, 200, 230, 500], - }, - ], - options: { - ...chartsConfig, - colors: ["#0288d1"], - stroke: { - lineCap: "round", - }, - markers: { - size: 5, - }, - xaxis: { - ...chartsConfig.xaxis, - categories: [ - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ], - }, - }, -}; - -const completedTaskChart = { - type: "line", - height: 220, - series: [ - { - name: "Sales", - data: [50, 40, 300, 320, 500, 350, 200, 230, 500], - }, - ], - options: { - ...chartsConfig, - colors: ["#388e3c"], - stroke: { - lineCap: "round", - }, - markers: { - size: 5, - }, - xaxis: { - ...chartsConfig.xaxis, - categories: [ - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ], - }, - }, -}; -const completedTasksChart = { - ...completedTaskChart, - series: [ - { - name: "Tasks", - data: [50, 40, 300, 220, 500, 250, 400, 230, 500], - }, - ], -}; - -export const statisticsChartsData = [ - { - color: "white", - title: "Website View", - description: "Last Campaign Performance", - footer: "campaign sent 2 days ago", - chart: websiteViewsChart, - }, - { - color: "white", - title: "Daily Sales", - description: "15% increase in today sales", - footer: "updated 4 min ago", - chart: dailySalesChart, - }, - { - color: "white", - title: "Completed Tasks", - description: "Last Campaign Performance", - footer: "just updated", - chart: completedTasksChart, - }, -]; - -export default statisticsChartsData; diff --git a/src/pages/dashboard/FarmDetail.tsx b/src/pages/dashboard/FarmDetail.tsx deleted file mode 100644 index 10f6e334..00000000 --- a/src/pages/dashboard/FarmDetail.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from "react"; -import axios from "axios"; -import { useEffect } from "react"; -import { useState } from "react"; -import { useNavigate } from "react-router-dom"; -import { useParams } from "react-router-dom"; -import { BaseUrl } from "@/ipconfig"; -export function FarmDetail(){ - const { id } = useParams(); - const tokenUser= localStorage.getItem("token") - const [product, setProduct] = useState(null) -const getDetail=async()=>{ -try { -const res= await axios(`${BaseUrl}/adminfarms/${id}` -,{ headers: { - Authorization: `Bearer ${tokenUser}`, } - }, -) -if(res.status===200){ - setProduct(res.data) -} } catch (error) { - console.log("Lỗi server",error) - } -} -useEffect(()=>{ -getDetail() -},[]) -console.log("data nè :",product) -return ( -
- {product ? ( -
- {product.location} - {/* Hiển thị các thông tin khác nếu cần */} -
- ) : ( -
- Không có data -
- )} -
-) - -} - -export default FarmDetail \ No newline at end of file diff --git a/src/pages/dashboard/Questions/Questions.tsx b/src/pages/dashboard/Questions/Questions.tsx index bfbd5977..71e0e14f 100644 --- a/src/pages/dashboard/Questions/Questions.tsx +++ b/src/pages/dashboard/Questions/Questions.tsx @@ -6,11 +6,11 @@ import { useNavigate } from 'react-router-dom' import { Oval } from 'react-loader-spinner'; export const Questions = () => { - const [loading, setLoading] = useState(true); - const tokenUser= localStorage.getItem("token") -const [questions,setQuestions]=useState([]) -const navigate=useNavigate() -const getData=async()=>{ + const [loading, setLoading] = useState(true); + const tokenUser= localStorage.getItem("token") + const [questions,setQuestions]=useState([]) + const navigate=useNavigate() + const getData=async()=>{ try { const res= await axios.get(`${BaseUrl}/admin-questions`,{ headers:{Authorization:`Bearer ${tokenUser}` } diff --git a/src/pages/dashboard/Questions/UpdateQuestion.tsx b/src/pages/dashboard/Questions/UpdateQuestion.tsx index 9c4d0143..c3b03a7a 100644 --- a/src/pages/dashboard/Questions/UpdateQuestion.tsx +++ b/src/pages/dashboard/Questions/UpdateQuestion.tsx @@ -5,10 +5,10 @@ import { BaseUrl } from '@/ipconfig' import { useState } from 'react' export const UpdateQuestion = () => { - const tokenUser= localStorage.getItem("token") + const tokenUser= localStorage.getItem("token") const [questionOptions,setQuestionOptions]=useState([]) const [questionText,setQuestionText]=useState() - const [questionDetail,setQuestionDetail]=useState() + const [questionDetail,setQuestionDetail]=useState() const {id}=useParams() const getQuestionByid =async()=>{ diff --git a/src/pages/dashboard/farms.jsx b/src/pages/dashboard/farms.jsx index f5fc8bbc..b054fd8c 100644 --- a/src/pages/dashboard/farms.jsx +++ b/src/pages/dashboard/farms.jsx @@ -6,7 +6,7 @@ import { import { PencilSquareIcon, TrashIcon, PlusIcon, } from "@heroicons/react/24/solid"; -import FarmForm from "./FarmForm"; +import FarmForm from "../../widgets/layout/FarmForm"; const BASE_URL = "https://api-ndolv2.nongdanonline.vn"; const getOpts = () => ({ diff --git a/src/pages/dashboard/FarmForm.jsx b/src/widgets/layout/FarmForm.jsx similarity index 100% rename from src/pages/dashboard/FarmForm.jsx rename to src/widgets/layout/FarmForm.jsx From fef4c409373cceac11e20c49bff8cab32606844c Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Fri, 4 Jul 2025 18:22:24 +0700 Subject: [PATCH 035/248] thanh --- src/data/authors-table-data.js | 52 ++++++++++++ src/data/conversations-data.js | 29 +++++++ src/data/index.js | 8 ++ src/data/orders-overview-data.js | 49 +++++++++++ src/data/platform-settings-data.js | 38 +++++++++ src/data/projects-data.js | 60 +++++++++++++ src/data/projects-table-data.js | 65 ++++++++++++++ src/data/statistics-cards-data.js | 55 ++++++++++++ src/data/statistics-charts-data.js | 131 +++++++++++++++++++++++++++++ 9 files changed, 487 insertions(+) create mode 100644 src/data/authors-table-data.js create mode 100644 src/data/conversations-data.js create mode 100644 src/data/index.js create mode 100644 src/data/orders-overview-data.js create mode 100644 src/data/platform-settings-data.js create mode 100644 src/data/projects-data.js create mode 100644 src/data/projects-table-data.js create mode 100644 src/data/statistics-cards-data.js create mode 100644 src/data/statistics-charts-data.js diff --git a/src/data/authors-table-data.js b/src/data/authors-table-data.js new file mode 100644 index 00000000..7ae9d9cb --- /dev/null +++ b/src/data/authors-table-data.js @@ -0,0 +1,52 @@ +export const authorsTableData = [ + { + img: "/img/team-2.jpeg", + name: "John Michael", + email: "john@creative-tim.com", + job: ["Manager", "Organization"], + online: true, + date: "23/04/18", + }, + { + img: "/img/team-1.jpeg", + name: "Alexa Liras", + email: "alexa@creative-tim.com", + job: ["Programator", "Developer"], + online: false, + date: "11/01/19", + }, + { + img: "/img/team-4.jpeg", + name: "Laurent Perrier", + email: "laurent@creative-tim.com", + job: ["Executive", "Projects"], + online: true, + date: "19/09/17", + }, + { + img: "/img/team-3.jpeg", + name: "Michael Levi", + email: "michael@creative-tim.com", + job: ["Programator", "Developer"], + online: true, + date: "24/12/08", + }, + { + img: "/img/bruce-mars.jpeg", + name: "Bruce Mars", + email: "bruce@creative-tim.com", + job: ["Manager", "Executive"], + online: false, + date: "04/10/21", + }, + { + img: "/img/team-2.jpeg", + name: "Alexander", + email: "alexander@creative-tim.com", + job: ["Programator", "Developer"], + online: false, + date: "14/09/20", + }, +]; + +export default authorsTableData; diff --git a/src/data/conversations-data.js b/src/data/conversations-data.js new file mode 100644 index 00000000..2b488644 --- /dev/null +++ b/src/data/conversations-data.js @@ -0,0 +1,29 @@ +export const conversationsData = [ + { + img: "/img/team-1.jpeg", + name: "Sophie B.", + message: "Hi! I need more information...", + }, + { + img: "/img/team-2.jpeg", + name: "Alexander", + message: "Awesome work, can you...", + }, + { + img: "/img/team-3.jpeg", + name: "Ivanna", + message: "About files I can...", + }, + { + img: "/img/team-4.jpeg", + name: "Peterson", + message: "Have a great afternoon...", + }, + { + img: "/img/bruce-mars.jpeg", + name: "Bruce Mars", + message: "Hi! I need more information...", + }, +]; + +export default conversationsData; diff --git a/src/data/index.js b/src/data/index.js new file mode 100644 index 00000000..7ca52a08 --- /dev/null +++ b/src/data/index.js @@ -0,0 +1,8 @@ +export * from "@/data/statistics-cards-data"; +export * from "@/data/statistics-charts-data"; +export * from "@/data/projects-table-data"; +export * from "@/data/orders-overview-data"; +export * from "@/data/platform-settings-data"; +export * from "@/data/conversations-data"; +export * from "@/data/projects-data"; +export * from "@/data/authors-table-data"; diff --git a/src/data/orders-overview-data.js b/src/data/orders-overview-data.js new file mode 100644 index 00000000..d1ba49d9 --- /dev/null +++ b/src/data/orders-overview-data.js @@ -0,0 +1,49 @@ +import { + BellIcon, + PlusCircleIcon, + ShoppingCartIcon, + CreditCardIcon, + LockOpenIcon, + BanknotesIcon, +} from "@heroicons/react/24/solid"; + +export const ordersOverviewData = [ + { + icon: BellIcon, + color: "text-blue-gray-300", + title: "$2400, Design changes", + description: "22 DEC 7:20 PM", + }, + { + icon: PlusCircleIcon, + color: "text-blue-gray-300", + title: "New order #1832412", + description: "21 DEC 11 PM", + }, + { + icon: ShoppingCartIcon, + color: "text-blue-gray-300", + title: "Server payments for April", + description: "21 DEC 9:34 PM", + }, + { + icon: CreditCardIcon, + color: "text-blue-gray-300", + title: "New card added for order #4395133", + description: "20 DEC 2:20 AM", + }, + { + icon: LockOpenIcon, + color: "text-blue-gray-300", + title: "Unlock packages for development", + description: "18 DEC 4:54 AM", + }, + { + icon: BanknotesIcon, + color: "text-blue-gray-300", + title: "New order #9583120", + description: "17 DEC", + }, +]; + +export default ordersOverviewData; diff --git a/src/data/platform-settings-data.js b/src/data/platform-settings-data.js new file mode 100644 index 00000000..140c5dfd --- /dev/null +++ b/src/data/platform-settings-data.js @@ -0,0 +1,38 @@ +export const platformSettingsData = [ + { + title: "account", + options: [ + { + checked: true, + label: "Email me when someone follows me", + }, + { + checked: false, + label: "Email me when someone answers on my post", + }, + { + checked: true, + label: "Email me when someone mentions me", + }, + ], + }, + { + title: "application", + options: [ + { + checked: false, + label: "New launches and projects", + }, + { + checked: true, + label: "Monthly product updates", + }, + { + checked: false, + label: "Subscribe to newsletter", + }, + ], + }, +]; + +export default platformSettingsData; diff --git a/src/data/projects-data.js b/src/data/projects-data.js new file mode 100644 index 00000000..3c83bc08 --- /dev/null +++ b/src/data/projects-data.js @@ -0,0 +1,60 @@ +export const projectsData = [ + { + img: "/img/home-decor-1.jpeg", + title: "Modern", + tag: "Project #1", + description: + "As Uber works through a huge amount of internal management turmoil.", + route: "/dashboard/profile", + members: [ + { img: "/img/team-1.jpeg", name: "Romina Hadid" }, + { img: "/img/team-2.jpeg", name: "Ryan Tompson" }, + { img: "/img/team-3.jpeg", name: "Jessica Doe" }, + { img: "/img/team-4.jpeg", name: "Alexander Smith" }, + ], + }, + { + img: "/img/home-decor-2.jpeg", + title: "Scandinavian", + tag: "Project #2", + description: + "Music is something that every person has his or her own specific opinion about.", + route: "/dashboard/profile", + members: [ + { img: "/img/team-4.jpeg", name: "Alexander Smith" }, + { img: "/img/team-3.jpeg", name: "Jessica Doe" }, + { img: "/img/team-2.jpeg", name: "Ryan Tompson" }, + { img: "/img/team-1.jpeg", name: "Romina Hadid" }, + ], + }, + { + img: "/img/home-decor-3.jpeg", + title: "Minimalist", + tag: "Project #3", + description: + "Different people have different taste, and various types of music.", + route: "/dashboard/profile", + members: [ + { img: "/img/team-1.jpeg", name: "Romina Hadid" }, + { img: "/img/team-2.jpeg", name: "Ryan Tompson" }, + { img: "/img/team-3.jpeg", name: "Jessica Doe" }, + { img: "/img/team-4.jpeg", name: "Alexander Smith" }, + ], + }, + { + img: "/img/home-decor-4.jpeg", + title: "Gothic", + tag: "Project #4", + description: + "Why would anyone pick blue over pink? Pink is obviously a better color.", + route: "/dashboard/profile", + members: [ + { img: "/img/team-4.jpeg", name: "Alexander Smith" }, + { img: "/img/team-3.jpeg", name: "Jessica Doe" }, + { img: "/img/team-2.jpeg", name: "Ryan Tompson" }, + { img: "/img/team-1.jpeg", name: "Romina Hadid" }, + ], + }, +]; + +export default projectsData; diff --git a/src/data/projects-table-data.js b/src/data/projects-table-data.js new file mode 100644 index 00000000..d37e08ba --- /dev/null +++ b/src/data/projects-table-data.js @@ -0,0 +1,65 @@ +export const projectsTableData = [ + { + img: "/img/logo-xd.svg", + name: "Material XD Version", + members: [ + { img: "/img/team-1.jpeg", name: "Romina Hadid" }, + { img: "/img/team-2.jpeg", name: "Ryan Tompson" }, + { img: "/img/team-3.jpeg", name: "Jessica Doe" }, + { img: "/img/team-4.jpeg", name: "Alexander Smith" }, + ], + budget: "$14,000", + completion: 60, + }, + { + img: "/img/logo-atlassian.svg", + name: "Add Progress Track", + members: [ + { img: "/img/team-2.jpeg", name: "Ryan Tompson" }, + { img: "/img/team-4.jpeg", name: "Alexander Smith" }, + ], + budget: "$3,000", + completion: 10, + }, + { + img: "/img/logo-slack.svg", + name: "Fix Platform Errors", + members: [ + { img: "/img/team-3.jpeg", name: "Jessica Doe" }, + { img: "/img/team-1.jpeg", name: "Romina Hadid" }, + ], + budget: "Not set", + completion: 100, + }, + { + img: "/img/logo-spotify.svg", + name: "Launch our Mobile App", + members: [ + { img: "/img/team-4.jpeg", name: "Alexander Smith" }, + { img: "/img/team-3.jpeg", name: "Jessica Doe" }, + { img: "/img/team-2.jpeg", name: "Ryan Tompson" }, + { img: "/img/team-1.jpeg", name: "Romina Hadid" }, + ], + budget: "$20,500", + completion: 100, + }, + { + img: "/img/logo-jira.svg", + name: "Add the New Pricing Page", + members: [{ img: "/img/team-4.jpeg", name: "Alexander Smith" }], + budget: "$500", + completion: 25, + }, + { + img: "/img/logo-invision.svg", + name: "Redesign New Online Shop", + members: [ + { img: "/img/team-1.jpeg", name: "Romina Hadid" }, + { img: "/img/team-4.jpeg", name: "Alexander Smith" }, + ], + budget: "$2,000", + completion: 40, + }, +]; + +export default projectsTableData; diff --git a/src/data/statistics-cards-data.js b/src/data/statistics-cards-data.js new file mode 100644 index 00000000..d470e708 --- /dev/null +++ b/src/data/statistics-cards-data.js @@ -0,0 +1,55 @@ +import { + BanknotesIcon, + UserPlusIcon, + UsersIcon, + ChartBarIcon, +} from "@heroicons/react/24/solid"; + +export const statisticsCardsData = [ + { + color: "gray", + icon: BanknotesIcon, + title: "Today's Money", + value: "$53k", + footer: { + color: "text-green-500", + value: "+55%", + label: "than last week", + }, + }, + { + color: "gray", + icon: UsersIcon, + title: "Today's Users", + value: "2,300", + footer: { + color: "text-green-500", + value: "+3%", + label: "than last month", + }, + }, + { + color: "gray", + icon: UserPlusIcon, + title: "New Clients", + value: "3,462", + footer: { + color: "text-red-500", + value: "-2%", + label: "than yesterday", + }, + }, + { + color: "gray", + icon: ChartBarIcon, + title: "Sales", + value: "$103,430", + footer: { + color: "text-green-500", + value: "+5%", + label: "than yesterday", + }, + }, +]; + +export default statisticsCardsData; diff --git a/src/data/statistics-charts-data.js b/src/data/statistics-charts-data.js new file mode 100644 index 00000000..15a36464 --- /dev/null +++ b/src/data/statistics-charts-data.js @@ -0,0 +1,131 @@ +import { chartsConfig } from "@/configs"; + +const websiteViewsChart = { + type: "bar", + height: 220, + series: [ + { + name: "Views", + data: [50, 20, 10, 22, 50, 10, 40], + }, + ], + options: { + ...chartsConfig, + colors: "#388e3c", + plotOptions: { + bar: { + columnWidth: "16%", + borderRadius: 5, + }, + }, + xaxis: { + ...chartsConfig.xaxis, + categories: ["M", "T", "W", "T", "F", "S", "S"], + }, + }, +}; + +const dailySalesChart = { + type: "line", + height: 220, + series: [ + { + name: "Sales", + data: [50, 40, 300, 320, 500, 350, 200, 230, 500], + }, + ], + options: { + ...chartsConfig, + colors: ["#0288d1"], + stroke: { + lineCap: "round", + }, + markers: { + size: 5, + }, + xaxis: { + ...chartsConfig.xaxis, + categories: [ + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ], + }, + }, +}; + +const completedTaskChart = { + type: "line", + height: 220, + series: [ + { + name: "Sales", + data: [50, 40, 300, 320, 500, 350, 200, 230, 500], + }, + ], + options: { + ...chartsConfig, + colors: ["#388e3c"], + stroke: { + lineCap: "round", + }, + markers: { + size: 5, + }, + xaxis: { + ...chartsConfig.xaxis, + categories: [ + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ], + }, + }, +}; +const completedTasksChart = { + ...completedTaskChart, + series: [ + { + name: "Tasks", + data: [50, 40, 300, 220, 500, 250, 400, 230, 500], + }, + ], +}; + +export const statisticsChartsData = [ + { + color: "white", + title: "Website View", + description: "Last Campaign Performance", + footer: "campaign sent 2 days ago", + chart: websiteViewsChart, + }, + { + color: "white", + title: "Daily Sales", + description: "15% increase in today sales", + footer: "updated 4 min ago", + chart: dailySalesChart, + }, + { + color: "white", + title: "Completed Tasks", + description: "Last Campaign Performance", + footer: "just updated", + chart: completedTasksChart, + }, +]; + +export default statisticsChartsData; From d6455932a2b2cc0adee1803d9f6a85f14abcb38c Mon Sep 17 00:00:00 2001 From: Tie902 Date: Fri, 4 Jul 2025 18:27:04 +0700 Subject: [PATCH 036/248] capnhatuser --- src/pages/dashboard/users.jsx | 116 +++++++++++++++++++++++----------- 1 file changed, 80 insertions(+), 36 deletions(-) diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index c8b311e5..8163f55d 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -2,40 +2,83 @@ import React, { useEffect, useState } from "react"; import axios from "axios"; import { Card, CardHeader, CardBody, CardFooter, - Typography, Button, Tooltip + Typography, Button, Tooltip, Dialog, DialogHeader, + DialogBody, DialogFooter, Input } from "@material-tailwind/react"; import { Link } from "react-router-dom"; export function Users() { const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const [editOpen, setEditOpen] = useState(false); + const [selectedUser, setSelectedUser] = useState(null); + const [formData, setFormData] = useState({ fullName: '', email: '', phone: '', role: '' }); useEffect(() => { + const token = localStorage.getItem("accessToken") || + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MjQwMTYsImV4cCI6MTc1MTg4MzIxNn0.ID50CEtfWiVEMZ2p3_vwARX5FqC4QLMflLbZkFBbvJw"; + + if (!token) { + setError("Không tìm thấy access token!"); + setLoading(false); + return; + } + axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { - headers: { - Authorization: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE1NDIyMDgsImV4cCI6MTc1MTgwMTQwOH0.t5Vl4SVsFu5sty27uWWcedZXTI21uPIrzUqsoHHDV5k" - } - }) - .then(res => { - console.log("Response:", res.data); - setUsers(res.data); // res.data là mảng user + headers: { Authorization: `Bearer ${token}` } }) - .catch(err => console.error("Lỗi:", err)); + .then(res => setUsers(Array.isArray(res.data) ? res.data : [])) + .catch(() => setError("Lỗi khi tải danh sách người dùng.")) + .finally(() => setLoading(false)); }, []); + const openEdit = (user) => { + setSelectedUser(user); + setFormData({ + fullName: user.fullName, + email: user.email, + phone: user.phone || '', + role: Array.isArray(user.role) ? user.role[0] : user.role, + }); + setEditOpen(true); + }; + + const handleUpdate = () => { + const token = localStorage.getItem("accessToken") || + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MjQwMTYsImV4cCI6MTc1MTg4MzIxNn0.ID50CEtfWiVEMZ2p3_vwARX5FqC4QLMflLbZkFBbvJw"; + + if (!token || !selectedUser) return; + + axios.put(`https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}`, formData, { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }) + .then(() => { + alert("Cập nhật thành công!"); + setUsers(users.map(u => u.id === selectedUser.id ? { ...u, ...formData } : u)); + setEditOpen(false); + }) + .catch(() => alert("Cập nhật thất bại!")); + }; + return (
Users Management - - Danh sách người dùng - + {loading &&

Đang tải...

} + {error &&

{error}

} +
- {Array.isArray(users) && users.map((user) => ( + {users.map((user) => ( {user.fullName} @@ -44,34 +87,35 @@ export function Users() { {user.fullName} - - Email: {user.email} - - - Phone: {user.phone || "N/A"} - - - Role: {Array.isArray(user.role) ? user.role.join(", ") : user.role} - - - Active: {user.isActive ? "Yes" : "No"} - + Email: {user.email} + Phone: {user.phone || "N/A"} + Role: {Array.isArray(user.role) ? user.role.join(", ") : user.role} + Active: {user.isActive ? "Yes" : "No"} - - - - - - + ))}
+ + + Chỉnh sửa người dùng + +
+ setFormData({ ...formData, fullName: e.target.value })} /> + setFormData({ ...formData, email: e.target.value })} /> + setFormData({ ...formData, phone: e.target.value })} /> + setFormData({ ...formData, role: e.target.value })} /> +
+
+ + + + +
); } From 858a7ba6416d9ea62a4c3a1db6d52b4eec2d8929 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Fri, 4 Jul 2025 18:38:29 +0700 Subject: [PATCH 037/248] capnhatusers --- src/pages/dashboard/users.jsx | 127 +++++++++++++++++++++++----------- 1 file changed, 86 insertions(+), 41 deletions(-) diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index c8b311e5..566f87e3 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -2,76 +2,121 @@ import React, { useEffect, useState } from "react"; import axios from "axios"; import { Card, CardHeader, CardBody, CardFooter, - Typography, Button, Tooltip + Typography, Button, Tooltip, Dialog, DialogHeader, + DialogBody, DialogFooter, Input, Select, Option } from "@material-tailwind/react"; import { Link } from "react-router-dom"; export function Users() { const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const [editOpen, setEditOpen] = useState(false); + const [selectedUser, setSelectedUser] = useState(null); + const [formData, setFormData] = useState({ fullName: '', email: '', phone: '', role: '' }); useEffect(() => { + const token = localStorage.getItem("accessToken"); + if (!token) { + setError("Không tìm thấy access token!"); + setLoading(false); + return; + } axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { + headers: { Authorization: `Bearer ${token}` } + }) + .then(res => setUsers(Array.isArray(res.data) ? res.data : [])) + .catch(() => setError("Lỗi khi tải danh sách người dùng.")) + .finally(() => setLoading(false)); + }, []); + + const openEdit = (user) => { + setSelectedUser(user); + setFormData({ + fullName: user.fullName, + email: user.email, + phone: user.phone || '', + role: Array.isArray(user.role) ? user.role[0] : user.role, + }); + setEditOpen(true); + }; + + const handleUpdate = () => { + const token = localStorage.getItem("accessToken"); + if (!token || !selectedUser) return; + + axios.put(`https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}`, formData, { headers: { - Authorization: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE1NDIyMDgsImV4cCI6MTc1MTgwMTQwOH0.t5Vl4SVsFu5sty27uWWcedZXTI21uPIrzUqsoHHDV5k" - } + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, }) - .then(res => { - console.log("Response:", res.data); - setUsers(res.data); // res.data là mảng user + .then(() => { + alert("Cập nhật thành công!"); + setUsers(users.map(u => u.id === selectedUser.id ? { ...u, ...formData } : u)); + setEditOpen(false); }) - .catch(err => console.error("Lỗi:", err)); - }, []); + .catch(() => alert("Cập nhật thất bại!")); + }; return (
Users Management - - Danh sách người dùng - + {loading &&

Đang tải...

} + {error &&

{error}

} +
- {Array.isArray(users) && users.map((user) => ( + {users.map((user) => ( - - {user.fullName} - + {user.avatar && ( + + {user.fullName} + + )} {user.fullName} - - Email: {user.email} - - - Phone: {user.phone || "N/A"} - - - Role: {Array.isArray(user.role) ? user.role.join(", ") : user.role} - - - Active: {user.isActive ? "Yes" : "No"} - + Email: {user.email} + Phone: {user.phone || "N/A"} + Role: {Array.isArray(user.role) ? user.role.join(", ") : user.role} + Active: {user.isActive ? "Yes" : "No"} - - - - - - + ))}
+ + + Chỉnh sửa người dùng + +
+ setFormData({ ...formData, fullName: e.target.value })} /> + setFormData({ ...formData, email: e.target.value })} /> + setFormData({ ...formData, phone: e.target.value })} /> + +
+
+ + + + +
); } From 6b6da6f262ed5a20752653f75ffcbb35382d5c28 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Fri, 4 Jul 2025 18:43:06 +0700 Subject: [PATCH 038/248] capnhatusers --- src/pages/dashboard/users.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index 8163f55d..dc4ab1c4 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -108,7 +108,11 @@ export function Users() { setFormData({ ...formData, fullName: e.target.value })} /> setFormData({ ...formData, email: e.target.value })} /> setFormData({ ...formData, phone: e.target.value })} /> - setFormData({ ...formData, role: e.target.value })} /> +
From db0bad5d07c55ac4b7b5d965d6e1a722ba3b3bc4 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Fri, 4 Jul 2025 18:55:04 +0700 Subject: [PATCH 039/248] xoauser --- src/pages/dashboard/users.jsx | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index 566f87e3..d115240b 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -14,7 +14,7 @@ export function Users() { const [editOpen, setEditOpen] = useState(false); const [selectedUser, setSelectedUser] = useState(null); - const [formData, setFormData] = useState({ fullName: '', email: '', phone: '', role: '' }); + const [formData, setFormData] = useState({ fullName: '', email: '', phone: '', role: 'customer' }); useEffect(() => { const token = localStorage.getItem("accessToken"); @@ -60,6 +60,21 @@ export function Users() { .catch(() => alert("Cập nhật thất bại!")); }; + const handleDelete = (userId) => { + const token = localStorage.getItem("accessToken"); + if (!token) return; + if (!window.confirm("Bạn có chắc muốn xoá người dùng này?")) return; + + axios.delete(`https://api-ndolv2.nongdanonline.vn/admin-users/${userId}`, { + headers: { Authorization: `Bearer ${token}` }, + }) + .then(() => { + alert("Xoá người dùng thành công!"); + setUsers(users.filter(user => user.id !== userId)); + }) + .catch(() => alert("Xoá thất bại!")); + }; + return (
@@ -71,15 +86,9 @@ export function Users() {
{users.map((user) => ( - {user.avatar && ( - - {user.fullName} - - )} + + {user.avatar && {user.fullName}} + {user.fullName} @@ -90,9 +99,8 @@ export function Users() { Active: {user.isActive ? "Yes" : "No"} - + + ))} From 5b0a5459ff7a2ef3e13a033f675bc1763132ed2b Mon Sep 17 00:00:00 2001 From: Tie902 Date: Fri, 4 Jul 2025 18:58:18 +0700 Subject: [PATCH 040/248] xoausers --- src/pages/dashboard/users.jsx | 59 +++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index dc4ab1c4..6c3890cb 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -3,7 +3,7 @@ import axios from "axios"; import { Card, CardHeader, CardBody, CardFooter, Typography, Button, Tooltip, Dialog, DialogHeader, - DialogBody, DialogFooter, Input + DialogBody, DialogFooter, Input, Select, Option } from "@material-tailwind/react"; import { Link } from "react-router-dom"; @@ -14,24 +14,21 @@ export function Users() { const [editOpen, setEditOpen] = useState(false); const [selectedUser, setSelectedUser] = useState(null); - const [formData, setFormData] = useState({ fullName: '', email: '', phone: '', role: '' }); + const [formData, setFormData] = useState({ fullName: '', email: '', phone: '', role: 'customer' }); useEffect(() => { - const token = localStorage.getItem("accessToken") || - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MjQwMTYsImV4cCI6MTc1MTg4MzIxNn0.ID50CEtfWiVEMZ2p3_vwARX5FqC4QLMflLbZkFBbvJw"; - + const token = localStorage.getItem("accessToken"); if (!token) { setError("Không tìm thấy access token!"); setLoading(false); return; } - axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { - headers: { Authorization: `Bearer ${token}` } + headers: { Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MjQwMTYsImV4cCI6MTc1MTg4MzIxNn0.ID50CEtfWiVEMZ2p3_vwARX5FqC4QLMflLbZkFBbvJw` } }) - .then(res => setUsers(Array.isArray(res.data) ? res.data : [])) - .catch(() => setError("Lỗi khi tải danh sách người dùng.")) - .finally(() => setLoading(false)); + .then(res => setUsers(Array.isArray(res.data) ? res.data : [])) + .catch(() => setError("Lỗi khi tải danh sách người dùng.")) + .finally(() => setLoading(false)); }, []); const openEdit = (user) => { @@ -46,9 +43,7 @@ export function Users() { }; const handleUpdate = () => { - const token = localStorage.getItem("accessToken") || - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MjQwMTYsImV4cCI6MTc1MTg4MzIxNn0.ID50CEtfWiVEMZ2p3_vwARX5FqC4QLMflLbZkFBbvJw"; - + const token = localStorage.getItem("accessToken"); if (!token || !selectedUser) return; axios.put(`https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}`, formData, { @@ -57,12 +52,27 @@ export function Users() { "Content-Type": "application/json", }, }) - .then(() => { - alert("Cập nhật thành công!"); - setUsers(users.map(u => u.id === selectedUser.id ? { ...u, ...formData } : u)); - setEditOpen(false); - }) - .catch(() => alert("Cập nhật thất bại!")); + .then(() => { + alert("Cập nhật thành công!"); + setUsers(users.map(u => u.id === selectedUser.id ? { ...u, ...formData } : u)); + setEditOpen(false); + }) + .catch(() => alert("Cập nhật thất bại!")); + }; + + const handleDelete = (userId) => { + const token = localStorage.getItem("accessToken"); + if (!token) return; + if (!window.confirm("Bạn có chắc muốn xoá người dùng này?")) return; + + axios.delete(`https://api-ndolv2.nongdanonline.vn/admin-users/${userId}`, { + headers: { Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MjQwMTYsImV4cCI6MTc1MTg4MzIxNn0.ID50CEtfWiVEMZ2p3_vwARX5FqC4QLMflLbZkFBbvJw` }, + }) + .then(() => { + alert("Xoá người dùng thành công!"); + setUsers(users.filter(user => user.id !== userId)); + }) + .catch(() => alert("Xoá thất bại!")); }; return ( @@ -77,11 +87,7 @@ export function Users() { {users.map((user) => ( - {user.fullName} + {user.avatar && {user.fullName}} @@ -93,9 +99,8 @@ export function Users() { Active: {user.isActive ? "Yes" : "No"} - + + ))} From 916312057df20d38301c26414d805bf504660352 Mon Sep 17 00:00:00 2001 From: Longvu003 Date: Fri, 4 Jul 2025 19:04:03 +0700 Subject: [PATCH 041/248] edit code --- src/App.jsx | 2 - src/{ipconfig.tsx => ipconfig.jsx} | 0 src/pages/dashboard/FarmDetail.tsx | 46 ---- src/pages/dashboard/Questions/Questions.jsx | 263 ++++++++++++++++++++ src/pages/dashboard/Questions/Questions.tsx | 108 -------- 5 files changed, 263 insertions(+), 156 deletions(-) rename src/{ipconfig.tsx => ipconfig.jsx} (100%) delete mode 100644 src/pages/dashboard/FarmDetail.tsx create mode 100644 src/pages/dashboard/Questions/Questions.jsx delete mode 100644 src/pages/dashboard/Questions/Questions.tsx diff --git a/src/App.jsx b/src/App.jsx index 0c1c83bc..700d5f49 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,13 +1,11 @@ import { Routes, Route, Navigate } from "react-router-dom"; import { Dashboard, Auth } from "@/layouts"; -import FarmDetail from "./pages/dashboard/FarmDetail"; import UpdateQuestion from "./pages/dashboard/Questions/UpdateQuestion"; function App() { return ( } /> } /> - } /> } /> } /> diff --git a/src/ipconfig.tsx b/src/ipconfig.jsx similarity index 100% rename from src/ipconfig.tsx rename to src/ipconfig.jsx diff --git a/src/pages/dashboard/FarmDetail.tsx b/src/pages/dashboard/FarmDetail.tsx deleted file mode 100644 index 10f6e334..00000000 --- a/src/pages/dashboard/FarmDetail.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from "react"; -import axios from "axios"; -import { useEffect } from "react"; -import { useState } from "react"; -import { useNavigate } from "react-router-dom"; -import { useParams } from "react-router-dom"; -import { BaseUrl } from "@/ipconfig"; -export function FarmDetail(){ - const { id } = useParams(); - const tokenUser= localStorage.getItem("token") - const [product, setProduct] = useState(null) -const getDetail=async()=>{ -try { -const res= await axios(`${BaseUrl}/adminfarms/${id}` -,{ headers: { - Authorization: `Bearer ${tokenUser}`, } - }, -) -if(res.status===200){ - setProduct(res.data) -} } catch (error) { - console.log("Lỗi server",error) - } -} -useEffect(()=>{ -getDetail() -},[]) -console.log("data nè :",product) -return ( -
- {product ? ( -
- {product.location} - {/* Hiển thị các thông tin khác nếu cần */} -
- ) : ( -
- Không có data -
- )} -
-) - -} - -export default FarmDetail \ No newline at end of file diff --git a/src/pages/dashboard/Questions/Questions.jsx b/src/pages/dashboard/Questions/Questions.jsx new file mode 100644 index 00000000..80f50886 --- /dev/null +++ b/src/pages/dashboard/Questions/Questions.jsx @@ -0,0 +1,263 @@ +import React, { useEffect, useState } from 'react'; +import axios from 'axios'; +import { BaseUrl } from '@/ipconfig'; +import { Oval } from 'react-loader-spinner'; + +export const Questions = () => { + const [loading, setLoading] = useState(true); + const [questions, setQuestions] = useState([]); + const [openDialog, setOpenDialog] = useState(false); + const [editData, setEditData] = useState(null); + const [editValue, setEditValue] = useState({ options: [] }); + + const tokenUser = localStorage.getItem('token'); + + const getData = async () => { + try { + const res = await axios.get(`${BaseUrl}/admin-questions`, { + headers: { Authorization: `Bearer ${tokenUser}` }, + }); + if (res.status === 200) { + setQuestions(res.data); + } + } catch (error) { + console.log('Lỗi nè:', error); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + getData(); + }, []); + + const handleOpenDialog = (item) => { + setEditData(item); + setEditValue({ + text: item.text, + options: Array.isArray(item.options) ? [...item.options] : [], + type: item.type, + link: item.link || '', + }); + setOpenDialog(true); + }; + + const handleCloseDialog = () => { + setOpenDialog(false); + setEditData(null); + setEditValue({ options: [] }); + }; + + const handleEditChange = (e, idx) => { + if ( + editData && + ['option', 'single-choice', 'multiple-choice', 'multi-choice'].includes(editData.type) && + typeof idx === 'number' + ) { + const newOptions = [...editValue.options]; + newOptions[idx] = e.target.value; + setEditValue({ ...editValue, options: newOptions }); + } else { + setEditValue({ ...editValue, [e.target.name]: e.target.value }); + } + }; + + const handleSave = async () => { + try { + await axios.put( + `${BaseUrl}/admin-questions/${editData._id}`, + editValue, + { + headers: { Authorization: `Bearer ${tokenUser}` }, + } + ); + alert("Lưu thành công") + handleCloseDialog(); + getData(); + } catch (error) { + console.log('Lỗi khi cập nhật:', error); + } + }; + + return ( +
+ {loading ? ( +
+ +
+ ) : questions.length === 0 ? ( +
+ Không có câu hỏi nào. +
+ ) : ( + questions.map((item) => ( +
+
+
{item.text}
+
+ + +
+
+
+ {Array.isArray(item.options) && item.options.length > 0 ? ( + item.options.map((opt, idx) => ( + + )) + ) : item.type === 'upload' ? ( + + ) : item.type === 'link' ? ( + + ) : ( + + )} +
+
+ )) + )} + {openDialog && editData && ( +
+
+
Sửa câu hỏi
+
+ + +
+ {['option', 'single-choice', 'multi-choice'].includes( + editData.type + ) && Array.isArray(editValue.options) && ( +
+ + {editValue.options.length === 0 && ( +
+ Chưa có đáp án nào, hãy thêm đáp án mới. +
+ )} + {editValue.options.map((opt, idx) => ( +
+ + {String.fromCharCode(65 + idx)}. + + handleEditChange(e, idx)} + className="border px-3 py-2 rounded w-full" + placeholder={`Đáp án ${String.fromCharCode(65 + idx)}`} + /> + +
+ ))} + +
+ )} + {editData.type === 'link' && ( +
+ + +
+ )} +
+ + +
+
+
+ )} +
+ ); +}; + +export default Questions; diff --git a/src/pages/dashboard/Questions/Questions.tsx b/src/pages/dashboard/Questions/Questions.tsx deleted file mode 100644 index bfbd5977..00000000 --- a/src/pages/dashboard/Questions/Questions.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import React, { useEffect } from 'react' -import axios from 'axios' -import { useState } from 'react' -import { BaseUrl } from '@/ipconfig' -import { useNavigate } from 'react-router-dom' -import { Oval } from 'react-loader-spinner'; - -export const Questions = () => { - const [loading, setLoading] = useState(true); - const tokenUser= localStorage.getItem("token") -const [questions,setQuestions]=useState([]) -const navigate=useNavigate() -const getData=async()=>{ -try { -const res= await axios.get(`${BaseUrl}/admin-questions`,{ - headers:{Authorization:`Bearer ${tokenUser}` } -}) - -if(res.status===200){ -setQuestions(res.data) -setLoading(false) -} -console.log("Có lỗi trong lúc lấy data") -} catch (error) { - console.log("Lỗi nè: ",error) -} - -} - -const gotoUpdate =async(id)=>{ -navigate(`/UpdateQuestion/${id}`) -} -console.log(questions) -useEffect(()=>{ -getData() -},[]) - return ( -
- { - loading ? - ( ):( - questions.map((item) => ( -
-
-
{item.text}
-
- - -
-
-
- {Array.isArray(item.options) && item.options.length > 0 ? ( - item.options.map((opt: string, idx: number) => ( - - )) - ) : item.type === "image" ? ( - - ) : item.type === "link" ? ( - - ) : ( - - )} -
-
- )) - ) - } -
- ) -} - -export default Questions \ No newline at end of file From d30048a45e438fdb71acbdc565b013f6ae418be0 Mon Sep 17 00:00:00 2001 From: Longvu003 Date: Fri, 4 Jul 2025 19:20:08 +0700 Subject: [PATCH 042/248] . --- src/App.jsx | 2 - src/ipconfig.jsx | 2 +- src/pages/dashboard/Questions/Questions.tsx | 108 ++++++++++++++++++ .../dashboard/Questions/UpdateQuestion.tsx | 58 ---------- 4 files changed, 109 insertions(+), 61 deletions(-) create mode 100644 src/pages/dashboard/Questions/Questions.tsx delete mode 100644 src/pages/dashboard/Questions/UpdateQuestion.tsx diff --git a/src/App.jsx b/src/App.jsx index 700d5f49..80e354b2 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,12 +1,10 @@ import { Routes, Route, Navigate } from "react-router-dom"; import { Dashboard, Auth } from "@/layouts"; -import UpdateQuestion from "./pages/dashboard/Questions/UpdateQuestion"; function App() { return ( } /> } /> -} /> } /> ); diff --git a/src/ipconfig.jsx b/src/ipconfig.jsx index 51cd1b17..6df8e3d1 100644 --- a/src/ipconfig.jsx +++ b/src/ipconfig.jsx @@ -1 +1 @@ -export const BaseUrl=`https://api-ndolv2.nongdanonline.vn` \ No newline at end of file +export const BaseUrl= `https://api-ndolv2.nongdanonline.vn` \ No newline at end of file diff --git a/src/pages/dashboard/Questions/Questions.tsx b/src/pages/dashboard/Questions/Questions.tsx new file mode 100644 index 00000000..472b0bd9 --- /dev/null +++ b/src/pages/dashboard/Questions/Questions.tsx @@ -0,0 +1,108 @@ +import React, { useEffect } from 'react' +import axios from 'axios' +import { useState } from 'react' +import { BaseUrl } from '@/ipconfig' +import { useNavigate } from 'react-router-dom' +import { Oval } from 'react-loader-spinner'; + +export const Questions = () => { + const [loading, setLoading] = useState(true); + const tokenUser= localStorage.getItem("token") + const [questions,setQuestions]=useState([]) + const navigate=useNavigate() + const getData=async()=>{ +try { +const res= await axios.get(`${BaseUrl}/admin-questions`,{ + headers:{Authorization:`Bearer ${tokenUser}` } +}) + +if(res.status===200){ +setQuestions(res.data) +setLoading(false) +} +console.log("Có lỗi trong lúc lấy data") +} catch (error) { + console.log("Lỗi nè: ",error) +} + +} + +const gotoUpdate =async(id)=>{ +// navigate(`/UpdateQuestion/${id}`) +} +console.log(questions) +useEffect(()=>{ +getData() +},[]) + return ( +
+ { + loading ? + ( ):( + questions.map((item) => ( +
+
+
{item.text}
+
+ + +
+
+
+ {Array.isArray(item.options) && item.options.length > 0 ? ( + item.options.map((opt: string, idx: number) => ( + + )) + ) : item.type === "image" ? ( + + ) : item.type === "link" ? ( + + ) : ( + + )} +
+
+ )) + ) + } +
+ ) +} + +export default Questions \ No newline at end of file diff --git a/src/pages/dashboard/Questions/UpdateQuestion.tsx b/src/pages/dashboard/Questions/UpdateQuestion.tsx deleted file mode 100644 index 9c4d0143..00000000 --- a/src/pages/dashboard/Questions/UpdateQuestion.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React, { useEffect } from 'react' -import { useParams } from 'react-router-dom' -import axios from 'axios' -import { BaseUrl } from '@/ipconfig' -import { useState } from 'react' - - export const UpdateQuestion = () => { - const tokenUser= localStorage.getItem("token") - const [questionOptions,setQuestionOptions]=useState([]) - const [questionText,setQuestionText]=useState() - const [questionDetail,setQuestionDetail]=useState() - const {id}=useParams() - -const getQuestionByid =async()=>{ -try { - const res= await axios.get(`${BaseUrl}/admin-questions/${id}`,{headers:{Authorization:`Bearer ${tokenUser}`}}) -if(res.status===200){ - setQuestionText(res.data.text||"") - setQuestionOptions(res.data.options||[]) -} -console.log("Có lỗi khi lấy data") -} catch (error) { - console.log("Lỗi nè:",error) -} - -} - - const handleText=(item)=>{ - setQuestionText(item.target.value) - } - -const handleTitleChange = (e: React.ChangeEvent) => { - // setQuestionText(e.target.value) - } - -console.log(questionDetail) - useEffect(()=>{ -getQuestionByid() - },[]) - return ( -
-
- handleTitleChange} - placeholder='Nhấn vào đây'/> -
- - { - questionOptions.map((item,index)=>( - - - )) - } -
- ) -} - -export default UpdateQuestion \ No newline at end of file From 1cf4ed8eb64d1b5b080d39664b8be08572578daa Mon Sep 17 00:00:00 2001 From: Longvu003 Date: Fri, 4 Jul 2025 19:24:09 +0700 Subject: [PATCH 043/248] . --- src/pages/dashboard/AcceptFarm.jsx | 0 src/pages/dashboard/Questions/Questions.tsx | 108 -------------------- src/pages/dashboard/notifications.jsx | 0 3 files changed, 108 deletions(-) create mode 100644 src/pages/dashboard/AcceptFarm.jsx delete mode 100644 src/pages/dashboard/Questions/Questions.tsx create mode 100644 src/pages/dashboard/notifications.jsx diff --git a/src/pages/dashboard/AcceptFarm.jsx b/src/pages/dashboard/AcceptFarm.jsx new file mode 100644 index 00000000..e69de29b diff --git a/src/pages/dashboard/Questions/Questions.tsx b/src/pages/dashboard/Questions/Questions.tsx deleted file mode 100644 index 472b0bd9..00000000 --- a/src/pages/dashboard/Questions/Questions.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import React, { useEffect } from 'react' -import axios from 'axios' -import { useState } from 'react' -import { BaseUrl } from '@/ipconfig' -import { useNavigate } from 'react-router-dom' -import { Oval } from 'react-loader-spinner'; - -export const Questions = () => { - const [loading, setLoading] = useState(true); - const tokenUser= localStorage.getItem("token") - const [questions,setQuestions]=useState([]) - const navigate=useNavigate() - const getData=async()=>{ -try { -const res= await axios.get(`${BaseUrl}/admin-questions`,{ - headers:{Authorization:`Bearer ${tokenUser}` } -}) - -if(res.status===200){ -setQuestions(res.data) -setLoading(false) -} -console.log("Có lỗi trong lúc lấy data") -} catch (error) { - console.log("Lỗi nè: ",error) -} - -} - -const gotoUpdate =async(id)=>{ -// navigate(`/UpdateQuestion/${id}`) -} -console.log(questions) -useEffect(()=>{ -getData() -},[]) - return ( -
- { - loading ? - ( ):( - questions.map((item) => ( -
-
-
{item.text}
-
- - -
-
-
- {Array.isArray(item.options) && item.options.length > 0 ? ( - item.options.map((opt: string, idx: number) => ( - - )) - ) : item.type === "image" ? ( - - ) : item.type === "link" ? ( - - ) : ( - - )} -
-
- )) - ) - } -
- ) -} - -export default Questions \ No newline at end of file diff --git a/src/pages/dashboard/notifications.jsx b/src/pages/dashboard/notifications.jsx new file mode 100644 index 00000000..e69de29b From 0b9767e4ef4a5349c0a97de8887795dc9f9cafdb Mon Sep 17 00:00:00 2001 From: Tie902 Date: Fri, 4 Jul 2025 19:45:43 +0700 Subject: [PATCH 044/248] xausersvathemusers --- src/pages/dashboard/users.jsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index d115240b..4caff261 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -15,9 +15,12 @@ export function Users() { const [editOpen, setEditOpen] = useState(false); const [selectedUser, setSelectedUser] = useState(null); const [formData, setFormData] = useState({ fullName: '', email: '', phone: '', role: 'customer' }); + const [roles, setRoles] = useState([]); + + const getAccessToken = () => localStorage.getItem("accessToken"); useEffect(() => { - const token = localStorage.getItem("accessToken"); + const token = getAccessToken(); if (!token) { setError("Không tìm thấy access token!"); setLoading(false); @@ -29,6 +32,12 @@ export function Users() { .then(res => setUsers(Array.isArray(res.data) ? res.data : [])) .catch(() => setError("Lỗi khi tải danh sách người dùng.")) .finally(() => setLoading(false)); + + axios.get("https://api-ndolv2.nongdanonline.vn/admin-users/roles", { + headers: { Authorization: `Bearer ${token}` } + }) + .then(res => setRoles(Array.isArray(res.data) ? res.data : [])) + .catch(() => setRoles(["customer", "admin", "farmer"])); }, []); const openEdit = (user) => { @@ -43,7 +52,7 @@ export function Users() { }; const handleUpdate = () => { - const token = localStorage.getItem("accessToken"); + const token = getAccessToken(); if (!token || !selectedUser) return; axios.put(`https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}`, formData, { @@ -61,7 +70,7 @@ export function Users() { }; const handleDelete = (userId) => { - const token = localStorage.getItem("accessToken"); + const token = getAccessToken(); if (!token) return; if (!window.confirm("Bạn có chắc muốn xoá người dùng này?")) return; @@ -114,9 +123,7 @@ export function Users() { setFormData({ ...formData, email: e.target.value })} /> setFormData({ ...formData, phone: e.target.value })} />
From c14c11d837608d6e5db35962d2686d9c43ac8f3f Mon Sep 17 00:00:00 2001 From: Tie902 Date: Fri, 4 Jul 2025 19:48:13 +0700 Subject: [PATCH 045/248] xoathemusers --- src/pages/dashboard/users.jsx | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index 6c3890cb..4caff261 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -15,20 +15,29 @@ export function Users() { const [editOpen, setEditOpen] = useState(false); const [selectedUser, setSelectedUser] = useState(null); const [formData, setFormData] = useState({ fullName: '', email: '', phone: '', role: 'customer' }); + const [roles, setRoles] = useState([]); + + const getAccessToken = () => localStorage.getItem("accessToken"); useEffect(() => { - const token = localStorage.getItem("accessToken"); + const token = getAccessToken(); if (!token) { setError("Không tìm thấy access token!"); setLoading(false); return; } axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { - headers: { Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MjQwMTYsImV4cCI6MTc1MTg4MzIxNn0.ID50CEtfWiVEMZ2p3_vwARX5FqC4QLMflLbZkFBbvJw` } + headers: { Authorization: `Bearer ${token}` } }) .then(res => setUsers(Array.isArray(res.data) ? res.data : [])) .catch(() => setError("Lỗi khi tải danh sách người dùng.")) .finally(() => setLoading(false)); + + axios.get("https://api-ndolv2.nongdanonline.vn/admin-users/roles", { + headers: { Authorization: `Bearer ${token}` } + }) + .then(res => setRoles(Array.isArray(res.data) ? res.data : [])) + .catch(() => setRoles(["customer", "admin", "farmer"])); }, []); const openEdit = (user) => { @@ -43,7 +52,7 @@ export function Users() { }; const handleUpdate = () => { - const token = localStorage.getItem("accessToken"); + const token = getAccessToken(); if (!token || !selectedUser) return; axios.put(`https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}`, formData, { @@ -61,12 +70,12 @@ export function Users() { }; const handleDelete = (userId) => { - const token = localStorage.getItem("accessToken"); + const token = getAccessToken(); if (!token) return; if (!window.confirm("Bạn có chắc muốn xoá người dùng này?")) return; axios.delete(`https://api-ndolv2.nongdanonline.vn/admin-users/${userId}`, { - headers: { Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MjQwMTYsImV4cCI6MTc1MTg4MzIxNn0.ID50CEtfWiVEMZ2p3_vwARX5FqC4QLMflLbZkFBbvJw` }, + headers: { Authorization: `Bearer ${token}` }, }) .then(() => { alert("Xoá người dùng thành công!"); @@ -114,9 +123,7 @@ export function Users() { setFormData({ ...formData, email: e.target.value })} /> setFormData({ ...formData, phone: e.target.value })} />
From ea67e2f87902cea96e0f881c13891cd006e8d43c Mon Sep 17 00:00:00 2001 From: Tie902 Date: Fri, 4 Jul 2025 20:22:10 +0700 Subject: [PATCH 046/248] token --- src/pages/dashboard/users.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index 4caff261..e797cfed 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -27,14 +27,14 @@ export function Users() { return; } axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { - headers: { Authorization: `Bearer ${token}` } + headers: { Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MzQzMDUsImV4cCI6MTc1MTg5MzUwNX0.qfl2smCZ35xZ49gwqgx8NwlkolEY2NV9hohMmG0M8do` } }) .then(res => setUsers(Array.isArray(res.data) ? res.data : [])) .catch(() => setError("Lỗi khi tải danh sách người dùng.")) .finally(() => setLoading(false)); axios.get("https://api-ndolv2.nongdanonline.vn/admin-users/roles", { - headers: { Authorization: `Bearer ${token}` } + headers: { Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MzQzMDUsImV4cCI6MTc1MTg5MzUwNX0.qfl2smCZ35xZ49gwqgx8NwlkolEY2NV9hohMmG0M8do` } }) .then(res => setRoles(Array.isArray(res.data) ? res.data : [])) .catch(() => setRoles(["customer", "admin", "farmer"])); @@ -57,7 +57,7 @@ export function Users() { axios.put(`https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}`, formData, { headers: { - Authorization: `Bearer ${token}`, + Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MzQzMDUsImV4cCI6MTc1MTg5MzUwNX0.qfl2smCZ35xZ49gwqgx8NwlkolEY2NV9hohMmG0M8do`, "Content-Type": "application/json", }, }) @@ -75,7 +75,7 @@ export function Users() { if (!window.confirm("Bạn có chắc muốn xoá người dùng này?")) return; axios.delete(`https://api-ndolv2.nongdanonline.vn/admin-users/${userId}`, { - headers: { Authorization: `Bearer ${token}` }, + headers: { Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MzQzMDUsImV4cCI6MTc1MTg5MzUwNX0.qfl2smCZ35xZ49gwqgx8NwlkolEY2NV9hohMmG0M8do` }, }) .then(() => { alert("Xoá người dùng thành công!"); From 11b4cd2189a9fef9e5b1d365bc7fa7b6f0fbf777 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Fri, 4 Jul 2025 20:23:26 +0700 Subject: [PATCH 047/248] xoathemvatoken --- src/pages/dashboard/users.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index e797cfed..18cd803a 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -12,6 +12,8 @@ export function Users() { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + + const [editOpen, setEditOpen] = useState(false); const [selectedUser, setSelectedUser] = useState(null); const [formData, setFormData] = useState({ fullName: '', email: '', phone: '', role: 'customer' }); From ad687e1ffc7684d94fc4868541f8751dab862ccb Mon Sep 17 00:00:00 2001 From: Tie902 Date: Fri, 4 Jul 2025 20:24:56 +0700 Subject: [PATCH 048/248] themxoavatokenusers --- src/pages/dashboard/users.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index 4caff261..e797cfed 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -27,14 +27,14 @@ export function Users() { return; } axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { - headers: { Authorization: `Bearer ${token}` } + headers: { Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MzQzMDUsImV4cCI6MTc1MTg5MzUwNX0.qfl2smCZ35xZ49gwqgx8NwlkolEY2NV9hohMmG0M8do` } }) .then(res => setUsers(Array.isArray(res.data) ? res.data : [])) .catch(() => setError("Lỗi khi tải danh sách người dùng.")) .finally(() => setLoading(false)); axios.get("https://api-ndolv2.nongdanonline.vn/admin-users/roles", { - headers: { Authorization: `Bearer ${token}` } + headers: { Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MzQzMDUsImV4cCI6MTc1MTg5MzUwNX0.qfl2smCZ35xZ49gwqgx8NwlkolEY2NV9hohMmG0M8do` } }) .then(res => setRoles(Array.isArray(res.data) ? res.data : [])) .catch(() => setRoles(["customer", "admin", "farmer"])); @@ -57,7 +57,7 @@ export function Users() { axios.put(`https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}`, formData, { headers: { - Authorization: `Bearer ${token}`, + Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MzQzMDUsImV4cCI6MTc1MTg5MzUwNX0.qfl2smCZ35xZ49gwqgx8NwlkolEY2NV9hohMmG0M8do`, "Content-Type": "application/json", }, }) @@ -75,7 +75,7 @@ export function Users() { if (!window.confirm("Bạn có chắc muốn xoá người dùng này?")) return; axios.delete(`https://api-ndolv2.nongdanonline.vn/admin-users/${userId}`, { - headers: { Authorization: `Bearer ${token}` }, + headers: { Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MzQzMDUsImV4cCI6MTc1MTg5MzUwNX0.qfl2smCZ35xZ49gwqgx8NwlkolEY2NV9hohMmG0M8do` }, }) .then(() => { alert("Xoá người dùng thành công!"); From 97eaac37f270f4ff036cf21c9b4da455055b6b38 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Fri, 4 Jul 2025 20:55:31 +0700 Subject: [PATCH 049/248] thanh --- src/pages/dashboard/users.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index e797cfed..b866948c 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -17,7 +17,7 @@ export function Users() { const [formData, setFormData] = useState({ fullName: '', email: '', phone: '', role: 'customer' }); const [roles, setRoles] = useState([]); - const getAccessToken = () => localStorage.getItem("accessToken"); + const getAccessToken = () => localStorage.getItem("token"); useEffect(() => { const token = getAccessToken(); @@ -27,14 +27,14 @@ export function Users() { return; } axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { - headers: { Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MzQzMDUsImV4cCI6MTc1MTg5MzUwNX0.qfl2smCZ35xZ49gwqgx8NwlkolEY2NV9hohMmG0M8do` } + headers: { Authorization: `Bearer ${localStorage.getItem("token")}` } }) .then(res => setUsers(Array.isArray(res.data) ? res.data : [])) .catch(() => setError("Lỗi khi tải danh sách người dùng.")) .finally(() => setLoading(false)); axios.get("https://api-ndolv2.nongdanonline.vn/admin-users/roles", { - headers: { Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MzQzMDUsImV4cCI6MTc1MTg5MzUwNX0.qfl2smCZ35xZ49gwqgx8NwlkolEY2NV9hohMmG0M8do` } + headers: { Authorization: `Bearer ${localStorage.getItem("token")}` } }) .then(res => setRoles(Array.isArray(res.data) ? res.data : [])) .catch(() => setRoles(["customer", "admin", "farmer"])); @@ -57,7 +57,7 @@ export function Users() { axios.put(`https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}`, formData, { headers: { - Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MzQzMDUsImV4cCI6MTc1MTg5MzUwNX0.qfl2smCZ35xZ49gwqgx8NwlkolEY2NV9hohMmG0M8do`, + Authorization: `Bearer ${localStorage.getItem("token")}`, "Content-Type": "application/json", }, }) @@ -75,7 +75,7 @@ export function Users() { if (!window.confirm("Bạn có chắc muốn xoá người dùng này?")) return; axios.delete(`https://api-ndolv2.nongdanonline.vn/admin-users/${userId}`, { - headers: { Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MzQzMDUsImV4cCI6MTc1MTg5MzUwNX0.qfl2smCZ35xZ49gwqgx8NwlkolEY2NV9hohMmG0M8do` }, + headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }, }) .then(() => { alert("Xoá người dùng thành công!"); From bbfe5bd73650f24bdff81e7e57534e12f87578e3 Mon Sep 17 00:00:00 2001 From: Longvu003 Date: Fri, 4 Jul 2025 21:42:26 +0700 Subject: [PATCH 050/248] . --- src/pages/dashboard/Questions/Questions.tsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/pages/dashboard/Questions/Questions.tsx diff --git a/src/pages/dashboard/Questions/Questions.tsx b/src/pages/dashboard/Questions/Questions.tsx new file mode 100644 index 00000000..e69de29b From ac0cf5c28f6e5a39293292b5733954fe87d69081 Mon Sep 17 00:00:00 2001 From: Longvu003 Date: Sat, 5 Jul 2025 16:17:43 +0700 Subject: [PATCH 051/248] upcode question2 --- src/pages/dashboard/Questions/AddQuestion.jsx | 112 +++++++++++++ src/pages/dashboard/Questions/Questions.jsx | 158 +++++++++++++----- 2 files changed, 232 insertions(+), 38 deletions(-) create mode 100644 src/pages/dashboard/Questions/AddQuestion.jsx diff --git a/src/pages/dashboard/Questions/AddQuestion.jsx b/src/pages/dashboard/Questions/AddQuestion.jsx new file mode 100644 index 00000000..82278a52 --- /dev/null +++ b/src/pages/dashboard/Questions/AddQuestion.jsx @@ -0,0 +1,112 @@ +import React from 'react' + +const AddQuestion = ({ + handleAddChange, + handleAddSave, + handleCloseAddDialog, + handleOpenAddDialog, + addDialog, + addValue, + setAddValue +}) => { + return ( +
+ + + {addDialog && ( +
+
+
Thêm câu hỏi mới
+
+ + +
+
+ + +
+
+
+ {[ "single-choice", "multiple-choice", "multi-choice"].includes(addValue.type) && Array.isArray(addValue.options) && ( +
+ + {addValue.options.length === 0 && ( +
Chưa có đáp án nào, hãy thêm đáp án mới.
+ )} + + {addValue.options.map((opt, idx) => ( +
+ {String.fromCharCode(65 + idx)}. + handleAddChange(e, idx)} + className="border px-3 py-2 rounded w-full" + placeholder={`Đáp án ${String.fromCharCode(65 + idx)}`} + /> + +
+ ))} + +
+ )} + + +
+ + +
+
+
+ )} +
+ ) +} + +export default AddQuestion \ No newline at end of file diff --git a/src/pages/dashboard/Questions/Questions.jsx b/src/pages/dashboard/Questions/Questions.jsx index 80f50886..93a6a44b 100644 --- a/src/pages/dashboard/Questions/Questions.jsx +++ b/src/pages/dashboard/Questions/Questions.jsx @@ -2,13 +2,16 @@ import React, { useEffect, useState } from 'react'; import axios from 'axios'; import { BaseUrl } from '@/ipconfig'; import { Oval } from 'react-loader-spinner'; - +import AddQuestion from "./AddQuestion" export const Questions = () => { const [loading, setLoading] = useState(true); const [questions, setQuestions] = useState([]); const [openDialog, setOpenDialog] = useState(false); const [editData, setEditData] = useState(null); const [editValue, setEditValue] = useState({ options: [] }); +console.log(editValue) + const [addDialog, setAddDialog] = useState(false); + const [addValue, setAddValue] = useState({ text: '', options: [''], type: 'option', link: '' }); const tokenUser = localStorage.getItem('token'); @@ -63,6 +66,17 @@ export const Questions = () => { }; const handleSave = async () => { + if ( + ['option', 'single-choice', 'multiple-choice', 'multi-choice'].includes(editData.type) && + Array.isArray(editValue.options) && + editValue.options.some(opt => !opt || opt.trim() === '') + ) { + alert('Vui lòng điền đầy đủ tất cả các đáp án!'); + return; + } else if (!editValue.text || editValue.text.trim() === '') { + alert('Vui lòng nhập nội dung câu hỏi!'); + return; + } try { await axios.put( `${BaseUrl}/admin-questions/${editData._id}`, @@ -79,8 +93,74 @@ export const Questions = () => { } }; + const handleDelete = async (id) => { + if (!window.confirm('Bạn có chắc muốn xóa câu hỏi này?')) return; + try { + await axios.delete(`${BaseUrl}/admin-questions/${id}`, { + headers: { Authorization: `Bearer ${tokenUser}` }, + }); + alert('Xóa thành công!'); + getData(); + } catch (error) { + alert('Lỗi khi xóa!'); + console.log('Lỗi khi xóa:', error); + } + }; + + const handleOpenAddDialog = () => { + setAddValue({ text: '', options: [''], type: 'option', link: '' }); + setAddDialog(true); + }; + const handleCloseAddDialog = () => { + setAddDialog(false); + setAddValue({ text: '', options: [''], type: 'option', link: '' }); + }; + const handleAddChange = (e, idx) => { + if (["option", "single-choice", "multiple-choice", "multi-choice"].includes(addValue.type) && typeof idx === "number") { + const newOptions = [...addValue.options]; + newOptions[idx] = e.target.value; + setAddValue({ ...addValue, options: newOptions }); + } else { + setAddValue({ ...addValue, [e.target.name]: e.target.value }); + } + }; + const handleAddSave = async () => { + if (!addValue.text || addValue.text.trim() === '') { + alert('Vui lòng nhập nội dung câu hỏi!'); + return; + } + if (["option", "single-choice", "multiple-choice", "multi-choice"].includes(addValue.type) && addValue.options.some(opt => !opt || opt.trim() === '')) { + alert('Vui lòng điền đầy đủ tất cả các đáp án!'); + return; + } + + + try { + await axios.post(`${BaseUrl}/admin-questions`, addValue, { + headers: { Authorization: `Bearer ${tokenUser}` }, + }); + alert('Thêm thành công!'); + handleCloseAddDialog(); + getData(); + } catch (error) { + alert('Lỗi khi thêm!'); + console.log('Lỗi khi thêm:', error); + } + }; return (
+
+ +
+ + {loading ? (
{ Không có câu hỏi nào.
) : ( + questions.map((item) => ( +
{
- {Array.isArray(item.options) && item.options.length > 0 ? ( - item.options.map((opt, idx) => ( - - )) - ) : item.type === 'upload' ? ( - - ) : item.type === 'link' ? ( - - ) : ( - - )} -
+ {[ "single-choice", "multiple-choice", "multi-choice"].includes(item.type) && Array.isArray(item.options) && item.options.length > 0 ? ( + item.options.map((opt, idx) => ( + + )) + ) : item.type === 'upload' ? ( + + ) : item.type === 'link' ? ( + + ) : item.type === 'text' ? ( + + ) : null} +
)) - )} +)} + {openDialog && editData && (
@@ -173,7 +255,7 @@ export const Questions = () => { className="border px-3 py-2 rounded w-full" />
- {['option', 'single-choice', 'multi-choice'].includes( + {[ 'single-choice', 'multiple-choice', 'multi-choice'].includes( editData.type ) && Array.isArray(editValue.options) && (
@@ -189,7 +271,7 @@ export const Questions = () => {
{String.fromCharCode(65 + idx)}. - + handleEditChange(e, idx)} From af5d88fb3c7ce4adf225de6853574a99985f7cb8 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Sat, 5 Jul 2025 16:24:25 +0700 Subject: [PATCH 052/248] Update farms.jsx --- src/pages/dashboard/farms.jsx | 100 ++++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 4 deletions(-) diff --git a/src/pages/dashboard/farms.jsx b/src/pages/dashboard/farms.jsx index 2cd9c167..a8fa8e14 100644 --- a/src/pages/dashboard/farms.jsx +++ b/src/pages/dashboard/farms.jsx @@ -1,7 +1,10 @@ import React, { useState, useEffect } from "react"; import axios from "axios"; import { - Card, CardBody, Input, Button, Typography, Checkbox, Chip, Tabs, TabsHeader, Tab + Card, CardBody, Input, Button, Typography, Checkbox, Chip, Tabs, TabsHeader, Tab, + Dialog, + DialogHeader, + DialogBody } from "@material-tailwind/react"; import { PencilSquareIcon, TrashIcon, PlusIcon, @@ -13,6 +16,7 @@ const getOpts = () => ({ headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }, }); + export function Farms() { const [farms, setFarms] = useState([]); const [error, setError] = useState(null); @@ -24,6 +28,9 @@ export function Farms() { const [openForm, setOpenForm] = useState(false); const [editingFarm, setEditingFarm] = useState(null); + const [selectedFarm, setSelectedFarm] = useState(null); + const [openDetail, setOpenDetail] = useState(false); + const fetchFarms = async () => { try { const res = await axios.get(`${BASE_URL}/adminfarms`, getOpts()); @@ -35,6 +42,16 @@ export function Farms() { } }; + const getFarmDetail = async (id) => { + try { + const res = await axios.get(`${BASE_URL}/adminfarms/${id}`, getOpts()); + setSelectedFarm(res.data); + setOpenDetail(true); + }catch (err) { + alert("Lỗi lấy chi tiết: " + (err.response?.data?.message || err.message)); + } + }; + const addFarm = async (data) => { try { await axios.post(`${BASE_URL}/adminfarms`, data, getOpts()); @@ -156,7 +173,8 @@ export function Farms() { {displayedFarms.map((farm) => ( - + getFarmDetail(farm._id)}> {farm.name} {farm.code} @@ -178,7 +196,7 @@ export function Farms() { {/* Sửa */} +
+ Subscribe me to newsletter } containerProps={{ className: "-ml-2.5" }} /> - - Forgot Password - + Forgot Password
- Not registered? Create account -
+
- +
-
); } diff --git a/src/pages/dashboard/FarmDetail.jsx b/src/pages/dashboard/FarmDetail.jsx new file mode 100644 index 00000000..b02bfa30 --- /dev/null +++ b/src/pages/dashboard/FarmDetail.jsx @@ -0,0 +1,99 @@ +import React, { useState, useEffect } from "react"; +import axios from "axios"; +import { + Dialog, DialogHeader, DialogBody, Typography, Chip, +} from "@material-tailwind/react"; +import FarmPictures from "./FarmPictures"; + +const BASE_URL = "https://api-ndolv2.nongdanonline.vn"; +const getOpts = () => ({ + headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }, +}); + +export default function FarmDetail({ farmId, open, onClose }) { + const [farm, setFarm] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + if (!farmId) return; + + const fetchFarm = async () => { + try { + const res = await axios.get(`${BASE_URL}/adminfarms/${farmId}`, getOpts()); + setFarm(res.data); + } catch (err) { + setError(err.response?.data?.message || err.message); + } + }; + + fetchFarm(); + }, [farmId]); + + return ( + + Chi tiết nông trại + + {error ? ( + Lỗi: {error} + ) : farm ? ( + <> + Tên: {farm.name} + Mã: {farm.code} + Chủ sở hữu: {farm.ownerInfo?.name || "—"} + SĐT: {farm.phone || "—"} + Địa chỉ: {farm.location} + Diện tích: {farm.area} m² + Mô tả: {farm.description || "Không có"} + + {/* Dịch vụ */} +
+ Dịch vụ +
+ {farm.services?.length > 0 ? ( + farm.services.map((service, idx) => ( + + )) + ) : ( + Không có + )} +
+
+ + {/* Tính năng */} +
+ Tính năng +
+ {farm.features?.length > 0 ? ( + farm.features.map((feature, idx) => ( + + )) + ) : ( + Không có + )} +
+
+ + {/* FarmPictures */} +
+ +
+ + ) : ( + Đang tải chi tiết... + )} +
+
+ ); +} diff --git a/src/pages/dashboard/FarmPictures.jsx b/src/pages/dashboard/FarmPictures.jsx new file mode 100644 index 00000000..a2cfdf9d --- /dev/null +++ b/src/pages/dashboard/FarmPictures.jsx @@ -0,0 +1,51 @@ +import React, { useState, useEffect } from "react"; +import axios from "axios"; +import { Typography } from "@material-tailwind/react"; + +const BASE_URL = "https://api-ndolv2.nongdanonline.vn"; +const getOpts = () => ({ + headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }, +}); + +export default function FarmPictures({ farmId }) { + const [pictures, setPictures] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + if (!farmId) return; + + const fetchPictures = async () => { + try { + const res = await axios.get(`${BASE_URL}/farm-pictures/${farmId}`, getOpts()); + setPictures(res.data); + } catch (err) { + if (err.response?.status === 404) { + // Không tìm thấy => farm chưa có ảnh + setPictures([]); + } else { + setError(err.response?.data?.message || err.message); + } + } finally { + setLoading(false); + } + }; + + fetchPictures(); + }, [farmId]); + + if (loading) return Đang tải ảnh...; + if (error) return Lỗi: {error}; + if (pictures.length === 0) return Farm này chưa có ảnh nào.; + + return ( +
+ {pictures.map((pic) => ( +
+ Farm + {pic.title || "Ảnh farm"} +
+ ))} +
+ ); +} diff --git a/src/pages/dashboard/farms.jsx b/src/pages/dashboard/farms.jsx index e1f55111..29155860 100644 --- a/src/pages/dashboard/farms.jsx +++ b/src/pages/dashboard/farms.jsx @@ -2,33 +2,30 @@ import React, { useState, useEffect } from "react"; import axios from "axios"; import { Card, CardBody, Input, Button, Typography, Checkbox, Chip, Tabs, TabsHeader, Tab, - Dialog, - DialogHeader, - DialogBody } from "@material-tailwind/react"; import { PencilSquareIcon, TrashIcon, PlusIcon, } from "@heroicons/react/24/solid"; -import FarmForm from "./farmForm"; +import FarmForm from "./FarmForm"; +import FarmDetail from "./FarmDetail"; const BASE_URL = "https://api-ndolv2.nongdanonline.vn"; const getOpts = () => ({ headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }, }); - export function Farms() { const [farms, setFarms] = useState([]); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); - const [tab, setTab] = useState("all") + const [tab, setTab] = useState("all"); const [search, setSearch] = useState(""); const [openForm, setOpenForm] = useState(false); const [editingFarm, setEditingFarm] = useState(null); - const [selectedFarm, setSelectedFarm] = useState(null); + const [selectedFarmId, setSelectedFarmId] = useState(null); const [openDetail, setOpenDetail] = useState(false); const fetchFarms = async () => { @@ -42,23 +39,13 @@ export function Farms() { } }; - const getFarmDetail = async (id) => { - try { - const res = await axios.get(`${BASE_URL}/adminfarms/${id}`, getOpts()); - setSelectedFarm(res.data); - setOpenDetail(true); - }catch (err) { - alert("Lỗi lấy chi tiết: " + (err.response?.data?.message || err.message)); - } - }; - const addFarm = async (data) => { try { await axios.post(`${BASE_URL}/adminfarms`, data, getOpts()); await fetchFarms(); - alert(" Tạo farm thành công!"); + alert("Tạo farm thành công!"); } catch (err) { - alert("Lỗi thêm: " + (err.response?.data?.message || err.message)); + alert("Lỗi thêm: " + (err.response?.data?.message || err.message)); } }; @@ -72,49 +59,43 @@ export function Farms() { }; const deleteFarm = async (id) => { - if (!window.confirm("Bạn có chắc chắn muốn xoá không?")) return; - console.log(" Xoá farm với id:", id); + if (!window.confirm("Bạn có chắc chắn muốn xoá farm này?")) return; try { await axios.delete(`${BASE_URL}/adminfarms/${id}`, getOpts()); - setFarms((prevFarms) => prevFarms.filter((farm) => farm._id !== id)); + setFarms((prev) => prev.filter((farm) => farm._id !== id)); } catch (err) { alert("Lỗi xoá: " + (err.response?.data?.message || err.message)); } }; - const activateFarm = async (id) => { + const activateFarm = async (id, actionText) => { + if (!window.confirm(`Bạn có chắc chắn muốn ${actionText} farm này không?`)) return; try { await axios.patch(`${BASE_URL}/adminfarms/${id}/activate`, null, getOpts()); await fetchFarms(); - }catch (err) { - alert("Lỗi kích hoạt: " + (err.response?.data?.message || err.message)); + } catch (err) { + alert(`Lỗi ${actionText}: ` + (err.response?.data?.message || err.message)); } - } + }; - const deactivateFarm = async (id) => { + const deactivateFarm = async (id, actionText) => { + if (!window.confirm(`Bạn có chắc chắn muốn ${actionText} farm này không?`)) return; try { await axios.patch(`${BASE_URL}/adminfarms/${id}/deactivate`, null, getOpts()); await fetchFarms(); - }catch (err) { - alert("Lỗi kích hoạt: " + (err.response?.data?.message || err.message)); + } catch (err) { + alert(`Lỗi ${actionText}: ` + (err.response?.data?.message || err.message)); } - } + }; useEffect(() => { fetchFarms(); }, []); const displayedFarms = farms - .filter((farm) => { - if (tab === "all") return true; - return farm.status === tab; - }) - .filter((farm) => - farm.name?.toLowerCase().includes(search.toLowerCase()) - ); + .filter((farm) => (tab === "all" ? true : farm.status === tab)) + .filter((farm) => farm.name?.toLowerCase().includes(search.toLowerCase())); - const approveFarm = (id) => activateFarm(id); - const rejectFarm = (id) => deactivateFarm(id); return ( <> @@ -125,26 +106,18 @@ export function Farms() { - setTab("all")}> - Tất cả - - setTab("pending")}> - Chờ duyệt - - setTab("active")}> - Đang hoạt động - - setTab("inactive")}> - Đã khoá - + setTab("all")}>Tất cả + setTab("pending")}>Chờ duyệt + setTab("active")}>Đang hoạt động + setTab("inactive")}>Đã khoá - @@ -173,8 +146,11 @@ export function Farms() { {displayedFarms.map((farm) => ( - getFarmDetail(farm._id)}> + { setSelectedFarmId(farm._id); setOpenDetail(true); }} + > {farm.name} {farm.code} @@ -189,68 +165,70 @@ export function Farms() { size="sm" /> - -
- {/* Sửa */} - - - {/* Xoá */} - +
+ +
+ + +
- {/* Theo trạng thái */} - {farm.status === "pending" && ( - <> + +
+ {farm.status === "pending" && ( + <> + + + + )} + {farm.status === "active" && ( + )} + {farm.status === "inactive" && ( - - )} - - {farm.status === "inactive" && ( - - )} - - {farm.status === "active" && ( - - )} + )} +
+ ))} @@ -259,79 +237,7 @@ export function Farms() { )} - setOpenDetail(false)} size="lg" className="max-w-screen-md mx-auto"> - Chi tiết nông trại - - {selectedFarm ? ( -
- Tên: {selectedFarm.name} - Mã: {selectedFarm.code} - Chủ sở hữu: {selectedFarm.ownerInfo.name} - SĐT: {selectedFarm.phone} - Địa chỉ: {selectedFarm.location} - Diện tích: {selectedFarm.area} m² - Mô tả: {selectedFarm.description || "Không có"} - - {/* Dịch vụ */} -
- Dịch vụ -
- {[ - "direct_selling", - "feed_selling", - "custom_feed_blending", - "processing_service", - "storage_service", - "transport_service", - "other_services", - ]. map((service) => ( - - ))} -
-
- {/* Tính năng */} -
- Tính năng -
- {[ - "aquaponic_model", - "ras_ready", - "hydroponic", - "greenhouse", - "vertical_farming", - "viet_gap_cert", - "organic_cert", - "global_gap_cert", - "haccp_cert", - "camera_online", - "drone_monitoring", - "automated_pest_detection", - "precision_irrigation", - "auto_irrigation", - "soil_based_irrigation", - "iot_sensors", - "soil_moisture_monitoring", - "air_quality_sensor", - ].map((feature) => ( - - ))} -
-
-
- ) : ( - Đang tải chi tiết... - )} -
-
- + {/* Form thêm/sửa farm */} setOpenForm(false)} @@ -340,8 +246,14 @@ export function Farms() { editingFarm ? editFarm(editingFarm._id, data) : addFarm(data); }} /> + + {/* Chi tiết farm */} + setOpenDetail(false)} + /> ); } - -export default Farms \ No newline at end of file +export default Farms; From 108d15cf6cf6616553598c883a5cac60caf9220a Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Sun, 6 Jul 2025 18:32:02 +0700 Subject: [PATCH 056/248] thanh --- src/pages/dashboard/FarmPictures.jsx | 51 ---- src/pages/dashboard/{ => farm}/FarmDetail.jsx | 0 src/pages/dashboard/{ => farm}/FarmForm.jsx | 0 src/pages/dashboard/farm/FarmPictures.jsx | 222 ++++++++++++++++++ src/pages/dashboard/{ => farm}/farms.jsx | 4 +- src/pages/dashboard/index.js | 2 +- 6 files changed, 225 insertions(+), 54 deletions(-) delete mode 100644 src/pages/dashboard/FarmPictures.jsx rename src/pages/dashboard/{ => farm}/FarmDetail.jsx (100%) rename src/pages/dashboard/{ => farm}/FarmForm.jsx (100%) create mode 100644 src/pages/dashboard/farm/FarmPictures.jsx rename src/pages/dashboard/{ => farm}/farms.jsx (99%) diff --git a/src/pages/dashboard/FarmPictures.jsx b/src/pages/dashboard/FarmPictures.jsx deleted file mode 100644 index a2cfdf9d..00000000 --- a/src/pages/dashboard/FarmPictures.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { useState, useEffect } from "react"; -import axios from "axios"; -import { Typography } from "@material-tailwind/react"; - -const BASE_URL = "https://api-ndolv2.nongdanonline.vn"; -const getOpts = () => ({ - headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }, -}); - -export default function FarmPictures({ farmId }) { - const [pictures, setPictures] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - if (!farmId) return; - - const fetchPictures = async () => { - try { - const res = await axios.get(`${BASE_URL}/farm-pictures/${farmId}`, getOpts()); - setPictures(res.data); - } catch (err) { - if (err.response?.status === 404) { - // Không tìm thấy => farm chưa có ảnh - setPictures([]); - } else { - setError(err.response?.data?.message || err.message); - } - } finally { - setLoading(false); - } - }; - - fetchPictures(); - }, [farmId]); - - if (loading) return Đang tải ảnh...; - if (error) return Lỗi: {error}; - if (pictures.length === 0) return Farm này chưa có ảnh nào.; - - return ( -
- {pictures.map((pic) => ( -
- Farm - {pic.title || "Ảnh farm"} -
- ))} -
- ); -} diff --git a/src/pages/dashboard/FarmDetail.jsx b/src/pages/dashboard/farm/FarmDetail.jsx similarity index 100% rename from src/pages/dashboard/FarmDetail.jsx rename to src/pages/dashboard/farm/FarmDetail.jsx diff --git a/src/pages/dashboard/FarmForm.jsx b/src/pages/dashboard/farm/FarmForm.jsx similarity index 100% rename from src/pages/dashboard/FarmForm.jsx rename to src/pages/dashboard/farm/FarmForm.jsx diff --git a/src/pages/dashboard/farm/FarmPictures.jsx b/src/pages/dashboard/farm/FarmPictures.jsx new file mode 100644 index 00000000..03da0059 --- /dev/null +++ b/src/pages/dashboard/farm/FarmPictures.jsx @@ -0,0 +1,222 @@ +import React, { useState, useEffect } from "react"; +import axios from "axios"; +import { Button, Dialog, DialogBody, DialogFooter, DialogHeader, Input, Typography } from "@material-tailwind/react"; + +const BASE_URL = "https://api-ndolv2.nongdanonline.vn"; +const getOpts = () => ({ + headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }, +}); + +export default function FarmPictures({ farmId }) { + const [pictures, setPictures] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const [editingPic, setEditingPic] = useState(null) + const [editingDes, setEditingDes] = useState(''); + const [editingFile, setEditingFile] = useState(null); + const [confirmDelete, setConfirmDelete] = useState(null) + + const [adding, setAdding] = useState(false); + const [newImage, setNewImage] = useState(null); + const [newDes, setNewDes] = useState('') + useEffect(() => { + if (!farmId) return; + + const fetchPictures = async () => { + try { + const res = await axios.get(`${BASE_URL}/farm-pictures/${farmId}`, getOpts()); + const list = Array.isArray(res.data) ? res.data : res.data?.data || []; + setPictures(list); + console.log(pictures) + } catch (err) { + if (err.response?.status === 404) { + setPictures([]); + } else { + setError(err.response?.data?.message || err.message); + } + } finally { + setLoading(false); + } + }; + + fetchPictures(); + }, [farmId]); + + const handleAdd = async () => { + if(!newImage) { + alert("Vui lòng chọn file ảnh."); + return; + } + const formData = new FormData(); + formData.append("image", newImage); + formData.append("description", newDes); + + try { + await axios.post(`${BASE_URL}/farm-pictures/${farmId}`, formData, {... getOpts(), + headers: { + ... getOpts().headers, + "Content-Type" : "multipart/form-data", + }, + }); + alert("Thêm ảnh thành công"); + setAdding(false); + setNewImage(null); + setNewDes(""); + + const res = await axios.get(`${BASE_URL}/farm-pictures/${farmId}`, getOpts()); + const list = Array.isArray(res.data) ? res.data: res.data?.data || []; + setPictures(list); + } catch (err) { + alert("Lỗi thêm ảnh: " + (err.response?.data?.message || err.message)); + } + } + + const handleUpdate = async () => { + const formData = new FormData(); + formData.append("description", editingDes); + if (editingFile) { + formData.append("image", editingFile); // thêm file ảnh nếu chọn + } + + try { + await axios.put(`${BASE_URL}/farm-pictures/update/${editingPic._id}`, formData, { + ...getOpts(), + headers: { + ...getOpts().headers, + "Content-Type": "multipart/form-data", + }, + }); + + // Reload lại danh sách ảnh từ server để lấy ảnh mới + const res = await axios.get(`${BASE_URL}/farm-pictures/${farmId}`, getOpts()); + const list = Array.isArray(res.data) ? res.data : res.data?.data || []; + setPictures(list); + + alert("Cập nhật ảnh thành công!"); + setEditingPic(null); + setEditingFile(null); + } catch (err) { + alert("Lỗi cập nhật: " + (err.response?.data?.message || err.message)); + } +}; + + + const handleDelete = async (id) => { + try { + await axios.delete(`${BASE_URL}/farm-pictures/${id}`, getOpts()); + setPictures((prev) => prev.filter((pic) => pic._id !== id)); + alert("Xoá ảnh thành công!"); + setConfirmDelete(null); + } catch (err){ + alert("Lỗi xoá: " + (err.response?.data?.message || err.message)); + } + }; + + const getImgSrc = (p) => { + if(p.imageUrl) return `${BASE_URL}${p.imageUrl}`; + if (p.url) return p.url; + if (p.fileUrl) return p.fileUrl; + if (p.path) return `${BASE_URL}/${p.path}`; + return ""; + }; + + if (loading) return Đang tải ảnh...; + if (error) return Lỗi: {error}; + if (pictures.length === 0 && !adding) + return ( + <> + Farm này chưa có ảnh nào! + + + ); + + return ( + <> +
+ Ảnh của farm + +
+
+ {pictures.map((pic) => ( +
+ {pic.description} + {pic.description || "Ảnh farm"} +
+ + +
+
+ ))} +
+ + {editingPic && ( + setEditingPic(null)} size="sm"> + Sửa ảnh + + setEditingDes(e.target.value)}/> + setEditingFile(e.target.files[0])} /> + + + + + + + )} + + {confirmDelete && ( + setConfirmDelete(null)} size="xs"> + Xóa ảnh + + Bạn có chắc chắn muốn xóa ảnh này? Thao tác không thể hoàn tác. + + + + + + + )} + + {adding && ( + setAdding(false)} size="sm"> + Thêm ảnh mới + + setNewImage(e.target.files[0])} + /> + setNewDes(e.target.value)} + /> + + + + + + + )} + + ); +} diff --git a/src/pages/dashboard/farms.jsx b/src/pages/dashboard/farm/farms.jsx similarity index 99% rename from src/pages/dashboard/farms.jsx rename to src/pages/dashboard/farm/farms.jsx index 29155860..20e6011f 100644 --- a/src/pages/dashboard/farms.jsx +++ b/src/pages/dashboard/farm/farms.jsx @@ -195,7 +195,7 @@ export function Farms() { onClick={(e) => { e.stopPropagation(); activateFarm(farm._id, "duyệt"); }} className="flex gap-1" > - ✓ Duyệt + Duyệt )} diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index d650d0ab..b35d4b3e 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,4 +1,4 @@ export * from "@/pages/dashboard/home"; export * from "@/pages/dashboard/profile"; -export * from "@/pages/dashboard/farms"; +export * from "@/pages/dashboard/farm/farms"; export * from "@/pages/dashboard/notifications"; From 3eba2a37445238c366394dcd1b1e451c0e5168bc Mon Sep 17 00:00:00 2001 From: Tie902 Date: Sun, 6 Jul 2025 19:53:25 +0700 Subject: [PATCH 057/248] anhthemxoarole --- src/pages/dashboard/UserDetail.jsx | 69 ++++++++++ src/pages/dashboard/index.js | 8 +- src/pages/dashboard/users.jsx | 212 +++++++++++++++++++++-------- src/routes.jsx | 1 + 4 files changed, 227 insertions(+), 63 deletions(-) create mode 100644 src/pages/dashboard/UserDetail.jsx diff --git a/src/pages/dashboard/UserDetail.jsx b/src/pages/dashboard/UserDetail.jsx new file mode 100644 index 00000000..fba1ed6e --- /dev/null +++ b/src/pages/dashboard/UserDetail.jsx @@ -0,0 +1,69 @@ +import { useParams } from "react-router-dom"; +import { useEffect, useState } from "react"; +import axios from "axios"; +import { Card, CardHeader, CardBody, Typography, Spinner } from "@material-tailwind/react"; + +export default function UserDetail() { + const { id } = useParams(); + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const token = localStorage.getItem("token"); + if (!token) { + setError("Không tìm thấy access token!"); + setLoading(false); + return; + } + + axios.get(`https://api-ndolv2.nongdanonline.vn/admin-users/${id}`, { + headers: { Authorization: `Bearer ${token}` } + }) + .then(res => setUser(res.data)) + .catch(() => setError("Không thể tải chi tiết người dùng.")) + .finally(() => setLoading(false)); + }, [id]); + + if (loading) return ( +
+ +
+ ); + + if (error) return ( + {error} + ); + + if (!user) return Không có dữ liệu người dùng.; + + return ( +
+ + + {user.avatar ? ( + {user.fullName} + ) : ( +
+ No Avatar +
+ )} +
+ + {user.fullName} + Email: {user.email} + Phone: {user.phone || "N/A"} + Role: {Array.isArray(user.role) ? user.role.join(", ") : user.role} + Active: {user.isActive ? "Yes" : "No"} + Note: {user.note || "N/A"} + Created At: {new Date(user.createdAt).toLocaleString()} + Last Login: {user.lastLogin ? new Date(user.lastLogin).toLocaleString() : "N/A"} + +
+
+ ); +} diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index 5f29542d..f41d3c8c 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,4 +1,4 @@ -export * from "@/pages/dashboard/home"; -export * from "@/pages/dashboard/users"; -export * from "@/pages/dashboard/tables"; -export * from "@/pages/dashboard/AcceptFarm"; +export { default as Home } from "./home"; +export { default as Users } from "./users"; +export { default as Tables } from "./tables"; +export { default as AcceptFarm } from "./AcceptFarm"; diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index 18cd803a..9de13dca 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -2,44 +2,47 @@ import React, { useEffect, useState } from "react"; import axios from "axios"; import { Card, CardHeader, CardBody, CardFooter, - Typography, Button, Tooltip, Dialog, DialogHeader, - DialogBody, DialogFooter, Input, Select, Option + Typography, Button, Dialog, DialogHeader, + DialogBody, DialogFooter, Input, Select, Option, Spinner } from "@material-tailwind/react"; -import { Link } from "react-router-dom"; -export function Users() { +export default function Users() { const [users, setUsers] = useState([]); + const [roles, setRoles] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - - - const [editOpen, setEditOpen] = useState(false); const [selectedUser, setSelectedUser] = useState(null); - const [formData, setFormData] = useState({ fullName: '', email: '', phone: '', role: 'customer' }); - const [roles, setRoles] = useState([]); + const [formData, setFormData] = useState({ fullName: '', email: '', phone: '', role: '' }); + const [viewOpen, setViewOpen] = useState(false); + const [viewUser, setViewUser] = useState(null); + const [selectedRoles, setSelectedRoles] = useState({}); - const getAccessToken = () => localStorage.getItem("accessToken"); + const token = localStorage.getItem("token"); useEffect(() => { - const token = getAccessToken(); if (!token) { setError("Không tìm thấy access token!"); setLoading(false); return; } + axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { - headers: { Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MzQzMDUsImV4cCI6MTc1MTg5MzUwNX0.qfl2smCZ35xZ49gwqgx8NwlkolEY2NV9hohMmG0M8do` } + headers: { Authorization: `Bearer ${token}` } }) - .then(res => setUsers(Array.isArray(res.data) ? res.data : [])) - .catch(() => setError("Lỗi khi tải danh sách người dùng.")) - .finally(() => setLoading(false)); + .then(res => setUsers(Array.isArray(res.data) ? res.data : [])) + .catch(() => setError("Lỗi khi tải danh sách người dùng.")) + .finally(() => setLoading(false)); + }, [token]); + + useEffect(() => { + if (!token) return; axios.get("https://api-ndolv2.nongdanonline.vn/admin-users/roles", { - headers: { Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MzQzMDUsImV4cCI6MTc1MTg5MzUwNX0.qfl2smCZ35xZ49gwqgx8NwlkolEY2NV9hohMmG0M8do` } + headers: { Authorization: `Bearer ${token}` } }) - .then(res => setRoles(Array.isArray(res.data) ? res.data : [])) - .catch(() => setRoles(["customer", "admin", "farmer"])); + .then(res => setRoles(Array.isArray(res.data) ? res.data : [])) + .catch(() => setRoles(["customer", "admin", "farmer", "staff"])); }, []); const openEdit = (user) => { @@ -53,70 +56,139 @@ export function Users() { setEditOpen(true); }; + const handleView = (user) => { + setViewUser(user); + setViewOpen(true); + }; + const handleUpdate = () => { - const token = getAccessToken(); if (!token || !selectedUser) return; axios.put(`https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}`, formData, { - headers: { - Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MzQzMDUsImV4cCI6MTc1MTg5MzUwNX0.qfl2smCZ35xZ49gwqgx8NwlkolEY2NV9hohMmG0M8do`, - "Content-Type": "application/json", - }, - }) - .then(() => { - alert("Cập nhật thành công!"); - setUsers(users.map(u => u.id === selectedUser.id ? { ...u, ...formData } : u)); - setEditOpen(false); + headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, }) - .catch(() => alert("Cập nhật thất bại!")); + .then(() => { + alert("Cập nhật thành công!"); + setUsers(users.map(u => u.id === selectedUser.id ? { ...u, ...formData } : u)); + setEditOpen(false); + }) + .catch(() => alert("Cập nhật thất bại!")); }; const handleDelete = (userId) => { - const token = getAccessToken(); if (!token) return; if (!window.confirm("Bạn có chắc muốn xoá người dùng này?")) return; axios.delete(`https://api-ndolv2.nongdanonline.vn/admin-users/${userId}`, { - headers: { Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4NjU0YTMxNjRmMTRjZWVlN2JlNTRjYSIsInJvbGUiOlsiQ3VzdG9tZXIiLCJBZG1pbiJdLCJpYXQiOjE3NTE2MzQzMDUsImV4cCI6MTc1MTg5MzUwNX0.qfl2smCZ35xZ49gwqgx8NwlkolEY2NV9hohMmG0M8do` }, + headers: { Authorization: `Bearer ${token}` }, + }) + .then(() => { + alert("Xoá người dùng thành công!"); + setUsers(users.filter(user => user.id !== userId)); + }) + .catch(() => alert("Xoá thất bại!")); + }; + + const handleAddRole = (userId, role) => { + if (!token) return; + axios.patch(`https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/add-role`, { + role + }, { + headers: { Authorization: `Bearer ${token}` } + }) + .then(() => { + setUsers(users.map(u => { + if (u.id === userId && !u.role.includes(role)) { + return { ...u, role: [...u.role, role] }; + } + return u; + })); + }) + .catch(() => alert("Thêm vai trò thất bại")); + }; + + const handleRemoveRole = (userId, role) => { + if (!token) return; + axios.patch(`https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/remove-role`, { + role + }, { + headers: { Authorization: `Bearer ${token}` } }) .then(() => { - alert("Xoá người dùng thành công!"); - setUsers(users.filter(user => user.id !== userId)); + setUsers(users.map(u => { + if (u.id === userId) { + return { ...u, role: u.role.filter(r => r !== role) }; + } + return u; + })); }) - .catch(() => alert("Xoá thất bại!")); + .catch(() => alert("Xoá vai trò thất bại")); }; return (
- - Users Management - - {loading &&

Đang tải...

} + Users Management + {loading && ( +
+ +
+ )} {error &&

{error}

}
{users.map((user) => ( - - - {user.avatar && {user.fullName}} - - - - {user.fullName} - - Email: {user.email} - Phone: {user.phone || "N/A"} - Role: {Array.isArray(user.role) ? user.role.join(", ") : user.role} - Active: {user.isActive ? "Yes" : "No"} - - - - - - +
+ + + {user.avatar ? ( + {user.fullName} + ) : ( +
+ No Avatar +
+ )} +
+ + {user.fullName} + Email: {user.email} + Phone: {user.phone || "N/A"} + Role: {Array.isArray(user.role) ? user.role.join(", ") : user.role} + Active: {user.isActive ? "Yes" : "No"} + + +
+ + +
+
+ + +
+ {Array.isArray(user.role) && user.role.map(r => ( +
+ {r} + +
+ ))} +
+
+
))}
+ {/* Dialog chỉnh sửa */} Chỉnh sửa người dùng @@ -125,7 +197,9 @@ export function Users() { setFormData({ ...formData, email: e.target.value })} /> setFormData({ ...formData, phone: e.target.value })} />
@@ -134,8 +208,28 @@ export function Users() { + + {/* Dialog xem chi tiết */} + + Thông tin chi tiết + + {viewUser && ( +
+ {viewUser.fullName} + Email: {viewUser.email} + Phone: {viewUser.phone || "N/A"} + Roles: {Array.isArray(viewUser.role) ? viewUser.role.join(", ") : viewUser.role} + Active: {viewUser.isActive ? "Yes" : "No"} + ID: {viewUser.id} + Created At: {new Date(viewUser.createdAt).toLocaleString()} + Last Login: {viewUser.lastLogin ? new Date(viewUser.lastLogin).toLocaleString() : "N/A"} +
+ )} +
+ + + +
); } - -export default Users; diff --git a/src/routes.jsx b/src/routes.jsx index b48c72e6..5eca4a67 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -7,6 +7,7 @@ import { RectangleStackIcon, } from "@heroicons/react/24/solid"; import { Home, Users, Tables, AcceptFarm } from "@/pages/dashboard"; +import UserDetail from "@/pages/dashboard/UserDetail"; import { SignIn, SignUp } from "@/pages/auth"; From 81435f46e70abde62253513bb99420fcc591ef3c Mon Sep 17 00:00:00 2001 From: Tie902 Date: Sun, 6 Jul 2025 19:57:19 +0700 Subject: [PATCH 058/248] themxoarolelayanh --- src/pages/dashboard/users.jsx | 210 +++++++++++++++++++++++++--------- 1 file changed, 153 insertions(+), 57 deletions(-) diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index b866948c..9de13dca 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -2,42 +2,47 @@ import React, { useEffect, useState } from "react"; import axios from "axios"; import { Card, CardHeader, CardBody, CardFooter, - Typography, Button, Tooltip, Dialog, DialogHeader, - DialogBody, DialogFooter, Input, Select, Option + Typography, Button, Dialog, DialogHeader, + DialogBody, DialogFooter, Input, Select, Option, Spinner } from "@material-tailwind/react"; -import { Link } from "react-router-dom"; -export function Users() { +export default function Users() { const [users, setUsers] = useState([]); + const [roles, setRoles] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const [editOpen, setEditOpen] = useState(false); const [selectedUser, setSelectedUser] = useState(null); - const [formData, setFormData] = useState({ fullName: '', email: '', phone: '', role: 'customer' }); - const [roles, setRoles] = useState([]); + const [formData, setFormData] = useState({ fullName: '', email: '', phone: '', role: '' }); + const [viewOpen, setViewOpen] = useState(false); + const [viewUser, setViewUser] = useState(null); + const [selectedRoles, setSelectedRoles] = useState({}); - const getAccessToken = () => localStorage.getItem("token"); + const token = localStorage.getItem("token"); useEffect(() => { - const token = getAccessToken(); if (!token) { setError("Không tìm thấy access token!"); setLoading(false); return; } + axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { - headers: { Authorization: `Bearer ${localStorage.getItem("token")}` } + headers: { Authorization: `Bearer ${token}` } }) - .then(res => setUsers(Array.isArray(res.data) ? res.data : [])) - .catch(() => setError("Lỗi khi tải danh sách người dùng.")) - .finally(() => setLoading(false)); + .then(res => setUsers(Array.isArray(res.data) ? res.data : [])) + .catch(() => setError("Lỗi khi tải danh sách người dùng.")) + .finally(() => setLoading(false)); + }, [token]); + + useEffect(() => { + if (!token) return; axios.get("https://api-ndolv2.nongdanonline.vn/admin-users/roles", { - headers: { Authorization: `Bearer ${localStorage.getItem("token")}` } + headers: { Authorization: `Bearer ${token}` } }) - .then(res => setRoles(Array.isArray(res.data) ? res.data : [])) - .catch(() => setRoles(["customer", "admin", "farmer"])); + .then(res => setRoles(Array.isArray(res.data) ? res.data : [])) + .catch(() => setRoles(["customer", "admin", "farmer", "staff"])); }, []); const openEdit = (user) => { @@ -51,70 +56,139 @@ export function Users() { setEditOpen(true); }; + const handleView = (user) => { + setViewUser(user); + setViewOpen(true); + }; + const handleUpdate = () => { - const token = getAccessToken(); if (!token || !selectedUser) return; axios.put(`https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}`, formData, { - headers: { - Authorization: `Bearer ${localStorage.getItem("token")}`, - "Content-Type": "application/json", - }, - }) - .then(() => { - alert("Cập nhật thành công!"); - setUsers(users.map(u => u.id === selectedUser.id ? { ...u, ...formData } : u)); - setEditOpen(false); + headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, }) - .catch(() => alert("Cập nhật thất bại!")); + .then(() => { + alert("Cập nhật thành công!"); + setUsers(users.map(u => u.id === selectedUser.id ? { ...u, ...formData } : u)); + setEditOpen(false); + }) + .catch(() => alert("Cập nhật thất bại!")); }; const handleDelete = (userId) => { - const token = getAccessToken(); if (!token) return; if (!window.confirm("Bạn có chắc muốn xoá người dùng này?")) return; axios.delete(`https://api-ndolv2.nongdanonline.vn/admin-users/${userId}`, { - headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }, + headers: { Authorization: `Bearer ${token}` }, + }) + .then(() => { + alert("Xoá người dùng thành công!"); + setUsers(users.filter(user => user.id !== userId)); + }) + .catch(() => alert("Xoá thất bại!")); + }; + + const handleAddRole = (userId, role) => { + if (!token) return; + axios.patch(`https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/add-role`, { + role + }, { + headers: { Authorization: `Bearer ${token}` } }) .then(() => { - alert("Xoá người dùng thành công!"); - setUsers(users.filter(user => user.id !== userId)); + setUsers(users.map(u => { + if (u.id === userId && !u.role.includes(role)) { + return { ...u, role: [...u.role, role] }; + } + return u; + })); }) - .catch(() => alert("Xoá thất bại!")); + .catch(() => alert("Thêm vai trò thất bại")); + }; + + const handleRemoveRole = (userId, role) => { + if (!token) return; + axios.patch(`https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/remove-role`, { + role + }, { + headers: { Authorization: `Bearer ${token}` } + }) + .then(() => { + setUsers(users.map(u => { + if (u.id === userId) { + return { ...u, role: u.role.filter(r => r !== role) }; + } + return u; + })); + }) + .catch(() => alert("Xoá vai trò thất bại")); }; return (
- - Users Management - - {loading &&

Đang tải...

} + Users Management + {loading && ( +
+ +
+ )} {error &&

{error}

}
{users.map((user) => ( - - - {user.avatar && {user.fullName}} - - - - {user.fullName} - - Email: {user.email} - Phone: {user.phone || "N/A"} - Role: {Array.isArray(user.role) ? user.role.join(", ") : user.role} - Active: {user.isActive ? "Yes" : "No"} - - - - - - +
+ + + {user.avatar ? ( + {user.fullName} + ) : ( +
+ No Avatar +
+ )} +
+ + {user.fullName} + Email: {user.email} + Phone: {user.phone || "N/A"} + Role: {Array.isArray(user.role) ? user.role.join(", ") : user.role} + Active: {user.isActive ? "Yes" : "No"} + + +
+ + +
+
+ + +
+ {Array.isArray(user.role) && user.role.map(r => ( +
+ {r} + +
+ ))} +
+
+
))}
+ {/* Dialog chỉnh sửa */} Chỉnh sửa người dùng @@ -123,7 +197,9 @@ export function Users() { setFormData({ ...formData, email: e.target.value })} /> setFormData({ ...formData, phone: e.target.value })} />
@@ -132,8 +208,28 @@ export function Users() { + + {/* Dialog xem chi tiết */} + + Thông tin chi tiết + + {viewUser && ( +
+ {viewUser.fullName} + Email: {viewUser.email} + Phone: {viewUser.phone || "N/A"} + Roles: {Array.isArray(viewUser.role) ? viewUser.role.join(", ") : viewUser.role} + Active: {viewUser.isActive ? "Yes" : "No"} + ID: {viewUser.id} + Created At: {new Date(viewUser.createdAt).toLocaleString()} + Last Login: {viewUser.lastLogin ? new Date(viewUser.lastLogin).toLocaleString() : "N/A"} +
+ )} +
+ + + +
); } - -export default Users; From deedf3b338774ff6941b0b0af6f41aea15e61698 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Sun, 6 Jul 2025 19:59:33 +0700 Subject: [PATCH 059/248] themxoaanh --- src/pages/dashboard/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index 230ec3c5..5258084a 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,5 +1,5 @@ -export * from "@/pages/dashboard/home"; -export * from "@/pages/dashboard/users"; -export * from "@/pages/dashboard/farms"; -export * from "@/pages/dashboard/Questions/Questions"; +export { default as Home } from "./home"; +export { default as Users } from "./users"; +export { default as Tables } from "./tables"; +export { default as AcceptFarm } from "./AcceptFarm";; From 50919e34c416f5fe6a8b418329c0afc438675da7 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Sun, 6 Jul 2025 20:40:19 +0700 Subject: [PATCH 060/248] thanh --- src/pages/dashboard/farm/FarmForm.jsx | 126 ++++++++++---------------- src/pages/dashboard/farm/farms.jsx | 6 +- 2 files changed, 54 insertions(+), 78 deletions(-) diff --git a/src/pages/dashboard/farm/FarmForm.jsx b/src/pages/dashboard/farm/FarmForm.jsx index 5fc69bb9..e91dee1c 100644 --- a/src/pages/dashboard/farm/FarmForm.jsx +++ b/src/pages/dashboard/farm/FarmForm.jsx @@ -6,107 +6,79 @@ import { DialogFooter, Input, Button, + Checkbox, } from "@material-tailwind/react"; + const FarmForm = ({ open, onClose, initialData, onSubmit }) => { - const [formData, setFormData] = useState({ + const [form, setForm] = useState({ name: "", code: "", location: "", - area: "", - pricePerMonth: "", + area: 0, + cultivatedArea: 0, + services: [], + features: [], + tags: [], + phone: "", + zalo: "", + province: "", + district: "", + ward: "", + street: "", + isAvailable: true, status: "pending", - ownerName: "", - ownerPhone: "", + ownerId: "", }); useEffect(() => { if (initialData) { - setFormData({ - name: initialData.name || "", - code: initialData.code || "", - location: initialData.location || "", - area: initialData.area?.toString() || "", - pricePerMonth: initialData.pricePerMonth?.toString() || "", - status: initialData.status || "pending", - ownerName: initialData.ownerInfo?.name || "", - ownerPhone: initialData.ownerInfo?.phone || "", - }); - } else { - setFormData({ - name: "", - code: "", - location: "", - area: "", - pricePerMonth: "", - status: "pending", - ownerName: "", - ownerPhone: "", - }); + setForm(initialData); } }, [initialData]); - const handleChange = (e) => { - const { name, value } = e.target; - setFormData((prev) => ({ - ...prev, - [name]: value, - })); + const handleChange = (field, value) => { + setForm((prev) => ({ ...prev, [field]: value})); }; - const handleSubmit = () => { - if (!formData.name || !formData.code) { - return alert("Vui lòng nhập đầy đủ thông tin."); - } - - const parsedData = { - name: formData.name, - code: formData.code, - location: formData.location, - area: formData.area === "" ? 0 : parseFloat(formData.area), - pricePerMonth: - formData.pricePerMonth === "" ? 0 : parseFloat(formData.pricePerMonth), - status: formData.status || "pending", - - ownerInfo: { - name: formData.ownerName || "Chưa rõ", - phone: formData.ownerPhone || "", - email: "", // Optional, bạn có thể thêm input nếu muốn - }, - coordinates: { - lat: 0, - lng: 0, - }, - features: [], - phone: "", - zalo: "", - operationTime: "", - defaultImage: "", - services: [], - }; - - console.log("parsedData gửi về:", parsedData); + const handleArrayChange = (filed, value) => { + setForm((prev) => ({ + ... prev, [filed]:value.split(",").map((s) => s.trim()), + })); + }; - onSubmit(parsedData); + const handleSubmit = (e) => { + e.preventDefault(); + onSubmit(form); onClose(); - }; + + } return ( - + {initialData ? "Chỉnh sửa" : "Thêm"} nông trại - - - - - - - - + + handleChange ("code", e.target.value)} /> + handleChange ("name", e.target.value)} /> + handleChange ("location", e.target.value)} /> + handleChange ("area", e.target.value)} /> + handleChange ("cultivatedArea", e.target.value)} /> + handleArrayChange ("services", e.target.value)} /> + handleArrayChange ("features", e.target.value)} /> + handleArrayChange ("tags", e.target.value)} /> + handleChange('phone', e.target.value)} /> + handleChange('zalo', e.target.value)} /> + handleChange("province", e.target.value)} /> + handleChange("district", e.target.value)} /> + handleChange("ward", e.target.value)} /> + handleChange("street", e.target.value)} /> + handleChange("ownerId", e.target.value)} /> + handleChange("isAvailable", e.target.checked)} /> - diff --git a/src/pages/dashboard/farm/farms.jsx b/src/pages/dashboard/farm/farms.jsx index 20e6011f..e843eb9f 100644 --- a/src/pages/dashboard/farm/farms.jsx +++ b/src/pages/dashboard/farm/farms.jsx @@ -243,7 +243,11 @@ export function Farms() { onClose={() => setOpenForm(false)} initialData={editingFarm} onSubmit={(data) => { - editingFarm ? editFarm(editingFarm._id, data) : addFarm(data); + if (editingFarm) { + editingFarm(editingFarm._id, data); + } else { + addFarm(data); + } }} /> From 078097cae3a0c5bef0a8e6773dc08b274798b7ab Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Sun, 6 Jul 2025 20:44:08 +0700 Subject: [PATCH 061/248] Update users.jsx --- src/pages/dashboard/users.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index 9de13dca..3de42a04 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -6,7 +6,7 @@ import { DialogBody, DialogFooter, Input, Select, Option, Spinner } from "@material-tailwind/react"; -export default function Users() { +export function Users() { const [users, setUsers] = useState([]); const [roles, setRoles] = useState([]); const [loading, setLoading] = useState(true); @@ -233,3 +233,5 @@ export default function Users() { ); } + +export default Users; \ No newline at end of file From 92cd8de00926dceaae26aea107498d57564f96a8 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Sun, 6 Jul 2025 20:48:54 +0700 Subject: [PATCH 062/248] thanh --- src/pages/dashboard/index.js | 8 ++++---- src/routes.jsx | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index 5258084a..eadde32b 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,5 +1,5 @@ -export { default as Home } from "./home"; -export { default as Users } from "./users"; -export { default as Tables } from "./tables"; -export { default as AcceptFarm } from "./AcceptFarm";; +export * from "@/pages/dashboard/home"; +export * from "@/pages/dashboard/users"; +export * from "@/pages/dashboard/farms"; +export * from "@/pages/dashboard/questions";; diff --git a/src/routes.jsx b/src/routes.jsx index ceaaf6cd..0d32dce9 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -7,7 +7,7 @@ import { RectangleStackIcon, } from "@heroicons/react/24/solid"; -import { Home, Users , Farms,Questions } from "@/pages/dashboard"; +import { Home, Users , Farms, Questions } from "@/pages/dashboard"; import { SignIn, SignUp } from "@/pages/auth"; From 0636f4f163ab5065dbbae86defac68882c04f30c Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Sun, 6 Jul 2025 21:04:24 +0700 Subject: [PATCH 063/248] thanh --- src/pages/dashboard/index.js | 2 +- src/pages/dashboard/users.jsx | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index eadde32b..60593f42 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,5 +1,5 @@ export * from "@/pages/dashboard/home"; export * from "@/pages/dashboard/users"; export * from "@/pages/dashboard/farms"; -export * from "@/pages/dashboard/questions";; +export * from "@/pages/dashboard/Questions/Questions"; diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index 3de42a04..d56fa376 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -92,7 +92,7 @@ export function Users() { const handleAddRole = (userId, role) => { if (!token) return; axios.patch(`https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/add-role`, { - role + role : role.trim() }, { headers: { Authorization: `Bearer ${token}` } }) @@ -189,7 +189,7 @@ export function Users() { {/* Dialog chỉnh sửa */} - + setEditOpen(false)} size="sm"> Chỉnh sửa người dùng
@@ -213,7 +213,7 @@ export function Users() { Thông tin chi tiết - {viewUser && ( + {viewUser ? (
{viewUser.fullName} Email: {viewUser.email} @@ -224,6 +224,8 @@ export function Users() { Created At: {new Date(viewUser.createdAt).toLocaleString()} Last Login: {viewUser.lastLogin ? new Date(viewUser.lastLogin).toLocaleString() : "N/A"}
+ ) : ( + Không có dữ liệu )}
From c679deb6542ec2387d8ebd4683d38d8493ef4f96 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Sun, 6 Jul 2025 22:17:49 +0700 Subject: [PATCH 064/248] usersaddress --- src/pages/dashboard/users.jsx | 107 +++++++++++++++++----------------- 1 file changed, 52 insertions(+), 55 deletions(-) diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index 9de13dca..aa86c72a 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -11,12 +11,16 @@ export default function Users() { const [roles, setRoles] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const [editOpen, setEditOpen] = useState(false); const [selectedUser, setSelectedUser] = useState(null); const [formData, setFormData] = useState({ fullName: '', email: '', phone: '', role: '' }); + const [viewOpen, setViewOpen] = useState(false); const [viewUser, setViewUser] = useState(null); - const [selectedRoles, setSelectedRoles] = useState({}); + const [addresses, setAddresses] = useState([]); + + const [selectedRole, setSelectedRole] = useState("farmer"); const token = localStorage.getItem("token"); @@ -42,7 +46,7 @@ export default function Users() { headers: { Authorization: `Bearer ${token}` } }) .then(res => setRoles(Array.isArray(res.data) ? res.data : [])) - .catch(() => setRoles(["customer", "admin", "farmer", "staff"])); + .catch(() => setRoles(["customer", "admin", "farmer"])); }, []); const openEdit = (user) => { @@ -56,9 +60,18 @@ export default function Users() { setEditOpen(true); }; - const handleView = (user) => { + const handleView = async (user) => { setViewUser(user); setViewOpen(true); + try { + const res = await axios.get("https://api-ndolv2.nongdanonline.vn/user-addresses", { + headers: { Authorization: `Bearer ${token}` } + }); + const userAddresses = res.data.filter(addr => addr.userid === user.id); + setAddresses(userAddresses); + } catch (err) { + setAddresses([]); + } }; const handleUpdate = () => { @@ -89,40 +102,30 @@ export default function Users() { .catch(() => alert("Xoá thất bại!")); }; - const handleAddRole = (userId, role) => { - if (!token) return; - axios.patch(`https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/add-role`, { - role - }, { - headers: { Authorization: `Bearer ${token}` } - }) - .then(() => { - setUsers(users.map(u => { - if (u.id === userId && !u.role.includes(role)) { - return { ...u, role: [...u.role, role] }; - } - return u; - })); - }) - .catch(() => alert("Thêm vai trò thất bại")); + const handleAddRole = async (userId, role) => { + try { + await axios.patch( + `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/add-roles`, + { roles: [role] }, + { headers: { Authorization: `Bearer ${token}` } } + ); + alert("Đã thêm role thành công!"); + } catch (e) { + alert("Không thể thêm role!"); + } }; - const handleRemoveRole = (userId, role) => { - if (!token) return; - axios.patch(`https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/remove-role`, { - role - }, { - headers: { Authorization: `Bearer ${token}` } - }) - .then(() => { - setUsers(users.map(u => { - if (u.id === userId) { - return { ...u, role: u.role.filter(r => r !== role) }; - } - return u; - })); - }) - .catch(() => alert("Xoá vai trò thất bại")); + const handleRemoveRole = async (userId, role) => { + try { + await axios.patch( + `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/remove-roles`, + { roles: [role] }, + { headers: { Authorization: `Bearer ${token}` } } + ); + alert("Đã xoá role thành công!"); + } catch (e) { + alert("Không thể xoá role!"); + } }; return ( @@ -159,36 +162,25 @@ export default function Users() { Role: {Array.isArray(user.role) ? user.role.join(", ") : user.role} Active: {user.isActive ? "Yes" : "No"} - -
+ +
+
-
- + {roles.map(role => )} - + +
- {Array.isArray(user.role) && user.role.map(r => ( -
- {r} - -
- ))}
))}
- {/* Dialog chỉnh sửa */} Chỉnh sửa người dùng @@ -209,7 +201,6 @@ export default function Users() { - {/* Dialog xem chi tiết */} Thông tin chi tiết @@ -223,6 +214,12 @@ export default function Users() { ID: {viewUser.id} Created At: {new Date(viewUser.createdAt).toLocaleString()} Last Login: {viewUser.lastLogin ? new Date(viewUser.lastLogin).toLocaleString() : "N/A"} + Addresses: + {addresses.length > 0 ? addresses.map((addr, i) => ( +
+ {addr.address} - {addr.ward}, {addr.district}, {addr.province} +
+ )) : Không có địa chỉ} )}
From 142a2adb70749d58e06f0e5e9b92629cc16e56db Mon Sep 17 00:00:00 2001 From: Tie902 Date: Sun, 6 Jul 2025 22:19:33 +0700 Subject: [PATCH 065/248] usersadress --- src/pages/dashboard/users.jsx | 117 ++++++++++++++++------------------ 1 file changed, 55 insertions(+), 62 deletions(-) diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index d56fa376..aa86c72a 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -6,17 +6,21 @@ import { DialogBody, DialogFooter, Input, Select, Option, Spinner } from "@material-tailwind/react"; -export function Users() { +export default function Users() { const [users, setUsers] = useState([]); const [roles, setRoles] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const [editOpen, setEditOpen] = useState(false); const [selectedUser, setSelectedUser] = useState(null); const [formData, setFormData] = useState({ fullName: '', email: '', phone: '', role: '' }); + const [viewOpen, setViewOpen] = useState(false); const [viewUser, setViewUser] = useState(null); - const [selectedRoles, setSelectedRoles] = useState({}); + const [addresses, setAddresses] = useState([]); + + const [selectedRole, setSelectedRole] = useState("farmer"); const token = localStorage.getItem("token"); @@ -42,7 +46,7 @@ export function Users() { headers: { Authorization: `Bearer ${token}` } }) .then(res => setRoles(Array.isArray(res.data) ? res.data : [])) - .catch(() => setRoles(["customer", "admin", "farmer", "staff"])); + .catch(() => setRoles(["customer", "admin", "farmer"])); }, []); const openEdit = (user) => { @@ -56,9 +60,18 @@ export function Users() { setEditOpen(true); }; - const handleView = (user) => { + const handleView = async (user) => { setViewUser(user); setViewOpen(true); + try { + const res = await axios.get("https://api-ndolv2.nongdanonline.vn/user-addresses", { + headers: { Authorization: `Bearer ${token}` } + }); + const userAddresses = res.data.filter(addr => addr.userid === user.id); + setAddresses(userAddresses); + } catch (err) { + setAddresses([]); + } }; const handleUpdate = () => { @@ -89,40 +102,30 @@ export function Users() { .catch(() => alert("Xoá thất bại!")); }; - const handleAddRole = (userId, role) => { - if (!token) return; - axios.patch(`https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/add-role`, { - role : role.trim() - }, { - headers: { Authorization: `Bearer ${token}` } - }) - .then(() => { - setUsers(users.map(u => { - if (u.id === userId && !u.role.includes(role)) { - return { ...u, role: [...u.role, role] }; - } - return u; - })); - }) - .catch(() => alert("Thêm vai trò thất bại")); + const handleAddRole = async (userId, role) => { + try { + await axios.patch( + `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/add-roles`, + { roles: [role] }, + { headers: { Authorization: `Bearer ${token}` } } + ); + alert("Đã thêm role thành công!"); + } catch (e) { + alert("Không thể thêm role!"); + } }; - const handleRemoveRole = (userId, role) => { - if (!token) return; - axios.patch(`https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/remove-role`, { - role - }, { - headers: { Authorization: `Bearer ${token}` } - }) - .then(() => { - setUsers(users.map(u => { - if (u.id === userId) { - return { ...u, role: u.role.filter(r => r !== role) }; - } - return u; - })); - }) - .catch(() => alert("Xoá vai trò thất bại")); + const handleRemoveRole = async (userId, role) => { + try { + await axios.patch( + `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/remove-roles`, + { roles: [role] }, + { headers: { Authorization: `Bearer ${token}` } } + ); + alert("Đã xoá role thành công!"); + } catch (e) { + alert("Không thể xoá role!"); + } }; return ( @@ -159,37 +162,26 @@ export function Users() { Role: {Array.isArray(user.role) ? user.role.join(", ") : user.role} Active: {user.isActive ? "Yes" : "No"} - -
+ +
+
-
- + {roles.map(role => )} - + +
- {Array.isArray(user.role) && user.role.map(r => ( -
- {r} - -
- ))}
))} - {/* Dialog chỉnh sửa */} - setEditOpen(false)} size="sm"> + Chỉnh sửa người dùng
@@ -209,11 +201,10 @@ export function Users() {
- {/* Dialog xem chi tiết */} Thông tin chi tiết - {viewUser ? ( + {viewUser && (
{viewUser.fullName} Email: {viewUser.email} @@ -223,9 +214,13 @@ export function Users() { ID: {viewUser.id} Created At: {new Date(viewUser.createdAt).toLocaleString()} Last Login: {viewUser.lastLogin ? new Date(viewUser.lastLogin).toLocaleString() : "N/A"} + Addresses: + {addresses.length > 0 ? addresses.map((addr, i) => ( +
+ {addr.address} - {addr.ward}, {addr.district}, {addr.province} +
+ )) : Không có địa chỉ}
- ) : ( - Không có dữ liệu )}
@@ -235,5 +230,3 @@ export function Users() { ); } - -export default Users; \ No newline at end of file From 3e9baae7d9e70421a527f458aa339107c6523b02 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Mon, 7 Jul 2025 15:42:15 +0700 Subject: [PATCH 066/248] thanh --- src/layouts/auth.jsx | 4 --- src/pages/dashboard/index.js | 9 ++--- src/pages/dashboard/users.jsx | 64 ++++++++++++++++++++--------------- src/routes.jsx | 12 ++----- 4 files changed, 40 insertions(+), 49 deletions(-) diff --git a/src/layouts/auth.jsx b/src/layouts/auth.jsx index 4267c704..7810f8c8 100644 --- a/src/layouts/auth.jsx +++ b/src/layouts/auth.jsx @@ -16,11 +16,7 @@ export function Auth() { icon: ChartPieIcon, }, { -<<<<<<< HEAD name: "User", -======= - name: "Users", ->>>>>>> tien path: "/dashboard/home", icon: UserIcon, }, diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index 73304f4b..4bd4a1dc 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,12 +1,7 @@ -<<<<<<< HEAD + export * from "@/pages/dashboard/home"; export * from "@/pages/dashboard/users"; export * from "@/pages/dashboard/farms"; export * from "@/pages/dashboard/Questions/Questions"; -======= -export { default as Home } from "./home"; -export { default as Users } from "./users"; -export { default as Tables } from "./tables"; -export { default as AcceptFarm } from "./AcceptFarm"; ->>>>>>> tien + diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index aa86c72a..80e382b5 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -6,7 +6,7 @@ import { DialogBody, DialogFooter, Input, Select, Option, Spinner } from "@material-tailwind/react"; -export default function Users() { +export function Users() { const [users, setUsers] = useState([]); const [roles, setRoles] = useState([]); const [loading, setLoading] = useState(true); @@ -24,30 +24,32 @@ export default function Users() { const token = localStorage.getItem("token"); - useEffect(() => { - if (!token) { - setError("Không tìm thấy access token!"); + const fetchUsers = async () => { + setLoading(true); + try { + const res = await axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { + headers: { Authorization: `Bearer ${token}` } + }); + setUsers(Array.isArray(res.data) ? res.data : []); + } catch (err) { + setError("Lỗi khi tải danh sách người dùng."); + } finally { setLoading(false); - return; } - - axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { - headers: { Authorization: `Bearer ${token}` } - }) - .then(res => setUsers(Array.isArray(res.data) ? res.data : [])) - .catch(() => setError("Lỗi khi tải danh sách người dùng.")) - .finally(() => setLoading(false)); - }, [token]); - + }; + useEffect(() => { - if (!token) return; - - axios.get("https://api-ndolv2.nongdanonline.vn/admin-users/roles", { - headers: { Authorization: `Bearer ${token}` } - }) - .then(res => setRoles(Array.isArray(res.data) ? res.data : [])) - .catch(() => setRoles(["customer", "admin", "farmer"])); - }, []); + if (!token) { + setError("Không tìm thấy access token!"); + setLoading(false); + return; + } + fetchUsers(); + }, [token]); + + useEffect(() => { + setRoles(["Customer", "Admin", "Farmer"]); + }, []); const openEdit = (user) => { setSelectedUser(user); @@ -105,11 +107,12 @@ export default function Users() { const handleAddRole = async (userId, role) => { try { await axios.patch( - `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/add-roles`, - { roles: [role] }, + `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/add-role`, + { role: role.charAt(0).toUpperCase() + role.slice(1) }, { headers: { Authorization: `Bearer ${token}` } } - ); + ); alert("Đã thêm role thành công!"); + fetchUsers(); } catch (e) { alert("Không thể thêm role!"); } @@ -119,10 +122,11 @@ export default function Users() { try { await axios.patch( `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/remove-roles`, - { roles: [role] }, + { roles: [role.charAt(0).toUpperCase() + role.slice(1)] }, { headers: { Authorization: `Bearer ${token}` } } ); alert("Đã xoá role thành công!"); + fetchUsers(); } catch (e) { alert("Không thể xoá role!"); } @@ -204,7 +208,7 @@ export default function Users() { Thông tin chi tiết - {viewUser && ( + {viewUser ? (
{viewUser.fullName} Email: {viewUser.email} @@ -221,6 +225,8 @@ export default function Users() {
)) : Không có địa chỉ} + ) : ( + Đang tải dữ liệu... )}
@@ -228,5 +234,7 @@ export default function Users() {
- ); + ) } + +export default Users \ No newline at end of file diff --git a/src/routes.jsx b/src/routes.jsx index e7aed060..40dcebd9 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -6,14 +6,11 @@ import { ServerStackIcon, RectangleStackIcon, } from "@heroicons/react/24/solid"; -<<<<<<< HEAD + import { Home, Users , Farms, Questions } from "@/pages/dashboard"; -======= -import { Home, Users, Tables, AcceptFarm } from "@/pages/dashboard"; -import UserDetail from "@/pages/dashboard/UserDetail"; ->>>>>>> tien + import { SignIn, SignUp } from "@/pages/auth"; @@ -33,13 +30,8 @@ export const routes = [ }, { icon: , -<<<<<<< HEAD name: "Users", path: "/Users", -======= - name: "users", - path: "/users", ->>>>>>> tien element: , }, { From 819ee4d3e315b79f22b01116a1ff8f7d34f7f670 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Mon, 7 Jul 2025 15:55:49 +0700 Subject: [PATCH 067/248] Create answerstable.jsx --- src/pages/dashboard/answerstable.jsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/pages/dashboard/answerstable.jsx diff --git a/src/pages/dashboard/answerstable.jsx b/src/pages/dashboard/answerstable.jsx new file mode 100644 index 00000000..e69de29b From a42156b773aa07a4a979002b55f23ab4cb04d8cb Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Mon, 7 Jul 2025 15:56:07 +0700 Subject: [PATCH 068/248] Update answerstable.jsx --- src/pages/dashboard/answerstable.jsx | 218 +++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) diff --git a/src/pages/dashboard/answerstable.jsx b/src/pages/dashboard/answerstable.jsx index e69de29b..b8fdb2b0 100644 --- a/src/pages/dashboard/answerstable.jsx +++ b/src/pages/dashboard/answerstable.jsx @@ -0,0 +1,218 @@ +import React, { useState, useEffect } from "react"; +import { + Dialog, + DialogHeader, + DialogBody, + DialogFooter, + Input, + Button, + Typography, +} from "@material-tailwind/react"; + +const API_URL = "https://api-ndolv2.nongdanonline.vn/answers"; +const token = localStorage.getItem("accessToken"); + +const AnswersTable = () => { + const [answers, setAnswers] = useState([]); + const [open, setOpen] = useState(false); + const [editData, setEditData] = useState(null); + const [form, setForm] = useState({ + farmId: "", + questionId: "", + selectedOptions: [], + otherText: "", + uploadedFiles: [], + }); + + const fetchAnswers = async () => { + try { + const res = await fetch(API_URL, { + headers: { Authorization: `Bearer ${token}` }, + }); + const data = await res.json(); + setAnswers(data); + } catch (err) { + console.error("Lỗi khi tải dữ liệu:", err); + } + }; + + useEffect(() => { + fetchAnswers(); + }, []); + + const openForm = (data = null) => { + if (data) { + setForm({ + farmId: data.farmId || "", + questionId: data.questionId || "", + selectedOptions: data.selectedOptions || [], + otherText: data.otherText || "", + uploadedFiles: data.uploadedFiles || [], + }); + setEditData(data); + } else { + setForm({ + farmId: "", + questionId: "", + selectedOptions: [], + otherText: "", + uploadedFiles: [], + }); + setEditData(null); + } + setOpen(true); + }; + + const handleSubmit = async () => { + const url = editData ? `${API_URL}/${editData._id}` : API_URL; + const method = editData ? "PUT" : "POST"; + + try { + const res = await fetch(url, { + method, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify(form), + }); + + if (!res.ok) throw new Error("Không thể lưu dữ liệu"); + setOpen(false); + setEditData(null); + fetchAnswers(); + } catch (err) { + alert(err.message); + } + }; + + const handleDelete = async (id) => { + if (!window.confirm("Bạn có chắc muốn xoá?")) return; + try { + const res = await fetch(`${API_URL}/${id}`, { + method: "DELETE", + headers: { + Authorization: `Bearer ${token}`, + }, + }); + if (!res.ok) throw new Error("Không thể xoá dữ liệu"); + fetchAnswers(); + } catch (err) { + alert(err.message); + } + }; + + return ( +
+
+ Danh sách câu trả lời + +
+ + + + + + + + + + + + + + + {answers.map((item, index) => ( + + + + + + + + + + ))} + +
#Farm IDQuestion IDĐáp án chọnKhácTệp đính kèmHành động
{index + 1}{item.farmId}{item.questionId} + {item.selectedOptions?.filter(Boolean).join(", ") || "—"} + {item.otherText || "—"} + {item.uploadedFiles?.length > 0 + ? item.uploadedFiles.map((file, i) => ( + + File {i + 1} + + )) + : "—"} + + + +
+ + setOpen(!open)}> + {editData ? "Chỉnh sửa" : "Thêm mới"} câu trả lời + + setForm({ ...form, farmId: e.target.value })} + className="mb-4" + /> + setForm({ ...form, questionId: e.target.value })} + className="mb-4" + /> + + setForm({ + ...form, + selectedOptions: e.target.value + .split(",") + .map((s) => s.trim()) + .filter(Boolean), + }) + } + className="mb-4" + /> + setForm({ ...form, otherText: e.target.value })} + className="mb-4" + /> + + + + + + +
+ ); +}; + +export default AnswersTable; From fbb489777e1bd97d68f0243a9c0c97e3a34793e8 Mon Sep 17 00:00:00 2001 From: Longvu003 Date: Mon, 7 Jul 2025 16:01:28 +0700 Subject: [PATCH 069/248] up code video Farm --- src/App.jsx | 4 +- src/pages/dashboard/AcceptFarm.jsx | 0 src/pages/dashboard/Questions/Questions.jsx | 2 +- .../dashboard/VideoFarms/VideoFarmById.jsx | 171 ++++++++++++++++++ src/pages/dashboard/VideoFarms/VideoFarms.jsx | 84 +++++++++ src/pages/dashboard/index.js | 1 + src/pages/dashboard/notifications.jsx | 0 src/routes.jsx | 9 +- 8 files changed, 268 insertions(+), 3 deletions(-) create mode 100644 src/pages/dashboard/AcceptFarm.jsx create mode 100644 src/pages/dashboard/VideoFarms/VideoFarmById.jsx create mode 100644 src/pages/dashboard/VideoFarms/VideoFarms.jsx create mode 100644 src/pages/dashboard/notifications.jsx diff --git a/src/App.jsx b/src/App.jsx index 80e354b2..c02abb47 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,11 +1,13 @@ import { Routes, Route, Navigate } from "react-router-dom"; import { Dashboard, Auth } from "@/layouts"; +import VideoFarmById from "./pages/dashboard/VideoFarms/VideoFarmById"; function App() { return ( } /> } /> - } /> +} /> + } /> ); } diff --git a/src/pages/dashboard/AcceptFarm.jsx b/src/pages/dashboard/AcceptFarm.jsx new file mode 100644 index 00000000..e69de29b diff --git a/src/pages/dashboard/Questions/Questions.jsx b/src/pages/dashboard/Questions/Questions.jsx index 93a6a44b..ebbe3e2e 100644 --- a/src/pages/dashboard/Questions/Questions.jsx +++ b/src/pages/dashboard/Questions/Questions.jsx @@ -9,7 +9,7 @@ export const Questions = () => { const [openDialog, setOpenDialog] = useState(false); const [editData, setEditData] = useState(null); const [editValue, setEditValue] = useState({ options: [] }); -console.log(editValue) + const [addDialog, setAddDialog] = useState(false); const [addValue, setAddValue] = useState({ text: '', options: [''], type: 'option', link: '' }); diff --git a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx new file mode 100644 index 00000000..d5376439 --- /dev/null +++ b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx @@ -0,0 +1,171 @@ +import React, { useEffect, useState } from 'react' +import axios from 'axios' +import { BaseUrl } from '@/ipconfig' +import { useParams } from 'react-router-dom' +import { Audio } from 'react-loader-spinner' + +export const VideoFarmById = () => { + const [openDialog, setOpenDialog] = useState(false); + const [editData, setEditData] = useState(null); + const [editValue, setEditValue] = useState({ options: [] }); + const [loading, setLoading] = useState(true) + const [videoDetail,setVideoDetail]=useState([]) + const tokenUser = localStorage.getItem('token'); + const {farmId}=useParams() + const getDetailVideo = async () => { + try { + setLoading(true) + const res = await axios.get(`${BaseUrl}/admin-video-farm/farm/${farmId}`, { + headers: { Authorization: `Bearer ${tokenUser}` } + }); + if (res.status === 200) { + setVideoDetail(res.data) + setLoading(false) + } + } catch (error) { + console.log("Lỗi nè:", error) + setLoading(false) + + } + } +const handleOpenDialog =(item)=>{ +setEditData(item) + setEditValue({ + title:item.title, + youtubeLink:item.youtubeLink, + playlistName:item.playlistName + }) + +setOpenDialog(true) +} + +const handleCloseDialog =(item)=>{ + setEditData(null) + setEditValue({}) +setOpenDialog(false) +} +const handleSave = async()=>{ +try { + +} catch (error) { + console.log("Lỗi nè",error) +} +} + + useEffect(()=>{ +getDetailVideo() + },[]) + return ( +
+ {loading ? ( +
+
+ ) : videoDetail.length === 0 ? ( + Không có video nào. + ) : ( + videoDetail.map((item) => ( +
+ {item.title} + +
+ + + {/* */} + + {openDialog && ( +
+
+
Chỉnh sửa video
+
+ + setEditValue({ ...editValue, title: e.target.value })} + className="border px-3 py-2 rounded w-full" + /> +
+
+ + setEditValue({ ...editValue, youtubeLink: e.target.value })} + className="border px-3 py-2 rounded w-full" + /> +
+
+ + setEditValue({ ...editValue, playlistName: e.target.value })} + className="border px-3 py-2 rounded w-full" + /> +
+
+ + +
+
+
+)} +
+ {item.youtubeLink ? ( + + ▶ Xem video + + ) : ( + Chưa có video + )} + Danh sách phát: {item.playlistName} + Ngày đăng: {new Date(item.createdAt).toLocaleDateString()} + Người đăng: {item.uploadedBy?.fullName} +
+ )) + )} + + +
+ ) +} + +export default VideoFarmById \ No newline at end of file diff --git a/src/pages/dashboard/VideoFarms/VideoFarms.jsx b/src/pages/dashboard/VideoFarms/VideoFarms.jsx new file mode 100644 index 00000000..3a22c5cc --- /dev/null +++ b/src/pages/dashboard/VideoFarms/VideoFarms.jsx @@ -0,0 +1,84 @@ +import React, { useEffect, useState } from 'react' +import { BaseUrl } from '@/ipconfig' +import axios from 'axios' +import { useNavigate } from 'react-router-dom' +import { Audio } from 'react-loader-spinner' + +export const VideoFarms = () => { + const [loading, setLoading] = useState(true) + + const navigate=useNavigate() + const tokenUser = localStorage.getItem('token'); + const [video, setVideo] = useState([]); + + const CallVideoApi = async () => { + try { + setLoading(true) + const res = await axios.get(`${BaseUrl}/admin-video-farm`, { + headers: { Authorization: `Bearer ${tokenUser}` } + }); + if (res.status === 200) { + setVideo(res.data) + setLoading(false) + } + } catch (error) { + console.log("Lỗi nè:", error) + setLoading(false) + } + } + useEffect(() => { + CallVideoApi() + }, []) + const farmMap = {}; + video.forEach(item => { + if (item.farmId && item.farmId.id) { + farmMap[item.farmId.id] = item; + } + }); + const farmList = Object.values(farmMap); + + + +const gotoVideoById=(farmId)=>{ + navigate(`/dashboard/VideoFarmById/${farmId}`); +} + + return ( +
+ { + + loading ? ( +
+
+ ) : + + farmList.length === 0 ? ( + Đang Tải + ) : ( + farmList.map((item) => + ( +
gotoVideoById(item.farmId.id)} + key={item.farmId.id} + className=" cursor-pointer bg-white rounded-lg shadow p-5 flex flex-col gap-2 border hover:shadow-lg transition" + > + {item.title} + + Danh sách phát: {item.playlistName} + Ngày đăng: {new Date(item.createdAt).toLocaleDateString()} + Người đăng: {item.uploadedBy?.fullName} +
+ )) + )} +
+ ) +} + +export default VideoFarms \ No newline at end of file diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index 230ec3c5..5dadc7a7 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -2,4 +2,5 @@ export * from "@/pages/dashboard/home"; export * from "@/pages/dashboard/users"; export * from "@/pages/dashboard/farms"; export * from "@/pages/dashboard/Questions/Questions"; +export * from "@/pages/dashboard/VideoFarms/VideoFarms"; diff --git a/src/pages/dashboard/notifications.jsx b/src/pages/dashboard/notifications.jsx new file mode 100644 index 00000000..e69de29b diff --git a/src/routes.jsx b/src/routes.jsx index ceaaf6cd..4e22d61d 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -7,10 +7,11 @@ import { RectangleStackIcon, } from "@heroicons/react/24/solid"; -import { Home, Users , Farms,Questions } from "@/pages/dashboard"; +import { Home, Users , Farms,Questions, VideoFarms } from "@/pages/dashboard"; import { SignIn, SignUp } from "@/pages/auth"; +import { ViewfinderCircleIcon } from "@heroicons/react/24/outline"; const icon = { className: "w-5 h-5 text-inherit", @@ -45,6 +46,12 @@ export const routes = [ path: "/Questions", element: , }, + { + icon: , + name: "VideoFarms", + path: "/VideoFarms", + element: , + }, ], }, { From 3c22843e58f60f98a64e15b615e60a38c0bf7f18 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Mon, 7 Jul 2025 16:43:25 +0700 Subject: [PATCH 070/248] thanh --- src/pages/dashboard/answerstable.jsx | 277 ++++++++++++++++++--------- src/pages/dashboard/index.js | 2 +- src/routes.jsx | 8 +- 3 files changed, 197 insertions(+), 90 deletions(-) diff --git a/src/pages/dashboard/answerstable.jsx b/src/pages/dashboard/answerstable.jsx index b8fdb2b0..9f257306 100644 --- a/src/pages/dashboard/answerstable.jsx +++ b/src/pages/dashboard/answerstable.jsx @@ -10,9 +10,9 @@ import { } from "@material-tailwind/react"; const API_URL = "https://api-ndolv2.nongdanonline.vn/answers"; -const token = localStorage.getItem("accessToken"); +const token = localStorage.getItem("token"); -const AnswersTable = () => { +export function AnswersTable () { const [answers, setAnswers] = useState([]); const [open, setOpen] = useState(false); const [editData, setEditData] = useState(null); @@ -23,14 +23,21 @@ const AnswersTable = () => { otherText: "", uploadedFiles: [], }); + const [uploading, setUploading] = useState(false); + // Fetch all answers const fetchAnswers = async () => { try { const res = await fetch(API_URL, { headers: { Authorization: `Bearer ${token}` }, }); const data = await res.json(); - setAnswers(data); + if (Array.isArray(data)) { + setAnswers(data); + } else { + console.error("API không trả về danh sách"); + setAnswers([]); + } } catch (err) { console.error("Lỗi khi tải dữ liệu:", err); } @@ -40,6 +47,7 @@ const AnswersTable = () => { fetchAnswers(); }, []); + // Open form for add/edit const openForm = (data = null) => { if (data) { setForm({ @@ -63,37 +71,63 @@ const AnswersTable = () => { setOpen(true); }; - const handleSubmit = async () => { - const url = editData ? `${API_URL}/${editData._id}` : API_URL; - const method = editData ? "PUT" : "POST"; + // Save data (create or update) + const handleSubmit = async () => { + const url = editData ? `${API_URL}/${editData._id}` : `${API_URL}/batch`; + const method = editData ? "PUT" : "POST"; - try { - const res = await fetch(url, { - method, - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - body: JSON.stringify(form), - }); + const body = editData + ? { + farmId: form.farmId, + questionId: form.questionId, + selectedOptions: form.selectedOptions, + otherText: form.otherText, + uploadedFiles: form.uploadedFiles + } + : { + farmId: form.farmId, + answers: [ + { + questionId: form.questionId, + selectedOptions: form.selectedOptions, + otherText: form.otherText, + uploadedFiles: form.uploadedFiles + } + ] + }; - if (!res.ok) throw new Error("Không thể lưu dữ liệu"); - setOpen(false); - setEditData(null); - fetchAnswers(); - } catch (err) { - alert(err.message); + try { + const res = await fetch(url, { + method, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}` + }, + body: JSON.stringify(body) + }); + + if (!res.ok) { + const errorData = await res.json(); + console.error("Error response:", errorData); + throw new Error(errorData.message || "Không thể lưu dữ liệu"); } - }; + setOpen(false); + setEditData(null); + fetchAnswers(); + } catch (err) { + alert(`Lỗi: ${err.message}`); + } +}; + + + // Delete an answer const handleDelete = async (id) => { if (!window.confirm("Bạn có chắc muốn xoá?")) return; try { const res = await fetch(`${API_URL}/${id}`, { method: "DELETE", - headers: { - Authorization: `Bearer ${token}`, - }, + headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) throw new Error("Không thể xoá dữ liệu"); fetchAnswers(); @@ -102,6 +136,37 @@ const AnswersTable = () => { } }; + // Upload image + const handleUploadImage = async (e) => { + const file = e.target.files[0]; + if (!file) return; + + const formData = new FormData(); + formData.append("file", file); + + setUploading(true); + try { + const res = await fetch(`${API_URL}/upload-image`, { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + }, + body: formData, + }); + if (!res.ok) throw new Error("Không thể upload hình ảnh"); + const data = await res.json(); + setForm({ + ...form, + uploadedFiles: [...form.uploadedFiles, data.path], + }); + } catch (err) { + alert(err.message); + } finally { + setUploading(false); + } +}; + + return (
@@ -112,28 +177,28 @@ const AnswersTable = () => {
- + - - - - - - - + + + + + + + {answers.map((item, index) => ( - - - - + + + - - + - ))}
#Farm IDQuestion IDĐáp án chọnKhácTệp đính kèmHành động#Farm IDQuestion IDĐáp án chọnKhácTệp đính kèmHành động
{index + 1}{item.farmId}{item.questionId} - {item.selectedOptions?.filter(Boolean).join(", ") || "—"} + {index + 1}{item.farmId}{item.questionId} + {item.selectedOptions?.join(", ") || "—"} {item.otherText || "—"} + {item.otherText || "—"} {item.uploadedFiles?.length > 0 ? item.uploadedFiles.map((file, i) => ( { )) : "—"} - - + +
+ + +
+ + + {/* Dialog form */} setOpen(!open)}> {editData ? "Chỉnh sửa" : "Thêm mới"} câu trả lời - - setForm({ ...form, farmId: e.target.value })} - className="mb-4" - /> - setForm({ ...form, questionId: e.target.value })} - className="mb-4" - /> - - setForm({ - ...form, - selectedOptions: e.target.value - .split(",") - .map((s) => s.trim()) - .filter(Boolean), - }) - } - className="mb-4" - /> - setForm({ ...form, otherText: e.target.value })} - className="mb-4" - /> + +
+ + setForm({ ...form, farmId: e.target.value })} + className="rounded-lg shadow-sm" + /> +
+ +
+ + setForm({ ...form, questionId: e.target.value })} + className="rounded-lg shadow-sm" + /> +
+ +
+ + + setForm({ + ...form, + selectedOptions: e.target.value + .split(",") + .map((s) => s.trim()) + .filter(Boolean), + }) + } + className="rounded-lg shadow-sm" + /> +
+ +
+ + setForm({ ...form, otherText: e.target.value })} + className="rounded-lg shadow-sm" + /> +
+ +
+ + +
- - - +
); diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index 53b35e5f..eae74bf7 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,6 +1,6 @@ export * from "@/pages/dashboard/home"; - +export * from "@/pages/dashboard/answerstable"; export * from "@/pages/dashboard/users"; export * from "@/pages/dashboard/farm/farms"; export * from "@/pages/dashboard/Questions/Questions"; diff --git a/src/routes.jsx b/src/routes.jsx index 40dcebd9..64a86b5f 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -8,7 +8,7 @@ import { } from "@heroicons/react/24/solid"; -import { Home, Users , Farms, Questions } from "@/pages/dashboard"; +import { Home, Users , Farms, Questions, AnswersTable } from "@/pages/dashboard"; @@ -47,6 +47,12 @@ export const routes = [ path: "/Questions", element: , }, + { + icon: , + name: "AnswersTable", + path: "/AnswersTable", + element: , + }, ], }, { From 15d09d7b35de66e4458e7c5f169df82c7f9ac10e Mon Sep 17 00:00:00 2001 From: Longvu003 Date: Mon, 7 Jul 2025 18:52:41 +0700 Subject: [PATCH 071/248] . --- .../dashboard/VideoFarms/VideoDetail.jsx | 10 + .../dashboard/VideoFarms/VideoFarmById.jsx | 237 ++++++++++-------- 2 files changed, 140 insertions(+), 107 deletions(-) create mode 100644 src/pages/dashboard/VideoFarms/VideoDetail.jsx diff --git a/src/pages/dashboard/VideoFarms/VideoDetail.jsx b/src/pages/dashboard/VideoFarms/VideoDetail.jsx new file mode 100644 index 00000000..b61c0899 --- /dev/null +++ b/src/pages/dashboard/VideoFarms/VideoDetail.jsx @@ -0,0 +1,10 @@ +import React from 'react' + +export const VideoDetail = ({getDetailVideoInformation}) => { + + return ( +
VideoDetail
+ ) +} + +export default VideoDetail \ No newline at end of file diff --git a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx index d5376439..499288bf 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx @@ -3,16 +3,17 @@ import axios from 'axios' import { BaseUrl } from '@/ipconfig' import { useParams } from 'react-router-dom' import { Audio } from 'react-loader-spinner' - export const VideoFarmById = () => { const [openDialog, setOpenDialog] = useState(false); const [editData, setEditData] = useState(null); const [editValue, setEditValue] = useState({ options: [] }); const [loading, setLoading] = useState(true) + const [idVideo, setIdVideo] = useState([]) const [videoDetail,setVideoDetail]=useState([]) const tokenUser = localStorage.getItem('token'); const {farmId}=useParams() - const getDetailVideo = async () => { + console.log(videoDetail) + const getDetailVideo = async () => { try { setLoading(true) const res = await axios.get(`${BaseUrl}/admin-video-farm/farm/${farmId}`, { @@ -30,10 +31,15 @@ export const VideoFarmById = () => { } const handleOpenDialog =(item)=>{ setEditData(item) + setIdVideo(item._id) setEditValue({ title:item.title, youtubeLink:item.youtubeLink, - playlistName:item.playlistName + playlistName:item.playlistName, + status:item.status, + uploadedBy:item.uploadedBy.fullName, + createdAt:item.createdAt, + }) setOpenDialog(true) @@ -44,128 +50,145 @@ const handleCloseDialog =(item)=>{ setEditValue({}) setOpenDialog(false) } -const handleSave = async()=>{ -try { +// const handleSaveEdit = async()=>{ +// try { -} catch (error) { - console.log("Lỗi nè",error) -} -} +// const res= await axios.post(`${BaseUrl}/admin-video-farm/upload-youtube/${idVideo}`, editValue,{ +// headers: { Authorization: `Bearer ${tokenUser}` }}) +// if(res.status===200){ +// alert("Cập nhật thành công") +// setTimeout(() => { +// getDetailVideo(); +// }, 500); +// console.log(res) +// handleCloseDialog() +// } +// } catch (error) { +// console.log("Lỗi nè",error) +// } +// } useEffect(()=>{ getDetailVideo() },[]) - return ( -
- {loading ? ( -
-
- ) : videoDetail.length === 0 ? ( - Không có video nào. - ) : ( - videoDetail.map((item) => ( -
- {item.title} -
- - - {/* */} - - {openDialog && ( -
-
-
Chỉnh sửa video
-
- - setEditValue({ ...editValue, title: e.target.value })} - className="border px-3 py-2 rounded w-full" +return ( +
+ {loading ? ( +
+
-
- - setEditValue({ ...editValue, youtubeLink: e.target.value })} - className="border px-3 py-2 rounded w-full" - /> + ) : videoDetail.length === 0 ? ( + Không có video nào. + ) : ( + videoDetail.map((item) => ( +
handleOpenDialog(item)} + + key={item.id} + className="cursor-ponter bg-white rounded-lg shadow p-5 flex flex-col gap-2 border hover:shadow-lg transition" + > + {item.title} +
+ + +
+ {item.youtubeLink ? ( + + ▶ Xem video + + + ) : ( + Chưa có video + ) + } + + Danh sách phát: {item.playlistName} + + + Ngày đăng: {new Date(item.createdAt).toLocaleDateString()} + + + Người đăng: {item.uploadedBy?.fullName} + +
+ )) + )} + + + {openDialog && ( +
+
+
+
+

Thông tin video

-
- - setEditValue({ ...editValue, playlistName: e.target.value })} - className="border px-3 py-2 rounded w-full" - /> +
+
+ + +
+
+ + +
+
+ + +
+
+ Trạng thái: {editValue.status} + Người đăng: {editValue.uploadedBy} + Ngày tạo: {editValue.createdAt && new Date(editValue.createdAt).toLocaleString()} +
-
+
-
)} -
- {item.youtubeLink ? ( - - ▶ Xem video - - ) : ( - Chưa có video - )} - Danh sách phát: {item.playlistName} - Ngày đăng: {new Date(item.createdAt).toLocaleDateString()} - Người đăng: {item.uploadedBy?.fullName} -
- )) - )} - - -
- ) +
+) } export default VideoFarmById \ No newline at end of file From 40dbe73391c22200fcc5b8e0c90d4a531c34fc04 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Tue, 8 Jul 2025 10:17:53 +0700 Subject: [PATCH 072/248] thanh --- src/pages/auth/sign-in.jsx | 69 ++--- src/pages/dashboard/AcceptFarm.jsx | 0 src/pages/dashboard/answerstable.jsx | 203 +++++++++------ src/pages/dashboard/farm/farms.jsx | 2 +- src/pages/dashboard/farms.jsx | 347 -------------------------- src/pages/dashboard/notifications.jsx | 0 src/pages/dashboard/users.jsx | 25 +- 7 files changed, 174 insertions(+), 472 deletions(-) delete mode 100644 src/pages/dashboard/AcceptFarm.jsx delete mode 100644 src/pages/dashboard/farms.jsx delete mode 100644 src/pages/dashboard/notifications.jsx diff --git a/src/pages/auth/sign-in.jsx b/src/pages/auth/sign-in.jsx index 16870b9a..dc0f3ede 100644 --- a/src/pages/auth/sign-in.jsx +++ b/src/pages/auth/sign-in.jsx @@ -15,6 +15,7 @@ export function SignIn() { const emailRef = useRef(); const passwordRef = useRef(); + // Đăng nhập const handleLogin = async (e) => { e.preventDefault(); @@ -57,9 +58,10 @@ export function SignIn() { } }; - // Hàm refresh token + // Làm mới token const refreshAccessToken = async () => { const refreshToken = localStorage.getItem("refreshToken"); + if (!refreshToken) return null; try { const res = await fetch("https://api-ndolv2.nongdanonline.vn/auth/refresh-token", { @@ -67,22 +69,52 @@ export function SignIn() { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ refreshToken }), }); - const data = await res.json(); if (res.ok) { localStorage.setItem("token", data.accessToken); + console.log("✅ Access token đã được làm mới"); return data.accessToken; } else { - console.error("Làm mới token thất bại:", data.message); + console.error("❌ Làm mới token thất bại:", data.message); return null; } } catch (error) { - console.error("Lỗi khi gọi refresh-token:", error); + console.error("❌ Lỗi khi gọi refresh-token:", error); return null; } }; + // Hàm fetch có auto-refresh + const fetchWithAuth = async (url, options = {}) => { + let token = localStorage.getItem("token"); + let res = await fetch(url, { + ...options, + headers: { + ...(options.headers || {}), + Authorization: `Bearer ${token}`, + }, + }); + + if (res.status === 401 || res.status === 403) { + console.warn("⚠ Token hết hạn, đang làm mới..."); + const newToken = await refreshAccessToken(); + if (newToken) { + res = await fetch(url, { + ...options, + headers: { + ...(options.headers || {}), + Authorization: `Bearer ${newToken}`, + }, + }); + } else { + console.error("⚠ Không thể làm mới token"); + } + } + + return res; + }; + return (
@@ -142,37 +174,8 @@ export function SignIn() { Forgot Password
- -
- - -
- - - Not registered? - Create account -
-
diff --git a/src/pages/dashboard/AcceptFarm.jsx b/src/pages/dashboard/AcceptFarm.jsx deleted file mode 100644 index e69de29b..00000000 diff --git a/src/pages/dashboard/answerstable.jsx b/src/pages/dashboard/answerstable.jsx index 9f257306..4602ad16 100644 --- a/src/pages/dashboard/answerstable.jsx +++ b/src/pages/dashboard/answerstable.jsx @@ -10,9 +10,51 @@ import { } from "@material-tailwind/react"; const API_URL = "https://api-ndolv2.nongdanonline.vn/answers"; -const token = localStorage.getItem("token"); -export function AnswersTable () { +// Hàm fetch có auto-refresh token +const fetchWithAuth = async (url, options = {}) => { + let token = localStorage.getItem("token"); + + let res = await fetch(url, { + ...options, + headers: { + ...(options.headers || {}), + Authorization: `Bearer ${token}`, + }, + }); + + if (res.status === 401 || res.status === 403) { + console.warn("⚠ Token hết hạn, đang làm mới..."); + const refreshToken = localStorage.getItem("refreshToken"); + const refreshRes = await fetch("https://api-ndolv2.nongdanonline.vn/auth/refresh-token", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ refreshToken }), + }); + + if (refreshRes.ok) { + const refreshData = await refreshRes.json(); + localStorage.setItem("token", refreshData.accessToken); + token = refreshData.accessToken; + + // Gọi lại API với token mới + res = await fetch(url, { + ...options, + headers: { + ...(options.headers || {}), + Authorization: `Bearer ${token}`, + }, + }); + } else { + console.error("❌ Refresh token thất bại"); + throw new Error("Vui lòng đăng nhập lại!"); + } + } + + return res; +}; + +export function AnswersTable() { const [answers, setAnswers] = useState([]); const [open, setOpen] = useState(false); const [editData, setEditData] = useState(null); @@ -28,9 +70,7 @@ export function AnswersTable () { // Fetch all answers const fetchAnswers = async () => { try { - const res = await fetch(API_URL, { - headers: { Authorization: `Bearer ${token}` }, - }); + const res = await fetchWithAuth(API_URL); const data = await res.json(); if (Array.isArray(data)) { setAnswers(data); @@ -72,62 +112,64 @@ export function AnswersTable () { }; // Save data (create or update) - const handleSubmit = async () => { - const url = editData ? `${API_URL}/${editData._id}` : `${API_URL}/batch`; - const method = editData ? "PUT" : "POST"; - - const body = editData - ? { - farmId: form.farmId, - questionId: form.questionId, - selectedOptions: form.selectedOptions, - otherText: form.otherText, - uploadedFiles: form.uploadedFiles - } - : { - farmId: form.farmId, - answers: [ - { - questionId: form.questionId, - selectedOptions: form.selectedOptions, - otherText: form.otherText, - uploadedFiles: form.uploadedFiles - } - ] - }; - - try { - const res = await fetch(url, { - method, - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}` - }, - body: JSON.stringify(body) - }); - - if (!res.ok) { - const errorData = await res.json(); - console.error("Error response:", errorData); - throw new Error(errorData.message || "Không thể lưu dữ liệu"); + const handleSubmit = async () => { + if (!form.farmId || !form.questionId) { + alert("Vui lòng nhập đủ Farm ID và Question ID"); + return; } - setOpen(false); - setEditData(null); - fetchAnswers(); - } catch (err) { - alert(`Lỗi: ${err.message}`); - } -}; + const url = editData ? `${API_URL}/${editData._id}` : `${API_URL}/batch`; + const method = editData ? "PUT" : "POST"; + + const body = editData + ? { + farmId: form.farmId, + questionId: form.questionId, + selectedOptions: form.selectedOptions, + otherText: form.otherText, + uploadedFiles: form.uploadedFiles, + } + : { + farmId: form.farmId, + answers: [ + { + questionId: form.questionId, + selectedOptions: form.selectedOptions, + otherText: form.otherText, + uploadedFiles: form.uploadedFiles, + }, + ], + }; + try { + const res = await fetchWithAuth(url, { + method, + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + if (!res.ok) { + const errorData = await res.json(); + console.error("Error response:", errorData); + throw new Error(errorData.message || "Không thể lưu dữ liệu"); + } + + setOpen(false); + setEditData(null); + fetchAnswers(); + } catch (err) { + alert(`Lỗi: ${err.message}`); + } + }; // Delete an answer const handleDelete = async (id) => { if (!window.confirm("Bạn có chắc muốn xoá?")) return; try { - const res = await fetch(`${API_URL}/${id}`, { + const res = await fetchWithAuth(`${API_URL}/${id}`, { method: "DELETE", - headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) throw new Error("Không thể xoá dữ liệu"); fetchAnswers(); @@ -138,34 +180,36 @@ export function AnswersTable () { // Upload image const handleUploadImage = async (e) => { - const file = e.target.files[0]; - if (!file) return; + const file = e.target.files[0]; + if (!file) return; - const formData = new FormData(); - formData.append("file", file); + const formData = new FormData(); + formData.append("file", file); - setUploading(true); - try { - const res = await fetch(`${API_URL}/upload-image`, { - method: "POST", - headers: { - Authorization: `Bearer ${token}`, - }, - body: formData, - }); - if (!res.ok) throw new Error("Không thể upload hình ảnh"); - const data = await res.json(); - setForm({ - ...form, - uploadedFiles: [...form.uploadedFiles, data.path], - }); - } catch (err) { - alert(err.message); - } finally { - setUploading(false); - } -}; + setUploading(true); + try { + const res = await fetchWithAuth(`${API_URL}/upload-image`, { + method: "POST", + body: formData, + }); + + const result = await res.json(); + if (!res.ok) { + console.error("Upload lỗi:", result); + throw new Error(result.message || "Không thể upload hình ảnh"); + } + + setForm((prevForm) => ({ + ...prevForm, + uploadedFiles: [...prevForm.uploadedFiles, result.path], + })); + } catch (err) { + alert(`Upload lỗi: ${err.message}`); + } finally { + setUploading(false); + } + }; return (
@@ -232,8 +276,6 @@ export function AnswersTable () { - - {/* Dialog form */} setOpen(!open)}> {editData ? "Chỉnh sửa" : "Thêm mới"} câu trả lời @@ -310,10 +352,9 @@ export function AnswersTable () { {editData ? "Cập nhật" : "Tạo mới"} -
); -}; +} export default AnswersTable; diff --git a/src/pages/dashboard/farm/farms.jsx b/src/pages/dashboard/farm/farms.jsx index e843eb9f..86efbc82 100644 --- a/src/pages/dashboard/farm/farms.jsx +++ b/src/pages/dashboard/farm/farms.jsx @@ -244,7 +244,7 @@ export function Farms() { initialData={editingFarm} onSubmit={(data) => { if (editingFarm) { - editingFarm(editingFarm._id, data); + editFarm(editingFarm._id, data); } else { addFarm(data); } diff --git a/src/pages/dashboard/farms.jsx b/src/pages/dashboard/farms.jsx deleted file mode 100644 index d0d2204c..00000000 --- a/src/pages/dashboard/farms.jsx +++ /dev/null @@ -1,347 +0,0 @@ -import React, { useState, useEffect } from "react"; -import axios from "axios"; -import { - Card, CardBody, Input, Button, Typography, Checkbox, Chip, Tabs, TabsHeader, Tab, - Dialog, - DialogHeader, - DialogBody -} from "@material-tailwind/react"; -import { - PencilSquareIcon, TrashIcon, PlusIcon, -} from "@heroicons/react/24/solid"; -import FarmForm from "../../widgets/layout/FarmForm"; - -const BASE_URL = "https://api-ndolv2.nongdanonline.vn"; -const getOpts = () => ({ - headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }, -}); - - -export function Farms() { - const [farms, setFarms] = useState([]); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(true); - - const [tab, setTab] = useState("all") - const [search, setSearch] = useState(""); - - const [openForm, setOpenForm] = useState(false); - const [editingFarm, setEditingFarm] = useState(null); - - const [selectedFarm, setSelectedFarm] = useState(null); - const [openDetail, setOpenDetail] = useState(false); - - const fetchFarms = async () => { - try { - const res = await axios.get(`${BASE_URL}/adminfarms`, getOpts()); - setFarms(res.data); - } catch (err) { - setError(err.response?.data?.message || err.message); - } finally { - setLoading(false); - } - }; - - const getFarmDetail = async (id) => { - try { - const res = await axios.get(`${BASE_URL}/adminfarms/${id}`, getOpts()); - setSelectedFarm(res.data); - setOpenDetail(true); - }catch (err) { - alert("Lỗi lấy chi tiết: " + (err.response?.data?.message || err.message)); - } - }; - - const addFarm = async (data) => { - try { - await axios.post(`${BASE_URL}/adminfarms`, data, getOpts()); - await fetchFarms(); - alert(" Tạo farm thành công!"); - } catch (err) { - alert("Lỗi thêm: " + (err.response?.data?.message || err.message)); - } - }; - - const editFarm = async (id, data) => { - try { - await axios.put(`${BASE_URL}/adminfarms/${id}`, data, getOpts()); - await fetchFarms(); - } catch (err) { - alert("Lỗi sửa: " + (err.response?.data?.message || err.message)); - } - }; - - const deleteFarm = async (id) => { - if (!window.confirm("Bạn có chắc chắn muốn xoá không?")) return; - console.log(" Xoá farm với id:", id); - try { - await axios.delete(`${BASE_URL}/adminfarms/${id}`, getOpts()); - setFarms((prevFarms) => prevFarms.filter((farm) => farm._id !== id)); - } catch (err) { - alert("Lỗi xoá: " + (err.response?.data?.message || err.message)); - } - }; - - const activateFarm = async (id) => { - try { - await axios.patch(`${BASE_URL}/adminfarms/${id}/activate`, null, getOpts()); - await fetchFarms(); - }catch (err) { - alert("Lỗi kích hoạt: " + (err.response?.data?.message || err.message)); - } - } - - const deactivateFarm = async (id) => { - try { - await axios.patch(`${BASE_URL}/adminfarms/${id}/deactivate`, null, getOpts()); - await fetchFarms(); - }catch (err) { - alert("Lỗi kích hoạt: " + (err.response?.data?.message || err.message)); - } - } - - useEffect(() => { - fetchFarms(); - }, []); - - const displayedFarms = farms - .filter((farm) => { - if (tab === "all") return true; - return farm.status === tab; - }) - .filter((farm) => - farm.name?.toLowerCase().includes(search.toLowerCase()) - ); - - const approveFarm = (id) => activateFarm(id); - const rejectFarm = (id) => deactivateFarm(id); - return ( - <> - -
- - Quản lý nông trại - - - - - setTab("all")}> - Tất cả - - setTab("pending")}> - Chờ duyệt - - setTab("active")}> - Đang hoạt động - - setTab("inactive")}> - Đã khoá - - - - - -
- - setSearch(e.target.value)} - className="mb-6" - /> - - {loading ? ( - Đang tải dữ liệu... - ) : error ? ( - Lỗi: {error} - ) : ( - - - - - - {["Tên", "Mã", "Chủ sở hữu", "SĐT", "Địa chỉ", "Diện tích", "Trạng thái", "Thao tác"].map((head) => ( - - ))} - - - - {displayedFarms.map((farm) => ( - getFarmDetail(farm._id)}> - - - - - - - - - - - - ))} - -
{head}
{farm.name}{farm.code}{farm.ownerInfo?.name || "—"}{farm.phone || "—"}{farm.location}{farm.area} m² - - -
- {/* Sửa */} - - - {/* Xoá */} - - - {/* Theo trạng thái */} - {farm.status === "pending" && ( - <> - - - - )} - - {farm.status === "inactive" && ( - - )} - - {farm.status === "active" && ( - - )} -
-
-
- )} -
- - setOpenDetail(false)} size="lg" className="max-w-screen-md mx-auto"> - Chi tiết nông trại - - {selectedFarm ? ( -
- Tên: {selectedFarm.name} - Mã: {selectedFarm.code} - Chủ sở hữu: {selectedFarm.ownerInfo.name} - SĐT: {selectedFarm.phone} - Địa chỉ: {selectedFarm.location} - Diện tích: {selectedFarm.area} m² - Mô tả: {selectedFarm.description || "Không có"} - - {/* Dịch vụ */} -
- Dịch vụ -
- {[ - "direct_selling", - "feed_selling", - "custom_feed_blending", - "processing_service", - "storage_service", - "transport_service", - "other_services", - ]. map((service) => ( - - ))} -
-
- {/* Tính năng */} -
- Tính năng -
- {[ - "aquaponic_model", - "ras_ready", - "hydroponic", - "greenhouse", - "vertical_farming", - "viet_gap_cert", - "organic_cert", - "global_gap_cert", - "haccp_cert", - "camera_online", - "drone_monitoring", - "automated_pest_detection", - "precision_irrigation", - "auto_irrigation", - "soil_based_irrigation", - "iot_sensors", - "soil_moisture_monitoring", - "air_quality_sensor", - ].map((feature) => ( - - ))} -
-
-
- ) : ( - Đang tải chi tiết... - )} -
-
- - setOpenForm(false)} - initialData={editingFarm} - onSubmit={(data) => { - editingFarm ? editFarm(editingFarm._id, data) : addFarm(data); - }} - /> - - ); -} - -export default Farms \ No newline at end of file diff --git a/src/pages/dashboard/notifications.jsx b/src/pages/dashboard/notifications.jsx deleted file mode 100644 index e69de29b..00000000 diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index 80e382b5..5915a391 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -30,7 +30,9 @@ export function Users() { const res = await axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { headers: { Authorization: `Bearer ${token}` } }); - setUsers(Array.isArray(res.data) ? res.data : []); + const active = (Array.isArray(res.data) ? res.data : []) + .filter(u => u.isActive); + setUsers(active); } catch (err) { setError("Lỗi khi tải danh sách người dùng."); } finally { @@ -90,20 +92,23 @@ export function Users() { .catch(() => alert("Cập nhật thất bại!")); }; - const handleDelete = (userId) => { + const handleDelete = async (userId) => { if (!token) return; if (!window.confirm("Bạn có chắc muốn xoá người dùng này?")) return; - axios.delete(`https://api-ndolv2.nongdanonline.vn/admin-users/${userId}`, { - headers: { Authorization: `Bearer ${token}` }, - }) - .then(() => { - alert("Xoá người dùng thành công!"); - setUsers(users.filter(user => user.id !== userId)); - }) - .catch(() => alert("Xoá thất bại!")); + try { + await axios.delete( + `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}`, + { headers: { Authorization: `Bearer ${token}` } } + ); + alert("Xoá người dùng thành công!"); + await fetchUsers(); + } catch (e) { + alert("Xoá thất bại!"); + } }; + const handleAddRole = async (userId, role) => { try { await axios.patch( From 3c362d09d5ab367650dbcb0f9c8ec57677b06ba3 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Tue, 8 Jul 2025 14:36:42 +0700 Subject: [PATCH 073/248] videocomment --- .../dashboard/VideoFarms/VideoFarmById.jsx | 28 +++- src/pages/dashboard/commentVideo.jsx | 142 ++++++++++++++++++ src/widgets/layout/FarmForm.jsx | 116 -------------- 3 files changed, 167 insertions(+), 119 deletions(-) create mode 100644 src/pages/dashboard/commentVideo.jsx delete mode 100644 src/widgets/layout/FarmForm.jsx diff --git a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx index 499288bf..9e61de59 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx @@ -3,6 +3,7 @@ import axios from 'axios' import { BaseUrl } from '@/ipconfig' import { useParams } from 'react-router-dom' import { Audio } from 'react-loader-spinner' +import CommentVideo from '../commentvideo' export const VideoFarmById = () => { const [openDialog, setOpenDialog] = useState(false); const [editData, setEditData] = useState(null); @@ -10,6 +11,9 @@ export const VideoFarmById = () => { const [loading, setLoading] = useState(true) const [idVideo, setIdVideo] = useState([]) const [videoDetail,setVideoDetail]=useState([]) + + const [openComment, setOpenComment] = useState(null) + const tokenUser = localStorage.getItem('token'); const {farmId}=useParams() console.log(videoDetail) @@ -45,6 +49,10 @@ setEditData(item) setOpenDialog(true) } +const toggleComment = (videoId) => { + setOpenComment((prev) => (prev === videoId ? null : videoId)); + }; + const handleCloseDialog =(item)=>{ setEditData(null) setEditValue({}) @@ -73,6 +81,7 @@ getDetailVideo() },[]) return ( + <>
{loading ? (
@@ -89,8 +98,6 @@ return ( ) : ( videoDetail.map((item) => (
handleOpenDialog(item)} - key={item.id} className="cursor-ponter bg-white rounded-lg shadow p-5 flex flex-col gap-2 border hover:shadow-lg transition" > @@ -100,12 +107,19 @@ return ( onClick={() => handleOpenDialog(item)} className="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded shadow transition" > -Chi tiết + Chi tiết + +
{item.youtubeLink ? ( Ngày đăng: {new Date(item.createdAt).toLocaleDateString()} + Người đăng: {item.uploadedBy?.fullName} @@ -188,6 +203,13 @@ Chi tiết
)}
+ + setOpenComment(null)} + videoId={openComment} +/> + ) } diff --git a/src/pages/dashboard/commentVideo.jsx b/src/pages/dashboard/commentVideo.jsx new file mode 100644 index 00000000..9222e199 --- /dev/null +++ b/src/pages/dashboard/commentVideo.jsx @@ -0,0 +1,142 @@ +// CommentVideo.jsx +import { useEffect, useState } from "react"; +import axios from "axios"; +import { + Dialog, DialogHeader, DialogBody, DialogFooter, + Card, Input, Button, Typography, Spinner, +} from "@material-tailwind/react"; + +/* ---------- Cấu hình ---------- */ +const BASE = "https://api-ndolv2.nongdanonline.vn/video-comment"; +const token = () => localStorage.getItem("token"); + +/* ---------- Hàm API ---------- */ +const fetchComments = (videoId) => + axios + .get(`${BASE}/${videoId}/comments`, { + headers: { Authorization: `Bearer ${token()}` }, + }) + .then((r) => r.data); + +const addComment = (videoId, body) => + axios.post(`${BASE}/${videoId}/comment`, body, { + headers: { Authorization: `Bearer ${token()}` }, + }); + +const replyComment = (videoId, commentIndex, body) => axios.post(`${BASE}/${videoId}/comment/${commentIndex}/reply`, body ,{ + headers: {Authorization: `Bearer ${token()}`}, + }) + +const hideComment = (videoId, commentIndex) => axios.delete(`${BASE}/${videoId}/comment/${commentIndex}`, { + headers: { Authorization: `Bearer ${token()}` }, +}) + +const hideReply = (videoId, commentIndex, replyIndex) => + axios.delete(`${BASE}/${videoId}/comment/${commentIndex}/reply/${replyIndex}`, { + headers: { Authorization: `Bearer ${token()}` }, + }); + +/* ---------- Component ---------- */ +export default function CommentVideo({ open, onClose, videoId }) { + const [comments, setComments] = useState([]); + const [loading, setLoading] = useState(true); + + const [newComment, setNewComment] = useState(""); + const [replyBox, setReplyBox] = useState({commentIndex: null, text: ""}) + + const load = () => { + if (!videoId) return; + setLoading(true); + fetchComments(videoId).then(setComments).finally(() => setLoading(false)); + }; + + useEffect(() => { + if (open) load(); + }, [open, videoId]); + + const handleAdd = async (e) => { + e.preventDefault(); + if (!newComment.trim()) return; + await addComment(videoId, { content: newComment }); + setNewComment(""); + load(); + }; + + + const handleReply = async (e) => { + e.preventDefault(); + if (!replyBox.text.trim()) return; + await replyComment(videoId, replyBox.idx, { content: replyBox.text }); + setReplyBox({ idx: null, text: "" }); + load(); + }; + + + const handleHideComment = async (idx) => { + await hideComment(videoId, idx); + load(); + }; + const handleHideReply = async (cIdx, rIdx) => { + await hideReply(videoId, cIdx, rIdx); + load(); + }; + + return ( + + Bình luận video + + + {/* Form nhập bình luận */} + +
+ setText(e.target.value)} + className="flex-1" + /> + +
+
+ + {/* Danh sách */} + {loading ? ( +
+ +
+ ) : comments.length === 0 ? ( + + Chưa có bình luận. + + ) : ( +
    + {comments.map((c, i) => ( +
  • + {c.author} + {c.content} + + {c.replies?.map((r, j) => ( +
    + {r.author}: + {r.content} +
    + ))} +
  • + ))} +
+ )} +
+ + + + +
+ ); +} diff --git a/src/widgets/layout/FarmForm.jsx b/src/widgets/layout/FarmForm.jsx deleted file mode 100644 index 5fc69bb9..00000000 --- a/src/widgets/layout/FarmForm.jsx +++ /dev/null @@ -1,116 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { - Dialog, - DialogHeader, - DialogBody, - DialogFooter, - Input, - Button, -} from "@material-tailwind/react"; - -const FarmForm = ({ open, onClose, initialData, onSubmit }) => { - const [formData, setFormData] = useState({ - name: "", - code: "", - location: "", - area: "", - pricePerMonth: "", - status: "pending", - ownerName: "", - ownerPhone: "", - }); - - useEffect(() => { - if (initialData) { - setFormData({ - name: initialData.name || "", - code: initialData.code || "", - location: initialData.location || "", - area: initialData.area?.toString() || "", - pricePerMonth: initialData.pricePerMonth?.toString() || "", - status: initialData.status || "pending", - ownerName: initialData.ownerInfo?.name || "", - ownerPhone: initialData.ownerInfo?.phone || "", - }); - } else { - setFormData({ - name: "", - code: "", - location: "", - area: "", - pricePerMonth: "", - status: "pending", - ownerName: "", - ownerPhone: "", - }); - } - }, [initialData]); - - const handleChange = (e) => { - const { name, value } = e.target; - setFormData((prev) => ({ - ...prev, - [name]: value, - })); - }; - - const handleSubmit = () => { - if (!formData.name || !formData.code) { - return alert("Vui lòng nhập đầy đủ thông tin."); - } - - const parsedData = { - name: formData.name, - code: formData.code, - location: formData.location, - area: formData.area === "" ? 0 : parseFloat(formData.area), - pricePerMonth: - formData.pricePerMonth === "" ? 0 : parseFloat(formData.pricePerMonth), - status: formData.status || "pending", - - ownerInfo: { - name: formData.ownerName || "Chưa rõ", - phone: formData.ownerPhone || "", - email: "", // Optional, bạn có thể thêm input nếu muốn - }, - coordinates: { - lat: 0, - lng: 0, - }, - features: [], - phone: "", - zalo: "", - operationTime: "", - defaultImage: "", - services: [], - }; - - console.log("parsedData gửi về:", parsedData); - - onSubmit(parsedData); - onClose(); - }; - - return ( - - {initialData ? "Chỉnh sửa" : "Thêm"} nông trại - - - - - - - - - - - - - - - ); -}; - -export default FarmForm \ No newline at end of file From d937c571d71ee76d63a445391fedac452da8a8cb Mon Sep 17 00:00:00 2001 From: trongquy1213 Date: Tue, 8 Jul 2025 15:07:02 +0700 Subject: [PATCH 074/248] qu --- package.json | 3 +- src/pages/dashboard/Questions/AddQuestion.jsx | 1 + src/pages/dashboard/Questions/Questions.jsx | 223 ++++++------------ .../{ => Questions}/answerstable.jsx | 0 src/pages/dashboard/farm/FarmForm.jsx | 121 ++++++++-- src/pages/dashboard/farm/farms.jsx | 151 +++++++----- src/pages/dashboard/index.js | 2 +- 7 files changed, 264 insertions(+), 237 deletions(-) rename src/pages/dashboard/{ => Questions}/answerstable.jsx (100%) diff --git a/package.json b/package.json index cb7c7fd9..a77c7ce3 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "react-apexcharts": "1.4.1", "react-dom": "18.2.0", "react-loader-spinner": "^6.1.6", - "react-router-dom": "6.17.0" + "react-router-dom": "6.17.0", + "react-select": "^5.10.1" }, "devDependencies": { "@types/react": "18.2.31", diff --git a/src/pages/dashboard/Questions/AddQuestion.jsx b/src/pages/dashboard/Questions/AddQuestion.jsx index 82278a52..f75d5af9 100644 --- a/src/pages/dashboard/Questions/AddQuestion.jsx +++ b/src/pages/dashboard/Questions/AddQuestion.jsx @@ -17,6 +17,7 @@ const AddQuestion = ({ > Thêm câu hỏi + {addDialog && (
diff --git a/src/pages/dashboard/Questions/Questions.jsx b/src/pages/dashboard/Questions/Questions.jsx index ebbe3e2e..540032ed 100644 --- a/src/pages/dashboard/Questions/Questions.jsx +++ b/src/pages/dashboard/Questions/Questions.jsx @@ -2,7 +2,16 @@ import React, { useEffect, useState } from 'react'; import axios from 'axios'; import { BaseUrl } from '@/ipconfig'; import { Oval } from 'react-loader-spinner'; -import AddQuestion from "./AddQuestion" +import AddQuestion from './AddQuestion'; +import AnswersTable from './answerstable'; +import { + Dialog, + DialogBody, + DialogHeader, + DialogFooter, + Button, +} from '@material-tailwind/react'; + export const Questions = () => { const [loading, setLoading] = useState(true); const [questions, setQuestions] = useState([]); @@ -13,6 +22,8 @@ export const Questions = () => { const [addDialog, setAddDialog] = useState(false); const [addValue, setAddValue] = useState({ text: '', options: [''], type: 'option', link: '' }); + const [showAnswersDialog, setShowAnswersDialog] = useState(false); + const tokenUser = localStorage.getItem('token'); const getData = async () => { @@ -71,7 +82,7 @@ export const Questions = () => { Array.isArray(editValue.options) && editValue.options.some(opt => !opt || opt.trim() === '') ) { - alert('Vui lòng điền đầy đủ tất cả các đáp án!'); + alert('Vui lòng điền đủ tất cả các đáp án!'); return; } else if (!editValue.text || editValue.text.trim() === '') { alert('Vui lòng nhập nội dung câu hỏi!'); @@ -85,7 +96,7 @@ export const Questions = () => { headers: { Authorization: `Bearer ${tokenUser}` }, } ); - alert("Lưu thành công") + alert("Lưu thành công"); handleCloseDialog(); getData(); } catch (error) { @@ -111,10 +122,12 @@ export const Questions = () => { setAddValue({ text: '', options: [''], type: 'option', link: '' }); setAddDialog(true); }; + const handleCloseAddDialog = () => { setAddDialog(false); setAddValue({ text: '', options: [''], type: 'option', link: '' }); }; + const handleAddChange = (e, idx) => { if (["option", "single-choice", "multiple-choice", "multi-choice"].includes(addValue.type) && typeof idx === "number") { const newOptions = [...addValue.options]; @@ -124,17 +137,16 @@ export const Questions = () => { setAddValue({ ...addValue, [e.target.name]: e.target.value }); } }; + const handleAddSave = async () => { if (!addValue.text || addValue.text.trim() === '') { alert('Vui lòng nhập nội dung câu hỏi!'); return; } if (["option", "single-choice", "multiple-choice", "multi-choice"].includes(addValue.type) && addValue.options.some(opt => !opt || opt.trim() === '')) { - alert('Vui lòng điền đầy đủ tất cả các đáp án!'); + alert('Vui lòng điền đủ tất cả các đáp án!'); return; } - - try { await axios.post(`${BaseUrl}/admin-questions`, addValue, { headers: { Authorization: `Bearer ${tokenUser}` }, @@ -147,19 +159,39 @@ export const Questions = () => { console.log('Lỗi khi thêm:', error); } }; + return (
-
- +
+ + +
+ setShowAnswersDialog(false)} size="xl"> + Danh sách câu trả lời + + + + + + + {loading ? (
@@ -179,9 +211,7 @@ setAddValue={setAddValue} Không có câu hỏi nào.
) : ( - questions.map((item) => ( -
- {[ "single-choice", "multiple-choice", "multi-choice"].includes(item.type) && Array.isArray(item.options) && item.options.length > 0 ? ( - item.options.map((opt, idx) => ( - - )) - ) : item.type === 'upload' ? ( - - ) : item.type === 'link' ? ( - - ) : item.type === 'text' ? ( - - ) : null} -
-
- )) -)} - - {openDialog && editData && ( -
-
-
Sửa câu hỏi
-
- - -
- {[ 'single-choice', 'multiple-choice', 'multi-choice'].includes( - editData.type - ) && Array.isArray(editValue.options) && ( -
- - {editValue.options.length === 0 && ( -
- Chưa có đáp án nào, hãy thêm đáp án mới. -
- )} - {editValue.options.map((opt, idx) => ( -
- - {String.fromCharCode(65 + idx)}. - - handleEditChange(e, idx)} - className="border px-3 py-2 rounded w-full" - placeholder={`Đáp án ${String.fromCharCode(65 + idx)}`} - /> - -
- ))} - -
- )} - {editData.type === 'link' && ( -
- + {["single-choice", "multiple-choice", "multi-choice"].includes(item.type) && Array.isArray(item.options) && item.options.length > 0 ? ( + item.options.map((opt, idx) => ( + + )) + ) : item.type === 'upload' ? ( -
- )} -
- - + ) : item.type === 'link' ? ( + + ) : item.type === 'text' ? ( + + ) : null}
-
+ )) )}
); diff --git a/src/pages/dashboard/answerstable.jsx b/src/pages/dashboard/Questions/answerstable.jsx similarity index 100% rename from src/pages/dashboard/answerstable.jsx rename to src/pages/dashboard/Questions/answerstable.jsx diff --git a/src/pages/dashboard/farm/FarmForm.jsx b/src/pages/dashboard/farm/FarmForm.jsx index e91dee1c..d56ba7a2 100644 --- a/src/pages/dashboard/farm/FarmForm.jsx +++ b/src/pages/dashboard/farm/FarmForm.jsx @@ -8,7 +8,47 @@ import { Button, Checkbox, } from "@material-tailwind/react"; +import Select from "react-select"; +// Tùy chọn dịch vụ +const serviceOptions = [ + { value: "direct_selling", label: "Bán hàng trực tiếp" }, + { value: "feed_selling", label: "Bán thức ăn chăn nuôi" }, + { value: "custom_feed_blending", label: "Pha trộn thức ăn theo yêu cầu" }, + { value: "processing_service", label: "Dịch vụ chế biến" }, + { value: "storage_service", label: "Dịch vụ lưu trữ" }, + { value: "transport_service", label: "Dịch vụ vận chuyển" }, + { value: "other_services", label: "Dịch vụ khác" }, +]; + +// Tùy chọn tính năng +const featureOptions = [ + { value: "aquaponic_model", label: "Mô hình Aquaponic" }, + { value: "ras_ready", label: "Hệ thống RAS" }, + { value: "hydroponic", label: "Thuỷ canh" }, + { value: "greenhouse", label: "Nhà kính" }, + { value: "vertical_farming", label: "Nông trại thẳng đứng" }, + { value: "viet_gap_cert", label: "Chứng nhận VietGAP" }, + { value: "organic_cert", label: "Chứng nhận hữu cơ" }, + { value: "global_gap_cert", label: "Chứng nhận GlobalGAP" }, + { value: "haccp_cert", label: "Chứng nhận HACCP" }, + { value: "camera_online", label: "Camera trực tuyến" }, + { value: "drone_monitoring", label: "Giám sát bằng drone" }, + { value: "automated_pest_detection", label: "Phát hiện sâu bệnh tự động" }, + { value: "precision_irrigation", label: "Tưới tiêu chính xác" }, + { value: "auto_irrigation", label: "Tưới tiêu tự động" }, + { value: "soil_based_irrigation", label: "Tưới theo độ ẩm đất" }, + { value: "iot_sensors", label: "Cảm biến IoT" }, + { value: "soil_moisture_monitoring", label: "Giám sát độ ẩm đất" }, + { value: "air_quality_sensor", label: "Cảm biến chất lượng không khí" }, +]; + +// Tags +const tagOptions = [ + { value: "nông sản", label: "nông sản" }, + { value: "hữu cơ", label: "hữu cơ" }, + { value: "mùa vụ", label: "mùa vụ" }, +]; const FarmForm = ({ open, onClose, initialData, onSubmit }) => { const [form, setForm] = useState({ @@ -32,42 +72,68 @@ const FarmForm = ({ open, onClose, initialData, onSubmit }) => { }); useEffect(() => { - if (initialData) { - setForm(initialData); - } + if (initialData) setForm(initialData); }, [initialData]); const handleChange = (field, value) => { - setForm((prev) => ({ ...prev, [field]: value})); + setForm((prev) => ({ ...prev, [field]: value })); }; - const handleArrayChange = (filed, value) => { - setForm((prev) => ({ - ... prev, [filed]:value.split(",").map((s) => s.trim()), - })); + const handleArrayChange = (field, value) => { + setForm((prev) => ({ ...prev, [field]: value })); }; const handleSubmit = (e) => { e.preventDefault(); onSubmit(form); onClose(); - - } + }; return ( - {initialData ? "Chỉnh sửa" : "Thêm"} nông trại - - handleChange ("code", e.target.value)} /> - handleChange ("name", e.target.value)} /> - handleChange ("location", e.target.value)} /> - handleChange ("area", e.target.value)} /> - handleChange ("cultivatedArea", e.target.value)} /> - handleArrayChange ("services", e.target.value)} /> - handleArrayChange ("features", e.target.value)} /> - handleArrayChange ("tags", e.target.value)} /> - handleChange('phone', e.target.value)} /> - handleChange('zalo', e.target.value)} /> + {initialData ? "Chỉnh sửa" : "Thêm"} nông trại + + + handleChange("name", e.target.value)} /> + handleChange("location", e.target.value)} /> + handleChange("area", e.target.value)} /> + handleChange("cultivatedArea", e.target.value)} /> + +
+ + featureOptions.find(opt => opt.value === val))} + onChange={(selected) => handleArrayChange("features", selected.map(s => s.value))} + /> +
+ +
+ + handleChange("phone", e.target.value)} /> + handleChange("zalo", e.target.value)} /> handleChange("province", e.target.value)} /> handleChange("district", e.target.value)} /> handleChange("ward", e.target.value)} /> @@ -75,9 +141,12 @@ const FarmForm = ({ open, onClose, initialData, onSubmit }) => { handleChange("ownerId", e.target.value)} /> handleChange("isAvailable", e.target.checked)} /> - - - + @@ -85,4 +154,4 @@ const FarmForm = ({ open, onClose, initialData, onSubmit }) => { ); }; -export default FarmForm \ No newline at end of file +export default FarmForm; diff --git a/src/pages/dashboard/farm/farms.jsx b/src/pages/dashboard/farm/farms.jsx index 86efbc82..7b386d1c 100644 --- a/src/pages/dashboard/farm/farms.jsx +++ b/src/pages/dashboard/farm/farms.jsx @@ -1,10 +1,11 @@ + import React, { useState, useEffect } from "react"; import axios from "axios"; import { Card, CardBody, Input, Button, Typography, Checkbox, Chip, Tabs, TabsHeader, Tab, } from "@material-tailwind/react"; import { - PencilSquareIcon, TrashIcon, PlusIcon, + PencilSquareIcon, TrashIcon, PlusIcon, LockOpenIcon ,LockClosedIcon, XCircleIcon, CheckCircleIcon } from "@heroicons/react/24/solid"; import FarmForm from "./FarmForm"; import FarmDetail from "./FarmDetail"; @@ -165,69 +166,93 @@ export function Farms() { size="sm" /> - -
- -
- - -
+ +
+ {/* Nhóm nút Sửa & Xoá */} +
+ + + +
+ + + {/* Nhóm nút trạng thái */} +
+ {farm.status === "pending" && ( + <> + + + + +)} + +{farm.status === "active" && ( + +)} + +{farm.status === "inactive" && ( + +)} + +
+
+ - -
- {farm.status === "pending" && ( - <> - - - - )} - {farm.status === "active" && ( - - )} - {farm.status === "inactive" && ( - - )} -
-
- ))} diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index 0cca8c8f..2d419c6f 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,6 +1,6 @@ export * from "@/pages/dashboard/home"; -export * from "@/pages/dashboard/answerstable"; +export * from "@/pages/dashboard/Questions/answerstable"; export * from "@/pages/dashboard/users"; export * from "@/pages/dashboard/farm/farms"; export * from "@/pages/dashboard/Questions/Questions"; From 014a95ed8f5a6cc9ceca1207d01c029f7d4470c2 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Tue, 8 Jul 2025 16:12:29 +0700 Subject: [PATCH 075/248] thanh --- src/pages/dashboard/commentVideo.jsx | 156 +++++++++++++++++++++------ 1 file changed, 124 insertions(+), 32 deletions(-) diff --git a/src/pages/dashboard/commentVideo.jsx b/src/pages/dashboard/commentVideo.jsx index 9222e199..6570da40 100644 --- a/src/pages/dashboard/commentVideo.jsx +++ b/src/pages/dashboard/commentVideo.jsx @@ -1,4 +1,4 @@ -// CommentVideo.jsx + import { useEffect, useState } from "react"; import axios from "axios"; import { @@ -6,11 +6,10 @@ import { Card, Input, Button, Typography, Spinner, } from "@material-tailwind/react"; -/* ---------- Cấu hình ---------- */ const BASE = "https://api-ndolv2.nongdanonline.vn/video-comment"; const token = () => localStorage.getItem("token"); -/* ---------- Hàm API ---------- */ + const fetchComments = (videoId) => axios .get(`${BASE}/${videoId}/comments`, { @@ -23,31 +22,36 @@ const addComment = (videoId, body) => headers: { Authorization: `Bearer ${token()}` }, }); -const replyComment = (videoId, commentIndex, body) => axios.post(`${BASE}/${videoId}/comment/${commentIndex}/reply`, body ,{ - headers: {Authorization: `Bearer ${token()}`}, - }) +const replyComment = (videoId, commentIndex, body) => + axios.post(`${BASE}/${videoId}/comment/${commentIndex}/reply`, body, { + headers: { Authorization: `Bearer ${token()}` }, + }); -const hideComment = (videoId, commentIndex) => axios.delete(`${BASE}/${videoId}/comment/${commentIndex}`, { +const hideComment = (videoId, commentIndex) => + axios.delete(`${BASE}/${videoId}/comment/${commentIndex}`, { headers: { Authorization: `Bearer ${token()}` }, -}) + }); const hideReply = (videoId, commentIndex, replyIndex) => axios.delete(`${BASE}/${videoId}/comment/${commentIndex}/reply/${replyIndex}`, { headers: { Authorization: `Bearer ${token()}` }, }); -/* ---------- Component ---------- */ + export default function CommentVideo({ open, onClose, videoId }) { const [comments, setComments] = useState([]); const [loading, setLoading] = useState(true); const [newComment, setNewComment] = useState(""); - const [replyBox, setReplyBox] = useState({commentIndex: null, text: ""}) + const [replyBox, setReplyBox] = useState({ idx: null, text: "" }); const load = () => { if (!videoId) return; setLoading(true); - fetchComments(videoId).then(setComments).finally(() => setLoading(false)); + fetchComments(videoId) + .then(setComments) + .catch((err) => console.error("Lỗi khi fetch comments:", err)) + .finally(() => setLoading(false)); }; useEffect(() => { @@ -56,29 +60,56 @@ export default function CommentVideo({ open, onClose, videoId }) { const handleAdd = async (e) => { e.preventDefault(); - if (!newComment.trim()) return; - await addComment(videoId, { content: newComment }); - setNewComment(""); - load(); + if (!newComment.trim()) { + alert("Bạn chưa nhập bình luận!"); + return; + } + try { + await addComment(videoId, { comment: newComment }); + setNewComment(""); + load(); + } catch (err) { + console.error("Lỗi khi gửi bình luận:", err.response?.data || err); + alert("Không thể gửi bình luận."); + } }; - const handleReply = async (e) => { e.preventDefault(); - if (!replyBox.text.trim()) return; - await replyComment(videoId, replyBox.idx, { content: replyBox.text }); - setReplyBox({ idx: null, text: "" }); - load(); + if (!replyBox.text.trim()) { + alert("Bạn chưa nhập phản hồi!"); + return; + } + try { + await replyComment(videoId, replyBox.idx, { comment: replyBox.text }); + setReplyBox({ idx: null, text: "" }); + load(); + } catch (err) { + console.error("Lỗi khi gửi phản hồi:", err.response?.data || err); + alert("Không thể gửi phản hồi."); + } }; - const handleHideComment = async (idx) => { - await hideComment(videoId, idx); - load(); + if (!window.confirm("Bạn có chắc muốn ẩn bình luận này?")) return; + try { + await hideComment(videoId, idx); + load(); + } catch (err) { + console.error("Lỗi khi ẩn bình luận:", err.response?.data || err); + alert("Không thể ẩn bình luận."); + } }; + const handleHideReply = async (cIdx, rIdx) => { - await hideReply(videoId, cIdx, rIdx); - load(); + if (!window.confirm("Bạn có chắc muốn ẩn phản hồi này?")) return; + try { + await hideReply(videoId, cIdx, rIdx); + load(); + } catch (err) { + console.error("Lỗi khi ẩn phản hồi:", err.response?.data || err); + alert("Không thể ẩn phản hồi."); + } }; return ( @@ -88,11 +119,11 @@ export default function CommentVideo({ open, onClose, videoId }) { {/* Form nhập bình luận */} -
+ setText(e.target.value)} + value={newComment} + onChange={(e) => setNewComment(e.target.value)} className="flex-1" /> + +
+
+ + {replyBox.idx === i && ( + + + setReplyBox((prev) => ({ ...prev, text: e.target.value })) + } + className="flex-1" + /> + + + )} {c.replies?.map((r, j) => (
- {r.author}: - {r.content} +
+ + {r.userId?.fullName || "Ẩn danh"}: + {r.comment} + +
+ + +
+
))} From b08a2f9f6a71b5a5cd6f8ee944864addf036d55a Mon Sep 17 00:00:00 2001 From: Tie902 Date: Tue, 8 Jul 2025 16:32:30 +0700 Subject: [PATCH 076/248] videolikelist --- src/components/LikeButton.jsx | 58 ++++ src/components/VideoLikeBox.jsx | 65 ++++ .../dashboard/VideoFarms/VideoDetail.jsx | 43 ++- .../dashboard/VideoFarms/VideoFarmById.jsx | 282 +++++++----------- src/pages/dashboard/VideoLikeList.jsx | 14 + src/routes.jsx | 7 +- 6 files changed, 292 insertions(+), 177 deletions(-) create mode 100644 src/components/LikeButton.jsx create mode 100644 src/components/VideoLikeBox.jsx create mode 100644 src/pages/dashboard/VideoLikeList.jsx diff --git a/src/components/LikeButton.jsx b/src/components/LikeButton.jsx new file mode 100644 index 00000000..a3beb9f9 --- /dev/null +++ b/src/components/LikeButton.jsx @@ -0,0 +1,58 @@ +// src/components/LikeButton.jsx +import React, { useState } from 'react'; +import axios from 'axios'; +import { BaseUrl } from '@/ipconfig'; +import { useNavigate } from 'react-router-dom'; + +export default function LikeButton({ videoId }) { + const [liked, setLiked] = useState(false); + const [loading, setLoading] = useState(false); + const token = localStorage.getItem('token'); + const navigate = useNavigate(); + + const handleLikeToggle = async () => { + if (!videoId || !token) return; + setLoading(true); + + try { + const url = liked + ? `${BaseUrl}/video-like/${videoId}/unlike` + : `${BaseUrl}/video-like/${videoId}/like`; + + await axios.post(url, {}, { + headers: { Authorization: `Bearer ${token}` }, + }); + + setLiked(!liked); + } catch (error) { + console.error('Lỗi khi Like/Unlike video:', error); + } finally { + setLoading(false); + } + }; + + const handleViewLikes = () => { + navigate(`/dashboard/video-like/${videoId}`); + }; + + return ( +
+ + + +
+ ); +} diff --git a/src/components/VideoLikeBox.jsx b/src/components/VideoLikeBox.jsx new file mode 100644 index 00000000..705fe3f2 --- /dev/null +++ b/src/components/VideoLikeBox.jsx @@ -0,0 +1,65 @@ +import React, { useEffect, useState } from 'react'; +import axios from 'axios'; +import { BaseUrl } from '@/ipconfig'; + +export default function VideoLikeBox({ videoId }) { + const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const token = localStorage.getItem('token'); + + useEffect(() => { + const fetchLikes = async () => { + if (!videoId || videoId === ':videoId') { + console.warn('videoId không hợp lệ:', videoId); + setLoading(false); + setError('Video ID không hợp lệ.'); + return; + } + + setLoading(true); + setError(''); + try { + const res = await axios.get(`${BaseUrl}/video-like/${videoId}/users`, { + headers: { Authorization: `Bearer ${token}` } + }); + + // Dữ liệu có thể là res.data.users hoặc res.data trực tiếp là mảng + const usersList = Array.isArray(res.data) + ? res.data + : res.data?.users || []; + + setUsers(usersList); + } catch (err) { + console.error('Lỗi khi lấy danh sách like:', err); + setError('Không thể lấy danh sách người like. Vui lòng thử lại sau.'); + } finally { + setLoading(false); + } + }; + + fetchLikes(); + }, [videoId, token]); + + return ( +
+

Người đã like video:

+ + {loading ? ( + Đang tải... + ) : error ? ( + {error} + ) : users.length === 0 ? ( + Chưa có ai like + ) : ( +
    + {users.map((user, idx) => ( +
  • + {user.fullName || user.username || 'Ẩn danh'} +
  • + ))} +
+ )} +
+ ); +} diff --git a/src/pages/dashboard/VideoFarms/VideoDetail.jsx b/src/pages/dashboard/VideoFarms/VideoDetail.jsx index b61c0899..4d485d42 100644 --- a/src/pages/dashboard/VideoFarms/VideoDetail.jsx +++ b/src/pages/dashboard/VideoFarms/VideoDetail.jsx @@ -1,10 +1,41 @@ -import React from 'react' +import React from 'react'; -export const VideoDetail = ({getDetailVideoInformation}) => { +export const VideoDetail = ({ getDetailVideoInformation }) => { + if (!getDetailVideoInformation) return null; return ( -
VideoDetail
- ) -} +
+
+ + +
+
+ + +
+
+ + +
+
+ Trạng thái: {getDetailVideoInformation.status} + Người đăng: {getDetailVideoInformation.uploadedBy} + Ngày tạo: {getDetailVideoInformation.createdAt && new Date(getDetailVideoInformation.createdAt).toLocaleString()} +
+
+ ); +}; -export default VideoDetail \ No newline at end of file +export default VideoDetail; diff --git a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx index 499288bf..a6e34864 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx @@ -1,194 +1,136 @@ -import React, { useEffect, useState } from 'react' -import axios from 'axios' -import { BaseUrl } from '@/ipconfig' -import { useParams } from 'react-router-dom' -import { Audio } from 'react-loader-spinner' +import React, { useEffect, useState } from 'react'; +import axios from 'axios'; +import { BaseUrl } from '@/ipconfig'; +import { useParams } from 'react-router-dom'; +import { Audio } from 'react-loader-spinner'; +import VideoDetail from './VideoDetail'; +import LikeButton from '@/components/LikeButton'; + export const VideoFarmById = () => { const [openDialog, setOpenDialog] = useState(false); - const [editData, setEditData] = useState(null); - const [editValue, setEditValue] = useState({ options: [] }); - const [loading, setLoading] = useState(true) - const [idVideo, setIdVideo] = useState([]) - const [videoDetail,setVideoDetail]=useState([]) + const [selectedVideo, setSelectedVideo] = useState(null); + const [videoList, setVideoList] = useState([]); + const [loading, setLoading] = useState(true); const tokenUser = localStorage.getItem('token'); - const {farmId}=useParams() - console.log(videoDetail) + const { farmId } = useParams(); + const getDetailVideo = async () => { try { - setLoading(true) + setLoading(true); const res = await axios.get(`${BaseUrl}/admin-video-farm/farm/${farmId}`, { headers: { Authorization: `Bearer ${tokenUser}` } }); if (res.status === 200) { - setVideoDetail(res.data) - setLoading(false) + setVideoList(res.data); } } catch (error) { - console.log("Lỗi nè:", error) - setLoading(false) - + console.log("Lỗi nè:", error); + } finally { + setLoading(false); } - } -const handleOpenDialog =(item)=>{ -setEditData(item) - setIdVideo(item._id) - setEditValue({ - title:item.title, - youtubeLink:item.youtubeLink, - playlistName:item.playlistName, - status:item.status, - uploadedBy:item.uploadedBy.fullName, - createdAt:item.createdAt, + }; - }) + const handleOpenDialog = (item) => { + setSelectedVideo({ + title: item.title, + youtubeLink: item.youtubeLink, + playlistName: item.playlistName, + status: item.status, + uploadedBy: item.uploadedBy?.fullName, + createdAt: item.createdAt + }); + setOpenDialog(true); + }; -setOpenDialog(true) -} + const handleCloseDialog = () => { + setSelectedVideo(null); + setOpenDialog(false); + }; -const handleCloseDialog =(item)=>{ - setEditData(null) - setEditValue({}) -setOpenDialog(false) -} -// const handleSaveEdit = async()=>{ -// try { - -// const res= await axios.post(`${BaseUrl}/admin-video-farm/upload-youtube/${idVideo}`, editValue,{ -// headers: { Authorization: `Bearer ${tokenUser}` }}) -// if(res.status===200){ -// alert("Cập nhật thành công") -// setTimeout(() => { -// getDetailVideo(); -// }, 500); -// console.log(res) -// handleCloseDialog() -// } -// } catch (error) { -// console.log("Lỗi nè",error) -// } -// } + useEffect(() => { + getDetailVideo(); + }, []); - useEffect(()=>{ -getDetailVideo() - },[]) + return ( +
+ {loading ? ( +
+
+ ) : videoList.length === 0 ? ( + Không có video nào. + ) : ( + videoList.map((item) => ( +
handleOpenDialog(item)} + className="cursor-pointer bg-white rounded-lg shadow p-5 flex flex-col gap-2 border hover:shadow-lg transition" + > + {item.title} + + {/* ✅ Thêm LikeButton */} +
+ +
-return ( -
- {loading ? ( -
-
- ) : videoDetail.length === 0 ? ( - Không có video nào. - ) : ( - videoDetail.map((item) => ( -
handleOpenDialog(item)} +
+ + +
- key={item.id} - className="cursor-ponter bg-white rounded-lg shadow p-5 flex flex-col gap-2 border hover:shadow-lg transition" - > - {item.title} -
- - + {item.youtubeLink ? ( + e.stopPropagation()} + > + ▶ Xem video + + ) : ( + Chưa có video + )} + Danh sách phát: {item.playlistName} + Ngày đăng: {new Date(item.createdAt).toLocaleDateString()} + Người đăng: {item.uploadedBy?.fullName}
- {item.youtubeLink ? ( - - ▶ Xem video - + )) + )} - ) : ( - Chưa có video - ) - } - - Danh sách phát: {item.playlistName} - - - Ngày đăng: {new Date(item.createdAt).toLocaleDateString()} - - - Người đăng: {item.uploadedBy?.fullName} - -
- )) - )} + {/* Dialog chi tiết video */} + {openDialog && selectedVideo && ( +
+
+
+
+

Thông tin video

+
- - {openDialog && ( -
-
-
-
-

Thông tin video

-
-
-
- - -
-
- - -
-
- - -
-
- Trạng thái: {editValue.status} - Người đăng: {editValue.uploadedBy} - Ngày tạo: {editValue.createdAt && new Date(editValue.createdAt).toLocaleString()} + + +
+ +
+
-
-
- -
+ )}
-
-)} -
-) -} + ); +}; -export default VideoFarmById \ No newline at end of file +export default VideoFarmById; diff --git a/src/pages/dashboard/VideoLikeList.jsx b/src/pages/dashboard/VideoLikeList.jsx new file mode 100644 index 00000000..5a85e596 --- /dev/null +++ b/src/pages/dashboard/VideoLikeList.jsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { useParams } from 'react-router-dom'; +import VideoLikeBox from '@/components/VideoLikeBox'; + +export default function VideoLikeList() { + const { videoId } = useParams(); + + return ( +
+

Danh sách người đã like video

+ +
+ ); +} diff --git a/src/routes.jsx b/src/routes.jsx index bdcfa02d..0de154fa 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -7,7 +7,7 @@ import { RectangleStackIcon, } from "@heroicons/react/24/solid"; - +import VideoLikeList from "@/pages/dashboard/VideoLikeList"; import { Home, Users , Farms, Questions, AnswersTable, VideoFarms } from "@/pages/dashboard"; @@ -64,6 +64,11 @@ export const routes = [ element: , }, + { // bạn dùng icon tuỳ ý + name: "Video Likes", + path: "/video-like/:videoId", + element: , +}, ], }, { From 9dcacdada335a5e5fd9309237b1d24764275fc23 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Tue, 8 Jul 2025 16:45:08 +0700 Subject: [PATCH 077/248] Update VideoFarmById.jsx --- src/pages/dashboard/VideoFarms/VideoFarmById.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx index 7f772813..403d6473 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx @@ -3,6 +3,7 @@ import axios from 'axios' import { BaseUrl } from '@/ipconfig' import { useParams } from 'react-router-dom' import { Audio } from 'react-loader-spinner' +import CommentVideo from '../commentVideo' export const VideoFarmById = () => { const [openDialog, setOpenDialog] = useState(false); const [editData, setEditData] = useState(null); @@ -20,7 +21,7 @@ export const VideoFarmById = () => { headers: { Authorization: `Bearer ${tokenUser}` } }); if (res.status === 200) { - setVideoList(res.data); + setVideoDetail(res.data); } } catch (error) { console.log("Lỗi nè:", error); From 7696fe92f4603beaf7eff3a5a274c31d526eee7f Mon Sep 17 00:00:00 2001 From: trongquy1213 Date: Tue, 8 Jul 2025 16:53:30 +0700 Subject: [PATCH 078/248] quy --- src/pages/dashboard/VideoFarms/VideoFarmById.jsx | 3 ++- src/pages/dashboard/VideoFarms/VideoFarms.jsx | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx index 9e61de59..96ec8f9b 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx @@ -3,7 +3,8 @@ import axios from 'axios' import { BaseUrl } from '@/ipconfig' import { useParams } from 'react-router-dom' import { Audio } from 'react-loader-spinner' -import CommentVideo from '../commentvideo' +import CommentVideo from "./commentVideo"; + export const VideoFarmById = () => { const [openDialog, setOpenDialog] = useState(false); const [editData, setEditData] = useState(null); diff --git a/src/pages/dashboard/VideoFarms/VideoFarms.jsx b/src/pages/dashboard/VideoFarms/VideoFarms.jsx index 3a22c5cc..ad6e08dc 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarms.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarms.jsx @@ -37,8 +37,6 @@ export const VideoFarms = () => { }); const farmList = Object.values(farmMap); - - const gotoVideoById=(farmId)=>{ navigate(`/dashboard/VideoFarmById/${farmId}`); } From 5a1b667f3a57e7cbe1ea223b8dc423a6cf485ebe Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Tue, 8 Jul 2025 17:01:40 +0700 Subject: [PATCH 079/248] Update VideoFarmById.jsx --- src/pages/dashboard/VideoFarms/VideoFarmById.jsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx index 403d6473..13c4f5cd 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx @@ -106,6 +106,12 @@ Chi tiết > Xóa +
{item.youtubeLink ? ( Date: Tue, 8 Jul 2025 17:02:49 +0700 Subject: [PATCH 080/248] Update VideoFarmById.jsx --- src/pages/dashboard/VideoFarms/VideoFarmById.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx index 47681b31..13c4f5cd 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx @@ -3,7 +3,6 @@ import axios from 'axios' import { BaseUrl } from '@/ipconfig' import { useParams } from 'react-router-dom' import { Audio } from 'react-loader-spinner' - import CommentVideo from '../commentVideo' export const VideoFarmById = () => { const [openDialog, setOpenDialog] = useState(false); From dc0482670d65ce0928a412e4350a40a070dd4117 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Tue, 8 Jul 2025 17:09:42 +0700 Subject: [PATCH 081/248] videolistlikeusers --- .../dashboard/VideoFarms/VideoFarmById.jsx | 265 +++++++----------- 1 file changed, 104 insertions(+), 161 deletions(-) diff --git a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx index 7f772813..05e6b2a6 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx @@ -1,15 +1,16 @@ -import React, { useEffect, useState } from 'react' -import axios from 'axios' -import { BaseUrl } from '@/ipconfig' -import { useParams } from 'react-router-dom' -import { Audio } from 'react-loader-spinner' +import React, { useEffect, useState } from 'react'; +import axios from 'axios'; +import { BaseUrl } from '@/ipconfig'; +import { useParams } from 'react-router-dom'; +import { Audio } from 'react-loader-spinner'; +import VideoDetail from './VideoDetail'; +import LikeButton from '@/components/LikeButton'; + export const VideoFarmById = () => { const [openDialog, setOpenDialog] = useState(false); - const [editData, setEditData] = useState(null); - const [editValue, setEditValue] = useState({ options: [] }); - const [loading, setLoading] = useState(true) - const [idVideo, setIdVideo] = useState([]) - const [videoDetail,setVideoDetail]=useState([]) + const [selectedVideo, setSelectedVideo] = useState(null); + const [videoList, setVideoList] = useState([]); + const [loading, setLoading] = useState(true); const tokenUser = localStorage.getItem('token'); const { farmId } = useParams(); @@ -27,167 +28,109 @@ export const VideoFarmById = () => { } finally { setLoading(false); } - } -const handleOpenDialog =(item)=>{ -setEditData(item) - setIdVideo(item._id) - setEditValue({ - title:item.title, - youtubeLink:item.youtubeLink, - playlistName:item.playlistName, - status:item.status, - uploadedBy:item.uploadedBy.fullName, - createdAt:item.createdAt, + }; + + const handleOpenDialog = (item) => { + setSelectedVideo({ + title: item.title, + youtubeLink: item.youtubeLink, + playlistName: item.playlistName, + status: item.status, + uploadedBy: item.uploadedBy?.fullName, + createdAt: item.createdAt + }); + setOpenDialog(true); + }; - }) + const handleCloseDialog = () => { + setSelectedVideo(null); + setOpenDialog(false); + }; -setOpenDialog(true) -} + useEffect(() => { + getDetailVideo(); + }, []); -const handleCloseDialog =(item)=>{ - setEditData(null) - setEditValue({}) -setOpenDialog(false) -} -// const handleSaveEdit = async()=>{ -// try { - -// const res= await axios.post(`${BaseUrl}/admin-video-farm/upload-youtube/${idVideo}`, editValue,{ -// headers: { Authorization: `Bearer ${tokenUser}` }}) -// if(res.status===200){ -// alert("Cập nhật thành công") -// setTimeout(() => { -// getDetailVideo(); -// }, 500); -// console.log(res) -// handleCloseDialog() -// } -// } catch (error) { -// console.log("Lỗi nè",error) -// } -// } + return ( +
+ {loading ? ( +
+
+ ) : videoList.length === 0 ? ( + Không có video nào. + ) : ( + videoList.map((item) => ( +
handleOpenDialog(item)} + className="cursor-pointer bg-white rounded-lg shadow p-5 flex flex-col gap-2 border hover:shadow-lg transition" + > + {item.title} - useEffect(()=>{ -getDetailVideo() - },[]) + {/* ✅ Thêm LikeButton với danh sách like*/} +
+ +
-return ( -
- {loading ? ( -
-
- ) : videoDetail.length === 0 ? ( - Không có video nào. - ) : ( - videoDetail.map((item) => ( -
handleOpenDialog(item)} +
+ + +
- key={item.id} - className="cursor-ponter bg-white rounded-lg shadow p-5 flex flex-col gap-2 border hover:shadow-lg transition" - > - {item.title} -
- - + {item.youtubeLink ? ( + e.stopPropagation()} + > + ▶ Xem video + + ) : ( + Chưa có video + )} + Danh sách phát: {item.playlistName} + Ngày đăng: {new Date(item.createdAt).toLocaleDateString()} + Người đăng: {item.uploadedBy?.fullName}
- {item.youtubeLink ? ( - - ▶ Xem video - + )) + )} - ) : ( - Chưa có video - ) - } - - Danh sách phát: {item.playlistName} - - - Ngày đăng: {new Date(item.createdAt).toLocaleDateString()} - - - Người đăng: {item.uploadedBy?.fullName} - -
- )) - )} + {/* Dialog chi tiết video */} + {openDialog && selectedVideo && ( +
+
+
+
+

Thông tin video

+
- - {openDialog && ( -
-
-
-
-

Thông tin video

-
-
-
- - -
-
- - -
-
- - -
-
- Trạng thái: {editValue.status} - Người đăng: {editValue.uploadedBy} - Ngày tạo: {editValue.createdAt && new Date(editValue.createdAt).toLocaleString()} + + +
+ +
+
-
-
- -
+ )}
-
-)} -
-) -} + ); +}; export default VideoFarmById; From 61cf1b73a1087b5600facb1a7cbc86462323ab00 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Tue, 8 Jul 2025 17:11:11 +0700 Subject: [PATCH 082/248] codetestlist like --- src/routes.jsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/routes.jsx b/src/routes.jsx index 0de154fa..cb24187a 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -64,11 +64,6 @@ export const routes = [ element: , }, - { // bạn dùng icon tuỳ ý - name: "Video Likes", - path: "/video-like/:videoId", - element: , -}, ], }, { From c46c045fff4fa94c3654921f606e897786cab452 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Tue, 8 Jul 2025 17:16:30 +0700 Subject: [PATCH 083/248] Update VideoFarmById.jsx --- .../dashboard/VideoFarms/VideoFarmById.jsx | 37 ++----------------- 1 file changed, 4 insertions(+), 33 deletions(-) diff --git a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx index 6ab64c2c..35253d71 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx @@ -1,11 +1,6 @@ -<<<<<<< HEAD -import React, { useEffect, useState } from 'react' -import axios from 'axios' -import { BaseUrl } from '@/ipconfig' -import { useParams } from 'react-router-dom' -import { Audio } from 'react-loader-spinner' + + import CommentVideo from '../commentVideo' -======= import React, { useEffect, useState } from 'react'; import axios from 'axios'; import { BaseUrl } from '@/ipconfig'; @@ -14,7 +9,7 @@ import { Audio } from 'react-loader-spinner'; import VideoDetail from './VideoDetail'; import LikeButton from '@/components/LikeButton'; ->>>>>>> tien + export const VideoFarmById = () => { const [openDialog, setOpenDialog] = useState(false); const [selectedVideo, setSelectedVideo] = useState(null); @@ -81,30 +76,6 @@ export const VideoFarmById = () => {
- -<<<<<<< HEAD - key={item.id} - className="cursor-ponter bg-white rounded-lg shadow p-5 flex flex-col gap-2 border hover:shadow-lg transition" - > - {item.title} -
- - - -=======
Danh sách phát: {item.playlistName} Ngày đăng: {new Date(item.createdAt).toLocaleDateString()} Người đăng: {item.uploadedBy?.fullName} ->>>>>>> tien +
)) )} From 5c5174fcdb48987aff99a193c5bff153afa1ccf0 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Tue, 8 Jul 2025 17:18:50 +0700 Subject: [PATCH 084/248] Update VideoFarmById.jsx --- src/pages/dashboard/VideoFarms/VideoFarmById.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx index 35253d71..5fbfad64 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx @@ -25,7 +25,7 @@ export const VideoFarmById = () => { headers: { Authorization: `Bearer ${tokenUser}` } }); if (res.status === 200) { - setVideoDetail(res.data); + setVideoList(res.data); } } catch (error) { console.log("Lỗi nè:", error); From 45bb425fa10685354a3e27d2b83a8af9d475adea Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Tue, 8 Jul 2025 18:02:32 +0700 Subject: [PATCH 085/248] Update VideoFarmById.jsx --- .../dashboard/VideoFarms/VideoFarmById.jsx | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx index 5fbfad64..09c7ca9d 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx @@ -1,6 +1,4 @@ - -import CommentVideo from '../commentVideo' import React, { useEffect, useState } from 'react'; import axios from 'axios'; import { BaseUrl } from '@/ipconfig'; @@ -8,11 +6,14 @@ import { useParams } from 'react-router-dom'; import { Audio } from 'react-loader-spinner'; import VideoDetail from './VideoDetail'; import LikeButton from '@/components/LikeButton'; +import CommentVideo from '../commentVideo'; export const VideoFarmById = () => { const [openDialog, setOpenDialog] = useState(false); const [selectedVideo, setSelectedVideo] = useState(null); + const [openComment, setOpenComment] = useState(false); + const [selectedVideoId, setSelectedVideoId] = useState(null); const [videoList, setVideoList] = useState([]); const [loading, setLoading] = useState(true); const tokenUser = localStorage.getItem('token'); @@ -51,6 +52,16 @@ export const VideoFarmById = () => { setOpenDialog(false); }; +const handleOpenComment = (e, videoId) => { + e.stopPropagation(); + setSelectedVideoId(videoId); + setOpenComment(true); +}; + const handleCloseComment = () => { + setSelectedVideoId(null); + setOpenComment(false); + }; + useEffect(() => { getDetailVideo(); }, []); @@ -67,13 +78,13 @@ export const VideoFarmById = () => { videoList.map((item) => (
handleOpenDialog(item)} + onClick={(e) => { e.stopPropagation(); handleOpenDialog(item)}} className="cursor-pointer bg-white rounded-lg shadow p-5 flex flex-col gap-2 border hover:shadow-lg transition" > {item.title} {/* ✅ Thêm LikeButton với danh sách like*/} -
+
e.stopPropagation()}>
@@ -88,6 +99,12 @@ export const VideoFarmById = () => { > Xóa +
{item.youtubeLink ? ( @@ -133,6 +150,13 @@ export const VideoFarmById = () => {
)} + {openComment && ( + + )}
); }; From b0a77c0fa20d306004020757346de8ce0cb8bbad Mon Sep 17 00:00:00 2001 From: Tie902 Date: Tue, 8 Jul 2025 19:19:18 +0700 Subject: [PATCH 086/248] videolistlike users --- src/components/LikeButton.jsx | 55 ++++++--------- src/components/VideoLikeBox.jsx | 68 ++++++++----------- .../dashboard/VideoFarms/VideoFarmById.jsx | 45 ++++++++---- src/pages/dashboard/VideoLikeList.jsx | 5 +- 4 files changed, 85 insertions(+), 88 deletions(-) diff --git a/src/components/LikeButton.jsx b/src/components/LikeButton.jsx index a3beb9f9..d5ca8c0e 100644 --- a/src/components/LikeButton.jsx +++ b/src/components/LikeButton.jsx @@ -1,58 +1,45 @@ -// src/components/LikeButton.jsx import React, { useState } from 'react'; import axios from 'axios'; import { BaseUrl } from '@/ipconfig'; -import { useNavigate } from 'react-router-dom'; -export default function LikeButton({ videoId }) { +export default function LikeButton({ videoId, onLikeChange }) { const [liked, setLiked] = useState(false); const [loading, setLoading] = useState(false); const token = localStorage.getItem('token'); - const navigate = useNavigate(); const handleLikeToggle = async () => { if (!videoId || !token) return; setLoading(true); try { - const url = liked - ? `${BaseUrl}/video-like/${videoId}/unlike` - : `${BaseUrl}/video-like/${videoId}/like`; - - await axios.post(url, {}, { - headers: { Authorization: `Bearer ${token}` }, - }); + if (liked) { + await axios.post(`${BaseUrl}/video-like/${videoId}/unlike`, {}, { + headers: { Authorization: `Bearer ${token}` }, + }); + } else { + await axios.post(`${BaseUrl}/video-like/${videoId}`, {}, { + headers: { Authorization: `Bearer ${token}` }, + }); + } setLiked(!liked); + onLikeChange?.(); } catch (error) { - console.error('Lỗi khi Like/Unlike video:', error); + console.error('Lỗi Like/Unlike:', error); } finally { setLoading(false); } }; - const handleViewLikes = () => { - navigate(`/dashboard/video-like/${videoId}`); - }; - return ( -
- - - -
+ ); } diff --git a/src/components/VideoLikeBox.jsx b/src/components/VideoLikeBox.jsx index 705fe3f2..aa1c1215 100644 --- a/src/components/VideoLikeBox.jsx +++ b/src/components/VideoLikeBox.jsx @@ -1,58 +1,50 @@ +// src/components/VideoLikeBox.jsx import React, { useEffect, useState } from 'react'; import axios from 'axios'; import { BaseUrl } from '@/ipconfig'; -export default function VideoLikeBox({ videoId }) { +export default function VideoLikeBox({ videoId, refreshKey }) { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const token = localStorage.getItem('token'); - useEffect(() => { - const fetchLikes = async () => { - if (!videoId || videoId === ':videoId') { - console.warn('videoId không hợp lệ:', videoId); - setLoading(false); - setError('Video ID không hợp lệ.'); - return; - } - - setLoading(true); - setError(''); - try { - const res = await axios.get(`${BaseUrl}/video-like/${videoId}/users`, { - headers: { Authorization: `Bearer ${token}` } - }); - - // Dữ liệu có thể là res.data.users hoặc res.data trực tiếp là mảng - const usersList = Array.isArray(res.data) - ? res.data - : res.data?.users || []; - - setUsers(usersList); - } catch (err) { - console.error('Lỗi khi lấy danh sách like:', err); - setError('Không thể lấy danh sách người like. Vui lòng thử lại sau.'); - } finally { - setLoading(false); - } - }; + const fetchLikes = async () => { + if (!videoId || videoId === ':videoId') return; + setLoading(true); + try { + const res = await axios.get(`${BaseUrl}/video-like/${videoId}/users`, { + headers: { Authorization: `Bearer ${token}` }, + }); + + const usersList = Array.isArray(res.data) + ? res.data + : res.data?.users || []; + + setUsers(usersList); + } catch (err) { + console.error('Lỗi khi lấy danh sách like:', err); + setError('Không thể lấy danh sách người like.'); + } finally { + setLoading(false); + } + }; + useEffect(() => { fetchLikes(); - }, [videoId, token]); + }, [videoId, refreshKey]); return ( -
-

Người đã like video:

- +
+

Người đã like:

{loading ? ( - Đang tải... + Đang tải... ) : error ? ( - {error} + {error} ) : users.length === 0 ? ( - Chưa có ai like + Chưa có ai like ) : ( -
    +
      {users.map((user, idx) => (
    • {user.fullName || user.username || 'Ẩn danh'} diff --git a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx index 05e6b2a6..c835b443 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx @@ -5,6 +5,7 @@ import { useParams } from 'react-router-dom'; import { Audio } from 'react-loader-spinner'; import VideoDetail from './VideoDetail'; import LikeButton from '@/components/LikeButton'; +import VideoLikeBox from '@/components/VideoLikeBox'; export const VideoFarmById = () => { const [openDialog, setOpenDialog] = useState(false); @@ -18,13 +19,13 @@ export const VideoFarmById = () => { try { setLoading(true); const res = await axios.get(`${BaseUrl}/admin-video-farm/farm/${farmId}`, { - headers: { Authorization: `Bearer ${tokenUser}` } + headers: { Authorization: `Bearer ${tokenUser}` }, }); if (res.status === 200) { setVideoList(res.data); } } catch (error) { - console.log("Lỗi nè:", error); + console.log("Lỗi:", error); } finally { setLoading(false); } @@ -37,7 +38,7 @@ export const VideoFarmById = () => { playlistName: item.playlistName, status: item.status, uploadedBy: item.uploadedBy?.fullName, - createdAt: item.createdAt + createdAt: item.createdAt, }); setOpenDialog(true); }; @@ -68,21 +69,23 @@ export const VideoFarmById = () => { > {item.title} - {/* ✅ Thêm LikeButton với danh sách like*/} -
      - +
      +
      + +
      -
      @@ -100,20 +103,32 @@ export const VideoFarmById = () => { ) : ( Chưa có video )} - Danh sách phát: {item.playlistName} - Ngày đăng: {new Date(item.createdAt).toLocaleDateString()} - Người đăng: {item.uploadedBy?.fullName} + + Danh sách phát:{' '} + {item.playlistName} + + + Ngày đăng:{' '} + + {new Date(item.createdAt).toLocaleDateString()} + + + + Người đăng:{' '} + {item.uploadedBy?.fullName} +
      )) )} - {/* Dialog chi tiết video */} {openDialog && selectedVideo && (
      -

      Thông tin video

      +

      + Thông tin video +

      diff --git a/src/pages/dashboard/VideoLikeList.jsx b/src/pages/dashboard/VideoLikeList.jsx index 5a85e596..f3b06750 100644 --- a/src/pages/dashboard/VideoLikeList.jsx +++ b/src/pages/dashboard/VideoLikeList.jsx @@ -1,3 +1,4 @@ +// src/pages/VideoLikeList.jsx import React from 'react'; import { useParams } from 'react-router-dom'; import VideoLikeBox from '@/components/VideoLikeBox'; @@ -5,9 +6,11 @@ import VideoLikeBox from '@/components/VideoLikeBox'; export default function VideoLikeList() { const { videoId } = useParams(); + console.log('Video ID:', videoId); + return (
      -

      Danh sách người đã like video

      +

      Danh sách người đã Like video

      ); From 7829098f957a62cc4777cfdbc96c8d2dd5e7833f Mon Sep 17 00:00:00 2001 From: Tie902 Date: Tue, 8 Jul 2025 19:49:27 +0700 Subject: [PATCH 087/248] Update LikeButton.jsx --- src/components/LikeButton.jsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/components/LikeButton.jsx b/src/components/LikeButton.jsx index a3beb9f9..7330da59 100644 --- a/src/components/LikeButton.jsx +++ b/src/components/LikeButton.jsx @@ -15,6 +15,7 @@ export default function LikeButton({ videoId }) { setLoading(true); try { +<<<<<<< Updated upstream const url = liked ? `${BaseUrl}/video-like/${videoId}/unlike` : `${BaseUrl}/video-like/${videoId}/like`; @@ -24,6 +25,22 @@ export default function LikeButton({ videoId }) { }); setLiked(!liked); +======= + if (liked) { + // Unlike + await axios.post(`${BaseUrl}/video-like/${videoId}/unlike`, {}, { + headers: { Authorization: `Bearer ${token}` }, + }); + } else { + // Like + await axios.post(`${BaseUrl}/video-like/${videoId}`, {}, { + headers: { Authorization: `Bearer ${token}` }, + }); + } + + setLiked(!liked); + onLikeChange?.(); // gọi callback reload danh sách user +>>>>>>> Stashed changes } catch (error) { console.error('Lỗi khi Like/Unlike video:', error); } finally { From a114c58073bf12da72def3a5351ce6993fe899a9 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Tue, 8 Jul 2025 19:49:48 +0700 Subject: [PATCH 088/248] naf --- src/components/LikeButton.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/LikeButton.jsx b/src/components/LikeButton.jsx index 7330da59..60c4d47a 100644 --- a/src/components/LikeButton.jsx +++ b/src/components/LikeButton.jsx @@ -40,6 +40,9 @@ export default function LikeButton({ videoId }) { setLiked(!liked); onLikeChange?.(); // gọi callback reload danh sách user +<<<<<<< Updated upstream +>>>>>>> Stashed changes +======= >>>>>>> Stashed changes } catch (error) { console.error('Lỗi khi Like/Unlike video:', error); From ffe78473f9861ba7aa2b8291c5632e748e3db499 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Tue, 8 Jul 2025 19:50:02 +0700 Subject: [PATCH 089/248] naf --- src/components/LikeButton.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/LikeButton.jsx b/src/components/LikeButton.jsx index 60c4d47a..758962d9 100644 --- a/src/components/LikeButton.jsx +++ b/src/components/LikeButton.jsx @@ -41,6 +41,9 @@ export default function LikeButton({ videoId }) { setLiked(!liked); onLikeChange?.(); // gọi callback reload danh sách user <<<<<<< Updated upstream +<<<<<<< Updated upstream +>>>>>>> Stashed changes +======= >>>>>>> Stashed changes ======= >>>>>>> Stashed changes From e2efe4fff46f7df77dd8d7e3234a0f97e00eed59 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Tue, 8 Jul 2025 20:02:00 +0700 Subject: [PATCH 090/248] naf --- src/components/LikeButton.jsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/components/LikeButton.jsx b/src/components/LikeButton.jsx index 758962d9..097c4d80 100644 --- a/src/components/LikeButton.jsx +++ b/src/components/LikeButton.jsx @@ -15,7 +15,7 @@ export default function LikeButton({ videoId }) { setLoading(true); try { -<<<<<<< Updated upstream + const url = liked ? `${BaseUrl}/video-like/${videoId}/unlike` : `${BaseUrl}/video-like/${videoId}/like`; @@ -25,7 +25,6 @@ export default function LikeButton({ videoId }) { }); setLiked(!liked); -======= if (liked) { // Unlike await axios.post(`${BaseUrl}/video-like/${videoId}/unlike`, {}, { @@ -40,13 +39,6 @@ export default function LikeButton({ videoId }) { setLiked(!liked); onLikeChange?.(); // gọi callback reload danh sách user -<<<<<<< Updated upstream -<<<<<<< Updated upstream ->>>>>>> Stashed changes -======= ->>>>>>> Stashed changes -======= ->>>>>>> Stashed changes } catch (error) { console.error('Lỗi khi Like/Unlike video:', error); } finally { From cd50106e5ee8f7718aacbb177df91532c2cfa7f0 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Tue, 8 Jul 2025 20:53:06 +0700 Subject: [PATCH 091/248] Update LikeButton.jsx --- src/components/LikeButton.jsx | 66 ++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/src/components/LikeButton.jsx b/src/components/LikeButton.jsx index 758962d9..69d0d248 100644 --- a/src/components/LikeButton.jsx +++ b/src/components/LikeButton.jsx @@ -1,5 +1,4 @@ -// src/components/LikeButton.jsx -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import axios from 'axios'; import { BaseUrl } from '@/ipconfig'; import { useNavigate } from 'react-router-dom'; @@ -9,46 +8,55 @@ export default function LikeButton({ videoId }) { const [loading, setLoading] = useState(false); const token = localStorage.getItem('token'); const navigate = useNavigate(); + const userId = JSON.parse(localStorage.getItem('user'))?.id; + + const checkLikedStatus = async () => { + if (!videoId || !token) return; + try { + const res = await axios.get(`${BaseUrl}/video-like/${videoId}/users`, { + headers: { Authorization: `Bearer ${token}` }, + }); + + console.log("API trả về:", res.data); + + // Nếu API trả về { users: [...] } hoặc trực tiếp là mảng + const likedUsers = res.data.users || res.data || []; + + const isLiked = likedUsers.some((u) => u._id === userId || u.id === userId); + setLiked(isLiked); + } catch (error) { + console.error('Lỗi khi check trạng thái Like:', error.response?.data || error); + } + }; + + useEffect(() => { + checkLikedStatus(); + }, [videoId]); const handleLikeToggle = async () => { if (!videoId || !token) return; + + // Tạm thời đảo trạng thái để UI phản hồi nhanh + const previousLiked = liked; + setLiked(!liked); setLoading(true); try { -<<<<<<< Updated upstream - const url = liked + const url = previousLiked ? `${BaseUrl}/video-like/${videoId}/unlike` : `${BaseUrl}/video-like/${videoId}/like`; + console.log("Gọi API:", url); + await axios.post(url, {}, { headers: { Authorization: `Bearer ${token}` }, }); - - setLiked(!liked); -======= - if (liked) { - // Unlike - await axios.post(`${BaseUrl}/video-like/${videoId}/unlike`, {}, { - headers: { Authorization: `Bearer ${token}` }, - }); - } else { - // Like - await axios.post(`${BaseUrl}/video-like/${videoId}`, {}, { - headers: { Authorization: `Bearer ${token}` }, - }); - } - - setLiked(!liked); - onLikeChange?.(); // gọi callback reload danh sách user -<<<<<<< Updated upstream -<<<<<<< Updated upstream ->>>>>>> Stashed changes -======= ->>>>>>> Stashed changes -======= ->>>>>>> Stashed changes } catch (error) { - console.error('Lỗi khi Like/Unlike video:', error); + console.error('Lỗi khi Like/Unlike video:', error.response?.data || error); + alert('Không thể Like/Unlike video.'); + + // Nếu lỗi thì đảo lại trạng thái + setLiked(previousLiked); } finally { setLoading(false); } From 3abca2176cfd1f9665ec3a9637a27733c9f09ebd Mon Sep 17 00:00:00 2001 From: Tie902 Date: Tue, 8 Jul 2025 20:57:15 +0700 Subject: [PATCH 092/248] khoqua --- src/components/LikeButton.jsx | 28 +++++++++++++ src/pages/dashboard/VideoLikeList.jsx | 59 +++++++++++++++++++++++++++ src/routes.jsx | 19 +++------ 3 files changed, 93 insertions(+), 13 deletions(-) diff --git a/src/components/LikeButton.jsx b/src/components/LikeButton.jsx index 097c4d80..4aaf744b 100644 --- a/src/components/LikeButton.jsx +++ b/src/components/LikeButton.jsx @@ -1,4 +1,8 @@ // src/components/LikeButton.jsx +<<<<<<< Updated upstream +======= + +>>>>>>> Stashed changes import React, { useState } from 'react'; import axios from 'axios'; import { BaseUrl } from '@/ipconfig'; @@ -26,6 +30,7 @@ export default function LikeButton({ videoId }) { setLiked(!liked); if (liked) { +<<<<<<< Updated upstream // Unlike await axios.post(`${BaseUrl}/video-like/${videoId}/unlike`, {}, { headers: { Authorization: `Bearer ${token}` }, @@ -35,10 +40,29 @@ export default function LikeButton({ videoId }) { await axios.post(`${BaseUrl}/video-like/${videoId}`, {}, { headers: { Authorization: `Bearer ${token}` }, }); +======= + // ✅ GỌI API UNLIKE CHUẨN BE + await axios.post( + `${BaseUrl}/video-like/${videoId}/unlike`, + {}, + { headers: { Authorization: `Bearer ${token}` } } + ); + } else { + // ✅ GỌI API LIKE CHUẨN BE + await axios.post( + `${BaseUrl}/video-like/${videoId}/like`, + {}, + { headers: { Authorization: `Bearer ${token}` } } + ); +>>>>>>> Stashed changes } + // Toggle trạng thái setLiked(!liked); +<<<<<<< Updated upstream onLikeChange?.(); // gọi callback reload danh sách user +======= +>>>>>>> Stashed changes } catch (error) { console.error('Lỗi khi Like/Unlike video:', error); } finally { @@ -47,6 +71,10 @@ export default function LikeButton({ videoId }) { }; const handleViewLikes = () => { +<<<<<<< Updated upstream +======= + // Điều hướng đến trang hiển thị danh sách users đã Like +>>>>>>> Stashed changes navigate(`/dashboard/video-like/${videoId}`); }; diff --git a/src/pages/dashboard/VideoLikeList.jsx b/src/pages/dashboard/VideoLikeList.jsx index 5a85e596..0dabe95d 100644 --- a/src/pages/dashboard/VideoLikeList.jsx +++ b/src/pages/dashboard/VideoLikeList.jsx @@ -1,14 +1,73 @@ +<<<<<<< Updated upstream import React from 'react'; import { useParams } from 'react-router-dom'; import VideoLikeBox from '@/components/VideoLikeBox'; +======= +import React, { useEffect, useState } from 'react'; +import axios from 'axios'; +import { BaseUrl } from '@/ipconfig'; +import { useParams, useNavigate } from 'react-router-dom'; +import { Audio } from 'react-loader-spinner'; +>>>>>>> Stashed changes export default function VideoLikeList() { const { videoId } = useParams(); + const [likes, setLikes] = useState([]); + const [loading, setLoading] = useState(true); + const token = localStorage.getItem('token'); + const navigate = useNavigate(); +<<<<<<< Updated upstream return (

      Danh sách người đã like video

      +======= + const getLikes = async () => { + try { + setLoading(true); + const res = await axios.post(`${BaseUrl}/video-like/${videoId}/like`, {}, { + headers: { Authorization: `Bearer ${token}` }, + }); + setLikes(res.data?.data || []); + } catch (error) { + console.error('Lỗi lấy danh sách Like:', error); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + getLikes(); + }, []); + + return ( +
      +

      Danh sách người đã Like

      + + {loading ? ( +
      +
      + ) : likes.length === 0 ? ( +

      Chưa có ai Like video này.

      + ) : ( +
        + {likes.map((user, index) => ( +
      • + {user.fullName} +
      • + ))} +
      + )} + + +>>>>>>> Stashed changes
      ); } diff --git a/src/routes.jsx b/src/routes.jsx index cb24187a..46763f9d 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -8,12 +8,7 @@ import { } from "@heroicons/react/24/solid"; import VideoLikeList from "@/pages/dashboard/VideoLikeList"; - -import { Home, Users , Farms, Questions, AnswersTable, VideoFarms } from "@/pages/dashboard"; - - - - +import { Home, Users, Farms, Questions, AnswersTable, VideoFarms } from "@/pages/dashboard"; import { SignIn, SignUp } from "@/pages/auth"; import { ViewfinderCircleIcon } from "@heroicons/react/24/outline"; @@ -43,27 +38,25 @@ export const routes = [ path: "/Farms", element: , }, - - { + { icon: , name: "Questions", path: "/Questions", - element: , + element: , }, - { icon: , name: "AnswersTable", path: "/AnswersTable", - element: , + element: , }, - { + { icon: , name: "VideoFarms", path: "/VideoFarms", element: , - }, + ], }, { From 904741e591f2f16db266a610a579bb16d4b6b1bf Mon Sep 17 00:00:00 2001 From: Tie902 Date: Tue, 8 Jul 2025 20:58:19 +0700 Subject: [PATCH 093/248] khoqua --- .../dashboard/VideoFarms/VideoFarmById.jsx | 77 +++++++++++-------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx index c835b443..09c7ca9d 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx @@ -1,3 +1,4 @@ + import React, { useEffect, useState } from 'react'; import axios from 'axios'; import { BaseUrl } from '@/ipconfig'; @@ -5,11 +6,14 @@ import { useParams } from 'react-router-dom'; import { Audio } from 'react-loader-spinner'; import VideoDetail from './VideoDetail'; import LikeButton from '@/components/LikeButton'; -import VideoLikeBox from '@/components/VideoLikeBox'; +import CommentVideo from '../commentVideo'; + export const VideoFarmById = () => { const [openDialog, setOpenDialog] = useState(false); const [selectedVideo, setSelectedVideo] = useState(null); + const [openComment, setOpenComment] = useState(false); + const [selectedVideoId, setSelectedVideoId] = useState(null); const [videoList, setVideoList] = useState([]); const [loading, setLoading] = useState(true); const tokenUser = localStorage.getItem('token'); @@ -19,13 +23,13 @@ export const VideoFarmById = () => { try { setLoading(true); const res = await axios.get(`${BaseUrl}/admin-video-farm/farm/${farmId}`, { - headers: { Authorization: `Bearer ${tokenUser}` }, + headers: { Authorization: `Bearer ${tokenUser}` } }); if (res.status === 200) { setVideoList(res.data); } } catch (error) { - console.log("Lỗi:", error); + console.log("Lỗi nè:", error); } finally { setLoading(false); } @@ -38,7 +42,7 @@ export const VideoFarmById = () => { playlistName: item.playlistName, status: item.status, uploadedBy: item.uploadedBy?.fullName, - createdAt: item.createdAt, + createdAt: item.createdAt }); setOpenDialog(true); }; @@ -48,6 +52,16 @@ export const VideoFarmById = () => { setOpenDialog(false); }; +const handleOpenComment = (e, videoId) => { + e.stopPropagation(); + setSelectedVideoId(videoId); + setOpenComment(true); +}; + const handleCloseComment = () => { + setSelectedVideoId(null); + setOpenComment(false); + }; + useEffect(() => { getDetailVideo(); }, []); @@ -64,30 +78,33 @@ export const VideoFarmById = () => { videoList.map((item) => (
      handleOpenDialog(item)} + onClick={(e) => { e.stopPropagation(); handleOpenDialog(item)}} className="cursor-pointer bg-white rounded-lg shadow p-5 flex flex-col gap-2 border hover:shadow-lg transition" > {item.title} -
      - + {/* ✅ Thêm LikeButton với danh sách like*/} +
      e.stopPropagation()}> +
      - - -
      - +
      {item.youtubeLink ? ( @@ -103,32 +120,21 @@ export const VideoFarmById = () => { ) : ( Chưa có video )} - - Danh sách phát:{' '} - {item.playlistName} - - - Ngày đăng:{' '} - - {new Date(item.createdAt).toLocaleDateString()} - - - - Người đăng:{' '} - {item.uploadedBy?.fullName} - + Danh sách phát: {item.playlistName} + Ngày đăng: {new Date(item.createdAt).toLocaleDateString()} + Người đăng: {item.uploadedBy?.fullName} +
      )) )} + {/* Dialog chi tiết video */} {openDialog && selectedVideo && (
      -

      - Thông tin video -

      +

      Thông tin video

      @@ -144,6 +150,13 @@ export const VideoFarmById = () => {
      )} + {openComment && ( + + )}
      ); }; From 7745f365eecc873c4728d93434da5bdd448639f8 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Tue, 8 Jul 2025 21:33:49 +0700 Subject: [PATCH 094/248] Update LikeButton.jsx --- src/components/LikeButton.jsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/LikeButton.jsx b/src/components/LikeButton.jsx index 69d0d248..b41f09b0 100644 --- a/src/components/LikeButton.jsx +++ b/src/components/LikeButton.jsx @@ -19,7 +19,6 @@ export default function LikeButton({ videoId }) { console.log("API trả về:", res.data); - // Nếu API trả về { users: [...] } hoặc trực tiếp là mảng const likedUsers = res.data.users || res.data || []; const isLiked = likedUsers.some((u) => u._id === userId || u.id === userId); @@ -35,8 +34,6 @@ export default function LikeButton({ videoId }) { const handleLikeToggle = async () => { if (!videoId || !token) return; - - // Tạm thời đảo trạng thái để UI phản hồi nhanh const previousLiked = liked; setLiked(!liked); setLoading(true); @@ -55,7 +52,6 @@ export default function LikeButton({ videoId }) { console.error('Lỗi khi Like/Unlike video:', error.response?.data || error); alert('Không thể Like/Unlike video.'); - // Nếu lỗi thì đảo lại trạng thái setLiked(previousLiked); } finally { setLoading(false); From b6039e8fc10afb94329f986e42eeffbcaa64ddd7 Mon Sep 17 00:00:00 2001 From: Longvu003 Date: Tue, 8 Jul 2025 22:19:41 +0700 Subject: [PATCH 095/248] up code video continue --- .../dashboard/Questions/EditQuestion.jsx | 110 ++++++++++ src/pages/dashboard/Questions/Questions.jsx | 112 +--------- .../VideoFarms/DialogVideoDetail.jsx | 107 ++++++++++ .../dashboard/VideoFarms/VideoFarmById.jsx | 192 +++++++++--------- 4 files changed, 318 insertions(+), 203 deletions(-) create mode 100644 src/pages/dashboard/Questions/EditQuestion.jsx create mode 100644 src/pages/dashboard/VideoFarms/DialogVideoDetail.jsx diff --git a/src/pages/dashboard/Questions/EditQuestion.jsx b/src/pages/dashboard/Questions/EditQuestion.jsx new file mode 100644 index 00000000..19691192 --- /dev/null +++ b/src/pages/dashboard/Questions/EditQuestion.jsx @@ -0,0 +1,110 @@ +import React from 'react' + +export const EditQuestion = ({openDialog,editData,editValue,handleEditChange,handleSave,handleCloseDialog,setEditValue}) => { + return ( +
      + {openDialog && editData && ( +
      +
      +
      Sửa câu hỏi
      +
      + + +
      + {[ 'single-choice', 'multiple-choice', 'multi-choice'].includes( + editData.type + ) && Array.isArray(editValue.options) && ( +
      + + {editValue.options.length === 0 && ( +
      + Chưa có đáp án nào, hãy thêm đáp án mới. +
      + )} + {editValue.options.map((opt, idx) => ( +
      + + {String.fromCharCode(65 + idx)}. + + handleEditChange(e, idx)} + className="border px-3 py-2 rounded w-full" + placeholder={`Đáp án ${String.fromCharCode(65 + idx)}`} + /> + +
      + ))} + +
      + )} + {editData.type === 'link' && ( +
      + + +
      + )} +
      + + +
      +
      +
      + )} + + +
      + ) +} + +export default EditQuestion \ No newline at end of file diff --git a/src/pages/dashboard/Questions/Questions.jsx b/src/pages/dashboard/Questions/Questions.jsx index ebbe3e2e..125c0081 100644 --- a/src/pages/dashboard/Questions/Questions.jsx +++ b/src/pages/dashboard/Questions/Questions.jsx @@ -3,6 +3,7 @@ import axios from 'axios'; import { BaseUrl } from '@/ipconfig'; import { Oval } from 'react-loader-spinner'; import AddQuestion from "./AddQuestion" +import EditQuestion from './EditQuestion'; export const Questions = () => { const [loading, setLoading] = useState(true); const [questions, setQuestions] = useState([]); @@ -33,7 +34,6 @@ export const Questions = () => { useEffect(() => { getData(); }, []); - const handleOpenDialog = (item) => { setEditData(item); setEditValue({ @@ -159,8 +159,6 @@ addValue={addValue} setAddValue={setAddValue} />
      - - {loading ? (
      )) )} - - {openDialog && editData && ( -
      -
      -
      Sửa câu hỏi
      -
      - - -
      - {[ 'single-choice', 'multiple-choice', 'multi-choice'].includes( - editData.type - ) && Array.isArray(editValue.options) && ( -
      - - {editValue.options.length === 0 && ( -
      - Chưa có đáp án nào, hãy thêm đáp án mới. -
      - )} - {editValue.options.map((opt, idx) => ( -
      - - {String.fromCharCode(65 + idx)}. - - handleEditChange(e, idx)} - className="border px-3 py-2 rounded w-full" - placeholder={`Đáp án ${String.fromCharCode(65 + idx)}`} - /> - -
      - ))} - -
      - )} - {editData.type === 'link' && ( -
      - - -
      - )} -
      - - -
      -
      -
      - )} +
      ); }; diff --git a/src/pages/dashboard/VideoFarms/DialogVideoDetail.jsx b/src/pages/dashboard/VideoFarms/DialogVideoDetail.jsx new file mode 100644 index 00000000..a966161b --- /dev/null +++ b/src/pages/dashboard/VideoFarms/DialogVideoDetail.jsx @@ -0,0 +1,107 @@ +import React from 'react' + +export const DialogVideoDetail = ({openDialog,editData,editValue,handleSaveEdit,handleCloseDialog}) => { + return ( +
      + {openDialog && ( +
      +
      +
      +
      +

      Thông tin video

      +
      +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      + + {editValue.status==="pending" && editData.localFilePath ? + ( +
      + + +
      + + ):( +
      + +
      + ) + } + +
      +
      +)} +
      + +) +} + +export default DialogVideoDetail \ No newline at end of file diff --git a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx index 499288bf..392a044f 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx @@ -3,6 +3,7 @@ import axios from 'axios' import { BaseUrl } from '@/ipconfig' import { useParams } from 'react-router-dom' import { Audio } from 'react-loader-spinner' +import DialogVideoDetail from './DialogVideoDetail' export const VideoFarmById = () => { const [openDialog, setOpenDialog] = useState(false); const [editData, setEditData] = useState(null); @@ -12,7 +13,6 @@ export const VideoFarmById = () => { const [videoDetail,setVideoDetail]=useState([]) const tokenUser = localStorage.getItem('token'); const {farmId}=useParams() - console.log(videoDetail) const getDetailVideo = async () => { try { setLoading(true) @@ -33,41 +33,53 @@ const handleOpenDialog =(item)=>{ setEditData(item) setIdVideo(item._id) setEditValue({ - title:item.title, - youtubeLink:item.youtubeLink, - playlistName:item.playlistName, - status:item.status, - uploadedBy:item.uploadedBy.fullName, - createdAt:item.createdAt, + title: item.title, + youtubeLink: item.youtubeLink, + playlistName: item.playlistName, + status: item.status, + uploadedBy: item.uploadedBy?.fullName, + createdAt: item.createdAt, + // localFilePath: item.localFilePath, }) setOpenDialog(true) } +const deletevideo = async()=>{ + if (!window.confirm('Bạn có chắc muốn xóa video này?')) return; + try { + const res= await axios.delete(`${BaseUrl}/admin-video-farm/delete/${idVideo}`,{headers:{Authorization: `Bearer ${tokenUser}`}}) +if(res.status===200){ +await getDetailVideo() +alert("Xóa thành công") +} + } catch (error) { + console.log("Lỗi nè:",error) + } +} const handleCloseDialog =(item)=>{ setEditData(null) setEditValue({}) setOpenDialog(false) } -// const handleSaveEdit = async()=>{ -// try { - -// const res= await axios.post(`${BaseUrl}/admin-video-farm/upload-youtube/${idVideo}`, editValue,{ -// headers: { Authorization: `Bearer ${tokenUser}` }}) -// if(res.status===200){ -// alert("Cập nhật thành công") -// setTimeout(() => { -// getDetailVideo(); -// }, 500); -// console.log(res) -// handleCloseDialog() -// } -// } catch (error) { -// console.log("Lỗi nè",error) -// } -// } - +const handleSaveEdit = async()=>{ +try { + const updatedValue = { status: "uploaded" }; + const res= await axios.post(`${BaseUrl}/admin-video-farm/upload-youtube/${idVideo}`, updatedValue,{ + headers: { Authorization: `Bearer ${tokenUser}` }}) +if(res.status===200){ + console.log("data nè:",res.data) + alert("Cập nhật thành công") + await getDetailVideo(); + handleCloseDialog() +}else { + alert("Có lỗi trong lúc duyệt") + } +} catch (error) { + console.log("Lỗi nè",error) +} +} useEffect(()=>{ getDetailVideo() },[]) @@ -89,38 +101,52 @@ return ( ) : ( videoDetail.map((item) => (
      handleOpenDialog(item)} - key={item.id} - className="cursor-ponter bg-white rounded-lg shadow p-5 flex flex-col gap-2 border hover:shadow-lg transition" + className=" cursor-ponter bg-white rounded-lg shadow p-5 flex flex-col gap-2 border hover:shadow-lg transition" > {item.title}
      + -
      - {item.youtubeLink ? ( - - ▶ Xem video - - - ) : ( - Chưa có video - ) - } + + {item.status === "pending" && item.localFilePath ? ( + +) : item.youtubeLink && item.status === "uploaded" ? ( + +) : ( +
      + Video không tồn tại +
      +)} Danh sách phát: {item.playlistName} @@ -130,63 +156,27 @@ Chi tiết Người đăng: {item.uploadedBy?.fullName} + + Trạng thái: {item.status} + +
      )) )} - - - {openDialog && ( -
      -
      -
      -
      -

      Thông tin video

      -
      -
      -
      - - -
      -
      - - -
      -
      - - -
      -
      - Trạng thái: {editValue.status} - Người đăng: {editValue.uploadedBy} - Ngày tạo: {editValue.createdAt && new Date(editValue.createdAt).toLocaleString()} -
      -
      -
      - -
      -
      -
      -)} +
      ) } From 3097943c95ed7beda900fb5a545c8ab0cdd796a2 Mon Sep 17 00:00:00 2001 From: Longvu003 Date: Tue, 8 Jul 2025 22:34:33 +0700 Subject: [PATCH 096/248] . --- src/pages/dashboard/VideoFarms/VideoFarmById.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx index 392a044f..5671f1b8 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx @@ -19,9 +19,10 @@ export const VideoFarmById = () => { const res = await axios.get(`${BaseUrl}/admin-video-farm/farm/${farmId}`, { headers: { Authorization: `Bearer ${tokenUser}` } }); - if (res.status === 200) { + if (res.status === 200) { setVideoDetail(res.data) setLoading(false) + } } catch (error) { console.log("Lỗi nè:", error) From 38fee4c51f0f4242dfa903a05861bbfbc90c4e21 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Wed, 9 Jul 2025 14:11:14 +0700 Subject: [PATCH 097/248] thanh --- src/components/LikeButton.jsx | 66 ++++++++--------- src/components/VideoLikeBox.jsx | 125 +++++++++++++++++++------------- 2 files changed, 109 insertions(+), 82 deletions(-) diff --git a/src/components/LikeButton.jsx b/src/components/LikeButton.jsx index b41f09b0..d175cc1c 100644 --- a/src/components/LikeButton.jsx +++ b/src/components/LikeButton.jsx @@ -11,52 +11,52 @@ export default function LikeButton({ videoId }) { const userId = JSON.parse(localStorage.getItem('user'))?.id; const checkLikedStatus = async () => { - if (!videoId || !token) return; - try { - const res = await axios.get(`${BaseUrl}/video-like/${videoId}/users`, { - headers: { Authorization: `Bearer ${token}` }, - }); + if (!videoId || !token) return; + try { + const res = await axios.get(`${BaseUrl}/video-like/${videoId}/users`, { + headers: { Authorization: `Bearer ${token}` }, + }); - console.log("API trả về:", res.data); + console.log("Danh sách user đã like:", res.data.users); - const likedUsers = res.data.users || res.data || []; + const likedUsers = res.data.users || []; + const isLiked = likedUsers.some((u) => u._id === userId); + setLiked(isLiked); + } catch (error) { + console.error('Lỗi khi check trạng thái Like:', error.response?.data || error); + } +}; - const isLiked = likedUsers.some((u) => u._id === userId || u.id === userId); - setLiked(isLiked); - } catch (error) { - console.error('Lỗi khi check trạng thái Like:', error.response?.data || error); - } - }; useEffect(() => { checkLikedStatus(); }, [videoId]); const handleLikeToggle = async () => { - if (!videoId || !token) return; - const previousLiked = liked; - setLiked(!liked); - setLoading(true); + if (!videoId || !token) return; + setLoading(true); - try { - const url = previousLiked - ? `${BaseUrl}/video-like/${videoId}/unlike` - : `${BaseUrl}/video-like/${videoId}/like`; + try { + const url = liked + ? `${BaseUrl}/video-like/${videoId}/unlike` + : `${BaseUrl}/video-like/${videoId}/like`; - console.log("Gọi API:", url); + console.log("Gọi API:", url); - await axios.post(url, {}, { - headers: { Authorization: `Bearer ${token}` }, - }); - } catch (error) { - console.error('Lỗi khi Like/Unlike video:', error.response?.data || error); - alert('Không thể Like/Unlike video.'); + await axios.post(url, {}, { + headers: { Authorization: `Bearer ${token}` }, + }); + + // Sau khi like/unlike -> cập nhật trạng thái từ server + await checkLikedStatus(); + } catch (error) { + console.error('Lỗi khi Like/Unlike video:', error.response?.data || error); + alert('Không thể Like/Unlike video.'); + } finally { + setLoading(false); + } +}; - setLiked(previousLiked); - } finally { - setLoading(false); - } - }; const handleViewLikes = () => { navigate(`/dashboard/video-like/${videoId}`); diff --git a/src/components/VideoLikeBox.jsx b/src/components/VideoLikeBox.jsx index 705fe3f2..9680c4a6 100644 --- a/src/components/VideoLikeBox.jsx +++ b/src/components/VideoLikeBox.jsx @@ -1,65 +1,92 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState, useEffect } from 'react'; import axios from 'axios'; import { BaseUrl } from '@/ipconfig'; +import { useNavigate } from 'react-router-dom'; -export default function VideoLikeBox({ videoId }) { - const [users, setUsers] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(''); +export default function LikeButton({ videoId }) { + const [liked, setLiked] = useState(false); + const [loading, setLoading] = useState(false); const token = localStorage.getItem('token'); + const navigate = useNavigate(); + const userId = JSON.parse(localStorage.getItem('user'))?.id; + + // ✅ Hàm kiểm tra trạng thái Like + const checkLikedStatus = async () => { + if (!videoId || !token) return; + try { + const res = await axios.get(`${BaseUrl}/video-like/${videoId}/users`, { + headers: { Authorization: `Bearer ${token}` }, + }); + + console.log("Danh sách user đã like:", res.data.users); + + const likedUsers = res.data.users || []; + // ✅ So sánh userId đảm bảo kiểu dữ liệu + const isLiked = likedUsers.some( + (u) => String(u._id ?? u.id) === String(userId) + ); + setLiked(isLiked); + } catch (error) { + console.error('Lỗi khi check trạng thái Like:', error.response?.data || error); + } + }; useEffect(() => { - const fetchLikes = async () => { - if (!videoId || videoId === ':videoId') { - console.warn('videoId không hợp lệ:', videoId); - setLoading(false); - setError('Video ID không hợp lệ.'); - return; - } + checkLikedStatus(); + }, [videoId]); + + // ✅ Hàm xử lý Like/Unlike + const handleLikeToggle = async () => { + if (!videoId || !token) return; + setLoading(true); + + try { + const url = liked + ? `${BaseUrl}/video-like/${videoId}/unlike` + : `${BaseUrl}/video-like/${videoId}/like`; + + console.log("Gọi API:", url); - setLoading(true); - setError(''); - try { - const res = await axios.get(`${BaseUrl}/video-like/${videoId}/users`, { - headers: { Authorization: `Bearer ${token}` } - }); + await axios.post(url, {}, { + headers: { Authorization: `Bearer ${token}` }, + }); - // Dữ liệu có thể là res.data.users hoặc res.data trực tiếp là mảng - const usersList = Array.isArray(res.data) - ? res.data - : res.data?.users || []; + // ✅ Cập nhật trạng thái ngay lập tức + setLiked(!liked); - setUsers(usersList); - } catch (err) { - console.error('Lỗi khi lấy danh sách like:', err); - setError('Không thể lấy danh sách người like. Vui lòng thử lại sau.'); - } finally { - setLoading(false); - } - }; + // 🔄 Gọi lại API check trạng thái để đồng bộ dữ liệu + await checkLikedStatus(); + } catch (error) { + console.error('Lỗi khi Like/Unlike video:', error.response?.data || error); + alert('Không thể Like/Unlike video.'); + } finally { + setLoading(false); + } + }; - fetchLikes(); - }, [videoId, token]); + // ✅ Xem danh sách người Like + const handleViewLikes = () => { + navigate(`/dashboard/video-like/${videoId}`); + }; return ( -
      -

      Người đã like video:

      +
      + - {loading ? ( - Đang tải... - ) : error ? ( - {error} - ) : users.length === 0 ? ( - Chưa có ai like - ) : ( -
        - {users.map((user, idx) => ( -
      • - {user.fullName || user.username || 'Ẩn danh'} -
      • - ))} -
      - )} +
      ); } From fd5580f09328f2537a1a0aed2640a1079d8f07db Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Wed, 9 Jul 2025 14:54:57 +0700 Subject: [PATCH 098/248] thanh --- src/components/LikeButton.jsx | 85 ------------------- .../dashboard/VideoFarms/LikeButton.jsx} | 63 ++++++-------- .../dashboard/VideoFarms/VideoFarmById.jsx | 3 +- .../dashboard/VideoFarms/VideoLikeList.jsx | 66 ++++++++++++++ src/pages/dashboard/VideoLikeList.jsx | 14 --- src/routes.jsx | 2 - 6 files changed, 94 insertions(+), 139 deletions(-) delete mode 100644 src/components/LikeButton.jsx rename src/{components/VideoLikeBox.jsx => pages/dashboard/VideoFarms/LikeButton.jsx} (54%) create mode 100644 src/pages/dashboard/VideoFarms/VideoLikeList.jsx delete mode 100644 src/pages/dashboard/VideoLikeList.jsx diff --git a/src/components/LikeButton.jsx b/src/components/LikeButton.jsx deleted file mode 100644 index d175cc1c..00000000 --- a/src/components/LikeButton.jsx +++ /dev/null @@ -1,85 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import axios from 'axios'; -import { BaseUrl } from '@/ipconfig'; -import { useNavigate } from 'react-router-dom'; - -export default function LikeButton({ videoId }) { - const [liked, setLiked] = useState(false); - const [loading, setLoading] = useState(false); - const token = localStorage.getItem('token'); - const navigate = useNavigate(); - const userId = JSON.parse(localStorage.getItem('user'))?.id; - - const checkLikedStatus = async () => { - if (!videoId || !token) return; - try { - const res = await axios.get(`${BaseUrl}/video-like/${videoId}/users`, { - headers: { Authorization: `Bearer ${token}` }, - }); - - console.log("Danh sách user đã like:", res.data.users); - - const likedUsers = res.data.users || []; - const isLiked = likedUsers.some((u) => u._id === userId); - setLiked(isLiked); - } catch (error) { - console.error('Lỗi khi check trạng thái Like:', error.response?.data || error); - } -}; - - - useEffect(() => { - checkLikedStatus(); - }, [videoId]); - - const handleLikeToggle = async () => { - if (!videoId || !token) return; - setLoading(true); - - try { - const url = liked - ? `${BaseUrl}/video-like/${videoId}/unlike` - : `${BaseUrl}/video-like/${videoId}/like`; - - console.log("Gọi API:", url); - - await axios.post(url, {}, { - headers: { Authorization: `Bearer ${token}` }, - }); - - // Sau khi like/unlike -> cập nhật trạng thái từ server - await checkLikedStatus(); - } catch (error) { - console.error('Lỗi khi Like/Unlike video:', error.response?.data || error); - alert('Không thể Like/Unlike video.'); - } finally { - setLoading(false); - } -}; - - - const handleViewLikes = () => { - navigate(`/dashboard/video-like/${videoId}`); - }; - - return ( -
      - - - -
      - ); -} diff --git a/src/components/VideoLikeBox.jsx b/src/pages/dashboard/VideoFarms/LikeButton.jsx similarity index 54% rename from src/components/VideoLikeBox.jsx rename to src/pages/dashboard/VideoFarms/LikeButton.jsx index 9680c4a6..5d49956b 100644 --- a/src/components/VideoLikeBox.jsx +++ b/src/pages/dashboard/VideoFarms/LikeButton.jsx @@ -4,38 +4,37 @@ import { BaseUrl } from '@/ipconfig'; import { useNavigate } from 'react-router-dom'; export default function LikeButton({ videoId }) { - const [liked, setLiked] = useState(false); - const [loading, setLoading] = useState(false); + const [liked, setLiked] = useState(false); + const [loading, setLoading] = useState(false); const token = localStorage.getItem('token'); - const navigate = useNavigate(); const userId = JSON.parse(localStorage.getItem('user'))?.id; + const navigate = useNavigate(); - // ✅ Hàm kiểm tra trạng thái Like - const checkLikedStatus = async () => { - if (!videoId || !token) return; - try { - const res = await axios.get(`${BaseUrl}/video-like/${videoId}/users`, { - headers: { Authorization: `Bearer ${token}` }, - }); - console.log("Danh sách user đã like:", res.data.users); + useEffect(() => { + const fetchLikedStatus = async () => { + if (!videoId || !token || !userId) return; - const likedUsers = res.data.users || []; - // ✅ So sánh userId đảm bảo kiểu dữ liệu - const isLiked = likedUsers.some( - (u) => String(u._id ?? u.id) === String(userId) - ); - setLiked(isLiked); - } catch (error) { - console.error('Lỗi khi check trạng thái Like:', error.response?.data || error); - } - }; + try { + const res = await axios.get(`${BaseUrl}/video-like/${videoId}/users`, { + headers: { Authorization: `Bearer ${token}` }, + }); + console.log(' Danh sách người đã like:', res.data.users); - useEffect(() => { - checkLikedStatus(); - }, [videoId]); + const likedUsers = res.data.users || []; + const isLiked = likedUsers.some( + (u) => String(u._id ?? u.id) === String(userId) + ); + + setLiked(isLiked); + } catch (err) { + console.error(' Lỗi khi check trạng thái Like:', err.response?.data || err); + } + }; + + fetchLikedStatus(); + }, [videoId, token, userId]); - // ✅ Hàm xử lý Like/Unlike const handleLikeToggle = async () => { if (!videoId || !token) return; setLoading(true); @@ -44,27 +43,19 @@ export default function LikeButton({ videoId }) { const url = liked ? `${BaseUrl}/video-like/${videoId}/unlike` : `${BaseUrl}/video-like/${videoId}/like`; - - console.log("Gọi API:", url); - await axios.post(url, {}, { headers: { Authorization: `Bearer ${token}` }, }); - // ✅ Cập nhật trạng thái ngay lập tức - setLiked(!liked); - - // 🔄 Gọi lại API check trạng thái để đồng bộ dữ liệu - await checkLikedStatus(); - } catch (error) { - console.error('Lỗi khi Like/Unlike video:', error.response?.data || error); + setLiked(!liked); + } catch (err) { + console.error(' Lỗi khi Like/Unlike video:', err.response?.data || err); alert('Không thể Like/Unlike video.'); } finally { setLoading(false); } }; - // ✅ Xem danh sách người Like const handleViewLikes = () => { navigate(`/dashboard/video-like/${videoId}`); }; diff --git a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx index 09c7ca9d..0eecb0ec 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx @@ -5,7 +5,7 @@ import { BaseUrl } from '@/ipconfig'; import { useParams } from 'react-router-dom'; import { Audio } from 'react-loader-spinner'; import VideoDetail from './VideoDetail'; -import LikeButton from '@/components/LikeButton'; +import LikeButton from './LikeButton'; import CommentVideo from '../commentVideo'; @@ -83,7 +83,6 @@ const handleOpenComment = (e, videoId) => { > {item.title} - {/* ✅ Thêm LikeButton với danh sách like*/}
      e.stopPropagation()}>
      diff --git a/src/pages/dashboard/VideoFarms/VideoLikeList.jsx b/src/pages/dashboard/VideoFarms/VideoLikeList.jsx new file mode 100644 index 00000000..904d908a --- /dev/null +++ b/src/pages/dashboard/VideoFarms/VideoLikeList.jsx @@ -0,0 +1,66 @@ +import React, { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import axios from 'axios'; +import { BaseUrl } from '@/ipconfig'; + +export default function VideoLikeList() { + const { videoId } = useParams(); + const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(true); + const token = localStorage.getItem('token'); + + useEffect(() => { + const fetchLikes = async () => { + if (!videoId || !token) return; + + try { + const res = await axios.get(`${BaseUrl}/video-like/${videoId}/users`, { + headers: { Authorization: `Bearer ${token}` }, + }); + + console.log('✅ Danh sách người đã like:', res.data.users); + setUsers(res.data.users || []); + } catch (err) { + console.error('❌ Lỗi khi lấy danh sách người đã Like:', err.response?.data || err); + } finally { + setLoading(false); + } + }; + + fetchLikes(); + }, [videoId, token]); + + if (loading) { + return ( +
      Đang tải danh sách...
      + ); + } + + return ( +
      +

      + 👥 Danh sách người đã Like video +

      + + {users.length === 0 ? ( +

      Chưa có ai Like video này.

      + ) : ( +
      + {users.map((user) => ( +
      + {user.fullName} + {user.fullName} +
      + ))} +
      + )} +
      + ); +} diff --git a/src/pages/dashboard/VideoLikeList.jsx b/src/pages/dashboard/VideoLikeList.jsx deleted file mode 100644 index 5a85e596..00000000 --- a/src/pages/dashboard/VideoLikeList.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { useParams } from 'react-router-dom'; -import VideoLikeBox from '@/components/VideoLikeBox'; - -export default function VideoLikeList() { - const { videoId } = useParams(); - - return ( -
      -

      Danh sách người đã like video

      - -
      - ); -} diff --git a/src/routes.jsx b/src/routes.jsx index cb24187a..87f54d4d 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -7,8 +7,6 @@ import { RectangleStackIcon, } from "@heroicons/react/24/solid"; -import VideoLikeList from "@/pages/dashboard/VideoLikeList"; - import { Home, Users , Farms, Questions, AnswersTable, VideoFarms } from "@/pages/dashboard"; From e277468d3ef52bbc7278f8480a40e694359548c8 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Wed, 9 Jul 2025 15:18:45 +0700 Subject: [PATCH 099/248] Update App.jsx --- src/App.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/App.jsx b/src/App.jsx index c02abb47..082a4b9c 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,12 +1,14 @@ import { Routes, Route, Navigate } from "react-router-dom"; import { Dashboard, Auth } from "@/layouts"; import VideoFarmById from "./pages/dashboard/VideoFarms/VideoFarmById"; +import VideoLikeList from "./pages/dashboard/VideoFarms/VideoLikeList"; function App() { return ( } /> } /> -} /> + } /> + } /> } /> ); From e79cf240d1cd7e13ec12d5e3ed4c42bf96462a0a Mon Sep 17 00:00:00 2001 From: trongquy1213 Date: Wed, 9 Jul 2025 15:28:05 +0700 Subject: [PATCH 100/248] 1 --- src/components/LikeButton.jsx | 10 +- src/pages/dashboard/VideoFarms/VideoFarms.jsx | 94 ++++++++++--------- 2 files changed, 49 insertions(+), 55 deletions(-) diff --git a/src/components/LikeButton.jsx b/src/components/LikeButton.jsx index 758962d9..1268bf4e 100644 --- a/src/components/LikeButton.jsx +++ b/src/components/LikeButton.jsx @@ -15,7 +15,6 @@ export default function LikeButton({ videoId }) { setLoading(true); try { -<<<<<<< Updated upstream const url = liked ? `${BaseUrl}/video-like/${videoId}/unlike` : `${BaseUrl}/video-like/${videoId}/like`; @@ -25,7 +24,6 @@ export default function LikeButton({ videoId }) { }); setLiked(!liked); -======= if (liked) { // Unlike await axios.post(`${BaseUrl}/video-like/${videoId}/unlike`, {}, { @@ -40,13 +38,7 @@ export default function LikeButton({ videoId }) { setLiked(!liked); onLikeChange?.(); // gọi callback reload danh sách user -<<<<<<< Updated upstream -<<<<<<< Updated upstream ->>>>>>> Stashed changes -======= ->>>>>>> Stashed changes -======= ->>>>>>> Stashed changes + } catch (error) { console.error('Lỗi khi Like/Unlike video:', error); } finally { diff --git a/src/pages/dashboard/VideoFarms/VideoFarms.jsx b/src/pages/dashboard/VideoFarms/VideoFarms.jsx index ad6e08dc..02174b4c 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarms.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarms.jsx @@ -5,47 +5,42 @@ import { useNavigate } from 'react-router-dom' import { Audio } from 'react-loader-spinner' export const VideoFarms = () => { - const [loading, setLoading] = useState(true) - - const navigate=useNavigate() - const tokenUser = localStorage.getItem('token'); - const [video, setVideo] = useState([]); + const [loading, setLoading] = useState(true) + const [videos, setVideos] = useState([]) + const navigate = useNavigate() + const tokenUser = localStorage.getItem('token') - const CallVideoApi = async () => { - try { - setLoading(true) - const res = await axios.get(`${BaseUrl}/admin-video-farm`, { - headers: { Authorization: `Bearer ${tokenUser}` } - }); - if (res.status === 200) { - setVideo(res.data) - setLoading(false) - } - } catch (error) { - console.log("Lỗi nè:", error) - setLoading(false) +const fetchAllVideos = async () => { + try { + setLoading(true) + const res = await axios.get(`${BaseUrl}/video-farm/new`, { + headers: { Authorization: `Bearer ${tokenUser}` } + }) + console.log("Dữ liệu video:", res.data) + if (res.status === 200) { + const videoArray = res.data.videos || [] + setVideos(videoArray) } + } catch (error) { + console.error("Lỗi khi lấy video:", error.response?.data || error.message) + } finally { + setLoading(false) } +} + useEffect(() => { - CallVideoApi() + fetchAllVideos() }, []) - const farmMap = {}; - video.forEach(item => { - if (item.farmId && item.farmId.id) { - farmMap[item.farmId.id] = item; - } - }); - const farmList = Object.values(farmMap); -const gotoVideoById=(farmId)=>{ - navigate(`/dashboard/VideoFarmById/${farmId}`); -} + const handleClickFarm = (farmId) => { + if (farmId) { + navigate(`/dashboard/VideoFarmById/${farmId}`) + } + } return (
      - { - - loading ? ( + {loading ? (
      - ) : - - farmList.length === 0 ? ( - Đang Tải + ) : Array.isArray(videos) && videos.length === 0 ? ( + Không có video nào. ) : ( - farmList.map((item) => - ( + videos.map((item) => (
      gotoVideoById(item.farmId.id)} - key={item.farmId.id} - className=" cursor-pointer bg-white rounded-lg shadow p-5 flex flex-col gap-2 border hover:shadow-lg transition" + key={item._id} + onClick={() => handleClickFarm(item.farmId?.id)} + className="cursor-pointer bg-white rounded-lg shadow p-5 flex flex-col gap-2 border hover:shadow-lg transition" > {item.title} - - Danh sách phát: {item.playlistName} - Ngày đăng: {new Date(item.createdAt).toLocaleDateString()} - Người đăng: {item.uploadedBy?.fullName} + + Danh sách phát:{" "} + {item.playlistName} + + + Ngày đăng:{" "} + + {new Date(item.createdAt).toLocaleDateString()} + + + + Người đăng:{" "} + {item.uploadedBy?.fullName} +
      )) )} @@ -79,4 +81,4 @@ const gotoVideoById=(farmId)=>{ ) } -export default VideoFarms \ No newline at end of file +export default VideoFarms From 527e06f21aaeaf69c565ccef6c315797755ce18f Mon Sep 17 00:00:00 2001 From: Tie902 Date: Wed, 9 Jul 2025 16:34:31 +0700 Subject: [PATCH 101/248] Update VideoLikeList.jsx --- src/pages/dashboard/VideoLikeList.jsx | 60 +++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/src/pages/dashboard/VideoLikeList.jsx b/src/pages/dashboard/VideoLikeList.jsx index f3b06750..b998a502 100644 --- a/src/pages/dashboard/VideoLikeList.jsx +++ b/src/pages/dashboard/VideoLikeList.jsx @@ -1,17 +1,61 @@ -// src/pages/VideoLikeList.jsx -import React from 'react'; -import { useParams } from 'react-router-dom'; -import VideoLikeBox from '@/components/VideoLikeBox'; + +import React, { useEffect, useState } from 'react'; +import axios from 'axios'; +import { BaseUrl } from '@/ipconfig'; +import { useParams, useNavigate } from 'react-router-dom'; +import { Audio } from 'react-loader-spinner'; export default function VideoLikeList() { const { videoId } = useParams(); + const [likes, setLikes] = useState([]); + const [loading, setLoading] = useState(true); + const token = localStorage.getItem('token'); + const navigate = useNavigate(); + + const getLikes = async () => { + try { + setLoading(true); + const res = await axios.post(`${BaseUrl}/video-like/${videoId}/like`, {}, { + headers: { Authorization: `Bearer ${token}` }, + }); + setLikes(res.data?.data || []); + } catch (error) { + console.error('Lỗi lấy danh sách Like:', error); + } finally { + setLoading(false); + } + }; - console.log('Video ID:', videoId); + useEffect(() => { + getLikes(); + }, []); return ( -
      -

      Danh sách người đã Like video

      - +
      +

      Danh sách người đã Like

      + + {loading ? ( +
      +
      + ) : likes.length === 0 ? ( +

      Chưa có ai Like video này.

      + ) : ( +
        + {likes.map((user, index) => ( +
      • + {user.fullName} +
      • + ))} +
      + )} + +
      ); } From e8f5a2599d7089165a4e2e3d202f1227cf50f395 Mon Sep 17 00:00:00 2001 From: Longvu003 Date: Wed, 9 Jul 2025 16:42:39 +0700 Subject: [PATCH 102/248] upcode layout video Farm --- src/pages/dashboard/AcceptFarm.jsx | 0 .../VideoFarms/DialogVideoDetail.jsx | 9 +++ .../dashboard/VideoFarms/VideoDetail.jsx | 41 ------------ .../dashboard/VideoFarms/VideoFarmById.jsx | 67 ++++++------------- src/pages/dashboard/notifications.jsx | 0 5 files changed, 29 insertions(+), 88 deletions(-) create mode 100644 src/pages/dashboard/AcceptFarm.jsx delete mode 100644 src/pages/dashboard/VideoFarms/VideoDetail.jsx create mode 100644 src/pages/dashboard/notifications.jsx diff --git a/src/pages/dashboard/AcceptFarm.jsx b/src/pages/dashboard/AcceptFarm.jsx new file mode 100644 index 00000000..e69de29b diff --git a/src/pages/dashboard/VideoFarms/DialogVideoDetail.jsx b/src/pages/dashboard/VideoFarms/DialogVideoDetail.jsx index cca65c97..71970674 100644 --- a/src/pages/dashboard/VideoFarms/DialogVideoDetail.jsx +++ b/src/pages/dashboard/VideoFarms/DialogVideoDetail.jsx @@ -28,6 +28,15 @@ export const DialogVideoDetail = ({openDialogInforVideo,editData,editValue,hand className="border border-blue-200 px-3 py-2 rounded w-full bg-gray-50 text-blue-700 font-medium" disabled /> +
      +
      + +
      diff --git a/src/pages/dashboard/VideoFarms/VideoDetail.jsx b/src/pages/dashboard/VideoFarms/VideoDetail.jsx deleted file mode 100644 index 4d485d42..00000000 --- a/src/pages/dashboard/VideoFarms/VideoDetail.jsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; - -export const VideoDetail = ({ getDetailVideoInformation }) => { - if (!getDetailVideoInformation) return null; - - return ( -
      -
      - - -
      -
      - - -
      -
      - - -
      -
      - Trạng thái: {getDetailVideoInformation.status} - Người đăng: {getDetailVideoInformation.uploadedBy} - Ngày tạo: {getDetailVideoInformation.createdAt && new Date(getDetailVideoInformation.createdAt).toLocaleString()} -
      -
      - ); -}; - -export default VideoDetail; diff --git a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx index ab4e19ec..3588fea6 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx @@ -4,7 +4,6 @@ import axios from 'axios'; import { BaseUrl } from '@/ipconfig'; import { useParams } from 'react-router-dom'; import { Audio } from 'react-loader-spinner'; -import VideoDetail from './VideoDetail'; import LikeButton from '@/components/LikeButton'; import CommentVideo from '../commentVideo'; import DialogVideoDetail from './DialogVideoDetail' @@ -88,7 +87,7 @@ setEditData(item) status: item.status, uploadedBy: item.uploadedBy?.fullName, createdAt: item.createdAt, - // localFilePath: item.localFilePath, + localFilePath: item.localFilePath, }) @@ -138,6 +137,9 @@ const handleOpenComment = (e, videoId) => { ) : ( videoDetail.map((item) => (
      { + handleOpenDialogInforvideo(item); + }} key={item._id} className="cursor-pointer bg-white rounded-lg shadow p-5 flex flex-col gap-2 border hover:shadow-lg transition" > @@ -149,31 +151,32 @@ const handleOpenComment = (e, videoId) => {
      - {item.status === "pending" && item.localFilePath ? ( + {item.status === "pending" && item.localFilePath ? + + ( ) : item.youtubeLink && item.status === "uploaded" ? ( - ) : ( -
      +
      Video không tồn tại
      @@ -213,15 +216,6 @@ const handleOpenComment = (e, videoId) => { Trạng thái: {item.status} -
      )) )} @@ -233,27 +227,6 @@ handleCloseDialogInforVideo={handleCloseDialogInforVideo} editValue={editValue} openDialogInforVideo={openDialogInforVideo} /> - {/* {openDialog && selectedVideo && ( -
      -
      -
      -
      -

      Thông tin video

      -
      - - - -
      - -
      -
      -
      - )} */} {openComment && ( Date: Wed, 9 Jul 2025 16:55:59 +0700 Subject: [PATCH 103/248] thanh --- src/pages/auth/sign-in.jsx | 56 ---------------------------- src/pages/dashboard/commentVideo.jsx | 8 ++-- 2 files changed, 4 insertions(+), 60 deletions(-) diff --git a/src/pages/auth/sign-in.jsx b/src/pages/auth/sign-in.jsx index dc0f3ede..a4ccbe4c 100644 --- a/src/pages/auth/sign-in.jsx +++ b/src/pages/auth/sign-in.jsx @@ -58,62 +58,6 @@ export function SignIn() { } }; - // Làm mới token - const refreshAccessToken = async () => { - const refreshToken = localStorage.getItem("refreshToken"); - if (!refreshToken) return null; - - try { - const res = await fetch("https://api-ndolv2.nongdanonline.vn/auth/refresh-token", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ refreshToken }), - }); - const data = await res.json(); - - if (res.ok) { - localStorage.setItem("token", data.accessToken); - console.log("✅ Access token đã được làm mới"); - return data.accessToken; - } else { - console.error("❌ Làm mới token thất bại:", data.message); - return null; - } - } catch (error) { - console.error("❌ Lỗi khi gọi refresh-token:", error); - return null; - } - }; - - // Hàm fetch có auto-refresh - const fetchWithAuth = async (url, options = {}) => { - let token = localStorage.getItem("token"); - let res = await fetch(url, { - ...options, - headers: { - ...(options.headers || {}), - Authorization: `Bearer ${token}`, - }, - }); - - if (res.status === 401 || res.status === 403) { - console.warn("⚠ Token hết hạn, đang làm mới..."); - const newToken = await refreshAccessToken(); - if (newToken) { - res = await fetch(url, { - ...options, - headers: { - ...(options.headers || {}), - Authorization: `Bearer ${newToken}`, - }, - }); - } else { - console.error("⚠ Không thể làm mới token"); - } - } - - return res; - }; return (
      diff --git a/src/pages/dashboard/commentVideo.jsx b/src/pages/dashboard/commentVideo.jsx index 6570da40..0c838c4e 100644 --- a/src/pages/dashboard/commentVideo.jsx +++ b/src/pages/dashboard/commentVideo.jsx @@ -81,7 +81,7 @@ export default function CommentVideo({ open, onClose, videoId }) { return; } try { - await replyComment(videoId, replyBox.idx, { comment: replyBox.text }); + await replyComment(videoId, replyBox.commentId, { comment: replyBox.text }); setReplyBox({ idx: null, text: "" }); load(); } catch (err) { @@ -90,7 +90,7 @@ export default function CommentVideo({ open, onClose, videoId }) { } }; - const handleHideComment = async (idx) => { + const handleHideComment = async (commentId) => { if (!window.confirm("Bạn có chắc muốn ẩn bình luận này?")) return; try { await hideComment(videoId, idx); @@ -156,7 +156,7 @@ export default function CommentVideo({ open, onClose, videoId }) { @@ -164,7 +164,7 @@ export default function CommentVideo({ open, onClose, videoId }) { size="sm" variant="text" color="red" - onClick={() => handleHideComment(i)} + onClick={() => handleHideComment(c._id)} > Ẩn From 851dc9f90714bbf11e123b13d5178ebcced96dde Mon Sep 17 00:00:00 2001 From: Longvu003 Date: Wed, 9 Jul 2025 17:11:36 +0700 Subject: [PATCH 104/248] . --- src/pages/dashboard/VideoFarms/LikeButton.jsx | 6 ------ src/pages/dashboard/VideoFarms/VideoFarms.jsx | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/pages/dashboard/VideoFarms/LikeButton.jsx b/src/pages/dashboard/VideoFarms/LikeButton.jsx index 363acf04..51dc3429 100644 --- a/src/pages/dashboard/VideoFarms/LikeButton.jsx +++ b/src/pages/dashboard/VideoFarms/LikeButton.jsx @@ -63,12 +63,6 @@ export default function LikeButton({ videoId }) { setLiked(!liked); onLikeChange?.(); // gọi callback reload danh sách user - - } catch (error) { - console.error('Lỗi khi Like/Unlike video:', error); - - - setLiked(!liked); } catch (err) { console.error(' Lỗi khi Like/Unlike video:', err.response?.data || err); alert('Không thể Like/Unlike video.'); diff --git a/src/pages/dashboard/VideoFarms/VideoFarms.jsx b/src/pages/dashboard/VideoFarms/VideoFarms.jsx index 02174b4c..938c6c57 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarms.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarms.jsx @@ -16,7 +16,7 @@ const fetchAllVideos = async () => { const res = await axios.get(`${BaseUrl}/video-farm/new`, { headers: { Authorization: `Bearer ${tokenUser}` } }) - console.log("Dữ liệu video:", res.data) + console.log("Dữ liệu video:", videos) if (res.status === 200) { const videoArray = res.data.videos || [] setVideos(videoArray) @@ -54,6 +54,7 @@ const fetchAllVideos = async () => { Không có video nào. ) : ( videos.map((item) => ( +
      handleClickFarm(item.farmId?.id)} From 6223693e04d0c0e1145922be58515a07fca65eee Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Wed, 9 Jul 2025 17:25:16 +0700 Subject: [PATCH 105/248] t --- .../dashboard/VideoFarms/VideoLikeList.jsx | 4 +-- src/pages/dashboard/commentVideo.jsx | 28 ++++++++----------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/pages/dashboard/VideoFarms/VideoLikeList.jsx b/src/pages/dashboard/VideoFarms/VideoLikeList.jsx index 904d908a..c58d52f9 100644 --- a/src/pages/dashboard/VideoFarms/VideoLikeList.jsx +++ b/src/pages/dashboard/VideoFarms/VideoLikeList.jsx @@ -18,10 +18,10 @@ export default function VideoLikeList() { headers: { Authorization: `Bearer ${token}` }, }); - console.log('✅ Danh sách người đã like:', res.data.users); + console.log('Danh sách người đã like:', res.data.users); setUsers(res.data.users || []); } catch (err) { - console.error('❌ Lỗi khi lấy danh sách người đã Like:', err.response?.data || err); + console.error('Lỗi khi lấy danh sách người đã Like:', err.response?.data || err); } finally { setLoading(false); } diff --git a/src/pages/dashboard/commentVideo.jsx b/src/pages/dashboard/commentVideo.jsx index 0c838c4e..7441f0ce 100644 --- a/src/pages/dashboard/commentVideo.jsx +++ b/src/pages/dashboard/commentVideo.jsx @@ -1,4 +1,3 @@ - import { useEffect, useState } from "react"; import axios from "axios"; import { @@ -9,7 +8,6 @@ import { const BASE = "https://api-ndolv2.nongdanonline.vn/video-comment"; const token = () => localStorage.getItem("token"); - const fetchComments = (videoId) => axios .get(`${BASE}/${videoId}/comments`, { @@ -37,14 +35,12 @@ const hideReply = (videoId, commentIndex, replyIndex) => headers: { Authorization: `Bearer ${token()}` }, }); - export default function CommentVideo({ open, onClose, videoId }) { const [comments, setComments] = useState([]); const [loading, setLoading] = useState(true); const [newComment, setNewComment] = useState(""); - const [replyBox, setReplyBox] = useState({ idx: null, text: "" }); - + const [replyBox, setReplyBox] = useState({ commentIndex: null, text: "" }); const load = () => { if (!videoId) return; setLoading(true); @@ -81,8 +77,8 @@ export default function CommentVideo({ open, onClose, videoId }) { return; } try { - await replyComment(videoId, replyBox.commentId, { comment: replyBox.text }); - setReplyBox({ idx: null, text: "" }); + await replyComment(videoId, replyBox.commentIndex, { comment: replyBox.text }); + setReplyBox({ commentIndex: null, text: "" }); load(); } catch (err) { console.error("Lỗi khi gửi phản hồi:", err.response?.data || err); @@ -90,10 +86,10 @@ export default function CommentVideo({ open, onClose, videoId }) { } }; - const handleHideComment = async (commentId) => { + const handleHideComment = async (commentIndex) => { if (!window.confirm("Bạn có chắc muốn ẩn bình luận này?")) return; try { - await hideComment(videoId, idx); + await hideComment(videoId, commentIndex); load(); } catch (err) { console.error("Lỗi khi ẩn bình luận:", err.response?.data || err); @@ -101,10 +97,10 @@ export default function CommentVideo({ open, onClose, videoId }) { } }; - const handleHideReply = async (cIdx, rIdx) => { + const handleHideReply = async (commentIndex, replyIndex) => { if (!window.confirm("Bạn có chắc muốn ẩn phản hồi này?")) return; try { - await hideReply(videoId, cIdx, rIdx); + await hideReply(videoId, commentIndex, replyIndex); load(); } catch (err) { console.error("Lỗi khi ẩn phản hồi:", err.response?.data || err); @@ -156,7 +152,7 @@ export default function CommentVideo({ open, onClose, videoId }) { @@ -164,14 +160,14 @@ export default function CommentVideo({ open, onClose, videoId }) { size="sm" variant="text" color="red" - onClick={() => handleHideComment(c._id)} + onClick={() => handleHideComment(i)} > Ẩn
      - {replyBox.idx === i && ( + {replyBox.commentIndex === i && (
      setReplyBox({ idx: i, text: "" })} + onClick={() => setReplyBox({ commentIndex: i, text: "" })} > Trả lời @@ -210,7 +206,7 @@ export default function CommentVideo({ open, onClose, videoId }) { size="sm" variant="text" color="red" - onClick={() => handleHideReply(i, j)} + onClick={() => handleHideReply(i, j)} > Ẩn From f89d2e30f52b124b5eb22ad6ada0f7b4b343498c Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Wed, 9 Jul 2025 17:50:24 +0700 Subject: [PATCH 106/248] thanh --- src/pages/dashboard/commentVideo.jsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pages/dashboard/commentVideo.jsx b/src/pages/dashboard/commentVideo.jsx index 7441f0ce..c1851c34 100644 --- a/src/pages/dashboard/commentVideo.jsx +++ b/src/pages/dashboard/commentVideo.jsx @@ -152,7 +152,7 @@ export default function CommentVideo({ open, onClose, videoId }) { @@ -160,14 +160,14 @@ export default function CommentVideo({ open, onClose, videoId }) { size="sm" variant="text" color="red" - onClick={() => handleHideComment(i)} + onClick={() =>{console.log("Hide comment index:", i, "Comment:", c); handleHideComment(c.index)}} > Ẩn
      - {replyBox.commentIndex === i && ( + {replyBox.commentIndex === c.index && ( handleHideReply(i, j)} + onClick={() => {console.log("Hide reply index (backend):", c.index, "reply index (backend):", r.index); + handleHideReply(c.index, r.index)}} > Ẩn From 1781ee3c323203b22497954b164abf60612a2969 Mon Sep 17 00:00:00 2001 From: trongquy1213 Date: Wed, 9 Jul 2025 19:57:35 +0700 Subject: [PATCH 107/248] quy --- src/pages/dashboard/{farm => }/FarmForm.jsx | 48 +++++----- src/pages/dashboard/VideoFarms/LikeButton.jsx | 35 ++----- src/pages/dashboard/farm/farms.jsx | 10 +- src/pages/dashboard/users.jsx | 93 +++++++++++-------- 4 files changed, 87 insertions(+), 99 deletions(-) rename src/pages/dashboard/{farm => }/FarmForm.jsx (83%) diff --git a/src/pages/dashboard/farm/FarmForm.jsx b/src/pages/dashboard/FarmForm.jsx similarity index 83% rename from src/pages/dashboard/farm/FarmForm.jsx rename to src/pages/dashboard/FarmForm.jsx index d56ba7a2..bd21a3ea 100644 --- a/src/pages/dashboard/farm/FarmForm.jsx +++ b/src/pages/dashboard/FarmForm.jsx @@ -1,16 +1,10 @@ import React, { useState, useEffect } from "react"; import { - Dialog, - DialogHeader, - DialogBody, - DialogFooter, - Input, - Button, - Checkbox, + Dialog, DialogHeader, DialogBody, DialogFooter, + Input, Button, Checkbox, } from "@material-tailwind/react"; import Select from "react-select"; -// Tùy chọn dịch vụ const serviceOptions = [ { value: "direct_selling", label: "Bán hàng trực tiếp" }, { value: "feed_selling", label: "Bán thức ăn chăn nuôi" }, @@ -21,7 +15,6 @@ const serviceOptions = [ { value: "other_services", label: "Dịch vụ khác" }, ]; -// Tùy chọn tính năng const featureOptions = [ { value: "aquaponic_model", label: "Mô hình Aquaponic" }, { value: "ras_ready", label: "Hệ thống RAS" }, @@ -43,14 +36,13 @@ const featureOptions = [ { value: "air_quality_sensor", label: "Cảm biến chất lượng không khí" }, ]; -// Tags const tagOptions = [ { value: "nông sản", label: "nông sản" }, { value: "hữu cơ", label: "hữu cơ" }, { value: "mùa vụ", label: "mùa vụ" }, ]; -const FarmForm = ({ open, onClose, initialData, onSubmit }) => { +const FarmForm = ({ open, onClose, initialData = {}, onSubmit }) => { const [form, setForm] = useState({ name: "", code: "", @@ -72,7 +64,9 @@ const FarmForm = ({ open, onClose, initialData, onSubmit }) => { }); useEffect(() => { - if (initialData) setForm(initialData); + if (initialData) { + setForm({ ...form, ...initialData }); + } }, [initialData]); const handleChange = (field, value) => { @@ -91,7 +85,9 @@ const FarmForm = ({ open, onClose, initialData, onSubmit }) => { return ( - {initialData ? "Chỉnh sửa" : "Thêm"} nông trại + + {initialData?._id ? "Chỉnh sửa" : "Thêm"} nông trại + handleChange("name", e.target.value)} /> @@ -105,7 +101,11 @@ const FarmForm = ({ open, onClose, initialData, onSubmit }) => { isMulti options={serviceOptions} classNamePrefix="select-sm" - value={form.services.map(val => serviceOptions.find(opt => opt.value === val))} + value={ + Array.isArray(form.services) + ? form.services.map(val => serviceOptions.find(opt => opt.value === val)).filter(Boolean) + : [] + } onChange={(selected) => handleArrayChange("services", selected.map(s => s.value))} />
      @@ -116,7 +116,11 @@ const FarmForm = ({ open, onClose, initialData, onSubmit }) => { isMulti options={featureOptions} classNamePrefix="select-sm" - value={form.features.map(val => featureOptions.find(opt => opt.value === val))} + value={ + Array.isArray(form.features) + ? form.features.map(val => featureOptions.find(opt => opt.value === val)).filter(Boolean) + : [] + } onChange={(selected) => handleArrayChange("features", selected.map(s => s.value))} />
      @@ -127,7 +131,11 @@ const FarmForm = ({ open, onClose, initialData, onSubmit }) => { isMulti options={tagOptions} classNamePrefix="select-sm" - value={form.tags.map((val) => ({ value: val, label: val }))} + value={ + Array.isArray(form.tags) + ? form.tags.map((val) => ({ value: val, label: val })) + : [] + } onChange={(selected) => handleArrayChange("tags", selected.map((s) => s.value))} />
@@ -143,11 +151,9 @@ const FarmForm = ({ open, onClose, initialData, onSubmit }) => { - - +
diff --git a/src/pages/dashboard/VideoFarms/LikeButton.jsx b/src/pages/dashboard/VideoFarms/LikeButton.jsx index 363acf04..238c3ea4 100644 --- a/src/pages/dashboard/VideoFarms/LikeButton.jsx +++ b/src/pages/dashboard/VideoFarms/LikeButton.jsx @@ -3,14 +3,13 @@ import axios from 'axios'; import { BaseUrl } from '@/ipconfig'; import { useNavigate } from 'react-router-dom'; -export default function LikeButton({ videoId }) { +export default function LikeButton({ videoId, onLikeChange }) { const [liked, setLiked] = useState(false); const [loading, setLoading] = useState(false); const token = localStorage.getItem('token'); const userId = JSON.parse(localStorage.getItem('user'))?.id; const navigate = useNavigate(); - useEffect(() => { const fetchLikedStatus = async () => { if (!videoId || !token || !userId) return; @@ -19,16 +18,13 @@ export default function LikeButton({ videoId }) { const res = await axios.get(`${BaseUrl}/video-like/${videoId}/users`, { headers: { Authorization: `Bearer ${token}` }, }); - console.log(' Danh sách người đã like:', res.data.users); - const likedUsers = res.data.users || []; const isLiked = likedUsers.some( (u) => String(u._id ?? u.id) === String(userId) ); - - setLiked(isLiked); + setLiked(isLiked); } catch (err) { - console.error(' Lỗi khi check trạng thái Like:', err.response?.data || err); + console.error('Lỗi khi check trạng thái Like:', err.response?.data || err); } }; @@ -43,34 +39,15 @@ export default function LikeButton({ videoId }) { const url = liked ? `${BaseUrl}/video-like/${videoId}/unlike` : `${BaseUrl}/video-like/${videoId}/like`; + await axios.post(url, {}, { headers: { Authorization: `Bearer ${token}` }, }); - setLiked(!liked); - if (liked) { - // Unlike - await axios.post(`${BaseUrl}/video-like/${videoId}/unlike`, {}, { - headers: { Authorization: `Bearer ${token}` }, - }); - } else { - // Like - await axios.post(`${BaseUrl}/video-like/${videoId}`, {}, { - headers: { Authorization: `Bearer ${token}` }, - }); - } - - setLiked(!liked); - onLikeChange?.(); // gọi callback reload danh sách user - - } catch (error) { - console.error('Lỗi khi Like/Unlike video:', error); - - - setLiked(!liked); + onLikeChange?.(); // gọi callback nếu có } catch (err) { - console.error(' Lỗi khi Like/Unlike video:', err.response?.data || err); + console.error('Lỗi khi Like/Unlike video:', err.response?.data || err); alert('Không thể Like/Unlike video.'); } finally { setLoading(false); diff --git a/src/pages/dashboard/farm/farms.jsx b/src/pages/dashboard/farm/farms.jsx index 7b386d1c..547cb020 100644 --- a/src/pages/dashboard/farm/farms.jsx +++ b/src/pages/dashboard/farm/farms.jsx @@ -7,7 +7,7 @@ import { import { PencilSquareIcon, TrashIcon, PlusIcon, LockOpenIcon ,LockClosedIcon, XCircleIcon, CheckCircleIcon } from "@heroicons/react/24/solid"; -import FarmForm from "./FarmForm"; +import FarmForm from "../FarmForm"; import FarmDetail from "./FarmDetail"; const BASE_URL = "https://api-ndolv2.nongdanonline.vn"; @@ -113,14 +113,6 @@ export function Farms() { setTab("inactive")}>Đã khoá - - { @@ -30,8 +34,7 @@ export function Users() { const res = await axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { headers: { Authorization: `Bearer ${token}` } }); - const active = (Array.isArray(res.data) ? res.data : []) - .filter(u => u.isActive); + const active = (Array.isArray(res.data) ? res.data : []).filter(u => u.isActive); setUsers(active); } catch (err) { setError("Lỗi khi tải danh sách người dùng."); @@ -39,19 +42,19 @@ export function Users() { setLoading(false); } }; - + useEffect(() => { - if (!token) { - setError("Không tìm thấy access token!"); - setLoading(false); - return; - } - fetchUsers(); - }, [token]); + if (!token) { + setError("Không tìm thấy access token!"); + setLoading(false); + return; + } + fetchUsers(); + }, [token]); - useEffect(() => { - setRoles(["Customer", "Admin", "Farmer"]); - }, []); + useEffect(() => { + setRoles(["Customer", "Admin", "Farmer"]); + }, []); const openEdit = (user) => { setSelectedUser(user); @@ -102,20 +105,19 @@ export function Users() { { headers: { Authorization: `Bearer ${token}` } } ); alert("Xoá người dùng thành công!"); - await fetchUsers(); + await fetchUsers(); } catch (e) { alert("Xoá thất bại!"); } }; - const handleAddRole = async (userId, role) => { try { await axios.patch( `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/add-role`, - { role: role.charAt(0).toUpperCase() + role.slice(1) }, + { role: role.charAt(0).toUpperCase() + role.slice(1) }, { headers: { Authorization: `Bearer ${token}` } } - ); + ); alert("Đã thêm role thành công!"); fetchUsers(); } catch (e) { @@ -140,6 +142,7 @@ export function Users() { return (
Users Management + {loading && (
@@ -190,26 +193,6 @@ export function Users() { ))}
- - Chỉnh sửa người dùng - -
- setFormData({ ...formData, fullName: e.target.value })} /> - setFormData({ ...formData, email: e.target.value })} /> - setFormData({ ...formData, phone: e.target.value })} /> - -
-
- - - - -
- Thông tin chi tiết @@ -229,17 +212,47 @@ export function Users() { {addr.address} - {addr.ward}, {addr.district}, {addr.province}
)) : Không có địa chỉ} + + ) : ( - Đang tải dữ liệu... + Đang tải dữ liệu... )}
+ + setOpenFarmForm(false)} + initialData={farmFormData} + onSubmit={async (data) => { + try { + const payload = { ...data, ownerId: farmFormData.ownerId }; + await axios.post("https://api-ndolv2.nongdanonline.vn/adminfarms", payload, { + headers: { Authorization: `Bearer ${token}` }, + }); + alert("Tạo farm thành công!"); + setOpenFarmForm(false); + } catch (err) { + alert("Lỗi khi thêm farm: " + (err.response?.data?.message || err.message)); + } + }} + /> - ) + ); } -export default Users \ No newline at end of file +export default Users; \ No newline at end of file From 96a23acd2cd478e6faa0122ae9f44c755502e8de Mon Sep 17 00:00:00 2001 From: Tie902 Date: Wed, 9 Jul 2025 20:44:28 +0700 Subject: [PATCH 108/248] adminrepostvatoiuucodeusrs --- src/pages/dashboard/AdminReports.jsx | 150 ++++++++++++++++ src/pages/dashboard/index.js | 4 +- src/pages/dashboard/users.jsx | 256 ++++++++++++--------------- src/routes.jsx | 43 +++-- 4 files changed, 294 insertions(+), 159 deletions(-) create mode 100644 src/pages/dashboard/AdminReports.jsx diff --git a/src/pages/dashboard/AdminReports.jsx b/src/pages/dashboard/AdminReports.jsx new file mode 100644 index 00000000..c6f68991 --- /dev/null +++ b/src/pages/dashboard/AdminReports.jsx @@ -0,0 +1,150 @@ +import React, { useEffect, useState } from 'react'; +import axios from 'axios'; +import { BaseUrl } from '@/ipconfig'; + +export function AdminReports() { + const [reports, setReports] = useState([]); + const [loading, setLoading] = useState(false); + const [page, setPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [selectedReport, setSelectedReport] = useState(null); + const [status, setStatus] = useState('NEW'); + const [type, setType] = useState('USER'); + const token = localStorage.getItem('token'); + + const fetchReports = async () => { + setLoading(true); + try { + const res = await axios.get( + `${BaseUrl}/admin-reports?status=${status}&type=${type}&page=${page}&limit=5`, + { headers: { Authorization: `Bearer ${token}` } } + ); + setReports(res.data.reports || []); + setTotalPages(res.data.totalPages || 1); + } catch (err) { + console.error('Lỗi khi lấy danh sách báo cáo:', err.response?.data || err); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchReports(); + }, [page, status, type]); + + const handleApprove = async (reportId) => { + try { + await axios.post( + `${BaseUrl}/admin-reports/${reportId}/approve`, + {}, + { headers: { Authorization: `Bearer ${token}` } } + ); + alert('Đã duyệt thành công!'); + fetchReports(); + } catch (err) { + console.error('Lỗi khi duyệt báo cáo:', err.response?.data || err); + alert('Không thể duyệt báo cáo.'); + } + }; + + return ( +
+

Danh sách báo cáo

+ + {/* Bộ lọc */} +
+ + +
+ + {loading ? ( +

Đang tải...

+ ) : reports.length === 0 ? ( +

Không có báo cáo nào.

+ ) : ( +
+ {reports.map((report) => ( +
+
+
+

ID: {report.id}

+

Loại: {report.type}

+

Lý do: {report.reason}

+
+
+ + {report.status === 'NEW' && ( + + )} +
+
+
+ ))} +
+ )} + + {/* Phân trang */} +
+ + {page} / {totalPages} + +
+ + {/* Modal chi tiết */} + {selectedReport && ( +
+
+

Chi tiết báo cáo

+
+              {JSON.stringify(selectedReport, null, 2)}
+            
+
+ +
+
+
+ )} +
+ ); +} diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index 2d419c6f..50f39ddf 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,10 +1,10 @@ export * from "@/pages/dashboard/home"; export * from "@/pages/dashboard/Questions/answerstable"; -export * from "@/pages/dashboard/users"; +export { default as Users } from "./users"; export * from "@/pages/dashboard/farm/farms"; export * from "@/pages/dashboard/Questions/Questions"; export * from "@/pages/dashboard/VideoFarms/VideoFarms"; - +export * from "@/pages/dashboard/AdminReports" diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/users.jsx index 5915a391..124bf168 100644 --- a/src/pages/dashboard/users.jsx +++ b/src/pages/dashboard/users.jsx @@ -8,20 +8,19 @@ import { export function Users() { const [users, setUsers] = useState([]); - const [roles, setRoles] = useState([]); + const [roles] = useState(["Customer", "Admin", "Farmer"]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [editOpen, setEditOpen] = useState(false); const [selectedUser, setSelectedUser] = useState(null); - const [formData, setFormData] = useState({ fullName: '', email: '', phone: '', role: '' }); + const [formData, setFormData] = useState({ fullName: '', email: '', phone: '' }); + const [selectedRole, setSelectedRole] = useState("Farmer"); const [viewOpen, setViewOpen] = useState(false); const [viewUser, setViewUser] = useState(null); const [addresses, setAddresses] = useState([]); - const [selectedRole, setSelectedRole] = useState("farmer"); - const token = localStorage.getItem("token"); const fetchUsers = async () => { @@ -30,216 +29,193 @@ export function Users() { const res = await axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { headers: { Authorization: `Bearer ${token}` } }); - const active = (Array.isArray(res.data) ? res.data : []) - .filter(u => u.isActive); - setUsers(active); - } catch (err) { + const activeUsers = (Array.isArray(res.data) ? res.data : []).filter(u => u.isActive); + setUsers(activeUsers); + } catch { setError("Lỗi khi tải danh sách người dùng."); } finally { setLoading(false); } }; - - useEffect(() => { - if (!token) { - setError("Không tìm thấy access token!"); - setLoading(false); - return; - } - fetchUsers(); - }, [token]); - useEffect(() => { - setRoles(["Customer", "Admin", "Farmer"]); - }, []); + useEffect(() => { + if (!token) { + setError("Không tìm thấy access token!"); + setLoading(false); + return; + } + fetchUsers(); + }, [token]); const openEdit = (user) => { setSelectedUser(user); setFormData({ fullName: user.fullName, email: user.email, - phone: user.phone || '', - role: Array.isArray(user.role) ? user.role[0] : user.role, + phone: user.phone || '' }); setEditOpen(true); }; - const handleView = async (user) => { - setViewUser(user); - setViewOpen(true); + const handleUpdate = async () => { + if (!token || !selectedUser) return; try { - const res = await axios.get("https://api-ndolv2.nongdanonline.vn/user-addresses", { - headers: { Authorization: `Bearer ${token}` } + await axios.put(`https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}`, formData, { + headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, }); - const userAddresses = res.data.filter(addr => addr.userid === user.id); - setAddresses(userAddresses); - } catch (err) { - setAddresses([]); + alert("Cập nhật thành công!"); + fetchUsers(); + setEditOpen(false); + } catch { + alert("Cập nhật thất bại!"); } }; - const handleUpdate = () => { - if (!token || !selectedUser) return; - - axios.put(`https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}`, formData, { - headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, - }) - .then(() => { - alert("Cập nhật thành công!"); - setUsers(users.map(u => u.id === selectedUser.id ? { ...u, ...formData } : u)); - setEditOpen(false); - }) - .catch(() => alert("Cập nhật thất bại!")); - }; - - const handleDelete = async (userId) => { - if (!token) return; - if (!window.confirm("Bạn có chắc muốn xoá người dùng này?")) return; - + const handleAddRole = async () => { + if (!selectedUser) return; try { - await axios.delete( - `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}`, + await axios.patch( + `https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}/add-role`, + { role: selectedRole }, { headers: { Authorization: `Bearer ${token}` } } ); - alert("Xoá người dùng thành công!"); - await fetchUsers(); - } catch (e) { - alert("Xoá thất bại!"); + alert("Thêm role thành công!"); + fetchUsers(); + } catch { + alert("Không thể thêm role!"); } }; - - const handleAddRole = async (userId, role) => { + const handleRemoveRole = async (role) => { + if (!selectedUser) return; try { await axios.patch( - `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/add-role`, - { role: role.charAt(0).toUpperCase() + role.slice(1) }, + `https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}/remove-roles`, + { roles: [role] }, { headers: { Authorization: `Bearer ${token}` } } - ); - alert("Đã thêm role thành công!"); + ); + alert("Xoá role thành công!"); fetchUsers(); - } catch (e) { - alert("Không thể thêm role!"); + } catch { + alert("Không thể xoá role!"); } }; - const handleRemoveRole = async (userId, role) => { + const handleView = async (user) => { + setViewUser(user); + setViewOpen(true); try { - await axios.patch( - `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/remove-roles`, - { roles: [role.charAt(0).toUpperCase() + role.slice(1)] }, + const res = await axios.get("https://api-ndolv2.nongdanonline.vn/user-addresses", { + headers: { Authorization: `Bearer ${token}` } + }); + const userAddresses = res.data.filter(addr => addr.userid === user.id); + setAddresses(userAddresses); + } catch { + setAddresses([]); + } + }; + + const handleDelete = async (userId) => { + if (!window.confirm("Bạn chắc muốn xoá?")) return; + try { + await axios.delete( + `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}`, { headers: { Authorization: `Bearer ${token}` } } ); - alert("Đã xoá role thành công!"); + alert("Đã xoá người dùng!"); fetchUsers(); - } catch (e) { - alert("Không thể xoá role!"); + } catch { + alert("Xoá thất bại!"); } }; return (
- Users Management - {loading && ( -
- -
- )} + Quản lý người dùng + {loading &&
} {error &&

{error}

} -
- {users.map((user) => ( -
- - - {user.avatar ? ( - {user.fullName} - ) : ( -
- No Avatar -
- )} -
- - {user.fullName} - Email: {user.email} - Phone: {user.phone || "N/A"} - Role: {Array.isArray(user.role) ? user.role.join(", ") : user.role} - Active: {user.isActive ? "Yes" : "No"} - - -
- - - -
-
- - - -
-
-
-
+
+ {users.map(user => ( + + + {user.avatar ? ( + {user.fullName} + ) : ( +
No Avatar
+ )} +
+ + {user.fullName} + Email: {user.email} + Phone: {user.phone || "N/A"} + Roles: {Array.isArray(user.role) ? user.role.join(", ") : user.role} + + + + + + +
))}
+ {/* Dialog SỬA */} Chỉnh sửa người dùng -
+
setFormData({ ...formData, fullName: e.target.value })} /> setFormData({ ...formData, email: e.target.value })} /> setFormData({ ...formData, phone: e.target.value })} /> - + {roles.map(role => )} + + +
+ {(Array.isArray(selectedUser?.role) ? selectedUser.role : [selectedUser?.role]).map(role => ( + + {role} + + + ))} +
- - + +
+ {/* Dialog XEM */} - Thông tin chi tiết + Chi tiết người dùng {viewUser ? ( -
- {viewUser.fullName} + <> + {viewUser.fullName} Email: {viewUser.email} Phone: {viewUser.phone || "N/A"} Roles: {Array.isArray(viewUser.role) ? viewUser.role.join(", ") : viewUser.role} - Active: {viewUser.isActive ? "Yes" : "No"} ID: {viewUser.id} - Created At: {new Date(viewUser.createdAt).toLocaleString()} - Last Login: {viewUser.lastLogin ? new Date(viewUser.lastLogin).toLocaleString() : "N/A"} Addresses: - {addresses.length > 0 ? addresses.map((addr, i) => ( -
- {addr.address} - {addr.ward}, {addr.district}, {addr.province} -
+ {addresses.length ? addresses.map((addr, i) => ( + {addr.address} - {addr.ward}, {addr.district}, {addr.province} )) : Không có địa chỉ} -
- ) : ( - Đang tải dữ liệu... - )} + + ) : Đang tải...}
-
- ) +
+ ); } -export default Users \ No newline at end of file +export default Users; + diff --git a/src/routes.jsx b/src/routes.jsx index 87f54d4d..973bf02b 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -5,15 +5,21 @@ import { InformationCircleIcon, ServerStackIcon, RectangleStackIcon, + ViewfinderCircleIcon, + DocumentDuplicateIcon, // icon mới cho AdminReports } from "@heroicons/react/24/solid"; -import { Home, Users , Farms, Questions, AnswersTable, VideoFarms } from "@/pages/dashboard"; - - - +import { + Home, + Users, + Farms, + Questions, + AnswersTable, + VideoFarms, + AdminReports, // import thêm +} from "@/pages/dashboard"; import { SignIn, SignUp } from "@/pages/auth"; -import { ViewfinderCircleIcon } from "@heroicons/react/24/outline"; const icon = { className: "w-5 h-5 text-inherit", @@ -25,7 +31,7 @@ export const routes = [ pages: [ { icon: , - name: "dashboard", + name: "Dashboard", path: "/home", element: , }, @@ -41,26 +47,29 @@ export const routes = [ path: "/Farms", element: , }, - - { + { icon: , name: "Questions", path: "/Questions", - element: , + element: , }, - { icon: , - name: "AnswersTable", + name: "Answers Table", path: "/AnswersTable", - element: , + element: , }, - { + { icon: , - name: "VideoFarms", + name: "Video Farms", path: "/VideoFarms", element: , - + }, + { + icon: , + name: "Admin Reports", // tên hiển thị + path: "/AdminReports", // URL + element: , // component }, ], }, @@ -70,13 +79,13 @@ export const routes = [ pages: [ { icon: , - name: "sign in", + name: "Sign in", path: "/sign-in", element: , }, { icon: , - name: "sign up", + name: "Sign up", path: "/sign-up", element: , }, From eb98bb37a6c5e6103d7535f61bcd45bf8bad2725 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Wed, 9 Jul 2025 20:44:41 +0700 Subject: [PATCH 109/248] thanh --- src/pages/dashboard/AcceptFarm.jsx | 0 .../dashboard/VideoFarms/VideoFarmById.jsx | 2 +- .../{ => VideoFarms}/commentVideo.jsx | 0 src/pages/dashboard/farm/farms.jsx | 158 +++++++++--------- src/pages/dashboard/index.js | 5 +- src/pages/dashboard/notifications.jsx | 0 src/pages/dashboard/post/Post.jsx | 144 ++++++++++++++++ src/pages/dashboard/{ => user}/UserDetail.jsx | 0 src/pages/dashboard/{ => user}/users.jsx | 0 src/routes.jsx | 10 +- src/widgets/layout/sidenav.jsx | 10 +- 11 files changed, 241 insertions(+), 88 deletions(-) delete mode 100644 src/pages/dashboard/AcceptFarm.jsx rename src/pages/dashboard/{ => VideoFarms}/commentVideo.jsx (100%) delete mode 100644 src/pages/dashboard/notifications.jsx create mode 100644 src/pages/dashboard/post/Post.jsx rename src/pages/dashboard/{ => user}/UserDetail.jsx (100%) rename src/pages/dashboard/{ => user}/users.jsx (100%) diff --git a/src/pages/dashboard/AcceptFarm.jsx b/src/pages/dashboard/AcceptFarm.jsx deleted file mode 100644 index e69de29b..00000000 diff --git a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx index 99c6baa9..b9cb0f45 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx @@ -5,7 +5,7 @@ import { BaseUrl } from '@/ipconfig'; import { useParams } from 'react-router-dom'; import { Audio } from 'react-loader-spinner'; import LikeButton from './LikeButton'; -import CommentVideo from '../commentVideo'; +import CommentVideo from './commentVideo'; import DialogVideoDetail from './DialogVideoDetail' export const VideoFarmById = () => { diff --git a/src/pages/dashboard/commentVideo.jsx b/src/pages/dashboard/VideoFarms/commentVideo.jsx similarity index 100% rename from src/pages/dashboard/commentVideo.jsx rename to src/pages/dashboard/VideoFarms/commentVideo.jsx diff --git a/src/pages/dashboard/farm/farms.jsx b/src/pages/dashboard/farm/farms.jsx index 7b386d1c..16889785 100644 --- a/src/pages/dashboard/farm/farms.jsx +++ b/src/pages/dashboard/farm/farms.jsx @@ -167,93 +167,91 @@ export function Farms() { /> -
- {/* Nhóm nút Sửa & Xoá */} -
- +
+ {/* Nhóm nút Sửa & Xoá */} +
+ - -
+ +
- {/* Nhóm nút trạng thái */} -
- {farm.status === "pending" && ( - <> - - - - -)} - -{farm.status === "active" && ( - -)} + {/* Nhóm nút trạng thái */} +
+ {farm.status === "pending" && ( + <> + -{farm.status === "inactive" && ( - -)} + + + )} -
-
- + {farm.status === "active" && ( + + )} + {farm.status === "inactive" && ( + + )} +
+
+ ))} diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index 2d419c6f..f42bef88 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,10 +1,9 @@ - export * from "@/pages/dashboard/home"; export * from "@/pages/dashboard/Questions/answerstable"; -export * from "@/pages/dashboard/users"; +export * from "@/pages/dashboard/user/users"; export * from "@/pages/dashboard/farm/farms"; export * from "@/pages/dashboard/Questions/Questions"; export * from "@/pages/dashboard/VideoFarms/VideoFarms"; - +export * from "@/pages/dashboard/post/Post"; diff --git a/src/pages/dashboard/notifications.jsx b/src/pages/dashboard/notifications.jsx deleted file mode 100644 index e69de29b..00000000 diff --git a/src/pages/dashboard/post/Post.jsx b/src/pages/dashboard/post/Post.jsx new file mode 100644 index 00000000..e2af6702 --- /dev/null +++ b/src/pages/dashboard/post/Post.jsx @@ -0,0 +1,144 @@ +import React, { useEffect, useState } from "react"; +import { Button, Typography, Avatar, Chip } from "@material-tailwind/react"; + +const BASE_URL = "https://api-ndolv2.nongdanonline.vn"; + +export default function PostList() { + const [posts, setPosts] = useState([]); + const [loading, setLoading] = useState(false); + + // Lấy danh sách bài post + const fetchPosts = async () => { + setLoading(true); + try { + const token = localStorage.getItem("token"); + const res = await fetch(`${BASE_URL}/admin-post-feed`, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }); + const json = await res.json(); + if (res.ok) { + setPosts(json.data); + } else { + alert(json.message || "Lỗi khi lấy danh sách bài post"); + } + } catch (err) { + console.error("Fetch posts error:", err); + alert("Không thể kết nối tới server"); + } + setLoading(false); + }; + + // Xoá bài post + const deletePost = async (id) => { + const confirmDelete = window.confirm("Bạn có chắc chắn muốn xoá bài post này?"); + if (!confirmDelete) return; + + try { + const token = localStorage.getItem("token"); + const res = await fetch(`${BASE_URL}/admin-post-feed/${id}`, { + method: "DELETE", + headers: { Authorization: `Bearer ${token}` }, + }); + if (res.ok) { + alert("Xoá thành công!"); + setPosts(posts.filter((post) => post.id !== id)); + } else { + const json = await res.json(); + alert(json.message || "Xoá thất bại"); + } + } catch (err) { + console.error("Delete post error:", err); + alert("Không thể kết nối tới server"); + } + }; + + useEffect(() => { + fetchPosts(); + }, []); + + return ( +
+ Danh sách bài post + + {loading ? ( + Đang tải dữ liệu... + ) : ( +
+ + + + + + + + + + + + + + + {posts.map((post) => ( + + + + + + + + + + + ))} + {posts.length === 0 && ( + + + + )} + +
Tiêu đềMô tảTagsHìnhTác giảLikeTrạng tháiHành động
{post.title}{post.description} + {post.tags.map((tag, index) => ( + + ))} + + {post.images.length > 0 ? ( + Hình ảnh + ) : ( + "Không có" + )} + + + {post.authorId.fullName} + {post.like} + + + +
+ Không có dữ liệu +
+
+ )} +
+ ); +} diff --git a/src/pages/dashboard/UserDetail.jsx b/src/pages/dashboard/user/UserDetail.jsx similarity index 100% rename from src/pages/dashboard/UserDetail.jsx rename to src/pages/dashboard/user/UserDetail.jsx diff --git a/src/pages/dashboard/users.jsx b/src/pages/dashboard/user/users.jsx similarity index 100% rename from src/pages/dashboard/users.jsx rename to src/pages/dashboard/user/users.jsx diff --git a/src/routes.jsx b/src/routes.jsx index 87f54d4d..5a3aff71 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -5,9 +5,10 @@ import { InformationCircleIcon, ServerStackIcon, RectangleStackIcon, + NewspaperIcon, } from "@heroicons/react/24/solid"; -import { Home, Users , Farms, Questions, AnswersTable, VideoFarms } from "@/pages/dashboard"; +import { Home, Users , Farms, Questions, AnswersTable, VideoFarms, PostList } from "@/pages/dashboard"; @@ -62,6 +63,13 @@ export const routes = [ element: , }, + { + icon: , + name: "PostList", + path: "/PostList", + element: , + + }, ], }, { diff --git a/src/widgets/layout/sidenav.jsx b/src/widgets/layout/sidenav.jsx index c38a1f65..9880e603 100644 --- a/src/widgets/layout/sidenav.jsx +++ b/src/widgets/layout/sidenav.jsx @@ -19,9 +19,13 @@ export function Sidenav({ brandImg, brandName, routes }) { const navigate = useNavigate(); const handleLogout = () => { - localStorage.removeItem("token"); - setAuthStatus(dispatch, false); - navigate("/auth/sign-in"); + const confirmLogout = window.confirm("Bạn có chắc muốn đăng xuất?") + if (confirmLogout) { + localStorage.removeItem("token"); + setAuthStatus(dispatch, false); + navigate("/auth/sign-in"); + } + }; const sidenavTypes = { From e9571a79285c861b9eab9c7d1fdc3238e5dce646 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Wed, 9 Jul 2025 20:47:07 +0700 Subject: [PATCH 110/248] code --- src/components/LikeButton.jsx | 24 +++++++++++++++++++++ src/pages/dashboard/VideoLikeList.jsx | 31 +++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/src/components/LikeButton.jsx b/src/components/LikeButton.jsx index 4aaf744b..0ae73479 100644 --- a/src/components/LikeButton.jsx +++ b/src/components/LikeButton.jsx @@ -1,5 +1,9 @@ // src/components/LikeButton.jsx <<<<<<< Updated upstream +<<<<<<< Updated upstream +======= + +>>>>>>> Stashed changes ======= >>>>>>> Stashed changes @@ -30,6 +34,7 @@ export default function LikeButton({ videoId }) { setLiked(!liked); if (liked) { +<<<<<<< Updated upstream <<<<<<< Updated upstream // Unlike await axios.post(`${BaseUrl}/video-like/${videoId}/unlike`, {}, { @@ -48,20 +53,35 @@ export default function LikeButton({ videoId }) { { headers: { Authorization: `Bearer ${token}` } } ); } else { +======= + // ✅ GỌI API UNLIKE CHUẨN BE + await axios.post( + `${BaseUrl}/video-like/${videoId}/unlike`, + {}, + { headers: { Authorization: `Bearer ${token}` } } + ); + } else { +>>>>>>> Stashed changes // ✅ GỌI API LIKE CHUẨN BE await axios.post( `${BaseUrl}/video-like/${videoId}/like`, {}, { headers: { Authorization: `Bearer ${token}` } } ); +<<<<<<< Updated upstream +>>>>>>> Stashed changes +======= >>>>>>> Stashed changes } // Toggle trạng thái setLiked(!liked); +<<<<<<< Updated upstream <<<<<<< Updated upstream onLikeChange?.(); // gọi callback reload danh sách user ======= +>>>>>>> Stashed changes +======= >>>>>>> Stashed changes } catch (error) { console.error('Lỗi khi Like/Unlike video:', error); @@ -72,6 +92,10 @@ export default function LikeButton({ videoId }) { const handleViewLikes = () => { <<<<<<< Updated upstream +<<<<<<< Updated upstream +======= + // Điều hướng đến trang hiển thị danh sách users đã Like +>>>>>>> Stashed changes ======= // Điều hướng đến trang hiển thị danh sách users đã Like >>>>>>> Stashed changes diff --git a/src/pages/dashboard/VideoLikeList.jsx b/src/pages/dashboard/VideoLikeList.jsx index 0dabe95d..0076a1ec 100644 --- a/src/pages/dashboard/VideoLikeList.jsx +++ b/src/pages/dashboard/VideoLikeList.jsx @@ -1,13 +1,19 @@ <<<<<<< Updated upstream +<<<<<<< Updated upstream import React from 'react'; import { useParams } from 'react-router-dom'; import VideoLikeBox from '@/components/VideoLikeBox'; ======= +======= +>>>>>>> Stashed changes import React, { useEffect, useState } from 'react'; import axios from 'axios'; import { BaseUrl } from '@/ipconfig'; import { useParams, useNavigate } from 'react-router-dom'; import { Audio } from 'react-loader-spinner'; +<<<<<<< Updated upstream +>>>>>>> Stashed changes +======= >>>>>>> Stashed changes export default function VideoLikeList() { @@ -17,6 +23,7 @@ export default function VideoLikeList() { const token = localStorage.getItem('token'); const navigate = useNavigate(); +<<<<<<< Updated upstream <<<<<<< Updated upstream return (
@@ -42,6 +49,27 @@ export default function VideoLikeList() { }, []); return ( +======= + const getLikes = async () => { + try { + setLoading(true); + const res = await axios.post(`${BaseUrl}/video-like/${videoId}/like`, {}, { + headers: { Authorization: `Bearer ${token}` }, + }); + setLikes(res.data?.data || []); + } catch (error) { + console.error('Lỗi lấy danh sách Like:', error); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + getLikes(); + }, []); + + return ( +>>>>>>> Stashed changes

Danh sách người đã Like

@@ -67,6 +95,9 @@ export default function VideoLikeList() { > Quay lại +<<<<<<< Updated upstream +>>>>>>> Stashed changes +======= >>>>>>> Stashed changes
); From 19b3bfa254a1cc62d29c48d86183ee2b6aa42470 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Wed, 9 Jul 2025 20:48:54 +0700 Subject: [PATCH 111/248] code --- src/components/LikeButton.jsx | 69 +++++++++++++++++++++++++++ src/pages/dashboard/VideoLikeList.jsx | 31 ++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 src/components/LikeButton.jsx diff --git a/src/components/LikeButton.jsx b/src/components/LikeButton.jsx new file mode 100644 index 00000000..6c67aab1 --- /dev/null +++ b/src/components/LikeButton.jsx @@ -0,0 +1,69 @@ +// src/components/LikeButton.jsx + +import React, { useState } from 'react'; +import axios from 'axios'; +import { BaseUrl } from '@/ipconfig'; +import { useNavigate } from 'react-router-dom'; + +export default function LikeButton({ videoId }) { + const [liked, setLiked] = useState(false); + const [loading, setLoading] = useState(false); + const token = localStorage.getItem('token'); + const navigate = useNavigate(); + + const handleLikeToggle = async () => { + if (!videoId || !token) return; + setLoading(true); + + try { + if (liked) { + // ✅ GỌI API UNLIKE CHUẨN BE + await axios.post( + `${BaseUrl}/video-like/${videoId}/unlike`, + {}, + { headers: { Authorization: `Bearer ${token}` } } + ); + } else { + // ✅ GỌI API LIKE CHUẨN BE + await axios.post( + `${BaseUrl}/video-like/${videoId}/like`, + {}, + { headers: { Authorization: `Bearer ${token}` } } + ); + } + + // Toggle trạng thái + setLiked(!liked); + } catch (error) { + console.error('Lỗi khi Like/Unlike video:', error); + } finally { + setLoading(false); + } + }; + + const handleViewLikes = () => { + // Điều hướng đến trang hiển thị danh sách users đã Like + navigate(`/dashboard/video-like/${videoId}`); + }; + + return ( +
+ + + +
+ ); +} diff --git a/src/pages/dashboard/VideoLikeList.jsx b/src/pages/dashboard/VideoLikeList.jsx index 0076a1ec..b09ed4ba 100644 --- a/src/pages/dashboard/VideoLikeList.jsx +++ b/src/pages/dashboard/VideoLikeList.jsx @@ -1,17 +1,23 @@ <<<<<<< Updated upstream <<<<<<< Updated upstream +<<<<<<< Updated upstream import React from 'react'; import { useParams } from 'react-router-dom'; import VideoLikeBox from '@/components/VideoLikeBox'; ======= ======= >>>>>>> Stashed changes +======= +>>>>>>> Stashed changes import React, { useEffect, useState } from 'react'; import axios from 'axios'; import { BaseUrl } from '@/ipconfig'; import { useParams, useNavigate } from 'react-router-dom'; import { Audio } from 'react-loader-spinner'; <<<<<<< Updated upstream +<<<<<<< Updated upstream +>>>>>>> Stashed changes +======= >>>>>>> Stashed changes ======= >>>>>>> Stashed changes @@ -23,6 +29,7 @@ export default function VideoLikeList() { const token = localStorage.getItem('token'); const navigate = useNavigate(); +<<<<<<< Updated upstream <<<<<<< Updated upstream <<<<<<< Updated upstream return ( @@ -68,6 +75,27 @@ export default function VideoLikeList() { getLikes(); }, []); + return ( +>>>>>>> Stashed changes +======= + const getLikes = async () => { + try { + setLoading(true); + const res = await axios.post(`${BaseUrl}/video-like/${videoId}/like`, {}, { + headers: { Authorization: `Bearer ${token}` }, + }); + setLikes(res.data?.data || []); + } catch (error) { + console.error('Lỗi lấy danh sách Like:', error); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + getLikes(); + }, []); + return ( >>>>>>> Stashed changes
@@ -96,6 +124,9 @@ export default function VideoLikeList() { Quay lại <<<<<<< Updated upstream +<<<<<<< Updated upstream +>>>>>>> Stashed changes +======= >>>>>>> Stashed changes ======= >>>>>>> Stashed changes From 96bf9ff27c39c378bca0221d57f4a8e54b832948 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Wed, 9 Jul 2025 20:49:20 +0700 Subject: [PATCH 112/248] code --- src/pages/dashboard/VideoLikeList.jsx | 31 +++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/pages/dashboard/VideoLikeList.jsx b/src/pages/dashboard/VideoLikeList.jsx index b09ed4ba..2b0e5ba6 100644 --- a/src/pages/dashboard/VideoLikeList.jsx +++ b/src/pages/dashboard/VideoLikeList.jsx @@ -1,6 +1,7 @@ <<<<<<< Updated upstream <<<<<<< Updated upstream <<<<<<< Updated upstream +<<<<<<< Updated upstream import React from 'react'; import { useParams } from 'react-router-dom'; import VideoLikeBox from '@/components/VideoLikeBox'; @@ -9,6 +10,8 @@ import VideoLikeBox from '@/components/VideoLikeBox'; >>>>>>> Stashed changes ======= >>>>>>> Stashed changes +======= +>>>>>>> Stashed changes import React, { useEffect, useState } from 'react'; import axios from 'axios'; import { BaseUrl } from '@/ipconfig'; @@ -16,6 +19,9 @@ import { useParams, useNavigate } from 'react-router-dom'; import { Audio } from 'react-loader-spinner'; <<<<<<< Updated upstream <<<<<<< Updated upstream +<<<<<<< Updated upstream +>>>>>>> Stashed changes +======= >>>>>>> Stashed changes ======= >>>>>>> Stashed changes @@ -31,6 +37,7 @@ export default function VideoLikeList() { <<<<<<< Updated upstream <<<<<<< Updated upstream +<<<<<<< Updated upstream <<<<<<< Updated upstream return (
@@ -96,6 +103,27 @@ export default function VideoLikeList() { getLikes(); }, []); + return ( +>>>>>>> Stashed changes +======= + const getLikes = async () => { + try { + setLoading(true); + const res = await axios.post(`${BaseUrl}/video-like/${videoId}/like`, {}, { + headers: { Authorization: `Bearer ${token}` }, + }); + setLikes(res.data?.data || []); + } catch (error) { + console.error('Lỗi lấy danh sách Like:', error); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + getLikes(); + }, []); + return ( >>>>>>> Stashed changes
@@ -125,6 +153,9 @@ export default function VideoLikeList() { <<<<<<< Updated upstream <<<<<<< Updated upstream +<<<<<<< Updated upstream +>>>>>>> Stashed changes +======= >>>>>>> Stashed changes ======= >>>>>>> Stashed changes From fa8dacd308af3d93ec12accbbd5016648d12b139 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Wed, 9 Jul 2025 20:51:40 +0700 Subject: [PATCH 113/248] thanh --- src/pages/dashboard/{ => farm}/FarmForm.jsx | 0 src/pages/dashboard/farm/farms.jsx | 2 +- src/pages/dashboard/post/Post.jsx | 4 +++- src/pages/dashboard/user/users.jsx | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) rename src/pages/dashboard/{ => farm}/FarmForm.jsx (100%) diff --git a/src/pages/dashboard/FarmForm.jsx b/src/pages/dashboard/farm/FarmForm.jsx similarity index 100% rename from src/pages/dashboard/FarmForm.jsx rename to src/pages/dashboard/farm/FarmForm.jsx diff --git a/src/pages/dashboard/farm/farms.jsx b/src/pages/dashboard/farm/farms.jsx index ffe7db64..95289a2c 100644 --- a/src/pages/dashboard/farm/farms.jsx +++ b/src/pages/dashboard/farm/farms.jsx @@ -7,7 +7,7 @@ import { import { PencilSquareIcon, TrashIcon, PlusIcon, LockOpenIcon ,LockClosedIcon, XCircleIcon, CheckCircleIcon } from "@heroicons/react/24/solid"; -import FarmForm from "../FarmForm"; +import FarmForm from "./FarmForm"; import FarmDetail from "./FarmDetail"; const BASE_URL = "https://api-ndolv2.nongdanonline.vn"; diff --git a/src/pages/dashboard/post/Post.jsx b/src/pages/dashboard/post/Post.jsx index e2af6702..7e866499 100644 --- a/src/pages/dashboard/post/Post.jsx +++ b/src/pages/dashboard/post/Post.jsx @@ -3,7 +3,7 @@ import { Button, Typography, Avatar, Chip } from "@material-tailwind/react"; const BASE_URL = "https://api-ndolv2.nongdanonline.vn"; -export default function PostList() { +export function PostList() { const [posts, setPosts] = useState([]); const [loading, setLoading] = useState(false); @@ -142,3 +142,5 @@ export default function PostList() {
); } + +export default PostList; \ No newline at end of file diff --git a/src/pages/dashboard/user/users.jsx b/src/pages/dashboard/user/users.jsx index 0c2a5639..9effd7f4 100644 --- a/src/pages/dashboard/user/users.jsx +++ b/src/pages/dashboard/user/users.jsx @@ -5,7 +5,7 @@ import { Typography, Button, Dialog, DialogHeader, DialogBody, DialogFooter, Input, Select, Option, Spinner } from "@material-tailwind/react"; -import FarmForm from "./FarmForm"; +import FarmForm from "../farm/FarmForm"; export function Users() { const [users, setUsers] = useState([]); From 7404100ccf5382368914f006ba100bc1a7def144 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Wed, 9 Jul 2025 20:56:05 +0700 Subject: [PATCH 114/248] t --- src/pages/dashboard/VideoFarms/LikeButton.jsx | 64 ----------- src/pages/dashboard/VideoLikeList.jsx | 108 +----------------- 2 files changed, 1 insertion(+), 171 deletions(-) diff --git a/src/pages/dashboard/VideoFarms/LikeButton.jsx b/src/pages/dashboard/VideoFarms/LikeButton.jsx index 0ae73479..f2ab4bc0 100644 --- a/src/pages/dashboard/VideoFarms/LikeButton.jsx +++ b/src/pages/dashboard/VideoFarms/LikeButton.jsx @@ -1,12 +1,5 @@ // src/components/LikeButton.jsx -<<<<<<< Updated upstream -<<<<<<< Updated upstream -======= ->>>>>>> Stashed changes -======= - ->>>>>>> Stashed changes import React, { useState } from 'react'; import axios from 'axios'; import { BaseUrl } from '@/ipconfig'; @@ -23,7 +16,6 @@ export default function LikeButton({ videoId }) { setLoading(true); try { - const url = liked ? `${BaseUrl}/video-like/${videoId}/unlike` : `${BaseUrl}/video-like/${videoId}/like`; @@ -32,57 +24,8 @@ export default function LikeButton({ videoId }) { headers: { Authorization: `Bearer ${token}` }, }); - setLiked(!liked); - if (liked) { -<<<<<<< Updated upstream -<<<<<<< Updated upstream - // Unlike - await axios.post(`${BaseUrl}/video-like/${videoId}/unlike`, {}, { - headers: { Authorization: `Bearer ${token}` }, - }); - } else { - // Like - await axios.post(`${BaseUrl}/video-like/${videoId}`, {}, { - headers: { Authorization: `Bearer ${token}` }, - }); -======= - // ✅ GỌI API UNLIKE CHUẨN BE - await axios.post( - `${BaseUrl}/video-like/${videoId}/unlike`, - {}, - { headers: { Authorization: `Bearer ${token}` } } - ); - } else { -======= - // ✅ GỌI API UNLIKE CHUẨN BE - await axios.post( - `${BaseUrl}/video-like/${videoId}/unlike`, - {}, - { headers: { Authorization: `Bearer ${token}` } } - ); - } else { ->>>>>>> Stashed changes - // ✅ GỌI API LIKE CHUẨN BE - await axios.post( - `${BaseUrl}/video-like/${videoId}/like`, - {}, - { headers: { Authorization: `Bearer ${token}` } } - ); -<<<<<<< Updated upstream ->>>>>>> Stashed changes -======= ->>>>>>> Stashed changes - } - // Toggle trạng thái setLiked(!liked); -<<<<<<< Updated upstream -<<<<<<< Updated upstream - onLikeChange?.(); // gọi callback reload danh sách user -======= ->>>>>>> Stashed changes -======= ->>>>>>> Stashed changes } catch (error) { console.error('Lỗi khi Like/Unlike video:', error); } finally { @@ -91,14 +34,7 @@ export default function LikeButton({ videoId }) { }; const handleViewLikes = () => { -<<<<<<< Updated upstream -<<<<<<< Updated upstream -======= - // Điều hướng đến trang hiển thị danh sách users đã Like ->>>>>>> Stashed changes -======= // Điều hướng đến trang hiển thị danh sách users đã Like ->>>>>>> Stashed changes navigate(`/dashboard/video-like/${videoId}`); }; diff --git a/src/pages/dashboard/VideoLikeList.jsx b/src/pages/dashboard/VideoLikeList.jsx index 2b0e5ba6..d186bd6f 100644 --- a/src/pages/dashboard/VideoLikeList.jsx +++ b/src/pages/dashboard/VideoLikeList.jsx @@ -1,32 +1,8 @@ -<<<<<<< Updated upstream -<<<<<<< Updated upstream -<<<<<<< Updated upstream -<<<<<<< Updated upstream -import React from 'react'; -import { useParams } from 'react-router-dom'; -import VideoLikeBox from '@/components/VideoLikeBox'; -======= -======= ->>>>>>> Stashed changes -======= ->>>>>>> Stashed changes -======= ->>>>>>> Stashed changes import React, { useEffect, useState } from 'react'; import axios from 'axios'; import { BaseUrl } from '@/ipconfig'; import { useParams, useNavigate } from 'react-router-dom'; import { Audio } from 'react-loader-spinner'; -<<<<<<< Updated upstream -<<<<<<< Updated upstream -<<<<<<< Updated upstream ->>>>>>> Stashed changes -======= ->>>>>>> Stashed changes -======= ->>>>>>> Stashed changes -======= ->>>>>>> Stashed changes export default function VideoLikeList() { const { videoId } = useParams(); @@ -35,81 +11,10 @@ export default function VideoLikeList() { const token = localStorage.getItem('token'); const navigate = useNavigate(); -<<<<<<< Updated upstream -<<<<<<< Updated upstream -<<<<<<< Updated upstream -<<<<<<< Updated upstream - return ( -
-

Danh sách người đã like video

- -======= - const getLikes = async () => { - try { - setLoading(true); - const res = await axios.post(`${BaseUrl}/video-like/${videoId}/like`, {}, { - headers: { Authorization: `Bearer ${token}` }, - }); - setLikes(res.data?.data || []); - } catch (error) { - console.error('Lỗi lấy danh sách Like:', error); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - getLikes(); - }, []); - - return ( -======= - const getLikes = async () => { - try { - setLoading(true); - const res = await axios.post(`${BaseUrl}/video-like/${videoId}/like`, {}, { - headers: { Authorization: `Bearer ${token}` }, - }); - setLikes(res.data?.data || []); - } catch (error) { - console.error('Lỗi lấy danh sách Like:', error); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - getLikes(); - }, []); - - return ( ->>>>>>> Stashed changes -======= - const getLikes = async () => { - try { - setLoading(true); - const res = await axios.post(`${BaseUrl}/video-like/${videoId}/like`, {}, { - headers: { Authorization: `Bearer ${token}` }, - }); - setLikes(res.data?.data || []); - } catch (error) { - console.error('Lỗi lấy danh sách Like:', error); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - getLikes(); - }, []); - - return ( ->>>>>>> Stashed changes -======= const getLikes = async () => { try { setLoading(true); - const res = await axios.post(`${BaseUrl}/video-like/${videoId}/like`, {}, { + const res = await axios.get(`${BaseUrl}/video-like/${videoId}`, { headers: { Authorization: `Bearer ${token}` }, }); setLikes(res.data?.data || []); @@ -125,7 +30,6 @@ export default function VideoLikeList() { }, []); return ( ->>>>>>> Stashed changes

Danh sách người đã Like

@@ -151,16 +55,6 @@ export default function VideoLikeList() { > Quay lại -<<<<<<< Updated upstream -<<<<<<< Updated upstream -<<<<<<< Updated upstream ->>>>>>> Stashed changes -======= ->>>>>>> Stashed changes -======= ->>>>>>> Stashed changes -======= ->>>>>>> Stashed changes
); } From ac26337a644cefcc87a98474790a920247e50e0c Mon Sep 17 00:00:00 2001 From: Tie902 Date: Wed, 9 Jul 2025 21:11:17 +0700 Subject: [PATCH 115/248] users --- src/pages/dashboard/user/users.jsx | 270 ++++++++++++----------------- 1 file changed, 115 insertions(+), 155 deletions(-) diff --git a/src/pages/dashboard/user/users.jsx b/src/pages/dashboard/user/users.jsx index 0c2a5639..55b349ac 100644 --- a/src/pages/dashboard/user/users.jsx +++ b/src/pages/dashboard/user/users.jsx @@ -5,27 +5,22 @@ import { Typography, Button, Dialog, DialogHeader, DialogBody, DialogFooter, Input, Select, Option, Spinner } from "@material-tailwind/react"; -import FarmForm from "./FarmForm"; export function Users() { const [users, setUsers] = useState([]); - const [roles, setRoles] = useState([]); + const [roles] = useState(["Customer", "Admin", "Farmer"]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [editOpen, setEditOpen] = useState(false); const [selectedUser, setSelectedUser] = useState(null); - const [formData, setFormData] = useState({ fullName: '', email: '', phone: '', role: '' }); + const [formData, setFormData] = useState({ fullName: '', email: '', phone: '' }); + const [selectedRole, setSelectedRole] = useState("Farmer"); const [viewOpen, setViewOpen] = useState(false); const [viewUser, setViewUser] = useState(null); const [addresses, setAddresses] = useState([]); - const [selectedRole, setSelectedRole] = useState("farmer"); - - const [openFarmForm, setOpenFarmForm] = useState(false); - const [farmFormData, setFarmFormData] = useState(null); - const token = localStorage.getItem("token"); const fetchUsers = async () => { @@ -34,9 +29,9 @@ export function Users() { const res = await axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { headers: { Authorization: `Bearer ${token}` } }); - const active = (Array.isArray(res.data) ? res.data : []).filter(u => u.isActive); - setUsers(active); - } catch (err) { + const activeUsers = (Array.isArray(res.data) ? res.data : []).filter(u => u.isActive); + setUsers(activeUsers); + } catch { setError("Lỗi khi tải danh sách người dùng."); } finally { setLoading(false); @@ -52,207 +47,172 @@ export function Users() { fetchUsers(); }, [token]); - useEffect(() => { - setRoles(["Customer", "Admin", "Farmer"]); - }, []); - const openEdit = (user) => { setSelectedUser(user); setFormData({ fullName: user.fullName, email: user.email, - phone: user.phone || '', - role: Array.isArray(user.role) ? user.role[0] : user.role, + phone: user.phone || '' }); setEditOpen(true); }; - const handleView = async (user) => { - setViewUser(user); - setViewOpen(true); + const handleUpdate = async () => { + if (!token || !selectedUser) return; try { - const res = await axios.get("https://api-ndolv2.nongdanonline.vn/user-addresses", { - headers: { Authorization: `Bearer ${token}` } + await axios.put(`https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}`, formData, { + headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, }); - const userAddresses = res.data.filter(addr => addr.userid === user.id); - setAddresses(userAddresses); - } catch (err) { - setAddresses([]); + alert("Cập nhật thành công!"); + fetchUsers(); + setEditOpen(false); + } catch { + alert("Cập nhật thất bại!"); } }; - const handleUpdate = () => { - if (!token || !selectedUser) return; - - axios.put(`https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}`, formData, { - headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, - }) - .then(() => { - alert("Cập nhật thành công!"); - setUsers(users.map(u => u.id === selectedUser.id ? { ...u, ...formData } : u)); - setEditOpen(false); - }) - .catch(() => alert("Cập nhật thất bại!")); - }; - - const handleDelete = async (userId) => { - if (!token) return; - if (!window.confirm("Bạn có chắc muốn xoá người dùng này?")) return; - + const handleAddRole = async () => { + if (!selectedUser) return; try { - await axios.delete( - `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}`, + await axios.patch( + `https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}/add-role`, + { role: selectedRole }, { headers: { Authorization: `Bearer ${token}` } } ); - alert("Xoá người dùng thành công!"); - await fetchUsers(); - } catch (e) { - alert("Xoá thất bại!"); + alert("Thêm role thành công!"); + fetchUsers(); + } catch { + alert("Không thể thêm role!"); } }; - const handleAddRole = async (userId, role) => { + const handleRemoveRole = async (role) => { + if (!selectedUser) return; try { await axios.patch( - `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/add-role`, - { role: role.charAt(0).toUpperCase() + role.slice(1) }, + `https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}/remove-roles`, + { roles: [role] }, { headers: { Authorization: `Bearer ${token}` } } ); - alert("Đã thêm role thành công!"); + alert("Xoá role thành công!"); fetchUsers(); - } catch (e) { - alert("Không thể thêm role!"); + } catch { + alert("Không thể xoá role!"); + } + }; + + const handleView = async (user) => { + setViewUser(user); + setViewOpen(true); + try { + const res = await axios.get("https://api-ndolv2.nongdanonline.vn/user-addresses", { + headers: { Authorization: `Bearer ${token}` } + }); + const userAddresses = res.data.filter(addr => addr.userid === user.id); + setAddresses(userAddresses); + } catch { + setAddresses([]); } }; - const handleRemoveRole = async (userId, role) => { + const handleDelete = async (userId) => { + if (!window.confirm("Bạn chắc muốn xoá?")) return; try { - await axios.patch( - `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/remove-roles`, - { roles: [role.charAt(0).toUpperCase() + role.slice(1)] }, + await axios.delete( + `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}`, { headers: { Authorization: `Bearer ${token}` } } ); - alert("Đã xoá role thành công!"); + alert("Đã xoá người dùng!"); fetchUsers(); - } catch (e) { - alert("Không thể xoá role!"); + } catch { + alert("Xoá thất bại!"); } }; return (
- Users Management - - {loading && ( -
- -
- )} + Quản lý người dùng + {loading &&
} {error &&

{error}

} -
- {users.map((user) => ( -
- - - {user.avatar ? ( - {user.fullName} - ) : ( -
- No Avatar -
- )} -
- - {user.fullName} - Email: {user.email} - Phone: {user.phone || "N/A"} - Role: {Array.isArray(user.role) ? user.role.join(", ") : user.role} - Active: {user.isActive ? "Yes" : "No"} - - -
- - - -
-
- - - -
-
-
-
+
+ {users.map(user => ( + + + {user.avatar ? ( + {user.fullName} + ) : ( +
No Avatar
+ )} +
+ + {user.fullName} + Email: {user.email} + Phone: {user.phone || "N/A"} + Roles: {Array.isArray(user.role) ? user.role.join(", ") : user.role} + + + + + + +
))}
+ {/* Dialog SỬA */} + + Chỉnh sửa người dùng + +
+ setFormData({ ...formData, fullName: e.target.value })} /> + setFormData({ ...formData, email: e.target.value })} /> + setFormData({ ...formData, phone: e.target.value })} /> + Quản lý role + + +
+ {(Array.isArray(selectedUser?.role) ? selectedUser.role : [selectedUser?.role]).map(role => ( + + {role} + + + ))} +
+
+
+ + + + +
+ + {/* Dialog XEM */} - Thông tin chi tiết + Chi tiết người dùng {viewUser ? ( -
- {viewUser.fullName} + <> + {viewUser.fullName} Email: {viewUser.email} Phone: {viewUser.phone || "N/A"} Roles: {Array.isArray(viewUser.role) ? viewUser.role.join(", ") : viewUser.role} - Active: {viewUser.isActive ? "Yes" : "No"} ID: {viewUser.id} - Created At: {new Date(viewUser.createdAt).toLocaleString()} - Last Login: {viewUser.lastLogin ? new Date(viewUser.lastLogin).toLocaleString() : "N/A"} Addresses: - {addresses.length > 0 ? addresses.map((addr, i) => ( -
- {addr.address} - {addr.ward}, {addr.district}, {addr.province} -
+ {addresses.length ? addresses.map((addr, i) => ( + {addr.address} - {addr.ward}, {addr.district}, {addr.province} )) : Không có địa chỉ} - - -
- ) : ( - Đang tải dữ liệu... - )} + + ) : Đang tải...}
- - setOpenFarmForm(false)} - initialData={farmFormData} - onSubmit={async (data) => { - try { - const payload = { ...data, ownerId: farmFormData.ownerId }; - await axios.post("https://api-ndolv2.nongdanonline.vn/adminfarms", payload, { - headers: { Authorization: `Bearer ${token}` }, - }); - alert("Tạo farm thành công!"); - setOpenFarmForm(false); - } catch (err) { - alert("Lỗi khi thêm farm: " + (err.response?.data?.message || err.message)); - } - }} - />
); } -export default Users; \ No newline at end of file +export default Users; From c1c58c6c67ede2f90e4a1f5bcc9b8e4eb9ca4d5f Mon Sep 17 00:00:00 2001 From: Tie902 Date: Wed, 9 Jul 2025 21:29:34 +0700 Subject: [PATCH 116/248] goproi --- src/pages/dashboard/user/users.jsx | 234 ++++++++++++++--------------- 1 file changed, 112 insertions(+), 122 deletions(-) diff --git a/src/pages/dashboard/user/users.jsx b/src/pages/dashboard/user/users.jsx index 0c2a5639..ad5bb4de 100644 --- a/src/pages/dashboard/user/users.jsx +++ b/src/pages/dashboard/user/users.jsx @@ -9,20 +9,19 @@ import FarmForm from "./FarmForm"; export function Users() { const [users, setUsers] = useState([]); - const [roles, setRoles] = useState([]); + const [roles] = useState(["Customer", "Admin", "Farmer"]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [editOpen, setEditOpen] = useState(false); const [selectedUser, setSelectedUser] = useState(null); - const [formData, setFormData] = useState({ fullName: '', email: '', phone: '', role: '' }); + const [formData, setFormData] = useState({ fullName: '', email: '', phone: '' }); + const [selectedRole, setSelectedRole] = useState("Farmer"); const [viewOpen, setViewOpen] = useState(false); const [viewUser, setViewUser] = useState(null); const [addresses, setAddresses] = useState([]); - const [selectedRole, setSelectedRole] = useState("farmer"); - const [openFarmForm, setOpenFarmForm] = useState(false); const [farmFormData, setFarmFormData] = useState(null); @@ -34,9 +33,9 @@ export function Users() { const res = await axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { headers: { Authorization: `Bearer ${token}` } }); - const active = (Array.isArray(res.data) ? res.data : []).filter(u => u.isActive); - setUsers(active); - } catch (err) { + const activeUsers = (Array.isArray(res.data) ? res.data : []).filter(u => u.isActive); + setUsers(activeUsers); + } catch { setError("Lỗi khi tải danh sách người dùng."); } finally { setLoading(false); @@ -52,167 +51,159 @@ export function Users() { fetchUsers(); }, [token]); - useEffect(() => { - setRoles(["Customer", "Admin", "Farmer"]); - }, []); - const openEdit = (user) => { setSelectedUser(user); setFormData({ fullName: user.fullName, email: user.email, - phone: user.phone || '', - role: Array.isArray(user.role) ? user.role[0] : user.role, + phone: user.phone || '' }); setEditOpen(true); }; - const handleView = async (user) => { - setViewUser(user); - setViewOpen(true); + const handleUpdate = async () => { + if (!token || !selectedUser) return; try { - const res = await axios.get("https://api-ndolv2.nongdanonline.vn/user-addresses", { - headers: { Authorization: `Bearer ${token}` } + await axios.put(`https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}`, formData, { + headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, }); - const userAddresses = res.data.filter(addr => addr.userid === user.id); - setAddresses(userAddresses); - } catch (err) { - setAddresses([]); + alert("Cập nhật thành công!"); + fetchUsers(); + setEditOpen(false); + } catch { + alert("Cập nhật thất bại!"); } }; - const handleUpdate = () => { - if (!token || !selectedUser) return; - - axios.put(`https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}`, formData, { - headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, - }) - .then(() => { - alert("Cập nhật thành công!"); - setUsers(users.map(u => u.id === selectedUser.id ? { ...u, ...formData } : u)); - setEditOpen(false); - }) - .catch(() => alert("Cập nhật thất bại!")); - }; - - const handleDelete = async (userId) => { - if (!token) return; - if (!window.confirm("Bạn có chắc muốn xoá người dùng này?")) return; - + const handleAddRole = async () => { + if (!selectedUser) return; try { - await axios.delete( - `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}`, + await axios.patch( + `https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}/add-role`, + { role: selectedRole }, { headers: { Authorization: `Bearer ${token}` } } ); - alert("Xoá người dùng thành công!"); - await fetchUsers(); - } catch (e) { - alert("Xoá thất bại!"); + alert("Thêm role thành công!"); + fetchUsers(); + } catch { + alert("Không thể thêm role!"); } }; - const handleAddRole = async (userId, role) => { + const handleRemoveRole = async (role) => { + if (!selectedUser) return; try { await axios.patch( - `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/add-role`, - { role: role.charAt(0).toUpperCase() + role.slice(1) }, + `https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}/remove-roles`, + { roles: [role] }, { headers: { Authorization: `Bearer ${token}` } } ); - alert("Đã thêm role thành công!"); + alert("Xoá role thành công!"); fetchUsers(); - } catch (e) { - alert("Không thể thêm role!"); + } catch { + alert("Không thể xoá role!"); + } + }; + + const handleView = async (user) => { + setViewUser(user); + setViewOpen(true); + try { + const res = await axios.get("https://api-ndolv2.nongdanonline.vn/user-addresses", { + headers: { Authorization: `Bearer ${token}` } + }); + const userAddresses = res.data.filter(addr => addr.userid === user.id); + setAddresses(userAddresses); + } catch { + setAddresses([]); } }; - const handleRemoveRole = async (userId, role) => { + const handleDelete = async (userId) => { + if (!window.confirm("Bạn chắc muốn xoá?")) return; try { - await axios.patch( - `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}/remove-roles`, - { roles: [role.charAt(0).toUpperCase() + role.slice(1)] }, + await axios.delete( + `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}`, { headers: { Authorization: `Bearer ${token}` } } ); - alert("Đã xoá role thành công!"); + alert("Đã xoá người dùng!"); fetchUsers(); - } catch (e) { - alert("Không thể xoá role!"); + } catch { + alert("Xoá thất bại!"); } }; return (
- Users Management - - {loading && ( -
- -
- )} + Quản lý người dùng + {loading &&
} {error &&

{error}

} -
- {users.map((user) => ( -
- - - {user.avatar ? ( - {user.fullName} - ) : ( -
- No Avatar -
- )} -
- - {user.fullName} - Email: {user.email} - Phone: {user.phone || "N/A"} - Role: {Array.isArray(user.role) ? user.role.join(", ") : user.role} - Active: {user.isActive ? "Yes" : "No"} - - -
- - - -
-
- - - -
-
-
-
+
+ {users.map(user => ( + + + {user.avatar ? ( + {user.fullName} + ) : ( +
No Avatar
+ )} +
+ + {user.fullName} + Email: {user.email} + Phone: {user.phone || "N/A"} + Roles: {Array.isArray(user.role) ? user.role.join(", ") : user.role} + + +
+ + + +
+
+ + + +
+
+
))}
+ {/* Dialog Edit */} + + Chỉnh sửa người dùng + +
+ setFormData({ ...formData, fullName: e.target.value })} /> + setFormData({ ...formData, email: e.target.value })} /> + setFormData({ ...formData, phone: e.target.value })} /> +
+
+ + + + +
+ + {/* Dialog View */} - Thông tin chi tiết + Chi tiết người dùng {viewUser ? ( -
- {viewUser.fullName} + <> + {viewUser.fullName} Email: {viewUser.email} Phone: {viewUser.phone || "N/A"} Roles: {Array.isArray(viewUser.role) ? viewUser.role.join(", ") : viewUser.role} - Active: {viewUser.isActive ? "Yes" : "No"} ID: {viewUser.id} - Created At: {new Date(viewUser.createdAt).toLocaleString()} - Last Login: {viewUser.lastLogin ? new Date(viewUser.lastLogin).toLocaleString() : "N/A"} Addresses: - {addresses.length > 0 ? addresses.map((addr, i) => ( -
- {addr.address} - {addr.ward}, {addr.district}, {addr.province} -
+ {addresses.length ? addresses.map((addr, i) => ( + {addr.address} - {addr.ward}, {addr.district}, {addr.province} )) : Không có địa chỉ} - -
- ) : ( - Đang tải dữ liệu... - )} + + ) : Đang tải...}
+ {/* Farm Form */} setOpenFarmForm(false)} @@ -255,4 +245,4 @@ export function Users() { ); } -export default Users; \ No newline at end of file +export default Users; From ac128f56face4f5e18c292cddbf34ed6c2f52570 Mon Sep 17 00:00:00 2001 From: Tie902 Date: Wed, 9 Jul 2025 21:48:08 +0700 Subject: [PATCH 117/248] gopusersthanhcong --- src/pages/dashboard/user/users.jsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/pages/dashboard/user/users.jsx b/src/pages/dashboard/user/users.jsx index 487a3591..254a7180 100644 --- a/src/pages/dashboard/user/users.jsx +++ b/src/pages/dashboard/user/users.jsx @@ -15,7 +15,7 @@ export function Users() { const [editOpen, setEditOpen] = useState(false); const [selectedUser, setSelectedUser] = useState(null); - const [formData, setFormData] = useState({ fullName: '', email: '', phone: '' }); + const [formData, setFormData] = useState({ fullName: "", email: "", phone: "" }); const [selectedRole, setSelectedRole] = useState("Farmer"); const [viewOpen, setViewOpen] = useState(false); @@ -31,7 +31,7 @@ export function Users() { setLoading(true); try { const res = await axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { - headers: { Authorization: `Bearer ${token}` } + headers: { Authorization: `Bearer ${token}` }, }); const activeUsers = (Array.isArray(res.data) ? res.data : []).filter(u => u.isActive); setUsers(activeUsers); @@ -56,7 +56,7 @@ export function Users() { setFormData({ fullName: user.fullName, email: user.email, - phone: user.phone || '' + phone: user.phone || "", }); setEditOpen(true); }; @@ -65,7 +65,10 @@ export function Users() { if (!token || !selectedUser) return; try { await axios.put(`https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}`, formData, { - headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, }); alert("Cập nhật thành công!"); fetchUsers(); @@ -110,7 +113,7 @@ export function Users() { setViewOpen(true); try { const res = await axios.get("https://api-ndolv2.nongdanonline.vn/user-addresses", { - headers: { Authorization: `Bearer ${token}` } + headers: { Authorization: `Bearer ${token}` }, }); const userAddresses = res.data.filter(addr => addr.userid === user.id); setAddresses(userAddresses); @@ -135,7 +138,10 @@ export function Users() { return (
- Quản lý người dùng + + Quản lý người dùng + + {loading &&
} {error &&

{error}

} From a72d9f14111e9bd1a82cb5cda2f4d6a4e95a3b72 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Thu, 10 Jul 2025 10:45:08 +0700 Subject: [PATCH 118/248] Update routes.jsx --- src/routes.jsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/routes.jsx b/src/routes.jsx index 46763f9d..0a71cde2 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -5,10 +5,11 @@ import { InformationCircleIcon, ServerStackIcon, RectangleStackIcon, + NewspaperIcon, } from "@heroicons/react/24/solid"; import VideoLikeList from "@/pages/dashboard/VideoLikeList"; -import { Home, Users, Farms, Questions, AnswersTable, VideoFarms } from "@/pages/dashboard"; +import { Home, Users, Farms, Questions, AnswersTable, VideoFarms, PostList } from "@/pages/dashboard"; import { SignIn, SignUp } from "@/pages/auth"; import { ViewfinderCircleIcon } from "@heroicons/react/24/outline"; @@ -56,6 +57,12 @@ export const routes = [ path: "/VideoFarms", element: , }, + { + icon: , + name: "PostList", + path: "/PostList", + element: , + }, ], }, From 64302a9ef917ba239f3e9571ef26d50438e1d93f Mon Sep 17 00:00:00 2001 From: Tie902 Date: Thu, 10 Jul 2025 10:58:21 +0700 Subject: [PATCH 119/248] adminreports --- src/pages/dashboard/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index f42bef88..588171d2 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -5,5 +5,5 @@ export * from "@/pages/dashboard/farm/farms"; export * from "@/pages/dashboard/Questions/Questions"; export * from "@/pages/dashboard/VideoFarms/VideoFarms"; export * from "@/pages/dashboard/post/Post"; - +export * from "@/pages/dashboard/AdminReports" From 4dc970f99f5ecf1639d1fc1c225d21f45480127a Mon Sep 17 00:00:00 2001 From: Tie902 Date: Thu, 10 Jul 2025 11:06:29 +0700 Subject: [PATCH 120/248] admin repost --- src/routes.jsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/routes.jsx b/src/routes.jsx index 0a71cde2..30c581b8 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -6,12 +6,14 @@ import { ServerStackIcon, RectangleStackIcon, NewspaperIcon, + ReceiptPercentIcon } from "@heroicons/react/24/solid"; import VideoLikeList from "@/pages/dashboard/VideoLikeList"; -import { Home, Users, Farms, Questions, AnswersTable, VideoFarms, PostList } from "@/pages/dashboard"; +import { Home, Users, Farms, Questions, AnswersTable, VideoFarms, PostList, AdminReports } from "@/pages/dashboard"; import { SignIn, SignUp } from "@/pages/auth"; import { ViewfinderCircleIcon } from "@heroicons/react/24/outline"; +import { element } from "prop-types"; const icon = { className: "w-5 h-5 text-inherit", @@ -63,6 +65,12 @@ export const routes = [ path: "/PostList", element: , }, + { + icon: , + name: "AdminReports", + path: "/AdminReports", + element: , + } ], }, From 7033903874073209f6182a9341c70295e8ef5c0b Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Thu, 10 Jul 2025 13:36:03 +0700 Subject: [PATCH 121/248] thanh --- .../dashboard/VideoFarms/VideoLikeList.jsx | 88 +++++++++---------- src/pages/dashboard/VideoLikeList.jsx | 60 ------------- src/pages/dashboard/post/Post.jsx | 13 +-- src/routes.jsx | 2 +- 4 files changed, 49 insertions(+), 114 deletions(-) delete mode 100644 src/pages/dashboard/VideoLikeList.jsx diff --git a/src/pages/dashboard/VideoFarms/VideoLikeList.jsx b/src/pages/dashboard/VideoFarms/VideoLikeList.jsx index c58d52f9..d186bd6f 100644 --- a/src/pages/dashboard/VideoFarms/VideoLikeList.jsx +++ b/src/pages/dashboard/VideoFarms/VideoLikeList.jsx @@ -1,66 +1,60 @@ import React, { useEffect, useState } from 'react'; -import { useParams } from 'react-router-dom'; import axios from 'axios'; import { BaseUrl } from '@/ipconfig'; +import { useParams, useNavigate } from 'react-router-dom'; +import { Audio } from 'react-loader-spinner'; export default function VideoLikeList() { const { videoId } = useParams(); - const [users, setUsers] = useState([]); + const [likes, setLikes] = useState([]); const [loading, setLoading] = useState(true); const token = localStorage.getItem('token'); + const navigate = useNavigate(); + + const getLikes = async () => { + try { + setLoading(true); + const res = await axios.get(`${BaseUrl}/video-like/${videoId}`, { + headers: { Authorization: `Bearer ${token}` }, + }); + setLikes(res.data?.data || []); + } catch (error) { + console.error('Lỗi lấy danh sách Like:', error); + } finally { + setLoading(false); + } + }; useEffect(() => { - const fetchLikes = async () => { - if (!videoId || !token) return; - - try { - const res = await axios.get(`${BaseUrl}/video-like/${videoId}/users`, { - headers: { Authorization: `Bearer ${token}` }, - }); - - console.log('Danh sách người đã like:', res.data.users); - setUsers(res.data.users || []); - } catch (err) { - console.error('Lỗi khi lấy danh sách người đã Like:', err.response?.data || err); - } finally { - setLoading(false); - } - }; - - fetchLikes(); - }, [videoId, token]); - - if (loading) { - return ( -
Đang tải danh sách...
- ); - } + getLikes(); + }, []); return ( -
-

- 👥 Danh sách người đã Like video -

+
+

Danh sách người đã Like

- {users.length === 0 ? ( -

Chưa có ai Like video này.

+ {loading ? ( +
+
+ ) : likes.length === 0 ? ( +

Chưa có ai Like video này.

) : ( -
- {users.map((user) => ( -
- {user.fullName} - {user.fullName} -
+
    + {likes.map((user, index) => ( +
  • + {user.fullName} +
  • ))} -
+ )} + +
); } diff --git a/src/pages/dashboard/VideoLikeList.jsx b/src/pages/dashboard/VideoLikeList.jsx deleted file mode 100644 index d186bd6f..00000000 --- a/src/pages/dashboard/VideoLikeList.jsx +++ /dev/null @@ -1,60 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import axios from 'axios'; -import { BaseUrl } from '@/ipconfig'; -import { useParams, useNavigate } from 'react-router-dom'; -import { Audio } from 'react-loader-spinner'; - -export default function VideoLikeList() { - const { videoId } = useParams(); - const [likes, setLikes] = useState([]); - const [loading, setLoading] = useState(true); - const token = localStorage.getItem('token'); - const navigate = useNavigate(); - - const getLikes = async () => { - try { - setLoading(true); - const res = await axios.get(`${BaseUrl}/video-like/${videoId}`, { - headers: { Authorization: `Bearer ${token}` }, - }); - setLikes(res.data?.data || []); - } catch (error) { - console.error('Lỗi lấy danh sách Like:', error); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - getLikes(); - }, []); - - return ( -
-

Danh sách người đã Like

- - {loading ? ( -
-
- ) : likes.length === 0 ? ( -

Chưa có ai Like video này.

- ) : ( -
    - {likes.map((user, index) => ( -
  • - {user.fullName} -
  • - ))} -
- )} - - -
- ); -} diff --git a/src/pages/dashboard/post/Post.jsx b/src/pages/dashboard/post/Post.jsx index 7e866499..417315f1 100644 --- a/src/pages/dashboard/post/Post.jsx +++ b/src/pages/dashboard/post/Post.jsx @@ -7,7 +7,7 @@ export function PostList() { const [posts, setPosts] = useState([]); const [loading, setLoading] = useState(false); - // Lấy danh sách bài post + const fetchPosts = async () => { setLoading(true); try { @@ -19,6 +19,7 @@ export function PostList() { }, }); const json = await res.json(); + console.log("POST DATA:", json.data); if (res.ok) { setPosts(json.data); } else { @@ -31,7 +32,7 @@ export function PostList() { setLoading(false); }; - // Xoá bài post + const deletePost = async (id) => { const confirmDelete = window.confirm("Bạn có chắc chắn muốn xoá bài post này?"); if (!confirmDelete) return; @@ -93,7 +94,7 @@ export function PostList() { {post.images.length > 0 ? ( Hình ảnh @@ -103,11 +104,11 @@ export function PostList() { - {post.authorId.fullName} + {post.authorId?.fullName} {post.like} diff --git a/src/routes.jsx b/src/routes.jsx index 30c581b8..c8c275ce 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -9,7 +9,7 @@ import { ReceiptPercentIcon } from "@heroicons/react/24/solid"; -import VideoLikeList from "@/pages/dashboard/VideoLikeList"; +import VideoLikeList from "@/pages/dashboard/VideoFarms/VideoLikeList"; import { Home, Users, Farms, Questions, AnswersTable, VideoFarms, PostList, AdminReports } from "@/pages/dashboard"; import { SignIn, SignUp } from "@/pages/auth"; import { ViewfinderCircleIcon } from "@heroicons/react/24/outline"; From f94af6875703bdb15a5048bae45a7094c36ecfea Mon Sep 17 00:00:00 2001 From: trongquy1213 Date: Thu, 10 Jul 2025 13:48:55 +0700 Subject: [PATCH 122/248] =?UTF-8?q?qu=C3=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/dashboard/farm/farms.jsx | 50 ++++++++++--------- .../dashboard/{farm => user}/FarmForm.jsx | 43 ++++++++-------- src/pages/dashboard/user/users.jsx | 38 ++++++++++++++ 3 files changed, 86 insertions(+), 45 deletions(-) rename src/pages/dashboard/{farm => user}/FarmForm.jsx (86%) diff --git a/src/pages/dashboard/farm/farms.jsx b/src/pages/dashboard/farm/farms.jsx index 95289a2c..10a24d9e 100644 --- a/src/pages/dashboard/farm/farms.jsx +++ b/src/pages/dashboard/farm/farms.jsx @@ -7,7 +7,7 @@ import { import { PencilSquareIcon, TrashIcon, PlusIcon, LockOpenIcon ,LockClosedIcon, XCircleIcon, CheckCircleIcon } from "@heroicons/react/24/solid"; -import FarmForm from "./FarmForm"; +import FarmForm from "../user/FarmForm"; import FarmDetail from "./FarmDetail"; const BASE_URL = "https://api-ndolv2.nongdanonline.vn"; @@ -190,30 +190,32 @@ export function Farms() { {/* Nhóm nút trạng thái */}
{farm.status === "pending" && ( - <> - + <> + + + + + +)} - - - )} {farm.status === "active" && (
@@ -116,11 +118,7 @@ const FarmForm = ({ open, onClose, initialData = {}, onSubmit }) => { isMulti options={featureOptions} classNamePrefix="select-sm" - value={ - Array.isArray(form.features) - ? form.features.map(val => featureOptions.find(opt => opt.value === val)).filter(Boolean) - : [] - } + value={form.features.map(val => featureOptions.find(opt => opt.value === val)).filter(Boolean)} onChange={(selected) => handleArrayChange("features", selected.map(s => s.value))} />
@@ -131,12 +129,8 @@ const FarmForm = ({ open, onClose, initialData = {}, onSubmit }) => { isMulti options={tagOptions} classNamePrefix="select-sm" - value={ - Array.isArray(form.tags) - ? form.tags.map((val) => ({ value: val, label: val })) - : [] - } - onChange={(selected) => handleArrayChange("tags", selected.map((s) => s.value))} + value={form.tags.map(val => ({ value: val, label: val }))} + onChange={(selected) => handleArrayChange("tags", selected.map(s => s.value))} />
@@ -146,8 +140,15 @@ const FarmForm = ({ open, onClose, initialData = {}, onSubmit }) => { handleChange("district", e.target.value)} /> handleChange("ward", e.target.value)} /> handleChange("street", e.target.value)} /> - handleChange("ownerId", e.target.value)} /> - handleChange("isAvailable", e.target.checked)} /> + + {/* Chỉ đọc ID chủ sở hữu */} + + + handleChange("isAvailable", e.target.checked)} + /> diff --git a/src/pages/dashboard/user/users.jsx b/src/pages/dashboard/user/users.jsx index 55b349ac..35215055 100644 --- a/src/pages/dashboard/user/users.jsx +++ b/src/pages/dashboard/user/users.jsx @@ -5,6 +5,7 @@ import { Typography, Button, Dialog, DialogHeader, DialogBody, DialogFooter, Input, Select, Option, Spinner } from "@material-tailwind/react"; +import FarmForm from "./FarmForm"; export function Users() { const [users, setUsers] = useState([]); @@ -21,6 +22,9 @@ export function Users() { const [viewUser, setViewUser] = useState(null); const [addresses, setAddresses] = useState([]); + const [openFarmForm, setOpenFarmForm] = useState(false); + const [farmFormData, setFarmFormData] = useState({ ownerId: null }); + const token = localStorage.getItem("token"); const fetchUsers = async () => { @@ -129,6 +133,20 @@ export function Users() { } }; + // ✅ Thêm nông trại + const handleCreateFarm = async (farmData) => { + try { + await axios.post("https://api-ndolv2.nongdanonline.vn/farms", farmData, { + headers: { Authorization: `Bearer ${token}` }, + }); + alert("Tạo nông trại thành công!"); + setOpenFarmForm(false); + } catch (error) { + console.error(error); + alert("Tạo nông trại thất bại!"); + } + }; + return (
Quản lý người dùng @@ -204,6 +222,18 @@ export function Users() { {addresses.length ? addresses.map((addr, i) => ( {addr.address} - {addr.ward}, {addr.district}, {addr.province} )) : Không có địa chỉ} + + ) : Đang tải...} @@ -211,6 +241,14 @@ export function Users() {
+ + {/* Dialog Thêm Nông Trại */} + setOpenFarmForm(false)} + initialData={farmFormData} + onSubmit={handleCreateFarm} + /> ); } From aad32afda63d75119c7acd787a3cd8e7f0fc7c54 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Thu, 10 Jul 2025 16:00:23 +0700 Subject: [PATCH 123/248] thanh --- src/App.jsx | 2 + src/pages/dashboard/post/Post.jsx | 99 +++++++++++++++++++++++-- src/pages/dashboard/post/PostDetail.jsx | 82 ++++++++++++++++++++ 3 files changed, 177 insertions(+), 6 deletions(-) create mode 100644 src/pages/dashboard/post/PostDetail.jsx diff --git a/src/App.jsx b/src/App.jsx index 082a4b9c..7b5511c9 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,6 +2,7 @@ import { Routes, Route, Navigate } from "react-router-dom"; import { Dashboard, Auth } from "@/layouts"; import VideoFarmById from "./pages/dashboard/VideoFarms/VideoFarmById"; import VideoLikeList from "./pages/dashboard/VideoFarms/VideoLikeList"; +import PostDetail from "./pages/dashboard/post/PostDetail"; function App() { return ( @@ -9,6 +10,7 @@ function App() { } /> } /> } /> + } /> } /> ); diff --git a/src/pages/dashboard/post/Post.jsx b/src/pages/dashboard/post/Post.jsx index 417315f1..be501a7c 100644 --- a/src/pages/dashboard/post/Post.jsx +++ b/src/pages/dashboard/post/Post.jsx @@ -1,12 +1,16 @@ import React, { useEffect, useState } from "react"; import { Button, Typography, Avatar, Chip } from "@material-tailwind/react"; +import { useNavigate } from "react-router-dom"; +import { Dialog, Input } from "@material-tailwind/react"; const BASE_URL = "https://api-ndolv2.nongdanonline.vn"; export function PostList() { const [posts, setPosts] = useState([]); const [loading, setLoading] = useState(false); - + const [openEdit, setOpenEdit] = useState(false); + const [selectedPost, setSelectedPost] = useState(null); + const navigate = useNavigate(); const fetchPosts = async () => { setLoading(true); @@ -32,6 +36,41 @@ export function PostList() { setLoading(false); }; + const handleEditClick = (post) => { + setSelectedPost(post); + setOpenEdit(true); + }; + + const updatePost = async () => { + try { + const token = localStorage.getItem("token"); + const res = await fetch(`${BASE_URL}/admin-post-feed/${selectedPost.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + title: selectedPost.title, + description: selectedPost.description, + status: selectedPost.status, + }), + }); + + const json = await res.json(); + if (res.ok) { + alert("Cập nhật thành công!"); + setOpenEdit(false); + fetchPosts(); // Reload list + } else { + alert(json.message || "Cập nhật thất bại"); + } + } catch (err) { + console.error("PUT error:", err); + alert("Lỗi kết nối server khi cập nhật"); + } +}; + const deletePost = async (id) => { const confirmDelete = window.confirm("Bạn có chắc chắn muốn xoá bài post này?"); @@ -83,7 +122,10 @@ export function PostList() { {posts.map((post) => ( - + navigate(`/dashboard/post/${post.id}`)} + className="hover:bg-gray-50 cursor-pointer transition"> {post.title} {post.description} @@ -103,12 +145,12 @@ export function PostList() { )} - - {post.authorId?.fullName} + /> */} + {post.authorId} {post.like} @@ -118,7 +160,19 @@ export function PostList() { size="sm" /> - + + + + + + + +
); } diff --git a/src/pages/dashboard/post/PostDetail.jsx b/src/pages/dashboard/post/PostDetail.jsx new file mode 100644 index 00000000..8759c6cf --- /dev/null +++ b/src/pages/dashboard/post/PostDetail.jsx @@ -0,0 +1,82 @@ +import React, {useEffect, useState} from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import { Typography, Button, Chip, Avatar } from "@material-tailwind/react"; +const BASE_URL = 'https://api-ndolv2.nongdanonline.vn'; + +export function PostDetail() { + const { id } = useParams(); + const navigate = useNavigate(); + const [post, setPost] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchPost = async () => { + try { + const token = localStorage.getItem('token'); + const res = await fetch(`${BASE_URL}/admin-post-feed/${id}`, { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }); + const json = await res.json(); + console.log("RESPONSE:", res.status, json); + if(res.ok) { + setPost(json); + }else{ + alert(json.message || "không thể lấy bài viết"); + } + } catch (error) { + console.error("fetch post lỗi: ", error); + alert("Lỗi khi lấy dữ liệu bài post"); + } finally { + setLoading(false) + } + }; + fetchPost(); + },[id]) + if (loading) return Đang tải dữ liệu...; + if (!post) return Không tìm thấy bài viết; + + return ( +
+ + + {post.title} + {post.description} + +
+ {post.tags?.map((tag, i) => ( + + ))} +
+ +
+ {post.images.map((img, i) => ( + {`img-${i}`} + ))} +
+ +
+ {/* */} + {post.authorId} +
+ + Lượt thích: {post.like} + +
+ ); +} + +export default PostDetail; \ No newline at end of file From 6337c5e6df22b2e447115131e2c42c97ba06428d Mon Sep 17 00:00:00 2001 From: Longvu003 Date: Thu, 10 Jul 2025 16:20:01 +0700 Subject: [PATCH 124/248] upcode comment --- src/App.jsx | 9 +- .../AdminCommentPost/CommentPost.jsx | 105 ++++++++++++++++++ .../AdminCommentPost/CommentPostByIdUser.jsx | 9 ++ .../AdminCommentPost/CommentPostbyId.jsx | 37 ++++++ .../AdminCommentPost/CommentPostbyIdPost.jsx | 102 +++++++++++++++++ src/pages/dashboard/VideoFarms/VideoFarms.jsx | 4 +- src/pages/dashboard/index.js | 3 + src/routes.jsx | 16 ++- 8 files changed, 277 insertions(+), 8 deletions(-) create mode 100644 src/pages/dashboard/AdminCommentPost/CommentPost.jsx create mode 100644 src/pages/dashboard/AdminCommentPost/CommentPostByIdUser.jsx create mode 100644 src/pages/dashboard/AdminCommentPost/CommentPostbyId.jsx create mode 100644 src/pages/dashboard/AdminCommentPost/CommentPostbyIdPost.jsx diff --git a/src/App.jsx b/src/App.jsx index c02abb47..e707e908 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,13 +1,20 @@ import { Routes, Route, Navigate } from "react-router-dom"; import { Dashboard, Auth } from "@/layouts"; import VideoFarmById from "./pages/dashboard/VideoFarms/VideoFarmById"; +import CommentPostbyId from "./pages/dashboard/AdminCommentPost/CommentPostbyId"; +import CommentPostbyIdPost from "./pages/dashboard/AdminCommentPost/CommentPostbyIdPost"; +import CommentPostByIdUser from "./pages/dashboard/AdminCommentPost/CommentPostByIdUser"; function App() { return ( } /> } /> -} /> + } /> } /> + } /> + } /> + } /> + ); } diff --git a/src/pages/dashboard/AdminCommentPost/CommentPost.jsx b/src/pages/dashboard/AdminCommentPost/CommentPost.jsx new file mode 100644 index 00000000..bd8db7ed --- /dev/null +++ b/src/pages/dashboard/AdminCommentPost/CommentPost.jsx @@ -0,0 +1,105 @@ +import { BaseUrl } from '@/ipconfig' +import axios from 'axios' +import React, { useEffect, useState } from 'react' +import { Audio } from 'react-loader-spinner' +import { Navigate, useNavigate } from 'react-router-dom' +export const CommentPost = () => { + const tokenUser = localStorage.getItem('token') + const [loading, setLoading] = useState(true) + const navigate=useNavigate() +const [comment,setComment]=useState([]) + +const gotoCommentId=(id)=>{ +navigate(`/dashboard/CommentPostbyId/${id}`) +} + +const gotoCommentByIdPost=(postId)=>{ +navigate(`/dashboard/CommentPostbyIdPost/${postId}`) +} + +const callApiCommentPost=async()=>{ +try { + +const res= await axios.get(`${BaseUrl}/admin-comment-post?limit=10`,{ + headers:{Authorization:`Bearer ${tokenUser}`} +}) + +if(res.status===200){ +setComment(res.data.data) +} +} catch (error) { + console.log("Lỗi nè",error) + setLoading(false) +} + +} +console.log(comment) + +useEffect(()=>{ +callApiCommentPost() +setLoading(false) +},[]) + return ( +
+{loading? +(
+
+ ):( + comment.map((item)=>( +
gotoCommentByIdPost( item.postId)} key={item.postId} className="mb-4 p-4 border rounded bg-white " > +
gotoCommentId( item._id)} + className=" cursor-pointer font-bold mb-2 w-full flex flex-col">Post ID: {item.postId} + Ngày đăng: {item.createdAt? new Date(item.createdAt).toLocaleDateString(): "Chưa cập nhật"} + +{item.comments.map((cmt,index)=>( +
+
+ + {cmt.userId.fullName} + {new Date(cmt.createdAt).toLocaleDateString()} + +
+
{cmt.comment}
+ {cmt.replies && cmt.replies.length > 0 && ( +
+ {cmt.replies.map((rep, ridx) => ( +
+ + {rep.userId.fullName}: + {rep.comment} + { new Date(rep.createdAt).toLocaleDateString() } + +
+ ))} +
+ )} + {/*
+ + +
*/} +
+ +))} +
+ +
+ + )) + )} +
+ ) +} + +export default CommentPost \ No newline at end of file diff --git a/src/pages/dashboard/AdminCommentPost/CommentPostByIdUser.jsx b/src/pages/dashboard/AdminCommentPost/CommentPostByIdUser.jsx new file mode 100644 index 00000000..81b8e1e9 --- /dev/null +++ b/src/pages/dashboard/AdminCommentPost/CommentPostByIdUser.jsx @@ -0,0 +1,9 @@ +import React from 'react' + +export const CommentPostByIdUser = () => { + return ( +
CommentPostByIdUser
+ ) +} + +export default CommentPostByIdUser \ No newline at end of file diff --git a/src/pages/dashboard/AdminCommentPost/CommentPostbyId.jsx b/src/pages/dashboard/AdminCommentPost/CommentPostbyId.jsx new file mode 100644 index 00000000..40c8c74e --- /dev/null +++ b/src/pages/dashboard/AdminCommentPost/CommentPostbyId.jsx @@ -0,0 +1,37 @@ +import { BaseUrl } from '@/ipconfig'; +import axios from 'axios'; +import React, { useEffect, useState } from 'react' +import { useParams } from 'react-router-dom' +export const CommentPostbyId = () => { + const [commentDetail,setCommentDetail]=useState() + const [loading, setLoading] = useState(true) + const tokenUser = localStorage.getItem('token'); + const {id}=useParams() + const getCommentById=async()=>{ +try { + const res= await axios.get(`${BaseUrl}/admin-comment-post/${id}`, + {headers:{Authorization:`Bearer ${tokenUser}` }}) + if(res.status===200){ +setCommentDetail(res.data) + } + +} catch (error) { + console.log("Lỗi nè",error) + setLoading(false) +} + } +console.log("data nè",commentDetail) + + useEffect(()=>{ +getCommentById() + },[]) + setLoading(false) + return ( +
+ + +
+ ) +} + +export default CommentPostbyId \ No newline at end of file diff --git a/src/pages/dashboard/AdminCommentPost/CommentPostbyIdPost.jsx b/src/pages/dashboard/AdminCommentPost/CommentPostbyIdPost.jsx new file mode 100644 index 00000000..bc7d5976 --- /dev/null +++ b/src/pages/dashboard/AdminCommentPost/CommentPostbyIdPost.jsx @@ -0,0 +1,102 @@ +import React from 'react' +import { useState } from 'react' +import { useEffect } from 'react' +import { BaseUrl } from '@/ipconfig' +import { useParams } from 'react-router-dom' +import axios from 'axios' +import { Audio } from 'react-loader-spinner' +export const CommentPostbyIdPost = () => { + + const [CommentByIdPost,setCommentByIdPost]=useState([]) + const [loading, setLoading] = useState(true) + const tokenUser = localStorage.getItem('token'); + const {postId}=useParams() + const getCommentById=async()=>{ +try { + const res= await axios.get(`${BaseUrl}/admin-comment-post/post/${postId}`, + {headers:{Authorization:`Bearer ${tokenUser}` }}) + if(res.status===200){ +setCommentByIdPost(res.data) + } + +} catch (error) { + console.log("Lỗi nè",error) + setLoading(false) +} + } +console.log("data nè",CommentByIdPost) + + useEffect(()=>{ +getCommentById() + setLoading(false) + + },[]) + + return ( +
+ {loading ? ( +
+
+ ) : ( +
+ Id bài viết: {CommentByIdPost.postId} + Ngày đăng: {CommentByIdPost.createdAt ? new Date(CommentByIdPost.createdAt).toLocaleDateString() : ""} + Chỉnh sửa: {CommentByIdPost.updatedAt ? new Date(CommentByIdPost.updatedAt).toLocaleDateString() : ""} + {CommentByIdPost && Array.isArray(CommentByIdPost.comments) && CommentByIdPost.comments.length > 0 ? ( + CommentByIdPost.comments.map((item) => ( +
+
+ + {item.userId?.fullName} + + {item.createdAt ? new Date(item.createdAt).toLocaleDateString() : ""} + +
+
{item.comment}
+ {item.replies && item.replies.length > 0 && ( +
+ {item.replies.map((rep) => ( +
+ + {rep.userId?.fullName}: + {rep.comment} + {rep.createdAt ? new Date(rep.createdAt).toLocaleDateString() : ""} +
+ ))} +
+ )} +
+ )) + ) : ( +
Không có dữ liệu
+ )} +
+ )} +
+) + + +} + +export default CommentPostbyIdPost + + \ No newline at end of file diff --git a/src/pages/dashboard/VideoFarms/VideoFarms.jsx b/src/pages/dashboard/VideoFarms/VideoFarms.jsx index 685c3db3..04c32386 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarms.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarms.jsx @@ -16,10 +16,8 @@ const fetchAllVideos = async () => { const res = await axios.get(`${BaseUrl}/admin-video-farm`, { headers: { Authorization: `Bearer ${tokenUser}` } }) - console.log("Dữ liệu video:", videos) if (res.status === 200) { - console.log(res.data) - setVideos(res.data) + setVideos(res.data) } } catch (error) { console.error("Lỗi khi lấy video:", error.response?.data || error.message) diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index 2d419c6f..4f004ad7 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -5,6 +5,9 @@ export * from "@/pages/dashboard/users"; export * from "@/pages/dashboard/farm/farms"; export * from "@/pages/dashboard/Questions/Questions"; export * from "@/pages/dashboard/VideoFarms/VideoFarms"; +export * from "@/pages/dashboard/AdminCommentPost/CommentPost"; + + diff --git a/src/routes.jsx b/src/routes.jsx index 87f54d4d..88d1da2e 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -5,15 +5,16 @@ import { InformationCircleIcon, ServerStackIcon, RectangleStackIcon, + ChatBubbleOvalLeftEllipsisIcon, + VideoCameraIcon, } from "@heroicons/react/24/solid"; - -import { Home, Users , Farms, Questions, AnswersTable, VideoFarms } from "@/pages/dashboard"; - +import { Home, Users , Farms, Questions, AnswersTable, VideoFarms,CommentPost } from "@/pages/dashboard"; import { SignIn, SignUp } from "@/pages/auth"; import { ViewfinderCircleIcon } from "@heroicons/react/24/outline"; +import { Comment } from "react-loader-spinner"; const icon = { className: "w-5 h-5 text-inherit", @@ -56,11 +57,18 @@ export const routes = [ element: , }, { - icon: , + icon: , name: "VideoFarms", path: "/VideoFarms", element: , + }, + { + icon: , + name: "CommentPost", + path: "/CommentPost", + element: , + }, ], }, From ab290723ec7bd60e6534fdeb692240f7744fc3be Mon Sep 17 00:00:00 2001 From: trongquy1213 Date: Thu, 10 Jul 2025 16:20:02 +0700 Subject: [PATCH 125/248] quy --- src/components/AnswerDetailDialog.jsx | 55 ++++++ .../dashboard/Questions/answerstable.jsx | 177 ++++++++---------- 2 files changed, 132 insertions(+), 100 deletions(-) create mode 100644 src/components/AnswerDetailDialog.jsx diff --git a/src/components/AnswerDetailDialog.jsx b/src/components/AnswerDetailDialog.jsx new file mode 100644 index 00000000..8c8de809 --- /dev/null +++ b/src/components/AnswerDetailDialog.jsx @@ -0,0 +1,55 @@ +// src/components/AnswerDetailDialog.jsx +import React from "react"; +import { + Dialog, + DialogHeader, + DialogBody, + DialogFooter, + Typography, + Button, +} from "@material-tailwind/react"; + +const AnswerDetailDialog = ({ open, onClose, data }) => { + if (!data) return null; + return ( + + Chi tiết câu trả lời + + Farm ID: {data.farmId} + Question ID: {data.questionId} + + Selected Options: {data.selectedOptions?.join(", ") || "—"} + + Other Text: {data.otherText || "—"} + User ID: {data.userId || "—"} +
+ Tệp đính kèm: +
+ {data.uploadedFiles?.length > 0 ? ( + data.uploadedFiles.map((file, idx) => ( + + File {idx + 1} + + )) + ) : ( + Không có + )} +
+
+
+ + + +
+ ); +}; + +export default AnswerDetailDialog; diff --git a/src/pages/dashboard/Questions/answerstable.jsx b/src/pages/dashboard/Questions/answerstable.jsx index 4602ad16..e571112b 100644 --- a/src/pages/dashboard/Questions/answerstable.jsx +++ b/src/pages/dashboard/Questions/answerstable.jsx @@ -67,7 +67,6 @@ export function AnswersTable() { }); const [uploading, setUploading] = useState(false); - // Fetch all answers const fetchAnswers = async () => { try { const res = await fetchWithAuth(API_URL); @@ -87,7 +86,6 @@ export function AnswersTable() { fetchAnswers(); }, []); - // Open form for add/edit const openForm = (data = null) => { if (data) { setForm({ @@ -111,60 +109,50 @@ export function AnswersTable() { setOpen(true); }; - // Save data (create or update) - const handleSubmit = async () => { - if (!form.farmId || !form.questionId) { - alert("Vui lòng nhập đủ Farm ID và Question ID"); - return; - } - - const url = editData ? `${API_URL}/${editData._id}` : `${API_URL}/batch`; - const method = editData ? "PUT" : "POST"; - - const body = editData - ? { - farmId: form.farmId, - questionId: form.questionId, - selectedOptions: form.selectedOptions, - otherText: form.otherText, - uploadedFiles: form.uploadedFiles, - } - : { - farmId: form.farmId, - answers: [ - { - questionId: form.questionId, - selectedOptions: form.selectedOptions, - otherText: form.otherText, - uploadedFiles: form.uploadedFiles, - }, - ], - }; +const handleSubmit = async () => { + if (!form.farmId || !form.questionId) { + alert("Vui lòng nhập đủ Farm ID và Question ID"); + return; + } - try { - const res = await fetchWithAuth(url, { - method, - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(body), - }); + const url = `${API_URL}/batch`; // luôn dùng /batch + const method = "POST"; + + const body = { + farmId: form.farmId, + answers: [ + { + questionId: form.questionId, + selectedOptions: form.selectedOptions, + otherText: form.otherText, + uploadedFiles: form.uploadedFiles, + }, + ], + }; - if (!res.ok) { - const errorData = await res.json(); - console.error("Error response:", errorData); - throw new Error(errorData.message || "Không thể lưu dữ liệu"); - } + try { + const res = await fetchWithAuth(url, { + method, + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); - setOpen(false); - setEditData(null); - fetchAnswers(); - } catch (err) { - alert(`Lỗi: ${err.message}`); + if (!res.ok) { + const errorData = await res.json(); + console.error("Error response:", errorData); + throw new Error(errorData.message || "Không thể lưu dữ liệu"); } - }; - // Delete an answer + setOpen(false); + setEditData(null); + fetchAnswers(); + } catch (err) { + alert(`Lỗi: ${err.message}`); + } +}; + const handleDelete = async (id) => { if (!window.confirm("Bạn có chắc muốn xoá?")) return; try { @@ -178,38 +166,43 @@ export function AnswersTable() { } }; - // Upload image - const handleUploadImage = async (e) => { - const file = e.target.files[0]; - if (!file) return; - - const formData = new FormData(); - formData.append("file", file); - - setUploading(true); - try { - const res = await fetchWithAuth(`${API_URL}/upload-image`, { - method: "POST", - body: formData, - }); - - const result = await res.json(); +const handleUploadImage = async (e) => { + const file = e.target.files[0]; + if (!file) return; + + const formData = new FormData(); + formData.append("file", file); + + setUploading(true); + try { + const res = await fetchWithAuth(`https://api-ndolv2.nongdanonline.vn/answers/upload-image`, { + method: "POST", + body: formData, +}); + const contentType = res.headers.get("content-type"); + if (!contentType || !contentType.includes("application/json")) { + const text = await res.text(); + console.error("⚠️ Server không trả JSON:", text); + throw new Error("Phản hồi không phải JSON"); + } - if (!res.ok) { - console.error("Upload lỗi:", result); - throw new Error(result.message || "Không thể upload hình ảnh"); - } + const result = await res.json(); - setForm((prevForm) => ({ - ...prevForm, - uploadedFiles: [...prevForm.uploadedFiles, result.path], - })); - } catch (err) { - alert(`Upload lỗi: ${err.message}`); - } finally { - setUploading(false); + if (!res.ok) { + console.error("❌ Upload thất bại:", result); + throw new Error(result.message || "Không thể upload hình ảnh"); } - }; + setForm((prev) => ({ + ...prev, + uploadedFiles: [...prev.uploadedFiles, result.path], + })); + } catch (err) { + alert(`Upload lỗi: ${err.message}`); + } finally { + setUploading(false); + } +}; + return (
@@ -285,7 +278,6 @@ export function AnswersTable() { setForm({ ...form, farmId: e.target.value })} - className="rounded-lg shadow-sm" />
@@ -294,7 +286,6 @@ export function AnswersTable() { setForm({ ...form, questionId: e.target.value })} - className="rounded-lg shadow-sm" /> @@ -313,7 +304,6 @@ export function AnswersTable() { .filter(Boolean), }) } - className="rounded-lg shadow-sm" /> @@ -322,33 +312,20 @@ export function AnswersTable() { setForm({ ...form, otherText: e.target.value })} - className="rounded-lg shadow-sm" />
- + + {uploading && Đang tải lên...}
- - From be0e6955b1b18c45a804db295abadfec0a17487d Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Thu, 10 Jul 2025 16:51:23 +0700 Subject: [PATCH 126/248] thanh --- .../dashboard/VideoFarms/VideoFarmById.jsx | 1 + src/pages/dashboard/VideoFarms/VideoFarms.jsx | 2 +- .../dashboard/VideoFarms/VideoLikeList.jsx | 23 +++++++++++++++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx index b9cb0f45..ee7333b8 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarmById.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarmById.jsx @@ -66,6 +66,7 @@ handleCloseDialogInforVideo() headers: { Authorization: `Bearer ${tokenUser}` } }); if (res.status === 200) { + console.log("Dữ liệu từ API:", res.data) setVideoDetail(res.data) setLoading(false) diff --git a/src/pages/dashboard/VideoFarms/VideoFarms.jsx b/src/pages/dashboard/VideoFarms/VideoFarms.jsx index 685c3db3..d85e23c7 100644 --- a/src/pages/dashboard/VideoFarms/VideoFarms.jsx +++ b/src/pages/dashboard/VideoFarms/VideoFarms.jsx @@ -18,7 +18,7 @@ const fetchAllVideos = async () => { }) console.log("Dữ liệu video:", videos) if (res.status === 200) { - console.log(res.data) + console.log("Dữ liệu từ API:", res.data) setVideos(res.data) } } catch (error) { diff --git a/src/pages/dashboard/VideoFarms/VideoLikeList.jsx b/src/pages/dashboard/VideoFarms/VideoLikeList.jsx index d186bd6f..9ff376b6 100644 --- a/src/pages/dashboard/VideoFarms/VideoLikeList.jsx +++ b/src/pages/dashboard/VideoFarms/VideoLikeList.jsx @@ -14,10 +14,11 @@ export default function VideoLikeList() { const getLikes = async () => { try { setLoading(true); - const res = await axios.get(`${BaseUrl}/video-like/${videoId}`, { + const res = await axios.get(`${BaseUrl}/video-like/${videoId}/users`, { headers: { Authorization: `Bearer ${token}` }, }); - setLikes(res.data?.data || []); + console.log("Danh sách user đã like:", res.data); + setLikes(res.data?.users || []); } catch (error) { console.error('Lỗi lấy danh sách Like:', error); } finally { @@ -40,10 +41,22 @@ export default function VideoLikeList() { ) : likes.length === 0 ? (

Chưa có ai Like video này.

) : ( -
    +
      {likes.map((user, index) => ( -
    • - {user.fullName} +
    • + {user.fullName} + {user.fullName}
    • ))}
    From f9ae6c4f77ae89681121d6bcbdb3edef4f578ed2 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Thu, 10 Jul 2025 16:57:34 +0700 Subject: [PATCH 127/248] Update users.jsx --- src/pages/dashboard/user/users.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/dashboard/user/users.jsx b/src/pages/dashboard/user/users.jsx index 35215055..6f02b8c6 100644 --- a/src/pages/dashboard/user/users.jsx +++ b/src/pages/dashboard/user/users.jsx @@ -133,7 +133,7 @@ export function Users() { } }; - // ✅ Thêm nông trại + const handleCreateFarm = async (farmData) => { try { await axios.post("https://api-ndolv2.nongdanonline.vn/farms", farmData, { @@ -165,7 +165,7 @@ export function Users() { {user.fullName} - Email: {user.email} + Email: {user.email.length > 25 ? user.email.slice(0,20) + "...": user.email} Phone: {user.phone || "N/A"} Roles: {Array.isArray(user.role) ? user.role.join(", ") : user.role} From e213f708178b465d346b2f46e914d989a6644dbf Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Thu, 10 Jul 2025 17:00:01 +0700 Subject: [PATCH 128/248] Update sidenav.jsx --- src/widgets/layout/sidenav.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/layout/sidenav.jsx b/src/widgets/layout/sidenav.jsx index 9880e603..62007756 100644 --- a/src/widgets/layout/sidenav.jsx +++ b/src/widgets/layout/sidenav.jsx @@ -52,7 +52,7 @@ export function Sidenav({ brandImg, brandName, routes }) { variant="h6" color={sidenavType === "dark" ? "white" : "blue-gray"} > - {brandName} + Admin Farm Date: Thu, 10 Jul 2025 21:31:23 +0700 Subject: [PATCH 129/248] sidenav --- src/layouts/dashboard.jsx | 15 +++++- src/widgets/layout/sidenav.jsx | 86 +++++++++++++++++++++------------- 2 files changed, 67 insertions(+), 34 deletions(-) diff --git a/src/layouts/dashboard.jsx b/src/layouts/dashboard.jsx index 888a627a..db640e01 100644 --- a/src/layouts/dashboard.jsx +++ b/src/layouts/dashboard.jsx @@ -1,6 +1,8 @@ import { Routes, Route } from "react-router-dom"; +import { useState } from "react"; import { Cog6ToothIcon } from "@heroicons/react/24/solid"; import { IconButton } from "@material-tailwind/react"; + import { Sidenav, DashboardNavbar, @@ -14,15 +16,22 @@ export function Dashboard() { const [controller, dispatch] = useMaterialTailwindController(); const { sidenavType } = controller; + const [collapsed, setCollapsed] = useState(false); + return ( -
    +
    setCollapsed(value)} /> -
    +
    + {routes.map( ({ layout, pages }) => @@ -43,6 +53,7 @@ export function Dashboard() { )) )} +
    diff --git a/src/widgets/layout/sidenav.jsx b/src/widgets/layout/sidenav.jsx index 62007756..3fc1cbe0 100644 --- a/src/widgets/layout/sidenav.jsx +++ b/src/widgets/layout/sidenav.jsx @@ -1,8 +1,8 @@ import PropTypes from "prop-types"; import { Link, NavLink } from "react-router-dom"; -import { XMarkIcon } from "@heroicons/react/24/outline"; +import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/outline"; +import { useState, useEffect } from "react"; import { - Avatar, Button, IconButton, Typography, @@ -10,22 +10,28 @@ import { import { useMaterialTailwindController, setOpenSidenav, - setAuthStatus, + setAuthStatus, } from "@/context"; import { useNavigate } from "react-router-dom"; -export function Sidenav({ brandImg, brandName, routes }) { + +export function Sidenav({ brandImg, brandName, routes, onCollapse }) { const [controller, dispatch] = useMaterialTailwindController(); const { sidenavColor, sidenavType, openSidenav, isAuthenticated } = controller; + const [collapsed, setCollapsed] = useState(false); const navigate = useNavigate(); + // Gửi trạng thái collapse ra ngoài nếu cần dùng bên ngoài + useEffect(() => { + if (onCollapse) onCollapse(collapsed); + }, [collapsed]); + const handleLogout = () => { - const confirmLogout = window.confirm("Bạn có chắc muốn đăng xuất?") + const confirmLogout = window.confirm("Bạn có chắc muốn đăng xuất?"); if (confirmLogout) { localStorage.removeItem("token"); - setAuthStatus(dispatch, false); + setAuthStatus(dispatch, false); navigate("/auth/sign-in"); } - }; const sidenavTypes = { @@ -42,34 +48,42 @@ export function Sidenav({ brandImg, brandName, routes }) {
    +
    ); -} +}; + +export default FarmDetail; diff --git a/src/pages/dashboard/farm/farms.jsx b/src/pages/dashboard/farm/farms.jsx index 10a24d9e..72a1f46e 100644 --- a/src/pages/dashboard/farm/farms.jsx +++ b/src/pages/dashboard/farm/farms.jsx @@ -1,14 +1,26 @@ - import React, { useState, useEffect } from "react"; import axios from "axios"; import { - Card, CardBody, Input, Button, Typography, Checkbox, Chip, Tabs, TabsHeader, Tab, + Card, + CardBody, + Input, + Button, + Typography, + Chip, + Tabs, + TabsHeader, + Tab, + Menu, + MenuHandler, + MenuList, + MenuItem, } from "@material-tailwind/react"; import { - PencilSquareIcon, TrashIcon, PlusIcon, LockOpenIcon ,LockClosedIcon, XCircleIcon, CheckCircleIcon -} from "@heroicons/react/24/solid"; + EllipsisVerticalIcon, +} from "@heroicons/react/24/outline"; +import { useNavigate } from "react-router-dom"; + import FarmForm from "../user/FarmForm"; -import FarmDetail from "./FarmDetail"; const BASE_URL = "https://api-ndolv2.nongdanonline.vn"; const getOpts = () => ({ @@ -26,8 +38,9 @@ export function Farms() { const [openForm, setOpenForm] = useState(false); const [editingFarm, setEditingFarm] = useState(null); - const [selectedFarmId, setSelectedFarmId] = useState(null); - const [openDetail, setOpenDetail] = useState(false); + const [openMenuId, setOpenMenuId] = useState(null); + + const navigate = useNavigate(); const fetchFarms = async () => { try { @@ -128,124 +141,121 @@ export function Farms() { Lỗi: {error} ) : ( - +
    - - - {["Tên", "Mã", "Chủ sở hữu", "SĐT", "Địa chỉ", "Diện tích", "Trạng thái", "Thao tác"].map((head) => ( - - ))} + + + + + + + + + + {displayedFarms.map((farm) => ( { setSelectedFarmId(farm._id); setOpenDetail(true); }} + className="border-b hover:bg-indigo-50 transition text-base cursor-pointer" + onClick={() => navigate(`/admin/farms/${farm._id}`)} > - - - - - - - - + + + + + + - + {farm.status === "inactive" && ( + { + activateFarm(farm._id, "mở khóa"); + setOpenMenuId(null); + }} + > + Mở khóa + + )} + + + ))} @@ -254,7 +264,6 @@ export function Farms() { )} - {/* Form thêm/sửa farm */} setOpenForm(false)} @@ -263,18 +272,12 @@ export function Farms() { if (editingFarm) { editFarm(editingFarm._id, data); } else { - addFarm(data); - } + addFarm(data); + } }} /> - - {/* Chi tiết farm */} - setOpenDetail(false)} - /> ); } + export default Farms; diff --git a/src/pages/dashboard/user/FarmForm.jsx b/src/pages/dashboard/user/FarmForm.jsx index 369f12db..8db914d9 100644 --- a/src/pages/dashboard/user/FarmForm.jsx +++ b/src/pages/dashboard/user/FarmForm.jsx @@ -1,164 +1,122 @@ -import React, { useState, useEffect } from "react"; +import React, { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; import { - Dialog, DialogHeader, DialogBody, DialogFooter, - Input, Button, Checkbox, + Card, + CardBody, + Typography, + Button, } from "@material-tailwind/react"; -import Select from "react-select"; +import axios from "axios"; +import FarmForm from "./FarmForm"; -// Danh sách dịch vụ -const serviceOptions = [ - { value: "direct_selling", label: "Bán hàng trực tiếp" }, - { value: "feed_selling", label: "Bán thức ăn chăn nuôi" }, - { value: "custom_feed_blending", label: "Pha trộn thức ăn theo yêu cầu" }, - { value: "processing_service", label: "Dịch vụ chế biến" }, - { value: "storage_service", label: "Dịch vụ lưu trữ" }, - { value: "transport_service", label: "Dịch vụ vận chuyển" }, - { value: "other_services", label: "Dịch vụ khác" }, -]; - -// Danh sách tính năng -const featureOptions = [ - { value: "aquaponic_model", label: "Mô hình Aquaponic" }, - { value: "ras_ready", label: "Hệ thống RAS" }, - { value: "hydroponic", label: "Thuỷ canh" }, - { value: "greenhouse", label: "Nhà kính" }, - { value: "vertical_farming", label: "Nông trại thẳng đứng" }, - { value: "viet_gap_cert", label: "Chứng nhận VietGAP" }, - { value: "organic_cert", label: "Chứng nhận hữu cơ" }, - { value: "global_gap_cert", label: "Chứng nhận GlobalGAP" }, - { value: "haccp_cert", label: "Chứng nhận HACCP" }, - { value: "camera_online", label: "Camera trực tuyến" }, - { value: "drone_monitoring", label: "Giám sát bằng drone" }, - { value: "automated_pest_detection", label: "Phát hiện sâu bệnh tự động" }, - { value: "precision_irrigation", label: "Tưới tiêu chính xác" }, - { value: "auto_irrigation", label: "Tưới tiêu tự động" }, - { value: "soil_based_irrigation", label: "Tưới theo độ ẩm đất" }, - { value: "iot_sensors", label: "Cảm biến IoT" }, - { value: "soil_moisture_monitoring", label: "Giám sát độ ẩm đất" }, - { value: "air_quality_sensor", label: "Cảm biến chất lượng không khí" }, -]; - -// Danh sách tag -const tagOptions = [ - { value: "nông sản", label: "nông sản" }, - { value: "hữu cơ", label: "hữu cơ" }, - { value: "mùa vụ", label: "mùa vụ" }, -]; - -const FarmForm = ({ open, onClose, initialData = {}, onSubmit }) => { - const [form, setForm] = useState({ - name: "", - code: "", - location: "", - area: 0, - cultivatedArea: 0, - services: [], - features: [], - tags: [], - phone: "", - zalo: "", - province: "", - district: "", - ward: "", - street: "", - isAvailable: true, - status: "pending", - ownerId: "", - }); +const FarmDetail = () => { + const { id } = useParams(); + const [farm, setFarm] = useState(null); + const [openEdit, setOpenEdit] = useState(false); + const [image, setImage] = useState(null); useEffect(() => { - if (initialData) { - setForm((prev) => ({ - ...prev, - ...initialData, - })); + fetchFarm(); + }, [id]); + + const fetchFarm = async () => { + try { + const res = await axios.get(`/api/farms/${id}`); + setFarm(res.data); + } catch (err) { + console.error("Failed to fetch farm:", err); } - }, [initialData]); - - const handleChange = (field, value) => { - setForm((prev) => ({ ...prev, [field]: value })); }; - const handleArrayChange = (field, value) => { - setForm((prev) => ({ ...prev, [field]: value })); + const handleUpdate = async (updatedFarm) => { + try { + await axios.put(`/api/farms/${id}`, updatedFarm); + setFarm(updatedFarm); + } catch (err) { + console.error("Failed to update farm:", err); + } }; - const handleSubmit = (e) => { - e.preventDefault(); - onSubmit(form); // Gửi dữ liệu cho component cha - onClose(); // Đóng dialog + const handleImageUpload = async (e) => { + const file = e.target.files[0]; + if (!file) return; + const formData = new FormData(); + formData.append("image", file); + try { + const res = await axios.post(`/api/farms/${id}/upload-image`, formData); + setFarm((prev) => ({ ...prev, image: res.data.imageUrl })); + } catch (err) { + console.error("Upload failed:", err); + } }; - return ( - - - {initialData?._id ? "Chỉnh sửa" : "Thêm"} nông trại - - - - handleChange("name", e.target.value)} /> - handleChange("location", e.target.value)} /> - handleChange("area", e.target.value)} /> - handleChange("cultivatedArea", e.target.value)} /> + if (!farm) return
    Đang tải...
    ; -
    - - featureOptions.find(opt => opt.value === val)).filter(Boolean)} - onChange={(selected) => handleArrayChange("features", selected.map(s => s.value))} - /> -
    - -
    - - handleChange("phone", e.target.value)} /> - handleChange("zalo", e.target.value)} /> - handleChange("province", e.target.value)} /> - handleChange("district", e.target.value)} /> - handleChange("ward", e.target.value)} /> - handleChange("street", e.target.value)} /> - - {/* Chỉ đọc ID chủ sở hữu */} - - - handleChange("isAvailable", e.target.checked)} - /> - - - - - - -
    + return ( +
    + + +
    + Thông tin Nông trại + +
    + +
    + Tên: {farm.name} + Mã: {farm.code} + Vị trí: {farm.location} + Diện tích: {farm.area} m² + Diện tích canh tác: {farm.cultivatedArea} m² + + Sẵn sàng: {farm.isAvailable ? "Có" : "Không"} + + + Trạng thái: {farm.status || "Chưa xác định"} + + + Tags: {farm.tags?.join(", ") || "Không có"} + + + Dịch vụ: {farm.services?.join(", ") || "Không có"} + + + Tính năng: {farm.features?.join(", ") || "Không có"} + +
    + +
    + Hình ảnh: + {farm.image ? ( + Farm + ) : ( + + Chưa có hình ảnh + + )} + +
    +
    +
    + + setOpenEdit(false)} + initialData={farm} + onSubmit={handleUpdate} + /> +
    ); }; -export default FarmForm; +export default FarmDetail; diff --git a/src/routes.jsx b/src/routes.jsx index 02951460..c75b5889 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -15,6 +15,7 @@ import { SignIn, SignUp } from "@/pages/auth"; import { ViewfinderCircleIcon,VideoCameraIcon,ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline"; import { Comment } from "react-loader-spinner"; +import FarmDetail from "@/pages/dashboard/farm/FarmDetail"; const icon = { className: "w-5 h-5 text-inherit", From 8b0e7b7c2a89e6f1d186cdf06fd70b50a7f2b208 Mon Sep 17 00:00:00 2001 From: Longvu003 Date: Fri, 11 Jul 2025 00:02:45 +0700 Subject: [PATCH 133/248] . --- .../AdminCommentPost/CommentPostbyIdPost.jsx | 71 +++++++++++----- .../VideoFarms/DialogVideoDetail.jsx | 1 + .../dashboard/VideoFarms/VideoFarmById.jsx | 45 ++++------ src/pages/dashboard/VideoFarms/VideoFarms.jsx | 82 +++++++++---------- 4 files changed, 105 insertions(+), 94 deletions(-) diff --git a/src/pages/dashboard/AdminCommentPost/CommentPostbyIdPost.jsx b/src/pages/dashboard/AdminCommentPost/CommentPostbyIdPost.jsx index bc7d5976..e593d5bd 100644 --- a/src/pages/dashboard/AdminCommentPost/CommentPostbyIdPost.jsx +++ b/src/pages/dashboard/AdminCommentPost/CommentPostbyIdPost.jsx @@ -33,9 +33,9 @@ getCommentById() },[]) return ( -
    +
    {loading ? ( -
    +
    navigate(`/admin/farms/${farm._id}`)} + onClick={() => navigate(`/dashboard/admin/farms/${farm._id}`)} > From 6547ca2e011fbde5abc5d6281baa3a3f7f1810b5 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Fri, 11 Jul 2025 09:11:56 +0700 Subject: [PATCH 135/248] Update ipconfig.jsx --- src/ipconfig.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipconfig.jsx b/src/ipconfig.jsx index 6df8e3d1..995e4dd3 100644 --- a/src/ipconfig.jsx +++ b/src/ipconfig.jsx @@ -1 +1 @@ -export const BaseUrl= `https://api-ndolv2.nongdanonline.vn` \ No newline at end of file +export const BaseUrl= `https://api-ndolv2.nongdanonline.cc` \ No newline at end of file From c2075dac2475fb9db00a437d969142ec7e4876bc Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Fri, 11 Jul 2025 09:34:12 +0700 Subject: [PATCH 136/248] thanh --- src/pages/auth/sign-in.jsx | 2 +- src/pages/dashboard/Questions/answerstable.jsx | 2 +- src/pages/dashboard/VideoFarms/commentVideo.jsx | 2 +- src/pages/dashboard/farm/FarmDetail.jsx | 2 +- src/pages/dashboard/farm/FarmPictures.jsx | 2 +- src/pages/dashboard/farm/farms.jsx | 4 ++-- src/pages/dashboard/post/Post.jsx | 2 +- src/pages/dashboard/post/PostDetail.jsx | 7 ++++++- src/pages/dashboard/user/UserDetail.jsx | 4 ++-- src/pages/dashboard/user/users.jsx | 16 ++++++++-------- 10 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/pages/auth/sign-in.jsx b/src/pages/auth/sign-in.jsx index a4ccbe4c..8619908b 100644 --- a/src/pages/auth/sign-in.jsx +++ b/src/pages/auth/sign-in.jsx @@ -35,7 +35,7 @@ export function SignIn() { } try { - const res = await fetch("https://api-ndolv2.nongdanonline.vn/auth/login", { + const res = await fetch("https://api-ndolv2.nongdanonline.cc/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password }), diff --git a/src/pages/dashboard/Questions/answerstable.jsx b/src/pages/dashboard/Questions/answerstable.jsx index e571112b..e4976b5a 100644 --- a/src/pages/dashboard/Questions/answerstable.jsx +++ b/src/pages/dashboard/Questions/answerstable.jsx @@ -9,7 +9,7 @@ import { Typography, } from "@material-tailwind/react"; -const API_URL = "https://api-ndolv2.nongdanonline.vn/answers"; +const API_URL = "https://api-ndolv2.nongdanonline.cc/answers"; // Hàm fetch có auto-refresh token const fetchWithAuth = async (url, options = {}) => { diff --git a/src/pages/dashboard/VideoFarms/commentVideo.jsx b/src/pages/dashboard/VideoFarms/commentVideo.jsx index c1851c34..3bda01a9 100644 --- a/src/pages/dashboard/VideoFarms/commentVideo.jsx +++ b/src/pages/dashboard/VideoFarms/commentVideo.jsx @@ -5,7 +5,7 @@ import { Card, Input, Button, Typography, Spinner, } from "@material-tailwind/react"; -const BASE = "https://api-ndolv2.nongdanonline.vn/video-comment"; +const BASE = "https://api-ndolv2.nongdanonline.cc/video-comment"; const token = () => localStorage.getItem("token"); const fetchComments = (videoId) => diff --git a/src/pages/dashboard/farm/FarmDetail.jsx b/src/pages/dashboard/farm/FarmDetail.jsx index 05e49263..43b12bb6 100644 --- a/src/pages/dashboard/farm/FarmDetail.jsx +++ b/src/pages/dashboard/farm/FarmDetail.jsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react"; import { useParams, useNavigate } from "react-router-dom"; import axios from "axios"; -const BASE_URL = "https://api-ndolv2.nongdanonline.vn"; +const BASE_URL = "https://api-ndolv2.nongdanonline.cc"; const getOpts = () => ({ headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }, }); diff --git a/src/pages/dashboard/farm/FarmPictures.jsx b/src/pages/dashboard/farm/FarmPictures.jsx index 03da0059..f88f6d67 100644 --- a/src/pages/dashboard/farm/FarmPictures.jsx +++ b/src/pages/dashboard/farm/FarmPictures.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react"; import axios from "axios"; import { Button, Dialog, DialogBody, DialogFooter, DialogHeader, Input, Typography } from "@material-tailwind/react"; -const BASE_URL = "https://api-ndolv2.nongdanonline.vn"; +const BASE_URL = "https://api-ndolv2.nongdanonline.cc"; const getOpts = () => ({ headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }, }); diff --git a/src/pages/dashboard/farm/farms.jsx b/src/pages/dashboard/farm/farms.jsx index 5cec8f75..95826a66 100644 --- a/src/pages/dashboard/farm/farms.jsx +++ b/src/pages/dashboard/farm/farms.jsx @@ -22,7 +22,7 @@ import { useNavigate } from "react-router-dom"; import FarmForm from "../user/FarmForm"; -const BASE_URL = "https://api-ndolv2.nongdanonline.vn"; +const BASE_URL = "https://api-ndolv2.nongdanonline.cc"; const getOpts = () => ({ headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }, }); @@ -160,7 +160,7 @@ export function Farms() { navigate(`/dashboard/admin/farms/${farm._id}`)} + onClick={() => navigate(`/admin/farms/${farm._id}`)} > diff --git a/src/pages/dashboard/post/Post.jsx b/src/pages/dashboard/post/Post.jsx index bcaa5194..100d998a 100644 --- a/src/pages/dashboard/post/Post.jsx +++ b/src/pages/dashboard/post/Post.jsx @@ -9,7 +9,7 @@ import { } from "@material-tailwind/react"; import { useNavigate } from "react-router-dom"; -const BASE_URL = "https://api-ndolv2.nongdanonline.vn"; +const BASE_URL = "https://api-ndolv2.nongdanonline.cc"; export function PostList() { const [posts, setPosts] = useState([]); diff --git a/src/pages/dashboard/post/PostDetail.jsx b/src/pages/dashboard/post/PostDetail.jsx index 8759c6cf..cff91f32 100644 --- a/src/pages/dashboard/post/PostDetail.jsx +++ b/src/pages/dashboard/post/PostDetail.jsx @@ -1,7 +1,8 @@ import React, {useEffect, useState} from "react"; import { useParams, useNavigate } from "react-router-dom"; import { Typography, Button, Chip, Avatar } from "@material-tailwind/react"; -const BASE_URL = 'https://api-ndolv2.nongdanonline.vn'; +import CommentPostbyIdPost from "../AdminCommentPost/CommentPostbyIdPost"; +const BASE_URL = 'https://api-ndolv2.nongdanonline.cc'; export function PostDetail() { const { id } = useParams(); @@ -75,6 +76,10 @@ export function PostDetail() { color={post.status ? "green" : "red"} size="sm" /> +
    + Bình luận bài viết + +
    ); } diff --git a/src/pages/dashboard/user/UserDetail.jsx b/src/pages/dashboard/user/UserDetail.jsx index fba1ed6e..d4a96df2 100644 --- a/src/pages/dashboard/user/UserDetail.jsx +++ b/src/pages/dashboard/user/UserDetail.jsx @@ -17,7 +17,7 @@ export default function UserDetail() { return; } - axios.get(`https://api-ndolv2.nongdanonline.vn/admin-users/${id}`, { + axios.get(`https://api-ndolv2.nongdanonline.cc/admin-users/${id}`, { headers: { Authorization: `Bearer ${token}` } }) .then(res => setUser(res.data)) @@ -43,7 +43,7 @@ export default function UserDetail() { {user.avatar ? ( {user.fullName} diff --git a/src/pages/dashboard/user/users.jsx b/src/pages/dashboard/user/users.jsx index 6f02b8c6..118d8684 100644 --- a/src/pages/dashboard/user/users.jsx +++ b/src/pages/dashboard/user/users.jsx @@ -30,7 +30,7 @@ export function Users() { const fetchUsers = async () => { setLoading(true); try { - const res = await axios.get("https://api-ndolv2.nongdanonline.vn/admin-users", { + const res = await axios.get("https://api-ndolv2.nongdanonline.cc/admin-users", { headers: { Authorization: `Bearer ${token}` } }); const activeUsers = (Array.isArray(res.data) ? res.data : []).filter(u => u.isActive); @@ -64,7 +64,7 @@ export function Users() { const handleUpdate = async () => { if (!token || !selectedUser) return; try { - await axios.put(`https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}`, formData, { + await axios.put(`https://api-ndolv2.nongdanonline.cc/admin-users/${selectedUser.id}`, formData, { headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, }); alert("Cập nhật thành công!"); @@ -79,7 +79,7 @@ export function Users() { if (!selectedUser) return; try { await axios.patch( - `https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}/add-role`, + `https://api-ndolv2.nongdanonline.cc/admin-users/${selectedUser.id}/add-role`, { role: selectedRole }, { headers: { Authorization: `Bearer ${token}` } } ); @@ -94,7 +94,7 @@ export function Users() { if (!selectedUser) return; try { await axios.patch( - `https://api-ndolv2.nongdanonline.vn/admin-users/${selectedUser.id}/remove-roles`, + `https://api-ndolv2.nongdanonline.cc/admin-users/${selectedUser.id}/remove-roles`, { roles: [role] }, { headers: { Authorization: `Bearer ${token}` } } ); @@ -109,7 +109,7 @@ export function Users() { setViewUser(user); setViewOpen(true); try { - const res = await axios.get("https://api-ndolv2.nongdanonline.vn/user-addresses", { + const res = await axios.get("https://api-ndolv2.nongdanonline.cc/user-addresses", { headers: { Authorization: `Bearer ${token}` } }); const userAddresses = res.data.filter(addr => addr.userid === user.id); @@ -123,7 +123,7 @@ export function Users() { if (!window.confirm("Bạn chắc muốn xoá?")) return; try { await axios.delete( - `https://api-ndolv2.nongdanonline.vn/admin-users/${userId}`, + `https://api-ndolv2.nongdanonline.cc/admin-users/${userId}`, { headers: { Authorization: `Bearer ${token}` } } ); alert("Đã xoá người dùng!"); @@ -136,7 +136,7 @@ export function Users() { const handleCreateFarm = async (farmData) => { try { - await axios.post("https://api-ndolv2.nongdanonline.vn/farms", farmData, { + await axios.post("https://api-ndolv2.nongdanonline.cc/farms", farmData, { headers: { Authorization: `Bearer ${token}` }, }); alert("Tạo nông trại thành công!"); @@ -158,7 +158,7 @@ export function Users() { {user.avatar ? ( - {user.fullName} + {user.fullName} ) : (
    No Avatar
    )} From 2a42f4b49a753d4535a03b13a1ca8fd93ef87ccc Mon Sep 17 00:00:00 2001 From: Tie902 Date: Fri, 11 Jul 2025 11:13:42 +0700 Subject: [PATCH 137/248] dashboar --- src/pages/dashboard/home.jsx | 309 +++++++---------------------------- src/pages/dashboard/index.js | 4 +- 2 files changed, 60 insertions(+), 253 deletions(-) diff --git a/src/pages/dashboard/home.jsx b/src/pages/dashboard/home.jsx index 2c700669..5f3d1de1 100644 --- a/src/pages/dashboard/home.jsx +++ b/src/pages/dashboard/home.jsx @@ -1,258 +1,65 @@ -import React from "react"; -import { - Typography, - Card, - CardHeader, - CardBody, - IconButton, - Menu, - MenuHandler, - MenuList, - MenuItem, - Avatar, - Tooltip, - Progress, -} from "@material-tailwind/react"; -import { - EllipsisVerticalIcon, - ArrowUpIcon, -} from "@heroicons/react/24/outline"; -import { StatisticsCard } from "@/widgets/cards"; -import { StatisticsChart } from "@/widgets/charts"; -import { - statisticsCardsData, - statisticsChartsData, - projectsTableData, - ordersOverviewData, -} from "@/data"; -import { CheckCircleIcon, ClockIcon } from "@heroicons/react/24/solid"; +import React, { useEffect, useState } from "react"; +import axios from "axios"; +import { Typography, Card, CardBody } from "@material-tailwind/react"; +import { UserIcon } from "@heroicons/react/24/solid"; + +export default function Home() { + const [loginStats, setLoginStats] = useState(null); + + useEffect(() => { + const fetchLoginStats = async () => { + try { + const token = localStorage.getItem("token"); // nhớ lưu token trước đó + const response = await axios.get( + "https://api-ndolv2.nongdanonline.vn/user-dashboard/login-stats", + { + headers: { Authorization: `Bearer ${token}` } + } + ); + setLoginStats(response.data); + } catch (error) { + console.error("Error fetching login stats:", error); + } + }; + fetchLoginStats(); + }, []); -export function Home() { return ( -
    -
    - {statisticsCardsData.map(({ icon, title, footer, ...rest }) => ( - - {footer.value} -  {footer.label} - - } - /> - ))} -
    -
    - {statisticsChartsData.map((props) => ( - - -  {props.footer} - - } - /> - ))} -
    -
    - - +
    + {loginStats ? ( + <> + +
    - - Projects - - - - 30 done this month - + Today's Login + {loginStats.todayLogin}
    - - - - - - - - Action - Another Action - Something else here - - - - -
    {head}
    TênChủ sở hữuSĐTĐịa chỉDiện tíchTrạng tháiThao tác
    {farm.name}{farm.code}{farm.ownerInfo?.name || "—"}{farm.phone || "—"}{farm.location}{farm.area} m² + {farm.name}{farm.code}{farm.ownerInfo?.name || "—"}{farm.phone || "—"}{farm.location}{farm.area} m² -
    - {/* Nhóm nút Sửa & Xoá */} -
    - - - -
    - +
    e.stopPropagation()}> + setOpenMenuId(openMenuId === farm._id ? null : farm._id)} + allowHover={false} + placement="left-start" + > + + + - {/* Nhóm nút trạng thái */} -
    - {farm.status === "pending" && ( - <> - + + { + setEditingFarm(farm); + setOpenForm(true); + setOpenMenuId(null); + }} + > + Sửa + - + { + deleteFarm(farm._id); + setOpenMenuId(null); + }} + > + Xoá + - -)} + {farm.status === "pending" && ( + <> + { + activateFarm(farm._id, "duyệt"); + setOpenMenuId(null); + }} + > + Duyệt + + { + deactivateFarm(farm._id, "từ chối"); + setOpenMenuId(null); + }} + > + Từ chối + + + )} + {farm.status === "active" && ( + { + deactivateFarm(farm._id, "khóa"); + setOpenMenuId(null); + }} + > + Khóa + + )} - {farm.status === "active" && ( - - )} - - {farm.status === "inactive" && ( - - )} - -
    - -
    {farm.name} {farm.code}
    {farm.name} {farm.code}
    - - - {["companies", "members", "budget", "completion"].map( - (el) => ( - - ) - )} - - - - {projectsTableData.map( - ({ img, name, members, budget, completion }, key) => { - const className = `py-3 px-5 ${ - key === projectsTableData.length - 1 - ? "" - : "border-b border-blue-gray-50" - }`; - - return ( - - - - - - - ); - } - )} - -
    - - {el} - -
    -
    - - - {name} - -
    -
    - {members.map(({ img, name }, key) => ( - - - - ))} - - - {budget} - - -
    - - {completion}% - - -
    -
    -
    - - - - - Orders Overview - - - - 24% this month - - - - {ordersOverviewData.map( - ({ icon, color, title, description }, key) => ( -
    -
    - {React.createElement(icon, { - className: `!w-5 !h-5 ${color}`, - })} -
    -
    - - {title} - - - {description} - -
    -
    - ) - )} -
    -
    -
    + + + +
    + Yesterday's Login + {loginStats.yesterdayLogin} +
    +
    + + +
    + Last 7 Days Login + {loginStats.last7DaysLogin} +
    +
    + + +
    + Last 30 Days Login + {loginStats.last30DaysLogin} +
    +
    + + ) : ( + Loading login stats... + )}
    ); } - -export default Home; diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index fea3f6bb..5a7e1fe4 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -1,12 +1,12 @@ -export * from "@/pages/dashboard/home"; +export { default as Home } from "./home"; export * from "@/pages/dashboard/Questions/answerstable"; -export * from "@/pages/dashboard/user/users"; export * from "@/pages/dashboard/farm/farms"; export * from "@/pages/dashboard/Questions/Questions"; export * from "@/pages/dashboard/VideoFarms/VideoFarms"; export * from "@/pages/dashboard/post/Post"; export * from "@/pages/dashboard/AdminReports" export * from "@/pages/dashboard/AdminCommentPost/CommentPost"; +export * from "@/pages/dashboard/user/users" From 621de379d35c3549b3cbbe32442aaa4d2e8eb1eb Mon Sep 17 00:00:00 2001 From: trongquy1213 Date: Fri, 11 Jul 2025 13:35:26 +0700 Subject: [PATCH 138/248] quy --- src/pages/dashboard/farm/FarmDetail.jsx | 297 ++++++++++++------------ src/pages/dashboard/farm/farms.jsx | 64 +++-- 2 files changed, 192 insertions(+), 169 deletions(-) diff --git a/src/pages/dashboard/farm/FarmDetail.jsx b/src/pages/dashboard/farm/FarmDetail.jsx index 43b12bb6..2e42f537 100644 --- a/src/pages/dashboard/farm/FarmDetail.jsx +++ b/src/pages/dashboard/farm/FarmDetail.jsx @@ -1,94 +1,74 @@ -import React, { useEffect, useState } from "react"; -import { useParams, useNavigate } from "react-router-dom"; +import React, { useState, useEffect } from "react"; import axios from "axios"; +import { Typography, Button } from "@material-tailwind/react"; const BASE_URL = "https://api-ndolv2.nongdanonline.cc"; const getOpts = () => ({ headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }, }); -// serviceOptions và featureOptions ánh xạ tiếng Việt + +const Info = ({ label, value }) => ( +
    + {label} + {value || "—"} +
    +); + +const mapToLabel = (arr, options) => { + if (!arr || arr.length === 0) return "—"; + const values = typeof arr === "string" ? arr.split(",").map((v) => v.trim()) : arr; + return values.map((v) => options.find((o) => o.value === v)?.label || v).join(", "); +}; + +// Danh sách Dịch vụ const serviceOptions = [ - { value: "direct_selling", label: "Bán hàng trực tiếp" }, - { value: "feed_selling", label: "Bán thức ăn chăn nuôi" }, - { value: "custom_feed_blending", label: "Pha trộn thức ăn theo yêu cầu" }, - { value: "processing_service", label: "Dịch vụ chế biến" }, - { value: "storage_service", label: "Dịch vụ lưu trữ" }, - { value: "transport_service", label: "Dịch vụ vận chuyển" }, - { value: "other_services", label: "Dịch vụ khác" }, + { label: "Bán trực tiếp", value: "direct_selling" }, + { label: "Bán thức ăn", value: "feed_selling" }, + { label: "Phối trộn thức ăn", value: "custom_feed_blending" }, + { label: "Dịch vụ sơ chế", value: "processing_service" }, + { label: "Dịch vụ lưu kho", value: "storage_service" }, + { label: "Dịch vụ vận chuyển", value: "transport_service" }, + { label: "Dịch vụ khác", value: "other_services" }, ]; +// Danh sách Tính năng (KHÁC với dịch vụ) const featureOptions = [ - { value: "aquaponic_model", label: "Mô hình Aquaponic" }, - { value: "ras_ready", label: "Hệ thống RAS" }, - { value: "hydroponic", label: "Thuỷ canh" }, - { value: "greenhouse", label: "Nhà kính" }, - { value: "vertical_farming", label: "Nông trại thẳng đứng" }, - { value: "viet_gap_cert", label: "Chứng nhận VietGAP" }, - { value: "organic_cert", label: "Chứng nhận hữu cơ" }, - { value: "global_gap_cert", label: "Chứng nhận GlobalGAP" }, - { value: "haccp_cert", label: "Chứng nhận HACCP" }, - { value: "camera_online", label: "Camera trực tuyến" }, - { value: "drone_monitoring", label: "Giám sát bằng drone" }, - { value: "automated_pest_detection", label: "Phát hiện sâu bệnh tự động" }, - { value: "precision_irrigation", label: "Tưới tiêu chính xác" }, - { value: "auto_irrigation", label: "Tưới tiêu tự động" }, - { value: "soil_based_irrigation", label: "Tưới theo độ ẩm đất" }, - { value: "iot_sensors", label: "Cảm biến IoT" }, - { value: "soil_moisture_monitoring", label: "Giám sát độ ẩm đất" }, - { value: "air_quality_sensor", label: "Cảm biến chất lượng không khí" }, + { label: "Mô hình aquaponic", value: "aquaponic_model" }, + { label: "Chứng nhận VietGAP", value: "viet_gap_cert" }, + { label: "Chứng nhận hữu cơ", value: "organic_cert" }, + { label: "Nông trại thông minh", value: "smart_farm" }, + { label: "Tự động hóa", value: "automation" }, + { label: "Sử dụng IoT", value: "iot_enabled" }, ]; -// Hàm chuyển value -> label -const mapToLabel = (values, options) => - (values || []) - .map((val) => options.find((opt) => opt.value === val)?.label || val) - .join(", "); - -const FarmDetail = () => { - const { id } = useParams(); - const navigate = useNavigate(); +export default function FarmDetail({ open, onClose, farmId }) { const [farm, setFarm] = useState(null); const [error, setError] = useState(null); const [uploading, setUploading] = useState(false); - const [selectedFiles, setSelectedFiles] = useState([]); + const [files, setFiles] = useState([]); - useEffect(() => { - const fetchFarm = async () => { - try { - const res = await axios.get(`${BASE_URL}/adminfarms/${id}`, getOpts()); - setFarm(res.data); - } catch (err) { - setError(err.response?.data?.message || err.message); - } - }; - fetchFarm(); - }, [id]); - - const handleFileChange = (e) => { - setSelectedFiles(e.target.files); + const fetchDetail = async () => { + if (!farmId) return; + try { + const res = await axios.get(`${BASE_URL}/adminfarms/${farmId}`, getOpts()); + setFarm(res.data); + } catch (err) { + setError(err.response?.data?.message || err.message); + } }; - const handleUpload = async () => { - if (!selectedFiles.length) return; + const handleFileChange = (e) => setFiles([...e.target.files]); + const handleUpload = async () => { + if (!files.length || !farmId) return; const formData = new FormData(); - for (let file of selectedFiles) { - formData.append("images", file); - } + files.forEach((file) => formData.append("images", file)); + setUploading(true); try { - setUploading(true); - await axios.post(`${BASE_URL}/adminfarms/${id}/images`, formData, { - headers: { - Authorization: `Bearer ${localStorage.getItem("token")}`, - "Content-Type": "multipart/form-data", - }, - }); - - // Refresh farm data - const res = await axios.get(`${BASE_URL}/adminfarms/${id}`, getOpts()); - setFarm(res.data); - setSelectedFiles([]); + await axios.post(`${BASE_URL}/farm-pictures/${farmId}`, formData, getOpts()); + setFiles([]); + await fetchDetail(); } catch (err) { alert("Upload thất bại: " + (err.response?.data?.message || err.message)); } finally { @@ -96,87 +76,110 @@ const FarmDetail = () => { } }; + useEffect(() => { + if (open && farmId) fetchDetail(); + }, [open, farmId]); + + if (!open) return null; + return ( -
    -
    -
    -

    Chi tiết Nông trại

    - -
    - - {error &&

    Lỗi: {error}

    } - {!farm ? ( -

    Đang tải dữ liệu...

    - ) : ( -
    -
    -

    Tên nông trại: {farm.name}

    -

    Vị trí: {farm.location}

    -

    Diện tích (m²): {farm.area}

    -

    Diện tích đất canh tác (m²): {farm.cultivationArea}

    -

    Dịch vụ: {mapToLabel(farm.services, serviceOptions)}

    -

    Tính năng: {mapToLabel(farm.features, featureOptions)}

    -

    Tags: {(farm.tags || []).join(", ")}

    -

    Số điện thoại: {farm.phone}

    -

    Zalo: {farm.zalo || "—"}

    -

    Tỉnh/Thành phố: {farm.province}

    -

    Quận/Huyện: {farm.district}

    -

    Phường/Xã: {farm.ward}

    -

    Đường: {farm.street}

    -

    ID Chủ sở hữu: {farm.ownerId}

    -

    Chủ sở hữu: {farm.ownerInfo?.name || "—"}

    -

    Trạng thái: { - farm.status === "pending" ? "Chờ duyệt" : - farm.status === "active" ? "Đang hoạt động" : - "Đã khóa" - }

    - - {/* Cập nhật hình ảnh */} -
    -

    hình ảnh nông trại:

    - - {farm.images && farm.images.length > 0 ? ( -
    - {farm.images.map((img, index) => ( - {`Ảnh - ))} -
    - ) : ( -

    Chưa có hình ảnh

    - )} - -
    - + {error && {error}} + + {!farm ? ( + Đang tải... + ) : ( + <> + {/* Thông tin chung */} +
    + + + + + + + + + + + + + + + + +
    + + {/* Mô tả */} + {farm.description && ( +
    + + Mô tả + + + {farm.description} + +
    + )} + + {/* Hình ảnh */} +
    + Hình ảnh + + {farm.images?.length > 0 ? ( +
    + {farm.images.map((img, idx) => ( + {`Ảnh - -
    + ))}
    -
    + ) : ( + Chưa có hình ảnh + )} + + {farm.status === "active" ? ( +
    + + +
    + ) : ( + + Chỉ nông trại đang hoạt động mới được phép thêm hình ảnh. + + )}
    - )} -
    + + )}
    ); -}; - -export default FarmDetail; +} diff --git a/src/pages/dashboard/farm/farms.jsx b/src/pages/dashboard/farm/farms.jsx index 95826a66..697750aa 100644 --- a/src/pages/dashboard/farm/farms.jsx +++ b/src/pages/dashboard/farm/farms.jsx @@ -14,13 +14,15 @@ import { MenuHandler, MenuList, MenuItem, + Dialog, + DialogHeader, + DialogBody, + IconButton, } from "@material-tailwind/react"; -import { - EllipsisVerticalIcon, -} from "@heroicons/react/24/outline"; -import { useNavigate } from "react-router-dom"; +import { EllipsisVerticalIcon } from "@heroicons/react/24/outline"; import FarmForm from "../user/FarmForm"; +import FarmDetail from "./FarmDetail"; const BASE_URL = "https://api-ndolv2.nongdanonline.cc"; const getOpts = () => ({ @@ -40,7 +42,8 @@ export function Farms() { const [openMenuId, setOpenMenuId] = useState(null); - const navigate = useNavigate(); + const [openDetail, setOpenDetail] = useState(false); + const [selectedFarmId, setSelectedFarmId] = useState(null); const fetchFarms = async () => { try { @@ -82,24 +85,25 @@ export function Farms() { } }; - const activateFarm = async (id, actionText) => { - if (!window.confirm(`Bạn có chắc chắn muốn ${actionText} farm này không?`)) return; + const changeStatus = async (id, action) => { + const actionMap = { + activate: "kích hoạt", + deactivate: "khóa", + }; + + if (!window.confirm(`Bạn có chắc chắn muốn ${actionMap[action] || action} farm này không?`)) return; + try { - await axios.patch(`${BASE_URL}/adminfarms/${id}/activate`, null, getOpts()); + await axios.patch(`${BASE_URL}/adminfarms/${id}/${action}`, null, getOpts()); await fetchFarms(); } catch (err) { - alert(`Lỗi ${actionText}: ` + (err.response?.data?.message || err.message)); + alert(`Lỗi ${actionMap[action] || action}: ` + (err.response?.data?.message || err.message)); } }; - const deactivateFarm = async (id, actionText) => { - if (!window.confirm(`Bạn có chắc chắn muốn ${actionText} farm này không?`)) return; - try { - await axios.patch(`${BASE_URL}/adminfarms/${id}/deactivate`, null, getOpts()); - await fetchFarms(); - } catch (err) { - alert(`Lỗi ${actionText}: ` + (err.response?.data?.message || err.message)); - } + const handleOpenDetail = (id) => { + setSelectedFarmId(id); + setOpenDetail(true); }; useEffect(() => { @@ -160,7 +164,7 @@ export function Farms() { navigate(`/admin/farms/${farm._id}`)} + onClick={() => handleOpenDetail(farm._id)} > {farm.name} {farm.code} @@ -215,7 +219,7 @@ export function Farms() { <> { - activateFarm(farm._id, "duyệt"); + changeStatus(farm._id, "activate"); setOpenMenuId(null); }} > @@ -223,7 +227,7 @@ export function Farms() { { - deactivateFarm(farm._id, "từ chối"); + changeStatus(farm._id, "deactivate"); setOpenMenuId(null); }} > @@ -235,7 +239,7 @@ export function Farms() { {farm.status === "active" && ( { - deactivateFarm(farm._id, "khóa"); + changeStatus(farm._id, "deactivate"); setOpenMenuId(null); }} > @@ -246,7 +250,7 @@ export function Farms() { {farm.status === "inactive" && ( { - activateFarm(farm._id, "mở khóa"); + changeStatus(farm._id, "activate"); setOpenMenuId(null); }} > @@ -276,6 +280,22 @@ export function Farms() { } }} /> + + + + Chi tiết nông trại + setOpenDetail(false)} className="ml-auto"> + ✕ + + + + setOpenDetail(false)} + farmId={selectedFarmId} +/> + + ); } From 7a1701a25bbbd031e9881491ac3a45e2344a6798 Mon Sep 17 00:00:00 2001 From: thanhnt0208 Date: Fri, 11 Jul 2025 13:39:15 +0700 Subject: [PATCH 139/248] Update Post.jsx --- src/pages/dashboard/post/Post.jsx | 91 +++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 11 deletions(-) diff --git a/src/pages/dashboard/post/Post.jsx b/src/pages/dashboard/post/Post.jsx index 100d998a..b2687774 100644 --- a/src/pages/dashboard/post/Post.jsx +++ b/src/pages/dashboard/post/Post.jsx @@ -106,6 +106,7 @@ export function PostList() { title: selectedPost.title, description: selectedPost.description, status: selectedPost.status, + tags: selectedPost.tags, }), }); @@ -193,10 +194,19 @@ export function PostList() { - {post.tags.map((tag, index) => ( - - ))} + {Array.isArray(post.tags) && post.tags.length > 0 && ( +
    +
    + {post.tags[0]} +
    + {post.tags.length > 1 && ( + +{post.tags.length - 1} + )} +
    + )} + + {post.images.length > 0 ? ( {author ? ( <> - {author.avatar && ( + {/* {author.avatar && ( - )} + )} */} {author.fullName} ) : ( @@ -290,23 +300,80 @@ export function PostList() { {/* Dialog cập nhật */} setOpenEdit(false)}> -
    +
    Cập nhật bài post + + {/* Tiêu đề */} - setSelectedPost({ ...selectedPost, title: e.target.value }) - } + onChange={(e) => setSelectedPost({ ...selectedPost, title: e.target.value })} /> + + {/* Mô tả */}