diff --git a/package.json b/package.json index 44f14fa6..d31eb950 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,13 @@ "preview": "vite preview" }, "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", "@heroicons/react": "2.0.18", "@material-tailwind/react": "2.1.4", "apexcharts": "3.44.0", + "axios": "^1.12.2", + "jwt-decode": "^4.0.0", "prop-types": "15.8.1", "react": "18.2.0", "react-apexcharts": "1.4.1", @@ -27,6 +31,6 @@ "prettier": "3.0.3", "prettier-plugin-tailwindcss": "0.5.6", "tailwindcss": "3.3.4", - "vite": "4.5.0" + "vite": "^4.5.14" } -} \ No newline at end of file +} diff --git a/public/img/background.jpg b/public/img/background.jpg new file mode 100644 index 00000000..fb14759e Binary files /dev/null and b/public/img/background.jpg differ diff --git a/public/img/pattern.png b/public/img/pattern.png deleted file mode 100644 index 2b34e46f..00000000 Binary files a/public/img/pattern.png and /dev/null differ diff --git a/src/App.jsx b/src/App.jsx index 87826600..3933629b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,12 +1,28 @@ import { Routes, Route, Navigate } from "react-router-dom"; import { Dashboard, Auth } from "@/layouts"; +import PrivateRoute from "../src/component/PrivateRoute"; + function App() { return ( - } /> + {/* Dashboard'u sadece giriş yapmış kullanıcı görecek */} + + + + } + /> + + {/* Auth sayfaları (giriş, kayıt) herkes görebilir */} } /> - } /> + + + + {/* Varsayılan yönlendirme */} + } /> ); } diff --git a/src/api/axiosConfig.js b/src/api/axiosConfig.js new file mode 100644 index 00000000..8fabae67 --- /dev/null +++ b/src/api/axiosConfig.js @@ -0,0 +1,29 @@ +import axios from "axios"; + + + +const apiClient = axios.create({ + baseURL: "https://localhost:7093/api", +}); + + +apiClient.interceptors.request.use( + (config) => { + + const token = localStorage.getItem("authToken"); + + + if (token) { + + config.headers.Authorization = `Bearer ${token}`; + } + + return config; + }, + (error) => { + + return Promise.reject(error); + } +); + +export default apiClient; \ No newline at end of file diff --git a/src/component/PrivateRoute.jsx b/src/component/PrivateRoute.jsx new file mode 100644 index 00000000..63f8e93b --- /dev/null +++ b/src/component/PrivateRoute.jsx @@ -0,0 +1,15 @@ +import React from "react"; +import { Navigate } from "react-router-dom"; + +const PrivateRoute = ({ children }) => { + const token = localStorage.getItem("authToken"); + + if (!token) { + // Token yoksa login sayfasına yönlendir + return ; + } + + return children; +}; + +export default PrivateRoute; diff --git a/src/context/index.jsx b/src/context/index.jsx index 653a362d..232589ba 100644 --- a/src/context/index.jsx +++ b/src/context/index.jsx @@ -23,6 +23,10 @@ export function reducer(state, action) { } case "OPEN_CONFIGURATOR": { return { ...state, openConfigurator: action.value }; + } + // YENİ: Kullanıcı rolünü ayarlamak için yeni case eklendi. + case "SET_USER_ROLE": { + return { ...state, userRole: action.value }; } default: { throw new Error(`Unhandled action type: ${action.type}`); @@ -38,18 +42,20 @@ export function MaterialTailwindControllerProvider({ children }) { transparentNavbar: true, fixedNavbar: false, openConfigurator: false, + // YENİ: userRole state'i eklendi. Sayfa yenilendiğinde rolün kaybolmaması için localStorage'dan okunuyor. + userRole: localStorage.getItem("userRole") || null, }; const [controller, dispatch] = React.useReducer(reducer, initialState); const value = React.useMemo( - () => [controller, dispatch], - [controller, dispatch] + () => [controller, dispatch], + [controller, dispatch] ); return ( - - {children} - + + {children} + ); } @@ -58,7 +64,7 @@ export function useMaterialTailwindController() { if (!context) { throw new Error( - "useMaterialTailwindController should be used inside the MaterialTailwindControllerProvider." + "useMaterialTailwindController should be used inside the MaterialTailwindControllerProvider." ); } @@ -72,14 +78,18 @@ MaterialTailwindControllerProvider.propTypes = { }; export const setOpenSidenav = (dispatch, value) => - dispatch({ type: "OPEN_SIDENAV", value }); + dispatch({ type: "OPEN_SIDENAV", value }); export const setSidenavType = (dispatch, value) => - dispatch({ type: "SIDENAV_TYPE", value }); + dispatch({ type: "SIDENAV_TYPE", value }); export const setSidenavColor = (dispatch, value) => - dispatch({ type: "SIDENAV_COLOR", value }); + dispatch({ type: "SIDENAV_COLOR", value }); export const setTransparentNavbar = (dispatch, value) => - dispatch({ type: "TRANSPARENT_NAVBAR", value }); + dispatch({ type: "TRANSPARENT_NAVBAR", value }); export const setFixedNavbar = (dispatch, value) => - dispatch({ type: "FIXED_NAVBAR", value }); + dispatch({ type: "FIXED_NAVBAR", value }); export const setOpenConfigurator = (dispatch, value) => - dispatch({ type: "OPEN_CONFIGURATOR", value }); + dispatch({ type: "OPEN_CONFIGURATOR", value }); + + +export const setUserRole = (dispatch, value) => + dispatch({ type: "SET_USER_ROLE", value }); \ No newline at end of file diff --git a/src/layouts/dashboard.jsx b/src/layouts/dashboard.jsx index 888a627a..3041b4a5 100644 --- a/src/layouts/dashboard.jsx +++ b/src/layouts/dashboard.jsx @@ -2,53 +2,53 @@ import { Routes, Route } from "react-router-dom"; import { Cog6ToothIcon } from "@heroicons/react/24/solid"; import { IconButton } from "@material-tailwind/react"; import { - Sidenav, - DashboardNavbar, - Configurator, - Footer, +  Sidenav, +  DashboardNavbar, +  Configurator, +  Footer, } from "@/widgets/layout"; import routes from "@/routes"; import { useMaterialTailwindController, setOpenConfigurator } from "@/context"; export function Dashboard() { - const [controller, dispatch] = useMaterialTailwindController(); - const { sidenavType } = controller; +  const [controller, dispatch] = useMaterialTailwindController(); +  const { sidenavType } = controller; - return ( -
- -
- - - setOpenConfigurator(dispatch, true)} - > - - - - {routes.map( - ({ layout, pages }) => - layout === "dashboard" && - pages.map(({ path, element }) => ( - - )) - )} - -
-
-
-
-
- ); +  return ( +   
+      +     
+        +        +        setOpenConfigurator(dispatch, true)} +        > +          +        +        +          {routes.map( +            ({ layout, pages }) => +              layout === "dashboard" && +              pages.map(({ path, element }) => ( +                +              )) +          )} +        +       
+         
+       
+     
+   
+  ); } Dashboard.displayName = "/src/layout/dashboard.jsx"; diff --git a/src/pages/auth/index.js b/src/pages/auth/index.js index ca1bbcb6..425a5351 100644 --- a/src/pages/auth/index.js +++ b/src/pages/auth/index.js @@ -1,2 +1,2 @@ export * from "@/pages/auth/sign-in"; -export * from "@/pages/auth/sign-up"; + diff --git a/src/pages/auth/sign-in.jsx b/src/pages/auth/sign-in.jsx index 3b3da41a..9396ea96 100644 --- a/src/pages/auth/sign-in.jsx +++ b/src/pages/auth/sign-in.jsx @@ -1,126 +1,122 @@ +import React, { useState } from "react"; +import { Card, Input, Button, Typography } from "@material-tailwind/react"; +import { useNavigate } from "react-router-dom"; +import { jwtDecode } from "jwt-decode"; import { - Card, - Input, - Checkbox, - Button, - Typography, -} from "@material-tailwind/react"; -import { Link } from "react-router-dom"; - + useMaterialTailwindController, + setUserRole, +} from "@/context"; +// 1. Kendi oluşturduğunuz apiClient'ı import edin +import apiClient from "../../api/axiosConfig.js" export function SignIn() { + const [, dispatch] = useMaterialTailwindController(); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const navigate = useNavigate(); + + const handleSignIn = async () => { + const loginData = { + username: username, + password: password, + }; + + try { + + const response = await apiClient.post("/Auth/Login", loginData); + + + const token = response.data; + console.log(token); + + if (token) { + // 3. Token'ı localStorage'a kaydet. Bu, interceptor'ın çalışması için kritik. + localStorage.setItem("authToken", token); + + + // İYİ BİR PRATİK: Token'ı aldıktan sonra, interceptor'ın bir sonraki + // sayfa yenilemesini beklemeden çalışması için anında ayarlayın. + apiClient.defaults.headers.common['Authorization'] = `Bearer ${token}`; + + console.log("Giriş Başarılı!"); + const decodedToken = jwtDecode(token); + const userRoleClaim = decodedToken.role || decodedToken["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"]; + const userRole = userRoleClaim ? userRoleClaim.toLowerCase() : null; + console.log("Kullanıcı Rolü:", userRole); + + if (userRole) { + setUserRole(dispatch, userRole); + localStorage.setItem("userRole", userRole); + } + + navigate("/dashboard/home"); + } else { + alert("Giriş başarılı ancak sunucudan geçerli bir token alınamadı."); + } + } catch (error) { + // 4. Geliştirilmiş hata yönetimi. + // axios, 4xx veya 5xx gibi başarısız statü kodlarında otomatik olarak hata fırlatır ve bu blok çalışır. + console.error("Giriş sırasında hata:", error); + + if (error.response) { + // Sunucu bir hata koduyla (401, 404, 500 vb.) yanıt verdi. + alert("Kullanıcı adı veya şifre hatalı!"); + } else if (error.request) { + // İstek yapıldı ancak sunucudan yanıt alınamadı (network hatası). + alert("Sunucuya bağlanılamadı. Ağ bağlantınızı kontrol edin."); + } else { + // İsteği hazırlarken bir hata oluştu. + alert("Beklenmedik bir hata oluştu."); + } + } + }; + return ( -
-
-
- Sign In - Enter your email and password to Sign In. -
-
-
- - Your email +
+ +
+ + LOGO - - - Password - -
- - I agree the  - - Terms and Conditions - +
+
+ + Kullanıcı Adı - } - containerProps={{ className: "-ml-2.5" }} - /> - - -
- - Subscribe me to newsletter - - } - containerProps={{ className: "-ml-2.5" }} - /> - - - Forgot Password - - -
-
- -
+
+ + Şifre + + setPassword(e.target.value)} + /> +
+
- - Not registered? - Create account - - - -
-
- -
- -
+ +
); } -export default SignIn; +export default SignIn; \ No newline at end of file diff --git a/src/pages/dashboard/home.jsx b/src/pages/dashboard/home.jsx index 2c700669..257d5582 100644 --- a/src/pages/dashboard/home.jsx +++ b/src/pages/dashboard/home.jsx @@ -1,258 +1,110 @@ -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, { useState, useEffect } from "react"; +import { Typography, Card, Spinner, Button, CardHeader, CardBody } from "@material-tailwind/react"; +import { ForwardIcon, InformationCircleIcon } from "@heroicons/react/24/solid"; +import apiClient from "../../api/axiosConfig.js"; +import { useMaterialTailwindController } from "@/context"; +import { VehicleQueueCard } from "@/widgets/layout/VehicleQueueCard"; export function Home() { - return ( -
-
- {statisticsCardsData.map(({ icon, title, footer, ...rest }) => ( - - {footer.value} -  {footer.label} - - } - /> - ))} -
-
- {statisticsChartsData.map((props) => ( - - -  {props.footer} - - } - /> - ))} -
-
- - -
- - Projects - - - - 30 done this month - -
- - - - - - - - Action - Another Action - Something else here - - -
- - - - - {["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" - }`; + const [controller] = useMaterialTailwindController(); + const { userRole } = controller; - return ( - - - - - - - ); - } - )} - -
- - {el} - -
-
- - - {name} - -
-
- {members.map(({ img, name }, key) => ( - - - - ))} - - - {budget} - - -
- { + try { + const response = await apiClient.get("/queues/all"); + setRoutesWithQueues(response.data); + } catch (err) { + setError("Sıra verileri yüklenirken bir hata oluştu. Lütfen sayfayı yenileyin."); + console.error(err); + } finally { + if (loading) setLoading(false); + } + }; + + useEffect(() => { + fetchAllQueues(); + const interval = setInterval(fetchAllQueues, 15000); + return () => clearInterval(interval); + }, []); + + const handleNextVehicle = async (routeId) => { + try { + await apiClient.post(`/admin/vehicles/${routeId}/move-first-to-end`); + await fetchAllQueues(); + } catch (error) { + console.error("Sıra ilerletilirken hata:", error); + alert(error.response?.data?.message || "İşlem başarısız oldu."); + } + }; + + if (loading) { + return
; + } + + if (error) { + return {error}; + } + + return ( +
+
+ {routesWithQueues.map(route => ( +
+ {/* DEĞİŞİKLİK BURADA: min-h-[75vh] değerini min-h-[85vh] olarak artırdık */} + + - {completion}% - - -
-
-
-
- - - - Orders Overview - - - - 24% this month - - - - {ordersOverviewData.map( - ({ icon, color, title, description }, key) => ( -
-
- {React.createElement(icon, { - className: `!w-5 !h-5 ${color}`, - })} -
-
- - {title} - - - {description} - -
-
- ) - )} -
-
-
-
- ); +
+ + {route.routeName} + + + {route.queuedVehicles.length} Araç + +
+ {userRole === 'admin' && ( +
+ +
+ )} + + + {route.queuedVehicles.length > 0 ? ( +
+ {route.queuedVehicles.map((vehicle, index) => ( + + ))} +
+ ) : ( +
+ + + Sırada Araç Yok + +
+ )} +
+ + + ))} + + + ); } -export default Home; +export default Home; \ No newline at end of file diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js index 7651895e..3b32e950 100644 --- a/src/pages/dashboard/index.js +++ b/src/pages/dashboard/index.js @@ -2,3 +2,9 @@ export * from "@/pages/dashboard/home"; export * from "@/pages/dashboard/profile"; export * from "@/pages/dashboard/tables"; export * from "@/pages/dashboard/notifications"; +export * from "@/pages/dashboard/queuemanagementpage.jsx"; + + + + + diff --git a/src/pages/dashboard/notifications.jsx b/src/pages/dashboard/notifications.jsx index f4be88b0..50e1f755 100644 --- a/src/pages/dashboard/notifications.jsx +++ b/src/pages/dashboard/notifications.jsx @@ -1,88 +1,151 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import { Typography, - Alert, Card, CardHeader, CardBody, + Button, } from "@material-tailwind/react"; -import { InformationCircleIcon } from "@heroicons/react/24/outline"; +import apiClient from "../../api/axiosConfig.js"; +import { AddRouteModal } from "@/widgets/layout/AddRouteModal"; +import { EditRouteModal } from "@/widgets/layout/EditRouteModal"; // 1. Edit modal'ı import et 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"]; + const [routes, setRoutes] = useState([]); + + // "Ekle" modalı için state'ler + const [addModalOpen, setAddModalOpen] = useState(false); + const handleOpenAddModal = () => setAddModalOpen(!addModalOpen); + + // 2. "Düzenle" modalı için yeni state'ler + const [editModalOpen, setEditModalOpen] = useState(false); + const [currentRoute, setCurrentRoute] = useState(null); // Düzenlenecek güzergahı tutar + + // Düzenleme modal'ını açan fonksiyon + const handleOpenEditModal = (route) => { + setCurrentRoute(route); + setEditModalOpen(true); + }; + + // Düzenleme modal'ını kapatan fonksiyon + const handleCloseEditModal = () => { + setEditModalOpen(false); + setCurrentRoute(null); + }; + + // Güzergah listesini API'den çeken ana fonksiyon + const fetchRoutes = async () => { + try { + const response = await apiClient.get("/admin/routes"); + setRoutes(response.data); + } catch (error) { + console.error("Güzergahları çekerken hata oluştu:", error); + } + }; + + useEffect(() => { + fetchRoutes(); + }, []); + + // Ekleme, silme veya güncelleme sonrası listeyi yeniden çekmek için + const handleDataChange = () => { + fetchRoutes(); + }; + + const handleDelete = async (routeId) => { + if (window.confirm("Bu güzergahı silmek istediğinizden emin misiniz?")) { + try { + await apiClient.delete(`/admin/routes/${routeId}`); + handleDataChange(); // Silme sonrası listeyi yenile + } catch (error) { + console.error("Güzergah silinirken hata oluştu:", error); + } + } + }; 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, - }))} + <> + + + {/* 3. Yeni düzenleme modalını sayfaya ekle */} + + +
+ + - A simple {color} alert with an example link. Give - it a click if you like. - - ))} - - -
+ + Güzergahlar + + + + + + + + {["Güzergah", "İşlem"].map((el) => ( + + ))} + + + + {routes.map((route) => ( + + + + + ))} + +
+ + {el} + +
+ + {route.routeName} + + + {/* 4. Düzenle butonunun onClick olayını güncelle */} + + +
+
+
+
+ ); } -export default Notifications; +export default Notifications; \ No newline at end of file diff --git a/src/pages/dashboard/profile.jsx b/src/pages/dashboard/profile.jsx index 0d9f0115..bc40433a 100644 --- a/src/pages/dashboard/profile.jsx +++ b/src/pages/dashboard/profile.jsx @@ -1,221 +1,122 @@ -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"; +import React, { useState, useEffect } from "react"; +import { Card, CardHeader, CardBody, Typography, Button } from "@material-tailwind/react"; +import apiClient from "../../api/axiosConfig.js"; +import { AddUserModal } from "@/widgets/layout/AddUserModal"; +import { EditUserModal } from "@/widgets/layout/EditUserModal"; export function Profile() { + const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + // Ekleme ve Düzenleme Modalları için state'ler + const [openAddModal, setOpenAddModal] = useState(false); + const [openEditModal, setOpenEditModal] = useState(false); + const [currentUser, setCurrentUser] = useState(null); + + const handleOpenAddModal = () => setOpenAddModal(true); + const handleCloseAddModal = () => setOpenAddModal(false); + + // Düzenleme modalını açan fonksiyon + const handleOpenEditModal = (user) => { + setCurrentUser(user); + setOpenEditModal(true); + }; + // Düzenleme modalını kapatan fonksiyon + const handleCloseEditModal = () => { + setCurrentUser(null); + setOpenEditModal(false); + }; + + const fetchUsers = async () => { + try { + setLoading(true); + const response = await apiClient.get("/Users"); + setUsers(response.data); + setError(null); + } catch (err) { + setError("Kullanıcı verileri yüklenirken bir hata oluştu."); + console.error(err); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchUsers(); + }, []); + + // Ekleme, silme veya güncelleme sonrası listeyi yeniden çek + const handleDataChange = () => { + fetchUsers(); + }; + + const handleDeleteUser = async (userId, userName) => { + if (window.confirm(`'${userName}' adlı kullanıcıyı silmek istediğinizden emin misiniz?`)) { + try { + await apiClient.delete(`/Users/${userId}`); + handleDataChange(); // Silme sonrası listeyi yenile + } catch (err) { + alert("Kullanıcı silinirken bir hata oluştu."); + console.error(err); + } + } + }; + + if (loading) return
Kullanıcılar Yükleniyor...
; + if (error) return
{error}
; + 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 - - } - /> + <> + + + +
    + + + Kullanıcılar + + + + + + + {["Tam Ad", "Plaka", "Telefon", "İşlem"].map((el) => ( + + ))} + + + + {users.map((user) => ( + + + + + + ))} - - - -
    - - Projects - - - Architects design houses - -
    - {projectsData.map( - ({ img, title, description, tag, route, members }) => ( - - - {title} - - - - {tag} - - - {title} - - - {description} - - - - - - -
    - {members.map(({ img, name }, key) => ( - - - - ))} -
    -
    -
    - ) - )} -
    -
    - - - + +
    + {el} +
    + {user.fullName} + + {user.licensePlate} + + {user.phoneNumber} + + {/* 3. Düzenle butonunun onClick olayı güncellendi */} + + {/* 4. Buton boyutu `xs`'den `sm`'ye çevrildi */} + +
    +
    +
    +
    + ); } -export default Profile; +export default Profile; \ No newline at end of file diff --git a/src/pages/dashboard/queuemanagementpage.jsx b/src/pages/dashboard/queuemanagementpage.jsx new file mode 100644 index 00000000..f6151c3e --- /dev/null +++ b/src/pages/dashboard/queuemanagementpage.jsx @@ -0,0 +1,187 @@ +import React, { useState, useEffect, useMemo } from "react"; +import { + Typography, Card, CardHeader, CardBody, Button, Select, Option, List, ListItem, +} from "@material-tailwind/react"; +import apiClient from "../../api/axiosConfig.js"; + +export function QueueManagementPage() { + // --- STATE TANIMLAMALARI --- + const [routes, setRoutes] = useState([]); // Tüm güzergahların listesi (sol panel) + const [allVehicles, setAllVehicles] = useState([]); // Tüm araçların listesi (dropdown için) + const [selectedRoute, setSelectedRoute] = useState(null); // Seçili olan güzergah + const [queuedVehicles, setQueuedVehicles] = useState([]); // Seçili güzergahın sırasındaki araçlar + const [vehicleToAdd, setVehicleToAdd] = useState(""); // Dropdown'dan seçilen araç ID'si + const [loadingQueue, setLoadingQueue] = useState(false); // Sıra yüklenirken gösterilecek spinner için + + // --- VERİ ÇEKME İŞLEMLERİ --- + + // 1. Bileşen ilk yüklendiğinde tüm güzergahları ve tüm araçları çek + useEffect(() => { + const fetchInitialData = async () => { + try { + const routesPromise = apiClient.get("/admin/routes"); + const vehiclesPromise = apiClient.get("/admin/vehicles"); + + const [routesResponse, vehiclesResponse] = await Promise.all([routesPromise, vehiclesPromise]); + + setRoutes(routesResponse.data); + setAllVehicles(vehiclesResponse.data); + } catch (error) { + console.error("Ana veriler çekilirken hata oluştu:", error); + } + }; + fetchInitialData(); + }, []); + + // 2. Bir güzergah seçildiğinde, o güzergahın sırasını API'den çek + useEffect(() => { + if (selectedRoute) { + setLoadingQueue(true); + const fetchQueue = async () => { + try { + const response = await apiClient.get(`/routes/${selectedRoute.id}/queue`); + setQueuedVehicles(response.data); + } catch (error) { + console.error(`Sıra çekilirken hata (Güzergah ID: ${selectedRoute.id}):`, error); + setQueuedVehicles([]); // Hata durumunda listeyi boşalt + } finally { + setLoadingQueue(false); + } + }; + fetchQueue(); + } + }, [selectedRoute]); // selectedRoute her değiştiğinde bu blok çalışır + + + + const handleAddVehicleToQueue = async () => { + if (!vehicleToAdd || !selectedRoute) { + alert("Lütfen bir araç seçin."); + return; + } + try { + await apiClient.post(`/routes/${selectedRoute.id}/queue`, { vehicleId: vehicleToAdd }); + // Başarılı ekleme sonrası sırayı anında güncelle (Refetch) + const response = await apiClient.get(`/routes/${selectedRoute.id}/queue`); + setQueuedVehicles(response.data); + setVehicleToAdd(""); // Dropdown'ı temizle + } catch (error) { + console.error("Sıraya araç eklenirken hata:", error); + alert(error.response?.data || "Araç sıraya eklenemedi."); + } + }; + + const handleRemoveVehicleFromQueue = async (vehicleId) => { + if (window.confirm("Bu aracı sıradan çıkarmak istediğinizden emin misiniz?")) { + try { + await apiClient.delete(`/routes/${selectedRoute.id}/queue/${vehicleId}`); + // Başarılı silme sonrası sırayı anında güncelle (Refetch) + const response = await apiClient.get(`/routes/${selectedRoute.id}/queue`); + setQueuedVehicles(response.data); + } catch (error) { + console.error("Araç sıradan çıkarılırken hata:", error); + alert("Araç sıradan çıkarılamadı."); + } + } + }; + + // KULLANICI DENEYİMİ İYİLEŞTİRMESİ: Mevcut sırada olan araçları, ekleme dropdown'ında gösterme. + const availableVehicles = useMemo(() => { + const queuedVehicleIds = new Set(queuedVehicles.map(v => v.id)); + return allVehicles.filter(v => !queuedVehicleIds.has(v.id)); + }, [allVehicles, queuedVehicles]); + + return ( +
    + + + + Güzergah Sıra Yönetimi + + + + {/* SOL PANEL: Güzergah Listesi */} +
    + Güzergah Seçin + + + {routes.map(route => ( + setSelectedRoute(route)} + selected={selectedRoute?.id === route.id} + > + {route.routeName} + + ))} + + +
    + + {/* SAĞ PANEL: Sıra ve Ekleme Formu */} +
    + {!selectedRoute ? ( +
    + Lütfen bir güzergah seçin +
    + ) : ( +
    + {/* Sıradaki Araçlar Tablosu */} + + Sıradaki Araçlar: {selectedRoute.routeName} + +
    + + + + {["Sıra", "Plaka", "Şoför", "İşlem"].map(el => ( + + ))} + + + + {loadingQueue ? ( + + ) : ( + queuedVehicles.map((vehicle, index) => ( + + + + + + + )) + )} + +
    + {el} +
    Yükleniyor...
    #{index + 1}{vehicle.licensePlate}{vehicle.userFullName} + +
    +
    + + {/* Sıraya Araç Ekleme Formu */} +
    +
    + +
    + +
    +
    + )} +
    +
    +
    +
    + ); +} + +export default QueueManagementPage; \ No newline at end of file diff --git a/src/pages/dashboard/tables.jsx b/src/pages/dashboard/tables.jsx index 3d453ed7..0b583910 100644 --- a/src/pages/dashboard/tables.jsx +++ b/src/pages/dashboard/tables.jsx @@ -1,221 +1,129 @@ -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"; +import React, { useState, useEffect } from "react"; +import { Card, CardHeader, CardBody, Typography, Button } from "@material-tailwind/react"; +import apiClient from "../../api/axiosConfig.js"; +import { AddVehicleModal } from "@/widgets/layout/AddVehicleModal"; +import { EditVehicleModal } from "@/widgets/layout/EditVehicleModal"; export function Tables() { + const [vehicles, setVehicles] = useState([]); + + // "Ekle" modalı için state'ler + const [addModalOpen, setAddModalOpen] = useState(false); + const handleOpenAddModal = () => setAddModalOpen(!addModalOpen); + + // "Düzenle" modalı için state'ler + const [editModalOpen, setEditModalOpen] = useState(false); + const [currentVehicle, setCurrentVehicle] = useState(null); // Düzenlenecek aracı tutar + + // Düzenleme modal'ını açan fonksiyon + const handleOpenEditModal = (vehicle) => { + setCurrentVehicle(vehicle); // Hangi aracın düzenleneceğini state'e ata + setEditModalOpen(true); // Modal'ı aç + }; + + // Düzenleme modal'ını kapatan fonksiyon + const handleCloseEditModal = () => { + setEditModalOpen(false); + setCurrentVehicle(null); // Modal kapanınca seçili aracı temizle + } + + // Araç listesini API'den çeken ana fonksiyon + const fetchVehicles = async () => { + try { + const response = await apiClient.get("/admin/vehicles"); + setVehicles(response.data); + } catch (error) { + console.error("Araçlar çekilirken bir hata oluştu:", error); + } + }; + + // Bileşen ilk yüklendiğinde araçları çek + useEffect(() => { + fetchVehicles(); + }, []); + + // Ekleme, silme veya güncelleme sonrası listeyi yeniden çekmek için + const handleDataChange = () => { + fetchVehicles(); + }; + + // Araç silme fonksiyonu + const handleDelete = async (id) => { + if (window.confirm("Bu aracı silmek istediğinizden emin misiniz?")) { + try { + await apiClient.delete(`/admin/vehicles/${id}`); + handleDataChange(); // Silme sonrası listeyi yenile + } catch (error) { + alert("Araç silinirken bir sorun oluştu."); + console.error("Araç silinirken hata:", error); + } + } + }; + 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" - }`; + +
    + + + Araçlar + + + +
    - - {el} - -
    + + + {["Plaka", "Şoför Adı", "Telefon", "İşlem"].map((el) => ( + + ))} + + + + {vehicles.map((vehicle, key) => { + const className = `py-3 px-5 ${key === vehicles.length - 1 ? "" : "border-b border-blue-gray-50"}`; return ( - - - - - - - + + + + + + ); - } - )} - -
    + {el} +
    -
    - - - {name} - -
    -
    - {members.map(({ img, name }, key) => ( - - - - ))} - - - {budget} - - -
    - - {completion}% - - -
    -
    - - - -
    + {vehicle.licensePlate} + + {vehicle.userFullName} + + {vehicle.phoneNumber || "-"} + + + +
    -
    -
    -
    + })} + + + + +
+ ); } -export default Tables; +export default Tables; \ No newline at end of file diff --git a/src/routes.jsx b/src/routes.jsx index 3a5a8da0..e076afca 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -5,9 +5,25 @@ import { InformationCircleIcon, ServerStackIcon, RectangleStackIcon, + TruckIcon, + MapIcon, + Squares2X2Icon, + QueueListIcon, // 1. YENİ: Sıra Yönetimi sayfası için gerekli ikonu import edelim } from "@heroicons/react/24/solid"; -import { Home, Profile, Tables, Notifications } from "@/pages/dashboard"; -import { SignIn, SignUp } from "@/pages/auth"; + +// 1. YENİ: Yeni sayfa bileşenimizi de diğerlerinin yanına import edelim +import { + Home, + Profile, + Tables, + Notifications, + QueueManagementPage +} from "@/pages/dashboard"; + + +import { SignIn } from "@/pages/auth"; + + const icon = { className: "w-5 h-5 text-inherit", @@ -18,28 +34,40 @@ export const routes = [ layout: "dashboard", pages: [ { - icon: , - name: "dashboard", + icon: , + name: "Araç Sıraları", path: "/home", element: , }, + // 2. YENİ: "Sıra Yönetimi" sayfasını menüye ekleyelim + // Bu, feature branch'inden gelen ana özelliktir. + { + icon: , + name: "Sıra Yönetimi", + path: "/queue-management", + element: , + roles: ['admin'], // Sadece adminler görebilir + }, { icon: , - name: "profile", + name: "Kullanıcılar", path: "/profile", element: , + roles: ['admin'], }, { - icon: , - name: "tables", + icon: , + name: "Araçlar", path: "/tables", element: , + roles: ['admin'], }, { - icon: , - name: "notifications", + icon: , + name: "Güzergahlar", path: "/notifications", element: , + roles: ['admin'], }, ], }, @@ -53,14 +81,8 @@ export const routes = [ path: "/sign-in", element: , }, - { - icon: , - name: "sign up", - path: "/sign-up", - element: , - }, ], }, ]; -export default routes; +export default routes; \ No newline at end of file diff --git a/src/widgets/layout/AddRouteModal.jsx b/src/widgets/layout/AddRouteModal.jsx new file mode 100644 index 00000000..18446dba --- /dev/null +++ b/src/widgets/layout/AddRouteModal.jsx @@ -0,0 +1,68 @@ +import React, { useState } from "react"; +import { + Button, + Dialog, + DialogHeader, + DialogBody, + DialogFooter, + Input, + Typography, +} from "@material-tailwind/react"; +import apiClient from "../../api/axiosConfig.js"; + +export function AddRouteModal({ open, handleOpen, onRouteAdded }) { + const [routeName, setRouteName] = useState(""); + const [error, setError] = useState(""); + + const clearForm = () => { + setRouteName(""); + setError(""); + }; + + const handleClose = () => { + clearForm(); + handleOpen(); + }; + + const handleSubmit = async () => { + if (!routeName.trim()) { + setError("Güzergah adı boş olamaz."); + return; + } + + try { + // CreateRouteDto'ya uygun şekilde { "routeName": "değer" } gönderiyoruz + const response = await apiClient.post("/admin/routes", { routeName }); + alert("Güzergah başarıyla eklendi!"); + onRouteAdded(response.data); // Ana listeyi anında güncellemek için + handleClose(); // Formu temizle ve modal'ı kapat + } catch (err) { + console.error("Güzergah eklenirken hata:", err); + // Backend'den gelen "Bu rota adı zaten mevcut." gibi hataları göster + setError(err.response?.data || "Bir hata oluştu."); + } + }; + + return ( + + Yeni Güzergah Ekle + + {error && {error}} + setRouteName(e.target.value)} + error={!!error} // Hata varsa input'u kırmızı yap + /> + + + + + + + ); +} \ No newline at end of file diff --git a/src/widgets/layout/AddUserModal.jsx b/src/widgets/layout/AddUserModal.jsx new file mode 100644 index 00000000..46c9286e --- /dev/null +++ b/src/widgets/layout/AddUserModal.jsx @@ -0,0 +1,81 @@ +import React, { useState } from "react"; +import { + Button, + Dialog, + DialogHeader, + DialogBody, + DialogFooter, + Input, + Typography, +} from "@material-tailwind/react"; +import apiClient from "../../api/axiosConfig.js"; + +export function AddUserModal({ open, handleOpen, onUserAdded }) { + const [formData, setFormData] = useState({ + fullName: "", + userName: "", + password: "", + confirmPassword: "", + }); + const [error, setError] = useState(""); + + const clearForm = () => { + setFormData({ fullName: "", userName: "", password: "", confirmPassword: "" }); + setError(""); + }; + + const handleClose = () => { + clearForm(); + handleOpen(); + }; + + const handleSubmit = async () => { + if (!formData.fullName || !formData.userName || !formData.password) { + setError("Tüm zorunlu alanları doldurunuz."); + return; + } + if (formData.password !== formData.confirmPassword) { + setError("Şifreler uyuşmuyor."); + return; + } + + try { + await apiClient.post("/Users", formData); + alert("Kullanıcı başarıyla eklendi!"); + + // Başarılı ekleme sonrası ana sayfadaki listeyi yenilemesi için sinyal gönderiyoruz. + onUserAdded(); + + handleClose(); + } catch (err) { + console.error("Kullanıcı eklenirken hata:", err); + setError(err.response?.data || "Bir hata oluştu."); + } + }; + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData(prev => ({ ...prev, [name]: value })); + }; + + return ( + + Yeni Kullanıcı Ekle + + {error && {error}} + + + + + + + + + + + ); +} \ No newline at end of file diff --git a/src/widgets/layout/AddVehicleModal.jsx b/src/widgets/layout/AddVehicleModal.jsx new file mode 100644 index 00000000..0f0d14ae --- /dev/null +++ b/src/widgets/layout/AddVehicleModal.jsx @@ -0,0 +1,93 @@ +import React, { useState, useEffect } from "react"; +import { + Button, + Dialog, + DialogHeader, + DialogBody, + DialogFooter, + Input, + Select, + Option, + Typography, +} from "@material-tailwind/react"; +import apiClient from "../../api/axiosConfig.js"; + +export function AddVehicleModal({ open, handleOpen, onVehicleAdded }) { + const [users, setUsers] = useState([]); + const [formData, setFormData] = useState({ + licensePlate: "", + driverName: "", + phoneNumber: "", + appUserId: "", + }); + const [error, setError] = useState(""); + + useEffect(() => { + if (open) { + // DEĞİŞİKLİK: Backend'de oluşturduğumuz yeni ve filtrelenmiş endpoint'i kullanıyoruz. + apiClient.get("/Users/without-vehicle") + .then(response => { + setUsers(response.data); + }) + .catch(err => { + console.error("Kullanıcılar çekilirken hata oluştu:", err); + setError("Atanabilir kullanıcı listesi yüklenemedi."); + }); + } + }, [open]); + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData(prev => ({ ...prev, [name]: value })); + }; + + const handleSelectChange = (value) => { + setFormData(prev => ({ ...prev, appUserId: value })); + }; + + const clearForm = () => { + setFormData({ licensePlate: "", driverName: "", phoneNumber: "", appUserId: "" }); + setError(""); + }; + + const handleClose = () => { + clearForm(); + handleOpen(); + } + + const handleSubmit = async () => { + if (!formData.licensePlate || !formData.appUserId) { + setError("Plaka ve Kullanıcı alanları zorunludur."); + return; + } + try { + await apiClient.post("/admin/vehicles", formData); + alert("Araç başarıyla eklendi!"); + onVehicleAdded(); + handleClose(); + } catch (err) { + setError(err.response?.data || "Bir hata oluştu."); + } + }; + + return ( + + Yeni Araç Ekle + + {error && {error}} + + + + + + + + + + + ); +} \ No newline at end of file diff --git a/src/widgets/layout/EditRouteModal.jsx b/src/widgets/layout/EditRouteModal.jsx new file mode 100644 index 00000000..67629bb0 --- /dev/null +++ b/src/widgets/layout/EditRouteModal.jsx @@ -0,0 +1,96 @@ +import React, { useState, useEffect } from "react"; +import { + Button, + Dialog, + DialogHeader, + DialogBody, + DialogFooter, + Input, + Typography, + Checkbox, +} from "@material-tailwind/react"; +import apiClient from "../../api/axiosConfig.js"; + +export function EditRouteModal({ open, handleOpen, routeToEdit, onRouteUpdated }) { + const [formData, setFormData] = useState({ + routeName: "", + isActive: true, + }); + const [error, setError] = useState(""); + + // Modal'a düzenlenecek güzergah bilgisi geldiğinde formu doldur + useEffect(() => { + if (routeToEdit) { + setFormData({ + routeName: routeToEdit.routeName, + isActive: routeToEdit.isActive, + }); + setError(""); + } + }, [routeToEdit]); + + const handleChange = (e) => { + const { name, value, type, checked } = e.target; + setFormData(prev => ({ + ...prev, + [name]: type === "checkbox" ? checked : value, + })); + }; + + const clearForm = () => { + setFormData({ routeName: "", isActive: true }); + setError(""); + }; + + const handleClose = () => { + clearForm(); + handleOpen(); + }; + + const handleSubmit = async () => { + if (!formData.routeName.trim()) { + setError("Güzergah adı boş olamaz."); + return; + } + + try { + // PUT isteği ile güzergahı güncelle + await apiClient.put(`/admin/routes/${routeToEdit.id}`, formData); + alert("Güzergah başarıyla güncellendi!"); + onRouteUpdated(); // Ana listeyi yenilemesi için sinyal gönder + handleClose(); + } catch (err) { + console.error("Güzergah güncellenirken hata:", err); + setError(err.response?.data || "Bir hata oluştu."); + } + }; + + return ( + + Güzergahı Düzenle + + {error && {error}} + + + + + + + + + ); +} \ No newline at end of file diff --git a/src/widgets/layout/EditUserModal.jsx b/src/widgets/layout/EditUserModal.jsx new file mode 100644 index 00000000..0f377bc4 --- /dev/null +++ b/src/widgets/layout/EditUserModal.jsx @@ -0,0 +1,115 @@ +import React, { useState, useEffect } from "react"; +import { + Button, Dialog, DialogHeader, DialogBody, DialogFooter, Input, Typography, +} from "@material-tailwind/react"; +import apiClient from "../../api/axiosConfig.js"; + +export function EditUserModal({ open, handleOpen, userToEdit, onUserUpdated }) { + const [formData, setFormData] = useState({ + fullName: "", + userName: "", + password: "", + confirmPassword: "", + }); + const [error, setError] = useState(""); + + + useEffect(() => { + if (userToEdit) { + setFormData({ + fullName: userToEdit.fullName || "", + userName: userToEdit.userName || "", + password: "", + confirmPassword: "", + }); + setError(""); + } + }, [userToEdit]); + + const handleChange = (e) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleClose = () => { + setError(""); + handleOpen(); + }; + + const handleSubmit = async () => { + + if (formData.password && formData.password !== formData.confirmPassword) { + setError("Girilen şifreler uyuşmuyor."); + return; + } + + + const payload = { + fullName: formData.fullName, + userName: formData.userName, + }; + if (formData.password) { + payload.password = formData.password; + payload.confirmPassword = formData.confirmPassword; + } + + try { + await apiClient.put(`/Users/${userToEdit.id}`, payload); + alert("Kullanıcı başarıyla güncellendi!"); + onUserUpdated(); + handleClose(); + } catch (err) { + console.error("Kullanıcı güncellenirken hata:", err); + + setError(err.response?.data?.title || err.response?.data || "Bir hata oluştu."); + } + }; + + return ( + + Kullanıcıyı Düzenle: {userToEdit?.fullName} + + {error && {error}} + + +
+ + Şifreyi Değiştirmek İstemiyorsanız Boş Bırakın + + + +
+ + + + +
+ ); +} \ No newline at end of file diff --git a/src/widgets/layout/EditVehicleModal.jsx b/src/widgets/layout/EditVehicleModal.jsx new file mode 100644 index 00000000..12b14a96 --- /dev/null +++ b/src/widgets/layout/EditVehicleModal.jsx @@ -0,0 +1,75 @@ +import React, { useState, useEffect } from "react"; +import { + Button, Dialog, DialogHeader, DialogBody, DialogFooter, Input, Checkbox, Typography +} from "@material-tailwind/react"; +import apiClient from "../../api/axiosConfig.js"; + +export function EditVehicleModal({ open, handleOpen, onVehicleUpdated, vehicleToEdit }) { + const [formData, setFormData] = useState({ + licensePlate: "", + driverName: "", + phoneNumber: "", + isActive: true, + }); + const [error, setError] = useState(""); + + // Bu useEffect, modal'a düzenlenecek araç bilgisi geldiğinde formu doldurur. + useEffect(() => { + if (vehicleToEdit) { + setFormData({ + licensePlate: vehicleToEdit.licensePlate || "", + driverName: vehicleToEdit.driverName || "", + phoneNumber: vehicleToEdit.phoneNumber || "", + isActive: vehicleToEdit.isActive, + }); + } else { + + setFormData({ licensePlate: "", driverName: "", phoneNumber: "", isActive: true }); + setError(""); + } + }, [vehicleToEdit]); + + const handleChange = (e) => { + const { name, value, type, checked } = e.target; + setFormData(prev => ({ + ...prev, + [name]: type === 'checkbox' ? checked : value + })); + }; + + const handleSubmit = async () => { + if (!formData.licensePlate) { + setError("Plaka alanı zorunludur."); + return; + } + if (!vehicleToEdit) return; + + try { + // Backend'deki PUT endpoint'ine istek gönderiyoruz. + await apiClient.put(`/admin/vehicles/${vehicleToEdit.id}`, formData); + alert("Araç başarıyla güncellendi!"); + onVehicleUpdated(); + handleOpen(); + } catch (err) { + console.error("Araç güncellenirken hata:", err); + setError(err.response?.data || "Güncelleme sırasında bir hata oluştu."); + } + }; + + return ( + + Araç Düzenle + + {error && {error}} + + + + + + + + + + + ); +} \ No newline at end of file diff --git a/src/widgets/layout/VehicleQueueCard.jsx b/src/widgets/layout/VehicleQueueCard.jsx new file mode 100644 index 00000000..396d996a --- /dev/null +++ b/src/widgets/layout/VehicleQueueCard.jsx @@ -0,0 +1,29 @@ +import React from "react"; +import { Typography } from "@material-tailwind/react"; + +export function VehicleQueueCard({ vehicle, index }) { + const isFirstThree = index < 3; + const cardBgColor = isFirstThree ? "bg-green-100" : "bg-blue-gray-50/70"; + const textColor = isFirstThree ? "text-green-900" : "text-blue-gray-700"; + const borderColor = isFirstThree ? "border-green-300" : "border-transparent"; + + return ( +
+
+ + #{index + 1} + +
+ + {vehicle.licensePlate} + + + {vehicle.userFullName} + +
+
+
+ ); +} + +export default VehicleQueueCard; \ No newline at end of file diff --git a/src/widgets/layout/dashboard-navbar.jsx b/src/widgets/layout/dashboard-navbar.jsx index d91e23f7..00eaa89b 100644 --- a/src/widgets/layout/dashboard-navbar.jsx +++ b/src/widgets/layout/dashboard-navbar.jsx @@ -1,36 +1,23 @@ -import { useLocation, Link } from "react-router-dom"; -import { - Navbar, - Typography, - Button, - IconButton, - Breadcrumbs, - Input, - Menu, - MenuHandler, - MenuList, - MenuItem, - Avatar, -} from "@material-tailwind/react"; -import { - UserCircleIcon, - Cog6ToothIcon, - BellIcon, - ClockIcon, - CreditCardIcon, - Bars3Icon, -} from "@heroicons/react/24/solid"; -import { - useMaterialTailwindController, - setOpenConfigurator, - setOpenSidenav, -} from "@/context"; +import { ArrowRightOnRectangleIcon, Bars3Icon } from "@heroicons/react/24/solid"; +import { IconButton, Input, Navbar, Typography, Breadcrumbs } from "@material-tailwind/react"; +import { useMaterialTailwindController, setOpenSidenav } from "@/context"; +import { useLocation, Link, useNavigate } from "react-router-dom"; // 🔹 useNavigate eklendi export function DashboardNavbar() { const [controller, dispatch] = useMaterialTailwindController(); const { fixedNavbar, openSidenav } = controller; const { pathname } = useLocation(); const [layout, page] = pathname.split("/").filter((el) => el !== ""); + + + const navigate = useNavigate(); + +const handleLogout = () => { + localStorage.removeItem("authToken"); // Token sil + localStorage.removeItem("userRole"); // varsa rol bilgisini de sil + navigate("/auth/sign-in"); // Login ekranına yönlendir +}; + return ( -
+
+ {/* Breadcrumbs ve sayfa başlığı */}
- {layout} + {layout || "Home"} - - {page} + + {page || "Dashboard"} - - {page} + + {page || "Dashboard"}
-
+ + {/* Arama ve ikonlar */} +
- +
+ + {/* Sidenav toggle */} - - - - - - - - - - - - - - - -
- - New message from Laur - - - 13 minutes ago - -
-
- - -
- - New album by Travis Scott - - - 1 day ago - -
-
- -
- -
-
- - Payment successfully completed - - - 2 days ago - -
-
-
-
+ + {/* Çıkış butonu */} setOpenConfigurator(dispatch, true)} + color="red" + onClick={handleLogout} // 🔹 logout çağrıldı + className="rounded-full hover:bg-red-100 transition-colors" > - +
); } - -DashboardNavbar.displayName = "/src/widgets/layout/dashboard-navbar.jsx"; - -export default DashboardNavbar; diff --git a/src/widgets/layout/footer.jsx b/src/widgets/layout/footer.jsx index 1ea98e53..f2c59643 100644 --- a/src/widgets/layout/footer.jsx +++ b/src/widgets/layout/footer.jsx @@ -8,33 +8,7 @@ export function Footer({ brandName, brandLink, routes }) { return (
- - © {year}, made with{" "} - by{" "} - - {brandName} - {" "} - for a better web. - -
    - {routes.map(({ name, path }) => ( -
  • - - {name} - -
  • - ))} -
+
); diff --git a/src/widgets/layout/sidenav.jsx b/src/widgets/layout/sidenav.jsx index cc7e6ffe..e389bbdb 100644 --- a/src/widgets/layout/sidenav.jsx +++ b/src/widgets/layout/sidenav.jsx @@ -11,7 +11,8 @@ import { useMaterialTailwindController, setOpenSidenav } from "@/context"; export function Sidenav({ brandImg, brandName, routes }) { const [controller, dispatch] = useMaterialTailwindController(); - const { sidenavColor, sidenavType, openSidenav } = controller; + const { sidenavColor, sidenavType, openSidenav, userRole } = controller; + const sidenavTypes = { dark: "bg-gradient-to-br from-gray-800 to-gray-900", white: "bg-white shadow-sm", @@ -19,93 +20,74 @@ export function Sidenav({ brandImg, brandName, routes }) { }; return ( - + + +
+
+ {routes + .filter((route) => route.layout === "dashboard") // Sadece dashboard layout'unu al + .map(({ layout, pages }, key) => ( +
    + {pages + // BU SATIR SAYESİNDE OTOMATİK FİLTRELEME YAPILIYOR + .filter((page) => !page.roles || page.roles.includes(userRole)) + .map(({ icon, name, path }) => ( +
  • + + {({ isActive }) => ( + + )} + +
  • + ))} +
+ ))} +
+ ); } -Sidenav.defaultProps = { - brandImg: "/img/logo-ct.png", - brandName: "Material Tailwind React", -}; - -Sidenav.propTypes = { - brandImg: PropTypes.string, - brandName: PropTypes.string, - routes: PropTypes.arrayOf(PropTypes.object).isRequired, -}; -Sidenav.displayName = "/src/widgets/layout/sidnave.jsx"; -export default Sidenav; +export default Sidenav; \ No newline at end of file