diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..e0e55dab --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,26 @@ +name: Frontend CI/CD + +on: + push: + branches: [ "main", "master" ] + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - name: Kodları Çek + uses: actions/checkout@v3 + + - name: Docker Hub'a Giriş Yap + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Paketle ve Gönder (Frontend) + uses: docker/build-push-action@v4 + with: + context: . + file: Dockerfile + push: true + tags: batuhanxbayram1/koop-react:latest \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..5d6aca66 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +# 1. Aşama: Build (İnşaat) +FROM node:18-alpine AS build +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +RUN npm run build + +# 2. Aşama: Production (Sunum) +FROM nginx:stable-alpine +# Build klasörünü Nginx'in yayın klasörüne kopyala +# NOT: Eğer Vite kullanıyorsan '/app/build' yerine '/app/dist' yazmalısın! +COPY --from=build /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md deleted file mode 100644 index 9eeb114f..00000000 --- a/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,13 +0,0 @@ - - - diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 917de35a..00000000 --- a/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -MIT License - -Copyright (c) 2023 Creative Tim - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -Footer diff --git a/README.md b/README.md index 2898fcc0..ffb8a682 100644 --- a/README.md +++ b/README.md @@ -1,187 +1,4 @@ -# [Material Tailwind Dashboard React](http://demos.creative-tim.com/material-tailwind-dashboard-react/#/?ref=readme-mtdr) [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social&logo=twitter)](https://twitter.com/intent/tweet?url=https://www.creative-tim.com/product/material-tailwind-dashboard-react&text=Check%20Material%20Tailwind%20Dashboard%20React%20made%20by%20@CreativeTim%20#webdesign%20#kit%20#materialdesign%20#react%20#materialtailwind%20#tailwindcss%20https://www.creative-tim.com/product/material-tailwind-dashboard-react) - -![version](https://img.shields.io/badge/version-2.1.0-blue.svg) [![GitHub issues open](https://img.shields.io/github/issues/creativetimofficial/material-tailwind-dashboard-react.svg)](https://github.com/creativetimofficial/material-tailwind-dashboard-react/issues?q=is%3Aopen+is%3Aissue) [![GitHub issues closed](https://img.shields.io/github/issues-closed-raw/creativetimofficial/material-tailwind-dashboard-react.svg)](https://github.com/creativetimofficial/material-tailwind-dashboard-react/issues?q=is%3Aissue+is%3Aclosed) - -![Image](https://s3.amazonaws.com/creativetim_bucket/products/488/original/material-tailwind-dashboard-react.jpg) - -Material Tailwind Dashboard React is our newest free Material Tailwind Admin Template based on Tailwind CSS and React. If you’re a developer looking to create an admin dashboard that is developer-friendly, rich with features, and highly customisable, here is your match. Our innovative Material Tailwind, Tailwind CSS & React dashboard comes with a beautiful design inspired by Google's Material Design and it will help you create stunning websites & web apps to delight your clients. - -**Fully Coded Elements** - -Material Tailwind Dashboard React is built with over 40 frontend individual elements coming from @material-tailwind/react, like buttons, inputs, navbars, nav tabs, cards, or alerts, giving you the freedom of choosing and combining. All components can take variations in color, which you can easily modify using props and tailwind css classnames. You will save a lot of time going from prototyping to full-functional code because all elements are implemented. - -This free Material Tailwind, Tailwind CSS & React Dashboard is coming with prebuilt design blocks, so the development process is seamless, switching from our pages to the real website is very easy to be done. - -View [all components here](https://www.material-tailwind.com/docs/react/button). - -**Documentation built by Developers** - -Each element is well presented in very complex documentation. - -You can read more about the [documentation here](https://www.material-tailwind.com/docs/react/installation). - -**Example Pages** - -If you want to get inspiration or just show something directly to your clients, you can jump-start your development with our pre-built example pages. You will be able to quickly set up the basic structure for your web project. - -View [example pages here](https://demos.creative-tim.com/material-tailwind-dashboard-react/#/dashboard/home). - -**HELPFUL LINKS** - -- View [Github Repository](https://github.com/creativetimofficial/material-tailwind-dashboard-react) -- Check [FAQ Page](https://www.creative-tim.com/faq) - -#### Special thanks - -During the development of this dashboard, we have used many existing resources from awesome developers. We want to thank them for providing their tools open source: - -- [Material Tailwind](https://material-tailwind.com/) - Material Tailwind is an easy to use components library for Tailwind CSS and Material Design. -- [Hero Icons](https://heroicons.com/) - Beautiful hand-crafted SVG icons. -- [Apex Charts](https://apexcharts.com/) - Modern & Interactive open-source Charts. -- [Nepcha Analytics](https://nepcha.com?ref=readme) for the analytics tool. Nepcha is already integrated with Material Tailwind Dashboard React. You can use it to gain insights into your sources of traffic. - -Let us know your thoughts below. And good luck with development! - -## Table of Contents - -- [Versions](#versions) -- [Demo](#demo) -- [Quick Start](#quick-start) -- [Deploy](#deploy) -- [Documentation](#documentation) -- [File Structure](#file-structure) -- [Browser Support](#browser-support) -- [Resources](#resources) -- [Reporting Issues](#reporting-issues) -- [Technical Support or Questions](#technical-support-or-questions) -- [Licensing](#licensing) -- [Useful Links](#useful-links) - -## Versions - -[](https://www.creative-tim.com/product/material-tailwind-dashboard-react?ref=readme-mtdr) - -| React | -| ----- | - -| [![Material Tailwind Dashboard React](https://s3.amazonaws.com/creativetim_bucket/products/488/thumb/material-tailwind-dashboard-react.jpg)](http://demos.creative-tim.com/material-tailwind-dashboard-react/#/?ref=readme-mtdr) - -## Demo - -- [Dashboard page](https://demos.creative-tim.com/material-tailwind-dashboard-react/#/dashboard/home?ref=readme-mtdr) -- [Profile page](https://demos.creative-tim.com/material-tailwind-dashboard-react/#/dashboard/profile?ref=readme-mtdr) -- [Tables page](https://demos.creative-tim.com/material-tailwind-dashboard-react/#/dashboard/tables?ref=readme-mtdr) -- [Notifications page](https://demos.creative-tim.com/material-tailwind-dashboard-react/#/dashboard/notifications?ref=readme-mtdr) -- [Sign in page](https://demos.creative-tim.com/material-tailwind-dashboard-react/#/auth/sign-in?ref=readme-mtdr) -- [Sign up page](https://demos.creative-tim.com/material-tailwind-dashboard-react/#/auth/sign-up?ref=readme-mtdr) - -[View More](https://demos.creative-tim.com/material-tailwind-dashboard-react/#/?ref=readme-mtdr). - -## Quick start - -Quick start options: - -- Download from [Creative Tim](https://www.creative-tim.com/product/material-tailwind-dashboard-react?ref=readme-mtdr). - -## Deploy - -:rocket: You can deploy your own version of the template to Genezio with one click: - -[![Deploy to Genezio](https://raw.githubusercontent.com/Genez-io/graphics/main/svg/deploy-button.svg)](https://app.genez.io/start/deploy?repository=https://github.com/creativetimofficial/material-tailwind-dashboard-react&utm_source=github&utm_medium=referral&utm_campaign=github-creativetim&utm_term=deploy-project&utm_content=button-head) - -## Terminal Commands - -1. Download and Install NodeJs LTS version from [NodeJs Official Page](https://nodejs.org/en/download/). -2. Navigate to the root ./ directory of the product and run `npm install` or `yarn install` or `pnpm install` to install our local dependencies. - -## Documentation - -The documentation for the Material Tailwind Dashboard React is hosted at our [website](https://material-tailwind.com/?ref=readme-mtdr). - -### What's included - -Within the download you'll find the following directories and files: - -``` -material-tailwind-dashboard-react - ├── public - │   ├── css - │   └── img - ├── src - │   ├── configs - │   ├── context - │   ├── data - │   ├── layouts - │   ├── pages - │   ├── widgets - │   ├── App.jsx - │   ├── main.jsx - │   └── routes.jsx - ├── .gitignore - ├── CHANGELOG.md - ├── index.html - ├── ISSUE_TEMPLATE.md - ├── jsconfig.json - ├── LICENSE - ├── package.json - ├── postcsss.config.cjs - ├── prettier.config.cjs - ├── README.md - ├── tailwind.config.cjs - └── vite.config.js -``` - -## Browser Support - -At present, we officially aim to support the last two versions of the following browsers: - - - -## Resources - -- [Live Preview](https://demos.creative-tim.com/material-tailwind-dashboard-react/#/dashboard/home?ref=readme-mtdr) -- [Download Page](https://www.creative-tim.com/product/material-tailwind-dashboard-react?ref=readme-mtdr) -- Documentation is [here](https://material-tailwind.com/?ref=readme-mtdr) -- [License Agreement](https://www.creative-tim.com/license?ref=readme-mtdr) -- [Support](https://www.creative-tim.com/contact-us?ref=readme-mtdr) -- Issues: [Github Issues Page](https://github.com/creativetimofficial/material-tailwind-dashboard-react/issues) -- [Nepcha Analytics](https://nepcha.com?ref=readme) - Analytics tool for your website - -## Reporting Issues - -We use GitHub Issues as the official bug tracker for the Material Tailwind Dashboard React. Here are some advices for our users that want to report an issue: - -1. Make sure that you are using the latest version of the Material Tailwind Dashboard React. Check the CHANGELOG from your dashboard on our [website](https://www.creative-tim.com/product/material-tailwind-dashboard-react?ref=readme-mtdr). -2. Providing us reproducible steps for the issue will shorten the time it takes for it to be fixed. -3. Some issues may be browser specific, so specifying in what browser you encountered the issue might help. - -## Technical Support or Questions - -If you have questions or need help integrating the product please [contact us](https://www.creative-tim.com/contact-us?ref=readme-mtdr) instead of opening an issue. - -## Licensing - -- Copyright 2023 [Creative Tim](https://www.creative-tim.com?ref=readme-mtdr) -- Creative Tim [license](https://www.creative-tim.com/license?ref=readme-mtdr) - -## Useful Links - -- [More products](https://www.creative-tim.com/templates?ref=readme-mtdr) from Creative Tim - -- [Tutorials](https://www.youtube.com/channel/UCVyTG4sCw-rOvB9oHkzZD1w) - -- [Freebies](https://www.creative-tim.com/bootstrap-themes/free?ref=readme-mtdr) from Creative Tim - -- [Affiliate Program](https://www.creative-tim.com/affiliates/new?ref=readme-mtdr) (earn money) - -##### Social Media - -Twitter: - -Facebook: - -Dribbble: - -Google+: - -Instagram: +## Skills & Technologies +- **Backend:** .NET Core 8, C#, Entity Framework Core, SignalR, SQL Server +- **Frontend:** React, Vite, Material Tailwind, Axios +- **DevOps:** Docker, Nginx, GitHub Actions diff --git a/genezio.yaml b/genezio.yaml deleted file mode 100644 index f8266348..00000000 --- a/genezio.yaml +++ /dev/null @@ -1,16 +0,0 @@ -name: material-tailwind-dashboard-react -region: us-east-1 -frontend: - # Specifies the path of your code. - path: . - # Specifies the folder where the build is located. - # This is the folder that will be deployed. - publish: dist - # Scripts will run in the specified `path` folder. - scripts: - # The command to build your frontend project. This is custom to your project. - # It must to populate the specified `publish` folder with a `index.html` file. - deploy: - - npm install --legacy-peer-deps - - npm run build -yamlVersion: 2 \ No newline at end of file diff --git a/index.html b/index.html index 844e5941..4bb763d2 100644 --- a/index.html +++ b/index.html @@ -1,40 +1,33 @@ - - - - - - - Material Tailwind Dashboard React | By Creative Tim - - - - - - - - - -
- - - + + + + + + + + S.S. 75 No'lu Yarımca Motorlu Taşıyıcılar Kooperatifi + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 00000000..8756a98d --- /dev/null +++ b/nginx.conf @@ -0,0 +1,51 @@ +server { + listen 80; + server_name 75ymkt.com www.75ymkt.com; + + root /usr/share/nginx/html; + index index.html; + + + location / { + try_files $uri $uri/ /index.html; + } + + + location /api { + # Backend sunucusunun adresi (Senin IP ve Portun) + proxy_pass http://72.62.114.221:5000; + + # IP adresi ve header bilgilerini backend'e doğru iletmek için gerekli ayarlar + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection keep-alive; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # 3. SIGNALR (Canlı Veri Bağlantısı için WebSocket Ayarı) + location /hubs { + proxy_pass http://72.62.114.221:5000; + + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } + + + location ~* \.(jpg|jpeg|png|gif|ico|css|js|webp|svg)$ { + expires 6M; + access_log off; + add_header Cache-Control "public"; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} \ No newline at end of file diff --git a/package.json b/package.json index 44f14fa6..9d4a7f31 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,30 @@ { - "name": "material-tailwind-dashboard-react", + "name":"75-nolu-kooperatif-takip-sistemi", "private": true, - "version": "2.1.0", + "description": "S.S. 75 No'lu Yarımca Motorlu Taşıyıcılar Kooperatifi için araç takip sistemi.", + "author": "Batuhan Bayram", + "version": "1.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --host", "build": "vite build", "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", + "@microsoft/signalr": "^10.0.0", "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", "react-dom": "18.2.0", - "react-router-dom": "6.17.0" + "react-router-dom": "6.17.0", + "react-toastify": "^11.0.5" }, "devDependencies": { "@types/react": "18.2.31", @@ -27,6 +35,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-image.png b/public/img/background-image.png deleted file mode 100644 index 358c89af..00000000 Binary files a/public/img/background-image.png and /dev/null differ 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/background.webp b/public/img/background.webp new file mode 100644 index 00000000..fbe7fb1f Binary files /dev/null and b/public/img/background.webp differ diff --git a/public/img/bruce-mars.jpeg b/public/img/bruce-mars.jpeg deleted file mode 100644 index 25154c2d..00000000 Binary files a/public/img/bruce-mars.jpeg and /dev/null differ diff --git a/public/img/devto.svg b/public/img/devto.svg deleted file mode 100644 index 51fa2cef..00000000 --- a/public/img/devto.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - devto - - - - - - \ No newline at end of file diff --git a/public/img/favicon.png b/public/img/favicon.png deleted file mode 100644 index 08664e1f..00000000 Binary files a/public/img/favicon.png and /dev/null differ diff --git a/public/img/github.svg b/public/img/github.svg deleted file mode 100644 index bfa7a11e..00000000 --- a/public/img/github.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - github - - - - - - - \ No newline at end of file diff --git a/public/img/home-decor-1.jpeg b/public/img/home-decor-1.jpeg deleted file mode 100644 index 73ec9b56..00000000 Binary files a/public/img/home-decor-1.jpeg and /dev/null differ diff --git a/public/img/home-decor-2.jpeg b/public/img/home-decor-2.jpeg deleted file mode 100644 index ea5f1266..00000000 Binary files a/public/img/home-decor-2.jpeg and /dev/null differ diff --git a/public/img/home-decor-3.jpeg b/public/img/home-decor-3.jpeg deleted file mode 100644 index 3e3539a5..00000000 Binary files a/public/img/home-decor-3.jpeg and /dev/null differ diff --git a/public/img/home-decor-4.jpeg b/public/img/home-decor-4.jpeg deleted file mode 100644 index 06a4e183..00000000 Binary files a/public/img/home-decor-4.jpeg and /dev/null differ diff --git a/public/img/klogo.png b/public/img/klogo.png new file mode 100644 index 00000000..e443fd5a Binary files /dev/null and b/public/img/klogo.png differ diff --git a/public/img/klogo.webp b/public/img/klogo.webp new file mode 100644 index 00000000..a11256cd Binary files /dev/null and b/public/img/klogo.webp differ diff --git a/public/img/logo-asana.svg b/public/img/logo-asana.svg deleted file mode 100644 index c37d9bf9..00000000 --- a/public/img/logo-asana.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - Logos - - - - - - - - - - - \ No newline at end of file diff --git a/public/img/logo-atlassian.svg b/public/img/logo-atlassian.svg deleted file mode 100644 index 6df68253..00000000 --- a/public/img/logo-atlassian.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - Logos - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/img/logo-ct.png b/public/img/logo-ct.png deleted file mode 100644 index 477783c2..00000000 Binary files a/public/img/logo-ct.png and /dev/null differ diff --git a/public/img/logo-invision.svg b/public/img/logo-invision.svg deleted file mode 100644 index 44e72b61..00000000 --- a/public/img/logo-invision.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - Logos - - - - - - - \ No newline at end of file diff --git a/public/img/logo-jira.svg b/public/img/logo-jira.svg deleted file mode 100644 index dac3ddbb..00000000 --- a/public/img/logo-jira.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - Logos - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/img/logo-slack.svg b/public/img/logo-slack.svg deleted file mode 100644 index 6b8eba67..00000000 --- a/public/img/logo-slack.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - Logos - - - - \ No newline at end of file diff --git a/public/img/logo-spotify.svg b/public/img/logo-spotify.svg deleted file mode 100644 index 1c930b3b..00000000 --- a/public/img/logo-spotify.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - Logos - - - - - - \ No newline at end of file diff --git a/public/img/logo-xd.svg b/public/img/logo-xd.svg deleted file mode 100644 index 5cd1bd4a..00000000 --- a/public/img/logo-xd.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - Logos - - - - - - - - \ No newline at end of file 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/public/img/team-1.jpeg b/public/img/team-1.jpeg deleted file mode 100644 index 8f95305e..00000000 Binary files a/public/img/team-1.jpeg and /dev/null differ diff --git a/public/img/team-2.jpeg b/public/img/team-2.jpeg deleted file mode 100644 index 976c1150..00000000 Binary files a/public/img/team-2.jpeg and /dev/null differ diff --git a/public/img/team-3.jpeg b/public/img/team-3.jpeg deleted file mode 100644 index 97ddc6de..00000000 Binary files a/public/img/team-3.jpeg and /dev/null differ diff --git a/public/img/team-4.jpeg b/public/img/team-4.jpeg deleted file mode 100644 index abfa43fa..00000000 Binary files a/public/img/team-4.jpeg and /dev/null differ diff --git a/public/img/twitter-logo.svg b/public/img/twitter-logo.svg deleted file mode 100644 index ded743c9..00000000 --- a/public/img/twitter-logo.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/App.jsx b/src/App.jsx index 87826600..3dbbdc1e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,12 +1,29 @@ import { Routes, Route, Navigate } from "react-router-dom"; import { Dashboard, Auth } from "@/layouts"; +import PrivateRoute from "../src/component/PrivateRoute"; +import TVQueuePage from "./pages/dashboard/TVQueuePage"; function App() { return ( + + + + - } /> + + + + } + /> + + } /> + {/* 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..c09fd0d9 --- /dev/null +++ b/src/api/axiosConfig.js @@ -0,0 +1,33 @@ +import axios from "axios"; + + + +const apiClient = axios.create({ + + baseURL: "https://75ymkt.com/api", + headers: { + "Content-Type": "application/json", + }, +}); + + +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/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/layouts/dashboard.jsx b/src/layouts/dashboard.jsx index 888a627a..22a9b1f1 100644 --- a/src/layouts/dashboard.jsx +++ b/src/layouts/dashboard.jsx @@ -1,14 +1,11 @@ -import { Routes, Route } from "react-router-dom"; -import { Cog6ToothIcon } from "@heroicons/react/24/solid"; -import { IconButton } from "@material-tailwind/react"; +import { Routes, Route, Navigate } from "react-router-dom"; // 👈 Navigate eklendi import { Sidenav, DashboardNavbar, - Configurator, Footer, } from "@/widgets/layout"; import routes from "@/routes"; -import { useMaterialTailwindController, setOpenConfigurator } from "@/context"; +import { useMaterialTailwindController } from "@/context"; export function Dashboard() { const [controller, dispatch] = useMaterialTailwindController(); @@ -24,25 +21,23 @@ export function Dashboard() { />
- - setOpenConfigurator(dispatch, true)} - > - - + {routes.map( ({ layout, pages }) => - layout === "dashboard" && + layout === "anasayfa" && pages.map(({ path, element }) => ( - + )) )} + + {/* 🚀 BURASI SİHİRLİ DOKUNUŞ: + Kullanıcı panel ana dizinine geldiğinde veya + bulunmayan bir sayfaya gittiğinde 'arac-siralar' açılsın. */} + } /> + } /> +
@@ -53,4 +48,4 @@ export function Dashboard() { Dashboard.displayName = "/src/layout/dashboard.jsx"; -export default Dashboard; +export default Dashboard; \ No newline at end of file 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..e5673d93 100644 --- a/src/pages/auth/sign-in.jsx +++ b/src/pages/auth/sign-in.jsx @@ -1,126 +1,106 @@ +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"; +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) { + localStorage.setItem("authToken", token); + 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("/anasayfa/arac-siralari"); + } else { + alert("Giriş başarılı ancak sunucudan geçerli bir token alınamadı."); + } + } catch (error) { + console.error("Giriş sırasında hata:", error); + + if (error.response) { + alert("Kullanıcı adı veya şifre hatalı!"); + } else if (error.request) { + alert("Sunucuya bağlanılamadı. Ağ bağlantınızı kontrol edin."); + } else { + alert("Beklenmedik bir hata oluştu."); + } + } + }; + return ( -
-
-
- Sign In - Enter your email and password to Sign In. -
-
-
- - Your email - - - - Password - - +
+ +
+ Logo
- - 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/DispatchDetailPage.jsx b/src/pages/dashboard/DispatchDetailPage.jsx new file mode 100644 index 00000000..19a33043 --- /dev/null +++ b/src/pages/dashboard/DispatchDetailPage.jsx @@ -0,0 +1,244 @@ +// src/pages/dashboard/DispatchDetailPage.jsx +import React, { useState, useEffect, useRef } from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import { Typography, Button, Spinner, Card, CardHeader, CardBody } from "@material-tailwind/react"; +import { ArrowLeftIcon, PaperAirplaneIcon, MagnifyingGlassIcon } from "@heroicons/react/24/solid"; +import apiClient from "../../api/axiosConfig.js"; +import { toast } from "react-toastify"; +import { HubConnectionBuilder, HttpTransportType, LogLevel } from "@microsoft/signalr"; + +const HUB_URL = "https://75ymkt.com/hubs/queue"; + +function HighlightedPlate({ plate, query }) { + if (!query.trim()) return {plate}; + const idx = plate.toLowerCase().indexOf(query.toLowerCase()); + if (idx === -1) return {plate}; + return ( + + {plate.slice(0, idx)} + {plate.slice(idx, idx + query.length)} + {plate.slice(idx + query.length)} + + ); +} + +export default function DispatchDetailPage() { + const { routeId } = useParams(); + const navigate = useNavigate(); + const [routeName, setRouteName] = useState(""); + const [vehicles, setVehicles] = useState([]); + const [loading, setLoading] = useState(true); + const [search, setSearch] = useState(""); + const connectionRef = useRef(null); + const containerRef = useRef(null); + const [colCount, setColCount] = useState(4); + + useEffect(() => { + const calculate = () => { + if (!containerRef.current) return; + const containerWidth = containerRef.current.offsetWidth; + const cols = Math.max(1, Math.floor(containerWidth / 260)); + setColCount(cols); + }; + calculate(); + window.addEventListener("resize", calculate); + return () => window.removeEventListener("resize", calculate); + }, [loading]); + + const fetchData = async () => { + try { + const [routesRes, queueRes] = await Promise.all([ + apiClient.get("/admin/routes"), + apiClient.get(`/routes/${routeId}/queue`), + ]); + const found = routesRes.data.find(r => String(r.id) === String(routeId)); + setRouteName(found?.routeName || "Güzergah"); + setVehicles(queueRes.data); + } catch (err) { + console.error(err); + toast.error("Veri yüklenemedi."); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchData(); + if (connectionRef.current) return; + const connection = new HubConnectionBuilder() + .withUrl(HUB_URL, { + skipNegotiation: true, + transport: HttpTransportType.WebSockets, + }) + .withAutomaticReconnect() + .configureLogging(LogLevel.Information) + .build(); + connectionRef.current = connection; + connection.start() + .then(() => connection.on("ReceiveQueueUpdate", fetchData)) + .catch(console.error); + return () => { + connectionRef.current?.stop(); + connectionRef.current = null; + }; + }, [routeId]); + + const handleSendToEnd = async (vehicleId, licensePlate) => { + if (!window.confirm(`'${licensePlate}' aracını sıranın sonuna göndermek istiyor musunuz?`)) return; + try { + await apiClient.post(`/routes/${routeId}/queue/move-to-end`, { vehicleId }); + toast.success(`${licensePlate} sıranın sonuna gönderildi.`); + fetchData(); + } catch { + toast.error("İşlem başarısız."); + } + }; + + // Arama filtrelenmiş liste — orijinal index'i koruyarak + const filteredVehicles = search.trim() + ? vehicles + .map((v, i) => ({ ...v, originalIndex: i })) + .filter(v => v.licensePlate.toLowerCase().includes(search.toLowerCase())) + : vehicles.map((v, i) => ({ ...v, originalIndex: i })); + + // Sütun hesabı filtrelenmiş listeye göre + const rowsPerCol = Math.ceil(filteredVehicles.length / colCount); + const columns = Array.from({ length: colCount }, (_, ci) => + filteredVehicles.slice(ci * rowsPerCol, ci * rowsPerCol + rowsPerCol) + ).filter(col => col.length > 0); + + if (loading) return ( +
+ +
+ ); + + return ( +
+ + {/* ÜST BAR */} +
+
+ +
+ + {routeName} + + + {vehicles.length} araç sırada + {search && ` · ${filteredVehicles.length} sonuç gösteriliyor`} + +
+
+
+ + {/* TEK ÇERÇEVE */} + + +
+ + Sıradaki Araçlar — {routeName} + + + {/* ARAMA ÇUBUĞU — header içine sağa yerleştirildi */} +
+ + setSearch(e.target.value)} + placeholder="Plaka ara..." + className="w-full pl-9 pr-8 py-1.5 text-sm rounded-lg border border-white/20 bg-white/10 text-white placeholder-white/50 focus:outline-none focus:border-white/50 focus:bg-white/20 transition-colors" + /> + {search && ( + + )} +
+
+
+ + + {vehicles.length === 0 ? ( +
+ + Bu güzergahta sırada araç bulunmuyor. + +
+ ) : filteredVehicles.length === 0 ? ( +
+ + "{search}" ile eşleşen araç bulunamadı. + +
+ ) : ( +
+ {columns.map((colVehicles, colIndex) => ( +
+ {colVehicles.map((vehicle) => ( +
+ {/* Numara + Plaka */} +
+ + {String(vehicle.originalIndex + 1).padStart(2, "0")} + + + + +
+ + {/* Gönder */} + +
+ ))} +
+ ))} +
+ )} +
+
+
+ ); +} \ No newline at end of file diff --git a/src/pages/dashboard/TVQueuePage.jsx b/src/pages/dashboard/TVQueuePage.jsx new file mode 100644 index 00000000..4ff29733 --- /dev/null +++ b/src/pages/dashboard/TVQueuePage.jsx @@ -0,0 +1,203 @@ +import React, { useState, useEffect, useRef } from "react"; +import apiClient from "../../api/axiosConfig"; +import { HubConnectionBuilder, HttpTransportType, LogLevel } from '@microsoft/signalr'; + +const TVQueuePage = () => { + const [data, setData] = useState([]); + const [currentIndex, setCurrentIndex] = useState(0); + const [time, setTime] = useState(new Date()); + const [loading, setLoading] = useState(true); + + const connectionRef = useRef(null); + + const HUB_URL = "https://75ymkt.com/hubs/queue"; + + // --- PLAKA FORMATLAYICI --- + const formatLicensePlate = (plate) => { + if (!plate) return ""; + const cleanPlate = plate.replace(/\s/g, "").toUpperCase(); + const regex = /^(\d{2})([A-Z]+)(\d+)$/; + const match = cleanPlate.match(regex); + if (match) { + return `${match[1]} ${match[2]} ${match[3]}`; + } + return cleanPlate; + }; + // ------------------------- + + const fetchData = async () => { + try { + const routesRes = await apiClient.get("/admin/routes"); + const routes = routesRes.data; + + if (!routes || routes.length === 0) { + setData([]); + setLoading(false); + return; + } + + const fullData = await Promise.all( + routes.map(async (route) => { + try { + const queueRes = await apiClient.get(`/routes/${route.id}/queue`); + return { + routeName: route.routeName, + vehicles: queueRes.data.map((v) => ({ plate: v.licensePlate })), + }; + } catch (err) { + return { routeName: route.routeName, vehicles: [] }; + } + }) + ); + setData(fullData); + } catch (err) { + console.error("Veri çekme hatası:", err); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchData(); + + if (connectionRef.current) return; + + // BAĞLANTI AYARLARI + const connection = new HubConnectionBuilder() + .withUrl(HUB_URL, { + skipNegotiation: true, + transport: HttpTransportType.WebSockets + }) + .withAutomaticReconnect() + .configureLogging(LogLevel.Information) + .build(); + + connectionRef.current = connection; + + connection.start() + .then(() => { + console.log("🟢 TV Ekranı: SignalR (HTTPS) Bağlandı!"); + connection.on("ReceiveQueueUpdate", () => { + console.log("🔔 Güncelleme sinyali alındı."); + fetchData(); + }); + }) + .catch(err => console.error("🔴 SignalR Bağlantı Hatası:", err)); + + const dataTimer = setInterval(fetchData, 30000); + const clockTimer = setInterval(() => setTime(new Date()), 1000); + + return () => { + if (connectionRef.current) { + connectionRef.current.stop(); + connectionRef.current = null; + } + clearInterval(dataTimer); + clearInterval(clockTimer); + }; + }, []); + + useEffect(() => { + if (data.length > 2) { + const slideTimer = setInterval(() => { + setCurrentIndex((prevIndex) => (prevIndex + 2) % data.length); + }, 20000); + return () => clearInterval(slideTimer); + } else { + setCurrentIndex(0); + } + }, [data.length]); + + const getVisibleRoutes = () => { + if (data.length === 0) return []; + if (data.length <= 2) return data; + return [ + data[currentIndex % data.length], + data[(currentIndex + 1) % data.length] + ]; + }; + + const currentRoutes = getVisibleRoutes(); + + if (loading && data.length === 0) { + return ( +
+ SİSTEM YÜKLENİYOR... +
+ ); + } + + return ( +
+
+
+
+ 75 +
+

SIRA TAKİP SİSTEMİ

+
+ +
+
+ + {time.toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false })} + + + {time.toLocaleDateString('tr-TR', { day: '2-digit', month: 'long', year: 'numeric' })} + +
+
+
+ +
+ {currentRoutes.map((route, idx) => ( +
+ +
+
+
+

+ {route.routeName} +

+
+ + {route.vehicles.length} ARAÇ + +
+ + {/* 🟢 KRİTİK DEĞİŞİKLİK: 3 Sütun yerine 4 Sütun (grid-cols-4) yaptık */} +
+ {[0, 1, 2, 3].map((colIndex) => ( +
+ {/* 🟢 34 Satır yerine 25 Satır dönecek (4x25 = 100 araç) */} + {Array.from({ length: 25 }).map((_, rowIndex) => { + const vehicleIndex = colIndex * 25 + rowIndex; + const vehicle = route.vehicles && route.vehicles[vehicleIndex]; + if (vehicleIndex >= 100) return null; + + return ( +
+ + {String(vehicleIndex + 1).padStart(2, '0')} + + + {vehicle ? formatLicensePlate(vehicle.plate) : ""} + +
+ ); + })} +
+ ))} +
+
+ ))} +
+
+ ); +}; + +export default TVQueuePage; \ No newline at end of file diff --git a/src/pages/dashboard/dispatch.jsx b/src/pages/dashboard/dispatch.jsx new file mode 100644 index 00000000..26893958 --- /dev/null +++ b/src/pages/dashboard/dispatch.jsx @@ -0,0 +1,213 @@ +import React, { useState, useEffect } from "react"; +import { + Typography, + Card, + Spinner, + Button, + CardHeader, + CardBody +} from "@material-tailwind/react"; +import { InformationCircleIcon, PaperAirplaneIcon, MagnifyingGlassIcon } from "@heroicons/react/24/solid"; +import { useNavigate } from "react-router-dom"; +import apiClient from "../../api/axiosConfig.js"; +import { toast } from 'react-toastify'; + +function HighlightedPlate({ plate, query }) { + if (!query.trim()) return {plate}; + const idx = plate.toLowerCase().indexOf(query.toLowerCase()); + if (idx === -1) return {plate}; + return ( + + {plate.slice(0, idx)} + {plate.slice(idx, idx + query.length)} + {plate.slice(idx + query.length)} + + ); +} + +function DispatchVehicleCard({ vehicle, index, onSendToEnd, query }) { + 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"; + const isMatch = query.trim() && vehicle.licensePlate.toLowerCase().includes(query.toLowerCase()); + + return ( +
+
+ #{index + 1} + + + +
+ +
+ ); +} + +function RouteColumn({ route, onSendToEnd }) { + const [search, setSearch] = useState(""); + const navigate = useNavigate(); + + const filtered = search.trim() + ? route.queuedVehicles.filter(v => + v.licensePlate.toLowerCase().includes(search.toLowerCase()) + ) + : route.queuedVehicles; + + const getOriginalIndex = (vehicleId) => + route.queuedVehicles.findIndex(v => v.id === vehicleId); + + return ( +
+ + {/* HEADER — orijinal tasarım korundu, butona "Tüm Araçları Görüntüle" eklendi */} + +
+
+ + {route.routeName} + + + {route.queuedVehicles.length} Araç + +
+
+ {/* Orijinal tasarımdaki "Tüm Araçları Görüntüle" butonu */} + +
+ + {/* ARAMA ÇUBUĞU — header ile liste arasına eklendi */} +
+
+ + setSearch(e.target.value)} + placeholder="Plaka ara..." + className="w-full pl-9 pr-8 py-1.5 text-sm rounded-lg border border-blue-gray-200 bg-blue-gray-50/50 focus:outline-none focus:border-blue-gray-400 focus:bg-white transition-colors placeholder-blue-gray-300" + /> + {search && ( + + )} +
+ {search && ( + + {filtered.length} sonuç + + )} +
+ + + {filtered.length > 0 ? ( +
+ {filtered.map((vehicle) => { + const originalIndex = getOriginalIndex(vehicle.id); + return ( + onSendToEnd(route.routeId, vehicle.id, vehicle.licensePlate)} + /> + ); + })} +
+ ) : ( +
+ + + {search ? "Araç bulunamadı" : "Sırada Araç Yok !"} + +
+ )} +
+
+
+ ); +} + +export function DispatchPage() { + const [routesWithQueues, setRoutesWithQueues] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchAllQueues = async () => { + try { + const response = await apiClient.get("/queues/all"); + setRoutesWithQueues(response.data); + } catch (err) { + setError("Sıra verileri yüklenirken bir hata oluştu."); + console.error(err); + } finally { + if (loading) setLoading(false); + } + }; + + useEffect(() => { + fetchAllQueues(); + }, []); + + const handleSendToEnd = async (routeId, vehicleId, licensePlate) => { + if (window.confirm(`'${licensePlate}' plakalı aracı sıranın sonuna göndermek istediğinizden emin misiniz?`)) { + try { + await apiClient.post(`/routes/${routeId}/queue/move-to-end`, { vehicleId }); + toast.info(`'${licensePlate}' plakalı araç sıranın sonuna gönderildi.`); + fetchAllQueues(); + } catch (error) { + console.error("Araç sona gönderilirken hata:", error); + toast.error("İşlem sırasında bir hata oluştu."); + } + } + }; + + if (loading) { + return
; + } + + if (error) { + return {error}; + } + + return ( +
+
+ {routesWithQueues.map(route => ( + + ))} +
+
+ ); +} + +export default DispatchPage; \ No newline at end of file diff --git a/src/pages/dashboard/home.jsx b/src/pages/dashboard/home.jsx index 2c700669..5e9f7890 100644 --- a/src/pages/dashboard/home.jsx +++ b/src/pages/dashboard/home.jsx @@ -1,258 +1,196 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import { - Typography, - Card, - CardHeader, - CardBody, - IconButton, - Menu, - MenuHandler, - MenuList, - MenuItem, - Avatar, - Tooltip, - Progress, + Typography, + Card, + Spinner, + Button, + CardHeader, + CardBody, + Tabs, + TabsHeader, + Tab } 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 { ForwardIcon, InformationCircleIcon, TvIcon } from "@heroicons/react/24/solid"; +import apiClient from "@/api/axiosConfig"; +import { useMaterialTailwindController } from "@/context"; +import { VehicleQueueCard } from "@/widgets/layout/VehicleQueueCard"; +import { HubConnectionBuilder, LogLevel, HttpTransportType } from "@microsoft/signalr"; export function Home() { - return ( -
-
- {statisticsCardsData.map(({ icon, title, footer, ...rest }) => ( - - {footer.value} -  {footer.label} - - } - /> - ))} -
-
- {statisticsChartsData.map((props) => ( - - -  {props.footer} - + const [controller] = useMaterialTailwindController(); + const { userRole } = controller; + + const [routesWithQueues, setRoutesWithQueues] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [activeTab, setActiveTab] = useState(""); + const [isMobile, setIsMobile] = useState(window.innerWidth < 768); + + const HUB_URL = "https://75ymkt.com/hubs/queue"; + + useEffect(() => { + const handleResize = () => setIsMobile(window.innerWidth < 768); + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, []); + + const fetchAllQueues = async () => { + try { + const response = await apiClient.get("/queues/all"); + const data = response.data; + setRoutesWithQueues(data); + if (data.length > 0 && !activeTab) { + setActiveTab(data[0].routeId); } - /> - ))} -
-
- - -
- - Projects - - - - 30 done this month - + } catch (err) { + console.error(err); + if (loading) setError("Veri yüklenemedi."); + } finally { + if (loading) setLoading(false); + } + }; + + useEffect(() => { + fetchAllQueues(); + + const connection = new HubConnectionBuilder() + .withUrl(HUB_URL, { + skipNegotiation: true, + transport: HttpTransportType.WebSockets + }) + .withAutomaticReconnect() + .configureLogging(LogLevel.Information) + .build(); + + connection.start() + .then(() => { + console.log("🟢 Home: SignalR Bağlandı!"); + connection.on("ReceiveQueueUpdate", () => { + fetchAllQueues(); + }); + }) + .catch(err => console.error("🔴 SignalR Hatası:", err)); + + return () => connection.stop(); + }, []); + + const handleNextVehicle = async (routeId) => { + try { + await apiClient.post(`/admin/vehicles/${routeId}/move-first-to-end`); + } catch (error) { + console.error("Hata:", error); + alert("İşlem başarısız."); + } + }; + + if (loading) return ( +
+ +
+ ); + + if (error) return ( + {error} + ); + + const displayedRoutes = isMobile + ? routesWithQueues.filter(r => String(r.routeId) === String(activeTab)) + : routesWithQueues; + + return ( +
+ + {/* TV Ekranı Butonu */} +
+
- - - - - - - - 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" - }`; + {routesWithQueues.map(({ routeId, routeName }) => ( + setActiveTab(routeId)} + className={`w-max whitespace-nowrap px-5 py-2 shrink-0 ${activeTab === routeId ? "font-bold text-gray-900" : ""}`} + > + {routeName} + + ))} + + + + )} + + {/* Liste */} +
+ {displayedRoutes.map(route => { + const activeVehicles = route.queuedVehicles.filter(v => v.isActive); return ( -
- - - - - +
+ + +
+ + {route.routeName} + + + {activeVehicles.length} Araç + +
+ {userRole === 'admin' && ( +
+ +
+ )} +
+ + {activeVehicles.length > 0 ? ( +
+ {activeVehicles.map((vehicle, index) => ( + + ))} +
+ ) : ( +
+ + Sırada Araç Yok +
+ )} +
+
+
); - } - )} - -
- 0 && ( +
+ + - {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} - -
-
- ) - )} -
-
-
-
- ); + })} +
+
+ ); } -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..66629b85 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"; +export * from "@/pages/dashboard/dispatch"; +export * from "@/pages/dashboard/myprofile"; + + + diff --git a/src/pages/dashboard/myprofile.jsx b/src/pages/dashboard/myprofile.jsx new file mode 100644 index 00000000..2e26c795 --- /dev/null +++ b/src/pages/dashboard/myprofile.jsx @@ -0,0 +1,301 @@ +import React, { useState, useEffect } from "react"; +import { + Card, CardHeader, CardBody, Typography, Button, Input, Chip +} from "@material-tailwind/react"; +import { UserCircleIcon, TruckIcon } from "@heroicons/react/24/solid"; +import { toast } from "react-toastify"; +import { HubConnectionBuilder, LogLevel } from "@microsoft/signalr"; +import apiClient from "../../api/axiosConfig.js"; + +const HUB_URL = "https://75ymkt.com/hubs/queue"; + +const formatPlate = (plate) => { + if (!plate) return ""; + const clean = plate.replace(/\s/g, "").toUpperCase(); + const match = clean.match(/^(\d{2})([A-Z]+)(\d+)$/); + return match ? `${match[1]} ${match[2]} ${match[3]}` : clean; +}; + +export function MyProfile() { + const [userInfo, setUserInfo] = useState(null); + const [queueStatus, setQueueStatus] = useState([]); // [{routeName, position, total}] + const [loading, setLoading] = useState(true); + const [passwords, setPasswords] = useState({ + currentPassword: "", + password: "", + confirmPassword: "", + }); + const [saving, setSaving] = useState(false); + + const fetchMyInfo = async () => { + try { + const res = await apiClient.get("/Users/me"); + setUserInfo(res.data); + } catch { + toast.error("Bilgiler yüklenemedi."); + } finally { + setLoading(false); + } + }; + + const fetchQueueStatus = async (licensePlate) => { + if (!licensePlate || licensePlate === "-") return; + try { + const res = await apiClient.get("/queues/all"); + const cleanMyPlate = licensePlate.replace(/\s/g, "").toUpperCase(); + + const statuses = []; + for (const route of res.data) { + const idx = route.queuedVehicles.findIndex( + v => v.licensePlate.replace(/\s/g, "").toUpperCase() === cleanMyPlate + ); + if (idx !== -1) { + statuses.push({ + routeName: route.routeName, + position: idx + 1, + total: route.queuedVehicles.length, + }); + } + } + setQueueStatus(statuses); + } catch { + // Sessizce geç + } + }; + + useEffect(() => { + fetchMyInfo(); + }, []); + + useEffect(() => { + if (!userInfo?.licensePlate) return; + + fetchQueueStatus(userInfo.licensePlate); + + // SignalR — sıra değişince otomatik güncelle + const connection = new HubConnectionBuilder() + .withUrl(HUB_URL) + .withAutomaticReconnect() + .configureLogging(LogLevel.None) + .build(); + + connection.start().then(() => { + connection.on("ReceiveQueueUpdate", () => { + fetchQueueStatus(userInfo.licensePlate); + }); + }).catch(() => {}); + + return () => connection.stop(); + }, [userInfo]); + + const handlePasswordChange = async () => { + if (!passwords.currentPassword || !passwords.password || !passwords.confirmPassword) { + toast.error("Lütfen tüm şifre alanlarını doldurun."); + return; + } + if (passwords.password !== passwords.confirmPassword) { + toast.error("Yeni şifreler eşleşmiyor."); + return; + } + if (passwords.password.length < 6) { + toast.error("Şifre en az 6 karakter olmalıdır."); + return; + } + setSaving(true); + try { + await apiClient.post("/Users/change-my-password", { + currentPassword: passwords.currentPassword, + newPassword: passwords.password, + confirmNewPassword: passwords.confirmPassword, + }); + toast.success("Şifre başarıyla güncellendi!"); + setPasswords({ currentPassword: "", password: "", confirmPassword: "" }); + } catch (e) { + toast.error(e.response?.data?.message || "Şifre güncellenemedi."); + } finally { + setSaving(false); + } + }; + + if (loading) { + return ( +
+ Yükleniyor... +
+ ); + } + + if (!userInfo) { + return ( +
+ Kullanıcı bilgisi alınamadı. +
+ ); + } + + return ( +
+ + {/* Kişisel Bilgiler */} + + + + Kişisel Bilgilerim + + +
+ + Ad Soyad + + + {userInfo.fullName || "-"} + +
+
+ + Kullanıcı Adı + + + {userInfo.userName || "-"} + +
+
+ + Telefon Numarası + + + {userInfo.phoneNumber || "-"} + +
+
+ + Araç Plakası + + {userInfo.licensePlate && userInfo.licensePlate !== "-" ? ( + + ) : ( + + Atanmış araç yok + + )} +
+
+
+ + {/* Sıra Durumu */} + + + +
+ Sıra Durumum + + Gerçek zamanlı güncellenir + +
+
+ + {!userInfo.licensePlate || userInfo.licensePlate === "-" ? ( + + Atanmış araç olmadığı için sıra durumu görüntülenemiyor. + + ) : queueStatus.length === 0 ? ( + + Şu an hiçbir güzergah sırasında değilsiniz. + + ) : ( + + + + {["Güzergah", "Sıra No", "Toplam Araç", "Durum"].map(h => ( + + ))} + + + + {queueStatus.map((s, i) => ( + + + + + + + ))} + +
+ + {h} + +
+ + {s.routeName} + + + + {s.position} + + + + {s.total} araç + + + {s.position === 1 ? ( + + ) : s.position <= 3 ? ( + + ) : ( + + )} +
+ )} +
+
+ + {/* Şifre Değiştir */} + + + Şifre Değiştir + + + setPasswords(p => ({ ...p, currentPassword: e.target.value }))} + autoComplete="current-password" + /> + setPasswords(p => ({ ...p, password: e.target.value }))} + autoComplete="new-password" + /> + setPasswords(p => ({ ...p, confirmPassword: e.target.value }))} + autoComplete="new-password" + /> + + + + +
+ ); +} + +export default MyProfile; \ No newline at end of file diff --git a/src/pages/dashboard/notifications.jsx b/src/pages/dashboard/notifications.jsx index f4be88b0..69d8b01a 100644 --- a/src/pages/dashboard/notifications.jsx +++ b/src/pages/dashboard/notifications.jsx @@ -1,88 +1,208 @@ -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"; +// TOAST ENTEGRASYONU +import { ToastContainer, toast } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; + +import apiClient from "../../api/axiosConfig.js"; +import { AddRouteModal } from "@/widgets/layout/AddRouteModal"; +import { EditRouteModal } from "@/widgets/layout/EditRouteModal"; 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, - }))} + const [routes, setRoutes] = useState([]); + + // Modal State'leri + const [addModalOpen, setAddModalOpen] = useState(false); + const handleOpenAddModal = () => setAddModalOpen(!addModalOpen); + + const [editModalOpen, setEditModalOpen] = useState(false); + const [currentRoute, setCurrentRoute] = useState(null); + + const handleOpenEditModal = (route) => { + setCurrentRoute(route); + setEditModalOpen(true); + }; + + 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); + // Hata durumunda Toast bildirimi + toast.error("Güzergahlar yüklenirken bir sorun oluştu."); + } + }; + + useEffect(() => { + fetchRoutes(); + }, []); + + // Ekleme, silme veya güncelleme sonrası listeyi yeniden çekmek için + const handleDataChange = (message) => { + fetchRoutes(); + // Ekleme/Düzenleme modal'ları kendi başarı toast'larını göstersin + // Silme işlemi için burası tetiklenecek + if(message) { + toast.error(message, { position: "top-right", autoClose: 3000 }); + } + }; + + + + const handleDeleteToast = (routeId, routeName) => { + + toast.warn( +
+

+ **{routeName}** güzergahını silmek istediğinize emin misiniz? +

+
+ + +
+
, + { + position: "top-center", + autoClose: false, + closeOnClick: false, + draggable: false, + } + ); + }; + + const confirmDelete = async (routeId, routeName) => { + toast.dismiss(); // Onay Toast'ını kapat + + try { + await apiClient.delete(`/admin/routes/${routeId}`); + + // Başarı Toast'ı ve ardından veriyi yenile + handleDataChange(`${routeName} başarıyla silindi.`); + + } catch (error) { + console.error("Güzergah silinirken hata oluştu:", error); + toast.error("Güzergah silinirken bir hata oluştu."); + } + }; + +return ( + <> + {/* Toast Container'ı ekledik */} + + + {/* Modal bileşenleri */} + + + + {/* ANA KAPSAYICI DIV + - mx-auto ve max-w-screen-lg kaldırıldı. + - h-full (dikey) ve flex (varsayılan genişlik) ile alanı dolduracak. + */} +
+ + {/* Kartın da "h-full" olması gerekiyor */} + + + + Güzergahlar + +
+ Güzergah Ekle + + + + + + + {["Güzergah", "İşlem"].map((el) => ( + + ))} + + + + {routes.map((route) => ( + + + + + ))} + +
+ + {el} + +
+ + {route.routeName} + + + + +
+
+
+
+ ); } -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..c8ac59f7 100644 --- a/src/pages/dashboard/profile.jsx +++ b/src/pages/dashboard/profile.jsx @@ -1,221 +1,250 @@ +import React, { useState, useEffect } from "react"; import { - Card, - CardBody, - CardHeader, - CardFooter, - Avatar, - Typography, - Tabs, - TabsHeader, - Tab, - Switch, - Tooltip, - Button, + Card, + CardHeader, + CardBody, + Typography, + Button, + Dialog, + DialogHeader, + DialogBody, + DialogFooter, } 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 { toast } from "react-toastify"; +import apiClient from "../../api/axiosConfig.js"; +import { AddUserModal } from "@/widgets/layout/AddUserModal"; +import { EditUserModal } from "@/widgets/layout/EditUserModal"; export function Profile() { - return ( - <> -
-
-
- - -
-
- -
- - Richard Davis - - - CEO / Co-Founder - -
-
-
- - - - - App - - - - Message - - - - Settings - - - + const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const [openAddModal, setOpenAddModal] = useState(false); + const [openEditModal, setOpenEditModal] = useState(false); + const [currentUser, setCurrentUser] = useState(null); + + const [openDeleteModal, setOpenDeleteModal] = useState(false); + const [userToDelete, setUserToDelete] = useState(null); + + const handleOpenAddModal = () => setOpenAddModal(true); + const handleCloseAddModal = () => setOpenAddModal(false); + + const handleOpenEditModal = (user) => { + setCurrentUser(user); + setOpenEditModal(true); + }; + + const handleCloseEditModal = () => { + setCurrentUser(null); + setOpenEditModal(false); + }; + + const handleOpenDeleteModal = (user) => { + setUserToDelete(user); + setOpenDeleteModal(true); + }; + + const handleCloseDeleteModal = () => { + setUserToDelete(null); + setOpenDeleteModal(false); + }; + + const extractApiError = (err, fallback) => { + const data = err?.response?.data; + if (!data) return fallback; + + if (typeof data === "string") return data; + if (data?.title) return data.title; + if (data?.message) return data.message; + + // ValidationProblemDetails vb. + if (data?.errors && typeof data.errors === "object") { + const firstKey = Object.keys(data.errors)[0]; + if (firstKey && Array.isArray(data.errors[firstKey]) && data.errors[firstKey][0]) { + return data.errors[firstKey][0]; + } + } + + return fallback; + }; + + const fetchUsers = async () => { + try { + setLoading(true); + const response = await apiClient.get("/Users"); + setUsers(response.data || []); + setError(null); + } catch (err) { + console.error("Kullanıcı listesi hatası:", err); + setError(extractApiError(err, "Kullanıcı verileri yüklenirken bir hata oluştu.")); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchUsers(); + }, []); + + const handleDataChange = async (message) => { + await fetchUsers(); + if (message) toast.success(message); + }; + + const onUserAddedCallback = async () => { + await handleDataChange("Kullanıcı başarıyla eklendi."); + }; + + const onUserUpdatedCallback = async () => { + await handleDataChange("Kullanıcı bilgileri güncellendi."); + }; + + const confirmDelete = async () => { + if (!userToDelete) return; + + try { + await apiClient.delete(`/Users/${userToDelete.id}`); + await handleDataChange(`'${userToDelete.fullName}' adlı kullanıcı silindi.`); + handleCloseDeleteModal(); + } catch (err) { + console.error("Silme hatası:", err); + toast.error(extractApiError(err, "Silme işlemi başarısız oldu.")); + handleCloseDeleteModal(); + } + }; + + if (loading) { + return ( +
+ Kullanıcılar Yükleniyor...
-
-
-
- - Platform Settings - -
- {platformSettingsData.map(({ title, options }) => ( -
- - {title} - -
- {options.map(({ checked, label }) => ( - - ))} -
-
- ))} -
+ ); + } + + if (error) { + return ( +
+ {error}
- - - - -
- ), - }} - action={ - - - - } + ); + } + + return ( + <> + -
- - Platform Settings - -
    - {conversationsData.map((props) => ( - - reply - - } - /> - ))} -
-
-
-
- - Projects - - - Architects design houses - -
- {projectsData.map( - ({ img, title, description, tag, route, members }) => ( - - - {title} + + + + + Silme Onayı + + {userToDelete && ( + + {userToDelete.fullName} adlı kullanıcıyı silmek istediğinizden emin misiniz? +
+
+
+ )} +
+ + + + +
+ +
+ + + + Kullanıcılar + + - - - {tag} - - - {title} - - - {description} - + + + + + {["Tam Ad", "Plaka", "Telefon", "İşlem"].map((el) => ( + + ))} + + + + {users.map((user) => ( + + + + + + + ))} + +
+ + {el} + +
+ {user.fullName} + + {user.licensePlate || "-"} + + {user.phoneNumber || "-"} + +
+ { + e.preventDefault(); + handleOpenEditModal(user); + }} + > + Düzenle + + + { + e.preventDefault(); + handleOpenDeleteModal(user); + }} + > + Sil + +
+
- - - - -
- {members.map(({ img, name }, key) => ( - - - - ))} -
-
-
- ) - )} +
-
- - - - ); + + ); } -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..19fd5a40 --- /dev/null +++ b/src/pages/dashboard/queuemanagementpage.jsx @@ -0,0 +1,346 @@ +// src/pages/dashboard/queuemanagementpage.jsx +import React, { useState, useEffect, useMemo } from "react"; +import { + Typography, Card, CardHeader, CardBody, + Button, Select, Option, List, ListItem, +} from "@material-tailwind/react"; +import { + DndContext, + closestCenter, + PointerSensor, + useSensor, + useSensors, +} from "@dnd-kit/core"; +import { + arrayMove, + SortableContext, + useSortable, + verticalListSortingStrategy, +} from "@dnd-kit/sortable"; +// ❌ CSS import YOK - uyumsuz olduğu için kaldırıldı +import apiClient from "@/api/axiosConfig"; +import { HubConnectionBuilder, LogLevel } from "@microsoft/signalr"; +import { toast } from "react-toastify"; + +const HUB_URL = "https://75ymkt.com/hubs/queue"; + +// --- Sürüklenebilir Satır --- +function SortableRow({ vehicle, index, onRemove }) { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id: vehicle.id }); + + // CSS import yerine manuel transform string + const transformStr = transform + ? `translate3d(${transform.x}px, ${transform.y}px, 0) scaleX(${transform.scaleX ?? 1}) scaleY(${transform.scaleY ?? 1})` + : undefined; + + const style = { + transform: transformStr, + transition: transition ?? undefined, + opacity: isDragging ? 0.5 : 1, + backgroundColor: isDragging ? "#eff6ff" : "white", + position: "relative", + }; + + return ( + + +
+ {/* Hamburger ikonu - Heroicons olmadan */} + + + +
+ + + #{index + 1} + + {vehicle.licensePlate} + {vehicle.userFullName || vehicle.driverName || "-"} + + + + + ); +} + +export function QueueManagementPage() { + const [routes, setRoutes] = useState([]); + const [allVehicles, setAllVehicles] = useState([]); + const [selectedRoute, setSelectedRoute] = useState(null); + const [queuedVehicles, setQueuedVehicles] = useState([]); + const [vehicleToAdd, setVehicleToAdd] = useState(""); + const [loadingQueue, setLoadingQueue] = useState(false); + const [isSaving, setIsSaving] = useState(false); + + const sensors = useSensors( + useSensor(PointerSensor, { + // Yanlışlıkla sürüklemeyi önlemek için 8px eşik + activationConstraint: { distance: 8 }, + }) + ); + + const fetchBaseData = async () => { + try { + const [routesRes, vehiclesRes] = await Promise.all([ + apiClient.get("/admin/routes"), + apiClient.get("/admin/vehicles"), + ]); + setRoutes(routesRes.data); + setAllVehicles(vehiclesRes.data); + } catch (err) { + console.error("Veri hatası:", err); + } + }; + + const fetchQueueData = async () => { + if (!selectedRoute) return; + setLoadingQueue(true); + try { + const res = await apiClient.get(`/routes/${selectedRoute.id}/queue`); + setQueuedVehicles(res.data); + } catch { + setQueuedVehicles([]); + } finally { + setLoadingQueue(false); + } + }; + + useEffect(() => { + fetchBaseData(); + const connection = new HubConnectionBuilder() + .withUrl(HUB_URL) + .withAutomaticReconnect() + .configureLogging(LogLevel.Information) + .build(); + connection.start().then(() => { + connection.on("ReceiveQueueUpdate", fetchBaseData); + }).catch(console.error); + return () => connection.stop(); + }, []); + + useEffect(() => { + fetchQueueData(); + setVehicleToAdd(""); + }, [selectedRoute]); + + const handleDragEnd = async (event) => { + const { active, over } = event; + if (!over || active.id === over.id) return; + + const oldIndex = queuedVehicles.findIndex((v) => v.id === active.id); + const newIndex = queuedVehicles.findIndex((v) => v.id === over.id); + const reordered = arrayMove(queuedVehicles, oldIndex, newIndex); + + setQueuedVehicles(reordered); // Optimistic update + + setIsSaving(true); + try { + await apiClient.post(`/routes/${selectedRoute.id}/queue/reorder`, { + orderedVehicleIds: reordered.map((v) => v.id), + }); + toast.success("Sıra güncellendi!", { autoClose: 1500 }); + } catch { + toast.error("Sıra kaydedilemedi."); + fetchQueueData(); // Hata varsa geri al + } finally { + setIsSaving(false); + } + }; + + const handleAddVehicleToQueue = async () => { + if (!vehicleToAdd || !selectedRoute) return; + try { + await apiClient.post(`/routes/${selectedRoute.id}/queue`, { + vehicleId: Number(vehicleToAdd), + }); + setVehicleToAdd(""); + await fetchQueueData(); + } catch (err) { + toast.error(err.response?.data || "Araç eklenemedi."); + } + }; + + const handleRemoveVehicleFromQueue = async (vehicleId) => { + if (!window.confirm("Bu aracı sıradan çıkarmak istediğinize emin misiniz?")) return; + try { + await apiClient.delete(`/routes/${selectedRoute.id}/queue/${vehicleId}`); + await fetchQueueData(); + } catch { + toast.error("Araç çıkarılamadı."); + } + }; + + const availableVehicles = useMemo(() => { + const queuedIds = new Set(queuedVehicles.map((v) => v.id)); + return allVehicles.filter((v) => !queuedIds.has(v.id) && v.isActive); + }, [allVehicles, queuedVehicles]); + + const selectKey = selectedRoute + ? `select-${selectedRoute.id}-${availableVehicles.length}` + : "empty"; + + return ( +
+ + + Güzergah Sıra Yönetimi + + ☰ ikonuna basılı tutup sürükleyerek sıra değiştirebilirsiniz. + + + + + {/* SOL - Güzergah seçimi */} +
+ Güzergah Seçin + + + {routes.map((route) => ( + setSelectedRoute(route)} + selected={selectedRoute?.id === route.id} + className="cursor-pointer" + > + {route.routeName} + + ))} + + +
+ + {/* SAĞ - Sıra tablosu */} +
+ {!selectedRoute ? ( +
+ Lütfen soldan bir güzergah seçin +
+ ) : ( +
+
+ + {selectedRoute.routeName} — {queuedVehicles.length} Araç + + {isSaving && ( + + ⏳ Kaydediliyor... + + )} +
+ +
+ + + + + {["", "Sıra", "Plaka", "Şoför", "İşlem"].map((h) => ( + + ))} + + + v.id)} + strategy={verticalListSortingStrategy} + > + + {loadingQueue ? ( + + + + ) : queuedVehicles.length === 0 ? ( + + + + ) : ( + queuedVehicles.map((vehicle, index) => ( + + )) + )} + + +
+ + {h} + +
+ Yükleniyor... +
+ Bu güzergahta sırada araç yok. +
+
+
+ + {/* Araç ekleme */} +
+
+ +
+ +
+ {availableVehicles.length === 0 && ( + + * Eklenecek uygun (aktif) araç bulunamadı. + + )} +
+ )} +
+
+
+
+ ); +} + +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..2cb6775d 100644 --- a/src/pages/dashboard/tables.jsx +++ b/src/pages/dashboard/tables.jsx @@ -1,221 +1,325 @@ +import React, { useState, useEffect } from "react"; import { - Card, - CardHeader, - CardBody, - Typography, - Avatar, - Chip, - Tooltip, - Progress, + Card, CardHeader, CardBody, Typography, Button, Chip, Tooltip } from "@material-tailwind/react"; -import { EllipsisVerticalIcon } from "@heroicons/react/24/outline"; -import { authorsTableData, projectsTableData } from "@/data"; +import { + ExclamationTriangleIcon, ArchiveBoxXMarkIcon, EyeSlashIcon +} from "@heroicons/react/24/solid"; +import { ToastContainer, toast } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; + +import apiClient from "../../api/axiosConfig.js"; +import { AddVehicleModal } from "@/widgets/layout/AddVehicleModal"; +import { EditVehicleModal } from "@/widgets/layout/EditVehicleModal"; +import { AssignUserModal } from "@/widgets/layout/AssignUserModal"; + +const toTitleCase = (str) => + !str ? "-" : str.toLowerCase().split(" ").map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(" "); export function Tables() { + const [vehicles, setVehicles] = useState([]); + const [idleVehicles, setIdleVehicles] = useState([]); + + const [addModalOpen, setAddModalOpen] = useState(false); + const handleOpenAddModal = () => setAddModalOpen(!addModalOpen); + + const [editModalOpen, setEditModalOpen] = useState(false); + const [currentVehicle, setCurrentVehicle] = useState(null); + const handleOpenEditModal = (vehicle) => { setCurrentVehicle(vehicle); setEditModalOpen(true); }; + const handleCloseEditModal = () => { setEditModalOpen(false); setCurrentVehicle(null); }; + + const [assignModalOpen, setAssignModalOpen] = useState(false); + const [vehicleToAssign, setVehicleToAssign] = useState(null); + const handleOpenAssignModal = (vehicle) => { setVehicleToAssign(vehicle); setAssignModalOpen(true); }; + const handleCloseAssignModal = () => { setAssignModalOpen(false); setVehicleToAssign(null); }; + + const fetchVehicles = async () => { + try { + const response = await apiClient.get("/admin/vehicles"); + setVehicles(response.data || []); + } catch (error) { + console.error(error); + toast.error("Araç listesi yüklenemedi."); + } + }; + + const fetchIdleVehicles = async () => { + try { + const response = await apiClient.get("/admin/vehicles/idle-warnings?days=7"); + setIdleVehicles(response.data || []); + } catch (error) { + console.error("Uyarı verisi çekilemedi:", error); + } + }; + + useEffect(() => { + fetchVehicles(); + fetchIdleVehicles(); + }, []); + + const handleDataChange = (message) => { + fetchVehicles(); + fetchIdleVehicles(); + if (message) toast.success(message, { position: "top-right", autoClose: 3000 }); + }; + + const handleSetPassive = async (vehicle) => { + try { + await apiClient.patch(`/admin/vehicles/${vehicle.id}/set-active`, { isActive: false }); + toast.info(`${vehicle.licensePlate} pasife alındı.`); + setIdleVehicles((prev) => prev.filter((v) => v.id !== vehicle.id)); + fetchVehicles(); + } catch { + toast.error("İşlem başarısız."); + } + }; + + const handleIgnoreWarning = (vehicleId) => { + setIdleVehicles((prev) => prev.filter((v) => v.id !== vehicleId)); + toast.success("Uyarı listeden kaldırıldı.", { autoClose: 1000 }); + }; + + const unassignUser = async (vehicle) => { + if (!vehicle.appUserId) { + toast.info("Bu araçta zaten atanmış kullanıcı yok."); + return; + } + try { + await apiClient.patch(`/admin/vehicles/${vehicle.id}/assign-user`, { appUserId: null }); + handleDataChange(`${vehicle.licensePlate} aracından kullanıcı ataması kaldırıldı.`); + } catch (err) { + toast.error(err?.response?.data?.message || "Atama kaldırılamadı."); + } + }; + + const handleDeleteToast = (id, plaka) => { + toast.warn( +
+

{plaka} silinsin mi?

+
+ + +
+
, + { position: "top-center", autoClose: false, closeOnClick: false } + ); + }; + + const confirmDelete = async (id, plaka) => { + toast.dismiss(); + try { + await apiClient.delete(`/admin/vehicles/${id}`); + handleDataChange(`${plaka} silindi.`); + } catch { + toast.error("Silme işlemi başarısız."); + } + }; + 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" - }`; + <> + + + handleDataChange()} + /> + handleDataChange()} + vehicleToEdit={currentVehicle} + /> + handleDataChange()} + /> + +
+ + + Araç Listesi + + + +
- - {el} - -
+ + + {["Plaka", "Kullanıcı Adı", "Telefon", "Durum", "İşlem"].map((el) => ( + + ))} + + + + {vehicles.map((vehicle, key) => { + const className = `py-3 px-5 ${key === vehicles.length - 1 ? "" : "border-b border-blue-gray-50"}`; + const displayName = vehicle.userFullName && vehicle.userFullName !== "Atanmadı" + ? toTitleCase(vehicle.userFullName) + : vehicle.driverName && vehicle.driverName !== "Atanmadı" + ? toTitleCase(vehicle.driverName) + : null; return ( - - + + + + - - - - + + ); + })} + {vehicles.length === 0 && ( + + - ); - } - )} - -
+ + {el} + +
-
- -
- +
+ + {vehicle.licensePlate} + + + {displayName ? ( + + {displayName} + + ) : ( + + Atanmadı + + )} + + + {vehicle.phoneNumber || "-"} + + + + +
+ + + +
- -
- - {job[0]} - - - {job[1]} - - - - - - {date} - - - - Edit - +
+ Kayıtlı araç bulunamadı.
-
-
- - - - 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" - }`; + +
- - {el} - -
+
+
- return ( - - -
- - - {name} - -
- - - {members.map(({ img, name }, key) => ( - - - - ))} - - - - {budget} - - - -
- 0 && ( + + + +
+ Hareketsiz Araçlar + + Son 7 gündür işlem görmeyen araçlar. + +
+
+ + + + + {["Plaka", "Kullanıcı Adı", "Telefon", "Aksiyon"].map((el) => ( + + + {el} + + + ))} - ); - } - )} - -
- {completion}% - - - - - - - - -
-
-
-
+ + + {idleVehicles.map((vehicle, key) => { + const className = `py-3 px-5 ${key === idleVehicles.length - 1 ? "" : "border-b border-blue-gray-50"}`; + const displayName = vehicle.userFullName && vehicle.userFullName !== "Atanmadı" + ? toTitleCase(vehicle.userFullName) + : vehicle.driverName && vehicle.driverName !== "Atanmadı" + ? toTitleCase(vehicle.driverName) + : null; + + return ( + + + + {vehicle.licensePlate} + + + + {displayName ? ( + {displayName} + ) : ( + Atanmadı + )} + + + + {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..110e25ce 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -1,13 +1,26 @@ import { HomeIcon, UserCircleIcon, - TableCellsIcon, - InformationCircleIcon, + TruckIcon, + MapIcon, + Squares2X2Icon, + QueueListIcon, + PaperAirplaneIcon, ServerStackIcon, - RectangleStackIcon, } from "@heroicons/react/24/solid"; -import { Home, Profile, Tables, Notifications } from "@/pages/dashboard"; -import { SignIn, SignUp } from "@/pages/auth"; + +import { + Home, + Profile, + Tables, + Notifications, + QueueManagementPage, + DispatchPage, +} from "@/pages/dashboard"; + +import { SignIn } from "@/pages/auth"; +import TVQueuePage from "./pages/dashboard/TVQueuePage"; +import DispatchDetailPage from "./pages/dashboard/DispatchDetailPage"; const icon = { className: "w-5 h-5 text-inherit", @@ -15,52 +28,79 @@ const icon = { export const routes = [ { - layout: "dashboard", + layout: "anasayfa", pages: [ { - icon: , - name: "dashboard", - path: "/home", + icon: , + name: "Araç Sıraları", + path: "/arac-siralari", element: , }, + { + icon: , + name: "Sıra Yönetimi", + path: "/sira-yonetimi", + element: , + roles: ['admin'], + }, + { + icon: , + name: "Özel Görev", + path: "/ozel-gorev", + element: , + roles: ['admin'], + }, + { + hidden: true, + path: "/ozel-gorev/:routeId", + element: , + roles: ['admin'], + }, { icon: , - name: "profile", - path: "/profile", + name: "Kullanıcılar", + path: "/kullanicilar", element: , + roles: ['admin'], }, { - icon: , - name: "tables", - path: "/tables", + icon: , + name: "Araçlar", + path: "/araclar", element: , + roles: ['admin'], }, { - icon: , - name: "notifications", - path: "/notifications", + icon: , + name: "Güzergahlar", + path: "/guzergahlar", element: , + roles: ['admin'], }, ], }, { - title: "auth pages", + title: "Giriş İşlemleri", layout: "auth", pages: [ { icon: , - name: "sign in", - path: "/sign-in", + name: "Giriş Yap", + path: "/giris", element: , }, + ], + }, + { + layout: "tv", + pages: [ { - icon: , - name: "sign up", - path: "/sign-up", - element: , + name: "TV Monitor", + path: "/monitor", + 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..6558ec01 --- /dev/null +++ b/src/widgets/layout/AddRouteModal.jsx @@ -0,0 +1,77 @@ +import React, { useState } from "react"; +import { + Button, + Dialog, + DialogHeader, + DialogBody, + DialogFooter, + Input, + Typography, +} from "@material-tailwind/react"; +// TOAST ENTEGRASYONU +import { toast } from 'react-toastify'; +import apiClient from "../../api/axiosConfig.js"; + +export function AddRouteModal({ open, handleOpen, onRouteAdded }) { + const [routeName, setRouteName] = useState(""); + const [localError, setLocalError] = useState(""); // Hata mesajını local'de tutuyoruz + + const clearForm = () => { + setRouteName(""); + setLocalError(""); + }; + + const handleClose = () => { + clearForm(); + handleOpen(); + }; + + const handleSubmit = async () => { + if (!routeName.trim()) { + setLocalError("Güzergah adı boş olamaz."); + toast.error("Güzergah adı boş olamaz."); // Toast ile de bildir + return; + } + + try { + const response = await apiClient.post("/admin/routes", { routeName }); + + // Standart alert yerine BAŞARI TOAST'ı göster + toast.success(`${routeName} başarıyla eklendi!`, { position: "top-right" }); + + onRouteAdded(response.data); // Ana listeyi yenile + handleClose(); + } catch (err) { + console.error("Güzergah eklenirken hata:", err); + + // Backend'den gelen spesifik hata mesajını çek ve TOAST ile göster + const apiError = err.response?.data?.message || err.response?.data?.error || "Bir hata oluştu."; + setLocalError(apiError); + toast.error(apiError); + } + }; + + return ( + + Yeni Güzergah Ekle + + {/* Local hata gösterimi için typography */} + {localError && {localError}} + setRouteName(e.target.value)} + error={!!localError} // 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..e409689d --- /dev/null +++ b/src/widgets/layout/AddUserModal.jsx @@ -0,0 +1,71 @@ +import React, { useState } from "react"; +import { + Button, Dialog, DialogHeader, DialogBody, DialogFooter, Input, Typography, +} from "@material-tailwind/react"; +import { toast } from "react-toastify"; +import apiClient from "../../api/axiosConfig.js"; + +export function AddUserModal({ open, handleOpen, onUserAdded }) { + const [formData, setFormData] = useState({ + fullName: "", userName: "", password: "", confirmPassword: "", phoneNumber: "", + }); + const [error, setError] = useState(""); + + const clearForm = () => { + setFormData({ fullName: "", userName: "", password: "", confirmPassword: "", phoneNumber: "" }); + setError(""); + }; + + const handleClose = () => { clearForm(); handleOpen(); }; + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData(prev => ({ ...prev, [name]: value })); + setError(""); + }; + + const handleSubmit = async () => { + if (!formData.fullName || !formData.userName || !formData.password || !formData.confirmPassword) { + const msg = "Zorunlu alanları doldurunuz."; + setError(msg); toast.error(msg); return; + } + if (formData.password !== formData.confirmPassword) { + const msg = "Şifreler eşleşmiyor."; + setError(msg); toast.error(msg); return; + } + try { + await apiClient.post("/Users", { + fullName: formData.fullName, + userName: formData.userName, + password: formData.password, + confirmPassword: formData.confirmPassword, + phoneNumber: formData.phoneNumber?.trim() || null, + }); + onUserAdded(); + handleClose(); + } catch (err) { + const msg = err.response?.status === 409 + ? `'${formData.userName}' zaten kayıtlı.` + : (err.response?.data?.message || "Kullanıcı eklenirken hata oluştu."); + setError(msg); toast.error(msg); + } + }; + + 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..b32751f5 --- /dev/null +++ b/src/widgets/layout/AddVehicleModal.jsx @@ -0,0 +1,80 @@ +import React, { useState, useEffect } from "react"; +import { + Button, Dialog, DialogHeader, DialogBody, DialogFooter, + Input, Select, Option, Typography, +} from "@material-tailwind/react"; +import { toast } from "react-toastify"; +import apiClient from "../../api/axiosConfig.js"; + +const TURKISH_PLATE_REGEX = /^(\d{2})\s*([A-Z]{1,3})\s*(\d{1,4})$/; + +export function AddVehicleModal({ open, handleOpen, onVehicleAdded }) { + const [users, setUsers] = useState([]); + const [selectedUserId, setSelectedUserId] = useState(""); + const [licensePlate, setLicensePlate] = useState(""); + const [error, setError] = useState(""); + + useEffect(() => { + if (open) { + apiClient.get("/Users") + .then(r => { setUsers(r.data || []); setError(""); }) + .catch(() => toast.error("Kullanıcı listesi yüklenemedi.")); + } else { + setLicensePlate(""); + setSelectedUserId(""); + setError(""); + } + }, [open]); + + const handleSubmit = async () => { + const plate = licensePlate.trim().toUpperCase(); + if (!plate) { toast.error("Plaka zorunludur."); return; } + if (!TURKISH_PLATE_REGEX.test(plate)) { + toast.error("Plaka formatı uygun değil. Örn: 34 ABC 123"); return; + } + try { + await apiClient.post("/admin/vehicles", { + licensePlate: plate.replace(/\s+/g, ""), + appUserId: selectedUserId || null, + }); + toast.success(`'${plate}' plakalı araç eklendi!`); + onVehicleAdded?.(); + handleOpen(); + } catch (err) { + const msg = err?.response?.data?.message || "Araç eklenirken hata oluştu."; + toast.error(msg); + setError(msg); + } + }; + + return ( + + Yeni Araç Ekle + + {error && {error}} + setLicensePlate(e.target.value.toUpperCase())} + placeholder="34 ABC 123" + /> + + + + + + + + ); +} \ No newline at end of file diff --git a/src/widgets/layout/AssignUserModal.jsx b/src/widgets/layout/AssignUserModal.jsx new file mode 100644 index 00000000..fc0f2018 --- /dev/null +++ b/src/widgets/layout/AssignUserModal.jsx @@ -0,0 +1,115 @@ +import React, { useState, useEffect } from "react"; +import { + Button, + Dialog, + DialogHeader, + DialogBody, + DialogFooter, + Typography, +} from "@material-tailwind/react"; +import { toast } from "react-toastify"; +import apiClient from "../../api/axiosConfig.js"; + +export function AssignUserModal({ open, handleOpen, vehicle, onAssigned }) { + const [users, setUsers] = useState([]); + const [selectedUserId, setSelectedUserId] = useState(""); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (open) { + setSelectedUserId(vehicle?.appUserId || ""); + apiClient + .get("/Users") + .then((r) => setUsers(r.data || [])) + .catch(() => toast.error("Kullanıcı listesi yüklenemedi.")); + } + }, [open, vehicle]); + + const handleSubmit = async () => { + if (!selectedUserId) { + toast.error("Lütfen bir kullanıcı seçin."); + return; + } + setLoading(true); + try { + await apiClient.patch(`/admin/vehicles/${vehicle.id}/assign-user`, { + appUserId: selectedUserId, + }); + toast.success("Kullanıcı ataması güncellendi."); + onAssigned(); + handleOpen(); + } catch (err) { + toast.error(err?.response?.data?.message || "Atama yapılamadı."); + } finally { + setLoading(false); + } + }; + + return ( + + +
+ Kullanıcı Ata / Değiştir + {vehicle && ( + + Araç: {vehicle.licensePlate} + + )} +
+
+ + + {users.length === 0 ? ( + + Kullanıcı bulunamadı. + + ) : ( + users.map((u) => { + const isSelected = selectedUserId === u.id; + return ( +
setSelectedUserId(u.id)} + className={`flex items-center justify-between px-4 py-3 rounded-lg border cursor-pointer transition-all + ${isSelected + ? "border-gray-800 bg-gray-800 text-white" + : "border-blue-gray-100 hover:bg-blue-gray-50 text-blue-gray-700" + }`} + > +
+ + {u.fullName} + + {u.phoneNumber && ( + + {u.phoneNumber} + + )} +
+ {isSelected && ( +
+
+
+ )} +
+ ); + }) + )} + + + + + + +
+ ); +} \ 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..2613f211 --- /dev/null +++ b/src/widgets/layout/EditUserModal.jsx @@ -0,0 +1,146 @@ +import React, { useState, useEffect } from "react"; +import { Button, Dialog, DialogHeader, DialogBody, DialogFooter, Input, Typography } from "@material-tailwind/react"; +import apiClient from "../../api/axiosConfig.js"; +import { toast } from 'react-toastify'; + +export function EditUserModal({ open, handleOpen, userToEdit, onUserUpdated }) { + // Sadece Swagger'da istenen ve formda olan alanlar + const [formData, setFormData] = useState({ + fullName: "", + userName: "", // Sadece ekranda göstermek için, göndermeyeceğiz + password: "", + confirmPassword: "" + }); + + const notifyError = (msg) => { + toast.error(msg, { + className: "border-l-4 border-red-500 bg-white shadow-xl rounded-lg", + bodyClassName: "text-blue-gray-800 font-medium text-sm", + icon: "❌" + }); + }; + + useEffect(() => { + if (userToEdit) { + setFormData({ + fullName: userToEdit.fullName || "", + userName: userToEdit.userName || "", + password: "", + confirmPassword: "" + }); + } + }, [userToEdit]); + + const handleSubmit = async () => { + // Şifrelerden biri girilmişse Frontend kontrolü + if (formData.password || formData.confirmPassword) { + if (formData.password !== formData.confirmPassword) { + notifyError("Şifreler uyuşmuyor!"); + return; + } + if (formData.password.length < 6) { + notifyError("Şifre en az 6 karakter olmalıdır."); + return; + } + } + + // --- PAYLOAD HAZIRLIĞI (SWAGGER'A GÖRE) --- + // ID zaten URL'de (/Users/{id}) gidiyor, body'ye koymaya gerek yok. + // UserName Swagger'da yok, o yüzden onu da çıkardık. + const payload = { + fullName: formData.fullName + }; + + // Eğer şifre kutusu doluysa, password VE confirmPassword alanlarını ekle + // Swagger modelinde confirmPassword olduğu için onu da göndermeliyiz! + if (formData.password && formData.password.trim() !== "") { + payload.password = formData.password; + payload.confirmPassword = formData.confirmPassword; + } + + try { + await apiClient.put(`/Users/${userToEdit.id}`, payload); + + toast.success("Kullanıcı güncellendi!"); + onUserUpdated(); + handleOpen(); + } catch (err) { + console.error(err); + // Hata mesajını yakalama + const backendMsg = err.response?.data?.errors + ? JSON.stringify(err.response.data.errors) // Validation hatası dönerse + : (err.response?.data?.message || "Güncelleme başarısız."); + + notifyError(backendMsg); + } + }; + + return ( + + + Kullanıcı Düzenle + + + + {/* Ad Soyad */} +
+ Tam Ad + setFormData({...formData, fullName: e.target.value})} + size="lg" + /> +
+ + {/* Kullanıcı Adı (Salt Okunur) */} +
+ Kullanıcı Adı (Değiştirilemez) + +
+ + {/* Şifre Bölümü */} +
+ + 🔐 Şifre İşlemleri + + + Şifreyi değiştirmeyecekseniz boş bırakın. + + + setFormData({...formData, password: e.target.value})} + size="lg" + /> + setFormData({...formData, confirmPassword: e.target.value})} + size="lg" + /> +
+
+ + + + +
+ ); +} \ 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..0c4a9967 --- /dev/null +++ b/src/widgets/layout/EditVehicleModal.jsx @@ -0,0 +1,67 @@ +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: "", + isActive: true, + }); + const [error, setError] = useState(""); + + useEffect(() => { + if (vehicleToEdit) { + setFormData({ + licensePlate: vehicleToEdit.licensePlate || "", + isActive: vehicleToEdit.isActive, + }); + } else { + setFormData({ licensePlate: "", 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 { + await apiClient.put(`/admin/vehicles/${vehicleToEdit.id}`, { + licensePlate: formData.licensePlate, + isActive: formData.isActive, + appUserId: vehicleToEdit.appUserId ?? null, + }); + alert("Araç başarıyla güncellendi!"); + onVehicleUpdated(); + handleOpen(); + } catch (err) { + setError(err.response?.data?.message || "Güncelleme sırasında bir hata oluştu."); + } + }; + + return ( + + Aracı 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..056476f2 --- /dev/null +++ b/src/widgets/layout/VehicleQueueCard.jsx @@ -0,0 +1,28 @@ +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 ( +        // DEĞİŞİKLİK: Dikey boşluğu azaltmak için padding (p-1.5) küçültüldü. +       
+            {/* DEĞİŞİKLİK: Elemanlar arası boşluk (gap-2) azaltıldı. */} +           
+                {/* DEĞİŞİKLİK: Sıra numarasının fontu (text-md) küçültüldü. */} +                +                    #{index + 1} +                +                +                    {vehicle.licensePlate} +                + +           
+       
+    ); +} + +export default VehicleQueueCard; \ No newline at end of file diff --git a/src/widgets/layout/configurator.jsx b/src/widgets/layout/_configurator.jsx similarity index 100% rename from src/widgets/layout/configurator.jsx rename to src/widgets/layout/_configurator.jsx diff --git a/src/widgets/layout/dashboard-navbar.jsx b/src/widgets/layout/dashboard-navbar.jsx index d91e23f7..427c44ab 100644 --- a/src/widgets/layout/dashboard-navbar.jsx +++ b/src/widgets/layout/dashboard-navbar.jsx @@ -1,196 +1,100 @@ -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 React from "react"; +import { useLocation, Link, useNavigate } from "react-router-dom"; +import { IconButton, Navbar, Typography, Breadcrumbs } from "@material-tailwind/react"; +import { ArrowRightOnRectangleIcon, Bars3Icon } from "@heroicons/react/24/solid"; +import { useMaterialTailwindController, setOpenSidenav } from "@/context"; + +// ✨ URL'den gelen teknik ismi şık bir başlığa çeviren harita +const pageNamesMap = { + "arac-siralari": "Araç Sıraları", + "sira-yonetimi": "Sıra Yönetimi", + "ozel-gorev": "Özel Görev", + "kullanici-ayarlari": "Kullanıcılar", + "arac-listesi": "Araçlar", + "guzergahlar": "Güzergahlar", +}; export function DashboardNavbar() { - const [controller, dispatch] = useMaterialTailwindController(); - const { fixedNavbar, openSidenav } = controller; - const { pathname } = useLocation(); - const [layout, page] = pathname.split("/").filter((el) => el !== ""); + const [controller, dispatch] = useMaterialTailwindController(); + const { fixedNavbar, openSidenav } = controller; + const { pathname } = useLocation(); + const navigate = useNavigate(); + + // URL'yi parçalayıp layout (dashboard) ve sayfa (path) kısımlarını alıyoruz + const pathParts = pathname.split("/").filter((el) => el !== ""); + const layout = pathParts[0]; + const urlPath = pathParts[1]; + + // Eşleştirme listesinden Türkçe ismi çek, yoksa ham halini göster + const currentPageName = pageNamesMap[urlPath] || urlPath; - return ( - -
-
- { + localStorage.removeItem("authToken"); + localStorage.removeItem("userRole"); + navigate("/auth/giris"); // Yeni login path'imize yönlendiriyoruz + }; + + return ( + - - - {layout} - - - - {page} - - - - {page} - -
-
-
- -
- setOpenSidenav(dispatch, !openSidenav)} - > - - - - - - - - - - - - - - - - - -
- - New message from Laur - - - 13 minutes ago - -
-
- - -
- - New album by Travis Scott - - - 1 day ago - -
-
- -
- -
-
- - Payment successfully completed - - - 2 days ago + fullWidth + blurred={fixedNavbar} + > +
+
+ {/* Sayfa Yolu (Breadcrumbs) */} + + + + Anasayfa + + + + {currentPageName} + + + + {/* Büyük Sayfa Başlığı */} + + {currentPageName} +
+ + {/* Sağ Taraf: Mobil Menü ve Çıkış Butonu */} +
+ setOpenSidenav(dispatch, !openSidenav)} + > + + + + + +
- - -
- setOpenConfigurator(dispatch, true)} - > - - -
-
-
- ); +
+ + ); } DashboardNavbar.displayName = "/src/widgets/layout/dashboard-navbar.jsx"; -export default DashboardNavbar; +export default DashboardNavbar; \ No newline at end of file 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/index.js b/src/widgets/layout/index.js index e4fd0383..e1bd44ba 100644 --- a/src/widgets/layout/index.js +++ b/src/widgets/layout/index.js @@ -1,5 +1,4 @@ export * from "@/widgets/layout/sidenav"; export * from "@/widgets/layout/dashboard-navbar"; -export * from "@/widgets/layout/configurator"; export * from "@/widgets/layout/footer"; export * from "@/widgets/layout/navbar"; diff --git a/src/widgets/layout/sidenav.jsx b/src/widgets/layout/sidenav.jsx index cc7e6ffe..1208f18e 100644 --- a/src/widgets/layout/sidenav.jsx +++ b/src/widgets/layout/sidenav.jsx @@ -1,17 +1,14 @@ +import React from "react"; import PropTypes from "prop-types"; -import { Link, NavLink } from "react-router-dom"; +import { NavLink } from "react-router-dom"; import { XMarkIcon } from "@heroicons/react/24/outline"; -import { - Avatar, - Button, - IconButton, - Typography, -} from "@material-tailwind/react"; +import { Button, IconButton, Typography } from "@material-tailwind/react"; import { useMaterialTailwindController, setOpenSidenav } from "@/context"; -export function Sidenav({ brandImg, brandName, routes }) { +export function Sidenav({ 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 +16,79 @@ export function Sidenav({ brandImg, brandName, routes }) { }; return ( - +
+
+ Logo +
+
+ +
+ {routes && routes + .filter((route) => route.layout === "anasayfa") + .map(({ layout, pages }, key) => ( +
    + {pages && pages + .filter((page) => !page.hidden) // hidden olan route'ları menüden çıkar + .filter((page) => !page.roles || page.roles.includes(userRole)) + .map(({ icon, name, path }) => ( +
  • + { + if (window.innerWidth < 1200) { + setOpenSidenav(dispatch, false); + } + }} + > + {({ isActive }) => ( + + )} + +
  • + ))} +
+ ))} +
+ + ); } Sidenav.defaultProps = { - brandImg: "/img/logo-ct.png", - brandName: "Material Tailwind React", + brandName: "S.S. 75 NO'LU KOOP", }; Sidenav.propTypes = { - brandImg: PropTypes.string, brandName: PropTypes.string, - routes: PropTypes.arrayOf(PropTypes.object).isRequired, + routes: PropTypes.array.isRequired, }; -Sidenav.displayName = "/src/widgets/layout/sidnave.jsx"; - -export default Sidenav; +export default Sidenav; \ No newline at end of file