projec set up
This commit is contained in:
parent
6ddb62f3c1
commit
46b116f55c
|
|
@ -0,0 +1,24 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "src/index.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist'] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>TurkmenTv Sms ulgamy</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"name": "turkmentv-sms-dashboard-client",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"axios": "^1.7.7",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"framer-motion": "^11.11.9",
|
||||
"lucide-react": "^0.453.0",
|
||||
"react": "^18.3.1",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.27.0",
|
||||
"tailwind-merge": "^2.5.4",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"usehooks-ts": "^3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.11.1",
|
||||
"@types/node": "^22.7.8",
|
||||
"@types/react": "^18.3.10",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-react": "^4.3.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.11.1",
|
||||
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.12",
|
||||
"globals": "^15.9.0",
|
||||
"postcss": "^8.4.47",
|
||||
"tailwindcss": "^3.4.14",
|
||||
"typescript": "^5.5.3",
|
||||
"typescript-eslint": "^8.7.0",
|
||||
"vite": "^5.4.8"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.5 3C11.2239 3 12.8772 3.68482 14.0962 4.90381C15.3152 6.12279 16 7.77609 16 9.5C16 11.11 15.41 12.59 14.44 13.73L14.71 14H15.5L20.5 19L19 20.5L14 15.5V14.71L13.73 14.44C12.5505 15.4468 11.0507 15.9999 9.5 16C7.77609 16 6.12279 15.3152 4.90381 14.0962C3.68482 12.8772 3 11.2239 3 9.5C3 7.77609 3.68482 6.12279 4.90381 4.90381C6.12279 3.68482 7.77609 3 9.5 3ZM9.5 5C7 5 5 7 5 9.5C5 12 7 14 9.5 14C12 14 14 12 14 9.5C14 7 12 5 9.5 5Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 562 B |
|
|
@ -0,0 +1,31 @@
|
|||
<svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient x1="8.042%" y1="0%" x2="65.682%" y2="23.865%" id="a">
|
||||
<stop stop-color="#fff" stop-opacity="0" offset="0%"/>
|
||||
<stop stop-color="#fff" stop-opacity=".631" offset="63.146%"/>
|
||||
<stop stop-color="#fff" offset="100%"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g transform="translate(1 1)">
|
||||
<path d="M36 18c0-9.94-8.06-18-18-18" id="Oval-2" stroke="url(#a)" stroke-width="2">
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="rotate"
|
||||
from="0 18 18"
|
||||
to="360 18 18"
|
||||
dur="0.9s"
|
||||
repeatCount="indefinite" />
|
||||
</path>
|
||||
<circle fill="#fff" cx="36" cy="18" r="1">
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="rotate"
|
||||
from="0 18 18"
|
||||
to="360 18 18"
|
||||
dur="0.9s"
|
||||
repeatCount="indefinite" />
|
||||
</circle>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -0,0 +1,16 @@
|
|||
import { Outlet } from 'react-router-dom';
|
||||
import { AuthProvider } from './context/AuthContext';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
<div className="container">
|
||||
<AuthProvider>
|
||||
<Outlet />
|
||||
</AuthProvider>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import baseUrl from '@/lib/baseUrl';
|
||||
import { MessagesByTvAdmin } from '@/models/messagesByTvAdmis.model';
|
||||
import { IMyTvAdmins } from '@/models/my.tv.admins.model';
|
||||
import routes from '@/lib/routes';
|
||||
|
||||
export class Queries {
|
||||
// Sms ========================================================================================
|
||||
public static async getAdmins(): Promise<IMyTvAdmins> {
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
return await fetch(`${baseUrl.SMS_SRC}${routes.myTvAdmins}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
}).then((res) => res.json().then((res) => res as IMyTvAdmins));
|
||||
}
|
||||
|
||||
public static async getMessages(
|
||||
id: number,
|
||||
current_page: number,
|
||||
dateValue: string,
|
||||
activeSort: string,
|
||||
searchFetch: string,
|
||||
): Promise<MessagesByTvAdmin> {
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
return await fetch(
|
||||
`${baseUrl.SMS_SRC}${routes.messagesByTvAdmin(id)}?per_page=60&page=${current_page}${
|
||||
dateValue ? '&filter_by_date=' + dateValue.toString() : ''
|
||||
}${searchFetch ? '&search=' + searchFetch : ''}&order=${activeSort}`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
},
|
||||
).then((res) => res.json().then((res) => res as MessagesByTvAdmin));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
|
|
@ -0,0 +1,101 @@
|
|||
import { useContext, useEffect } from 'react';
|
||||
import { AuthContext } from '../context/AuthContext';
|
||||
import { SmsContext } from '../context/SmsContext';
|
||||
import FilterTable from './FilterTable';
|
||||
import { FitlerNumber } from './FitlerNumber';
|
||||
import Loader from './Loader';
|
||||
import ProtectedRoute from './ProtectedRoute';
|
||||
import SmsTable from './SmsTable';
|
||||
|
||||
const Dashboard = () => {
|
||||
const authContext = useContext(AuthContext);
|
||||
|
||||
if (!authContext) {
|
||||
throw new Error('AuthContext must be used within an AuthProvider');
|
||||
}
|
||||
|
||||
const { logout, userIsLoading } = authContext;
|
||||
|
||||
const smsContext = useContext(SmsContext);
|
||||
if (!smsContext) {
|
||||
throw new Error('smsContext must be used within an AuthProvider');
|
||||
}
|
||||
const { setCurrentPage, isError, smsTableData, currentPage } = smsContext;
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
}, [smsTableData, currentPage]);
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<main>
|
||||
<div className="container flex flex-col justify-center items-center h-screen">
|
||||
<h1
|
||||
className={`text-[44px] sm:text-[80px] leading-[100%] font-bold bg-fancyTitle bg-clip-text text-transparent text-center `}>
|
||||
Gysga belgi birikdirilmedi
|
||||
</h1>
|
||||
<button
|
||||
onClick={() => {
|
||||
logout();
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
type="submit"
|
||||
className="p-3 bg-[#7A7ACC] text-[18px] leading-[150%] font-medium text-white w-[200px] rounded-xl mt-[30px]">
|
||||
Yza
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
if (userIsLoading) {
|
||||
return (
|
||||
<main>
|
||||
<div className="container flex justify-center items-center h-screen">
|
||||
<Loader />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ProtectedRoute>
|
||||
<div className={`pt-[100px] pb-[200px]`}>
|
||||
<div className="container">
|
||||
<div className="flex gap-[40px]">
|
||||
<div className="flex flex-col gap-[32px]">
|
||||
<FitlerNumber />
|
||||
<span
|
||||
onClick={() => {
|
||||
logout();
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
className="text-textLight text-[16px] leading-[140%] font-semibold cursor-pointer w-full py-2">
|
||||
Ulgamdan çykmak
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col w-full gap-5">
|
||||
<FilterTable />
|
||||
<SmsTable />
|
||||
</div>
|
||||
{/* <div className="flex flex-col gap-5 w-full">
|
||||
<div className="flex gap-8 items-end justify-end w-full">
|
||||
<Sort />
|
||||
<Search />
|
||||
</div>
|
||||
<Calendar
|
||||
mode="single"
|
||||
// selected={date}
|
||||
// onSelect={setDate}
|
||||
className="rounded-md border"
|
||||
/>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ProtectedRoute>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useContext, useEffect, useRef, useState } from 'react';
|
||||
import { useOnClickOutside } from 'usehooks-ts';
|
||||
import { SmsContext } from '../context/SmsContext';
|
||||
import { Calendar } from './ui/calendar';
|
||||
|
||||
export const sortData = [
|
||||
{
|
||||
title: 'Newest',
|
||||
id: 'desc',
|
||||
},
|
||||
{
|
||||
title: 'Oldest',
|
||||
id: 'asc',
|
||||
},
|
||||
];
|
||||
|
||||
const FilterTable = () => {
|
||||
const smsContext = useContext(SmsContext);
|
||||
if (!smsContext) {
|
||||
throw new Error('smsContext must be used within an AuthProvider');
|
||||
}
|
||||
|
||||
const {
|
||||
datee,
|
||||
setDatee,
|
||||
setActiveSort,
|
||||
activeSort,
|
||||
searchValue,
|
||||
setSearchValue,
|
||||
setSearchFecth,
|
||||
searchFecth,
|
||||
timeDate,
|
||||
setTimeDate,
|
||||
smsTableData,
|
||||
} = smsContext;
|
||||
|
||||
const calendarRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [calendar, setCalendar] = useState(false);
|
||||
|
||||
const [filterActive, setFilterActive] = useState(false);
|
||||
|
||||
const handleClickOutside = () => {
|
||||
setCalendar(false);
|
||||
};
|
||||
|
||||
useOnClickOutside(calendarRef, handleClickOutside);
|
||||
|
||||
const checkSortActive = () => {
|
||||
if (activeSort === 'asc' || datee || searchFecth) {
|
||||
setFilterActive(true);
|
||||
} else {
|
||||
setFilterActive(false);
|
||||
}
|
||||
};
|
||||
|
||||
const resetFilters = () => {
|
||||
setActiveSort('desc');
|
||||
setDatee(undefined);
|
||||
setSearchFecth('');
|
||||
setSearchValue('');
|
||||
};
|
||||
const resetSearch = () => {
|
||||
setSearchFecth('');
|
||||
setSearchValue('');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
checkSortActive();
|
||||
}, [activeSort, datee, searchFecth]);
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<div className="flex gap-6 w-full justify-between items-start">
|
||||
<div className="flex flex-col gap-[16px]">
|
||||
<div className="bg-[#F0F0FA] rounded-full flex gap-6 shadow-tableShadow">
|
||||
<div className="flex items-center pr-[24px] w-fit gap-[24px]">
|
||||
<div
|
||||
className={clsx(
|
||||
'leading-[115%] text-[14px] py-[16px] px-[24px] bg-[#E1E1F5] text-black font-semibold rounded-l-full',
|
||||
{},
|
||||
)}>
|
||||
Filtr
|
||||
</div>
|
||||
{sortData.map((item, i) => (
|
||||
<div
|
||||
key={i}
|
||||
onClick={() => {
|
||||
setActiveSort(item.id);
|
||||
}}
|
||||
className={clsx('leading-[115%] text-[14px] cursor-pointer', {
|
||||
'text-fillButtonAccentDefault font-[600]': activeSort === item.id,
|
||||
'text-textDarkt': activeSort !== item.id,
|
||||
})}>
|
||||
{item.title}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="relative">
|
||||
<div
|
||||
onClick={() => setCalendar((prev) => !prev)}
|
||||
className={clsx(
|
||||
'leading-[115%] text-[14px] text-textDarkt cursor-pointer w-fit ',
|
||||
{},
|
||||
)}>
|
||||
Kalendar
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{calendar && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20, pointerEvents: 'none' }}
|
||||
animate={{ opacity: 1, y: 0, pointerEvents: 'all' }}
|
||||
exit={{ opacity: 0, y: -20, pointerEvents: 'none' }}
|
||||
transition={{ ease: 'easeInOut' }}
|
||||
ref={calendarRef}
|
||||
className="absolute top-5">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={datee}
|
||||
onSelect={setDatee}
|
||||
initialFocus
|
||||
onDayClick={() => setCalendar(false)}
|
||||
className={clsx(
|
||||
'my-20 bg-[#F5F5FA] w-fit rounded-[8px] -translate-y-[50px] -translate-x-[60px] shadow-[0_2px_32px_rgba(0,0,0,0.3)] transition-all',
|
||||
)}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{datee && smsTableData?.data.length !== 0 && (
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Sagat boýunça gözle... 18:1"
|
||||
onChange={(e) => setTimeDate(e.target.value)}
|
||||
value={timeDate}
|
||||
className="w-full outline-none px-6 py-3 shadow-tableShadow bg-[#F0F0FA] rounded-full"
|
||||
/>
|
||||
)}
|
||||
|
||||
{filterActive ? (
|
||||
<div
|
||||
className="cursor-pointer h-fit flex items-center gap-[8px] stroke-lightOutline "
|
||||
onClick={resetFilters}>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="transition-all ease-out duration-[0.3s]">
|
||||
<path
|
||||
d="M16 16L12 12M12 12L8 8M12 12L16 8M12 12L8 16"
|
||||
stroke="current"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
)
|
||||
</svg>
|
||||
<h1 className=" text-[14px] font-medium w-fit">Filteri aýyr</h1>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="flex shadow-tableShadow rounded-full w-fit">
|
||||
<div className="flex items-center justify-between bg-[#F0F0FA] w-fit py-3 px-4 rounded-[9999px_0_0_9999px] ">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Gözle..."
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
value={searchValue}
|
||||
className="w-full bg-transparent outline-none"
|
||||
/>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="cursor-pointer stroke-textGray hover:stroke-fillNavyBlue transition-all ease-out duration-[0.3s]"
|
||||
onClick={resetSearch}>
|
||||
{searchFecth ? (
|
||||
<path
|
||||
d="M16 16L12 12M12 12L8 8M12 12L16 8M12 12L8 16"
|
||||
stroke="current"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
) : null}
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setSearchFecth(searchValue)}
|
||||
className="bg-fillButtonAccentDefault rounded-[0_9999px_9999px_0] px-4 py-[12px] cursor-pointer">
|
||||
<img src={'/search.svg'} alt="search" className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterTable;
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
import { useContext, useEffect } from 'react';
|
||||
import { SmsContext } from '@/context/SmsContext';
|
||||
import clsx from 'clsx';
|
||||
import { Queries } from '@/api/queries';
|
||||
import Loader from './Loader';
|
||||
|
||||
export const FitlerNumber = () => {
|
||||
const smsContext = useContext(SmsContext);
|
||||
if (!smsContext) {
|
||||
throw new Error('smsContext must be used within an AuthProvider');
|
||||
}
|
||||
const { activeNumber, setActiveNumber, smsData, tableIsLoading, setSmsData, setIsError } =
|
||||
smsContext;
|
||||
|
||||
const getAdmins = () => {
|
||||
try {
|
||||
Queries.getAdmins().then((res) => {
|
||||
setSmsData(res);
|
||||
setActiveNumber(res.data[0].id);
|
||||
if (!res.data) {
|
||||
setIsError(true);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
setIsError(true);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getAdmins();
|
||||
}, []);
|
||||
|
||||
if (!smsData) {
|
||||
return (
|
||||
<div className="w-[314px]">
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-[#F0F0FA] rounded-3xl w-[314px] shadow-tableShadow">
|
||||
<div className="font-semibold leading-[125%] bg-[#E1E1F5] rounded-[25px_25px_0_0] py-6 px-5">
|
||||
Gysga belgi boýunça filtr
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col w-full">
|
||||
{smsData?.data.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className={clsx(
|
||||
`h-[60px] px-6 py-5 font-semibold cursor-pointer hover:text-fillButtonAccentDefault transition-all duration-75`,
|
||||
{
|
||||
'text-fillButtonAccentDefault': item.id === activeNumber,
|
||||
},
|
||||
)}
|
||||
onClick={() => setActiveNumber(item.id)}>
|
||||
{item.login}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import { CSSProperties } from 'react';
|
||||
|
||||
interface IProps {
|
||||
height?: CSSProperties['height'];
|
||||
}
|
||||
|
||||
const Loader = ({ height = 'auto' }: IProps) => {
|
||||
return (
|
||||
<div className="loader my-6 w-full flex justify-center items-center" style={{ height }}>
|
||||
<img src={'/spin-blue.svg'} alt="loader" className="object-contain w-[50px] h-[50px]" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Loader;
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { ReactNode, useEffect, useContext } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { AuthContext } from '@/context/AuthContext';
|
||||
|
||||
interface ProtectedRouteProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
|
||||
const authContext = useContext(AuthContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
if (!authContext) {
|
||||
throw new Error('AuthContext must be used within an AuthProvider');
|
||||
}
|
||||
|
||||
const { user } = authContext;
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
navigate('/');
|
||||
console.log('first');
|
||||
}
|
||||
}, [user, navigate]);
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export default ProtectedRoute;
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
import React, { useContext, useEffect } from 'react';
|
||||
|
||||
import { Queries } from '@/api/queries';
|
||||
import { SmsContext } from '@/context/SmsContext';
|
||||
import SmsPagination from './smsTable/SmsPagination';
|
||||
import SmsTableBody from './smsTable/SmsTableBody';
|
||||
import SmsTableHead from './smsTable/SmsTableHead';
|
||||
import Loader from './Loader';
|
||||
|
||||
const SmsTable = () => {
|
||||
const smsContext = useContext(SmsContext);
|
||||
if (!smsContext) {
|
||||
throw new Error('smsContext must be used within an AuthProvider');
|
||||
}
|
||||
const {
|
||||
tableIsLoading,
|
||||
smsData,
|
||||
smsTableData,
|
||||
setSmsTableData,
|
||||
setTableIsLoading,
|
||||
setCurrentPage,
|
||||
setIsError,
|
||||
currentPage,
|
||||
activeNumber,
|
||||
searchFecth,
|
||||
activeSort,
|
||||
dateValue,
|
||||
timeDate,
|
||||
} = smsContext;
|
||||
|
||||
const getMessages = () => {
|
||||
if (smsData && activeNumber) {
|
||||
setTableIsLoading(true);
|
||||
|
||||
try {
|
||||
Queries.getMessages(activeNumber, currentPage, dateValue, activeSort, searchFecth).then(
|
||||
(res) => {
|
||||
setSmsTableData(res);
|
||||
setTableIsLoading(false);
|
||||
|
||||
if (!res.data) {
|
||||
setTableIsLoading(true);
|
||||
|
||||
setIsError(true);
|
||||
}
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
setTableIsLoading(true);
|
||||
|
||||
setIsError(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getMessages();
|
||||
}, [smsData, currentPage, activeNumber, searchFecth, activeSort, dateValue]);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
}, [activeNumber]);
|
||||
|
||||
if (tableIsLoading) {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return smsTableData?.data.length ? (
|
||||
<div className="flex flex-col w-full gap-[24px] max-w-[900px]">
|
||||
<div className="flex flex-col w-full rounded-[25px] overflow-hidden shadow-tableShadow">
|
||||
<SmsTableHead />
|
||||
<SmsTableBody />
|
||||
</div>
|
||||
{smsTableData.meta.last_page > 1 ? <SmsPagination /> : null}
|
||||
</div>
|
||||
) : (
|
||||
<h1 className="mt-[20px] text-[32px] leading-[120%] font-semibold text-textBlack">
|
||||
Нет результатов!
|
||||
</h1>
|
||||
);
|
||||
};
|
||||
|
||||
export default SmsTable;
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
// Working pagination =========================================================================================================
|
||||
|
||||
// 'use client';
|
||||
// import Loader from '@/components/Loader';
|
||||
// import { SmsContext } from '@/context/SmsContext';
|
||||
// import clsx from 'clsx';
|
||||
// import React, { useContext } from 'react';
|
||||
|
||||
// const SmsPagination = () => {
|
||||
// const smsContext = useContext(SmsContext);
|
||||
// if (!smsContext) {
|
||||
// throw new Error('smsContext must be used within an AuthProvider');
|
||||
// }
|
||||
// const { setCurrentPage, smsTableData } = smsContext;
|
||||
|
||||
// if (!smsTableData) {
|
||||
// return (
|
||||
// <div>
|
||||
// <Loader />
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <div className="flex flex-col w-full gap-[10px] p-[20px] bg-fillLightBgLightContr rounded-[25px] items-center">
|
||||
// <h3 className="text-textDarkt leading-[150%] text-[16px] font-[400]">Sahypalar</h3>
|
||||
// <div className="flex gap-[24px] items-center justify-center">
|
||||
// <svg
|
||||
// className={clsx('cursor-pointer', {
|
||||
// 'pointer-events-none cursor-default': smsTableData?.meta.current_page === 1,
|
||||
// })}
|
||||
// onClick={() =>
|
||||
// setCurrentPage(
|
||||
// smsTableData?.meta.current_page !== 1
|
||||
// ? smsTableData.meta.current_page - 1
|
||||
// : smsTableData.meta.current_page,
|
||||
// )
|
||||
// }
|
||||
// width="24"
|
||||
// height="24"
|
||||
// viewBox="0 0 24 24"
|
||||
// fill="none"
|
||||
// xmlns="http://www.w3.org/2000/svg">
|
||||
// <path
|
||||
// d="M8.5415 12.8333L10.9165 15.2083C11.0832 15.375 11.1632 15.5694 11.1565 15.7916C11.1498 16.0138 11.0698 16.2083 10.9165 16.375C10.7498 16.5416 10.5521 16.6286 10.3232 16.6358C10.0943 16.643 9.89623 16.563 9.729 16.3958L5.9165 12.5833C5.74984 12.4166 5.6665 12.2222 5.6665 12C5.6665 11.7777 5.74984 11.5833 5.9165 11.4166L9.729 7.60412C9.89567 7.43745 10.0937 7.35773 10.3232 7.36495C10.5526 7.37217 10.7504 7.45884 10.9165 7.62495C11.0693 7.79162 11.1493 7.98606 11.1565 8.20828C11.1637 8.43051 11.0837 8.62495 10.9165 8.79162L8.5415 11.1666H17.8332C18.0693 11.1666 18.2673 11.2466 18.4273 11.4066C18.5873 11.5666 18.6671 11.7644 18.6665 12C18.6665 12.2361 18.5865 12.4341 18.4265 12.5941C18.2665 12.7541 18.0687 12.8338 17.8332 12.8333H8.5415Z"
|
||||
// fill={smsTableData?.meta.current_page === 1 ? '#C0C0CC' : '#878799'}
|
||||
// />
|
||||
// </svg>
|
||||
// <span className="text-fillLinkRest text-[14px] font-[500] leading-[125%]">
|
||||
// {smsTableData?.meta.current_page}/{smsTableData?.meta.last_page}
|
||||
// </span>
|
||||
// <svg
|
||||
// onClick={() =>
|
||||
// setCurrentPage(
|
||||
// smsTableData?.meta.current_page !== smsTableData.meta.last_page
|
||||
// ? smsTableData.meta.current_page + 1
|
||||
// : smsTableData.meta.last_page,
|
||||
// )
|
||||
// }
|
||||
// className={clsx('cursor-pointer', {
|
||||
// 'pointer-events-none cursor-default':
|
||||
// smsTableData?.meta.current_page === smsTableData?.meta.last_page,
|
||||
// })}
|
||||
// width="24"
|
||||
// height="24"
|
||||
// viewBox="0 0 24 24"
|
||||
// fill="none"
|
||||
// xmlns="http://www.w3.org/2000/svg">
|
||||
// <path
|
||||
// d="M15.4585 12.8332H6.16683C5.93072 12.8332 5.73294 12.7532 5.5735 12.5932C5.41405 12.4332 5.33405 12.2354 5.3335 11.9999C5.3335 11.7638 5.4135 11.566 5.5735 11.4066C5.7335 11.2471 5.93127 11.1671 6.16683 11.1666H15.4585L13.0835 8.79155C12.9168 8.62489 12.8368 8.43044 12.8435 8.20822C12.8502 7.986 12.9302 7.79155 13.0835 7.62489C13.2502 7.45822 13.4482 7.37155 13.6777 7.36489C13.9071 7.35822 14.1049 7.43794 14.271 7.60405L18.0835 11.4166C18.1668 11.4999 18.226 11.5902 18.261 11.6874C18.296 11.7846 18.3132 11.8888 18.3127 11.9999C18.3127 12.111 18.2954 12.2152 18.261 12.3124C18.2266 12.4096 18.1674 12.4999 18.0835 12.5832L14.271 16.3957C14.1043 16.5624 13.9066 16.6424 13.6777 16.6357C13.4488 16.6291 13.2507 16.5421 13.0835 16.3749C12.9307 16.2082 12.8507 16.0138 12.8435 15.7916C12.8363 15.5693 12.9163 15.3749 13.0835 15.2082L15.4585 12.8332Z"
|
||||
// fill={
|
||||
// smsTableData?.meta.current_page === smsTableData?.meta.last_page
|
||||
// ? '#C0C0CC'
|
||||
// : '#878799'
|
||||
// }
|
||||
// />
|
||||
// </svg>
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
|
||||
// export default SmsPagination;
|
||||
|
||||
// Working pagination =================================================================================================
|
||||
|
||||
import Loader from '@/components/Loader';
|
||||
import { SmsContext } from '@/context/SmsContext';
|
||||
import clsx from 'clsx';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
const SmsPagination = () => {
|
||||
const smsContext = useContext(SmsContext);
|
||||
if (!smsContext) {
|
||||
throw new Error('smsContext must be used within an AuthProvider');
|
||||
}
|
||||
const { setCurrentPage, smsTableData } = smsContext;
|
||||
|
||||
if (!smsTableData) {
|
||||
return (
|
||||
<div>
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const { current_page, last_page } = smsTableData.meta;
|
||||
|
||||
const generatePageNumbers = () => {
|
||||
const pages = [];
|
||||
if (last_page <= 7) {
|
||||
for (let i = 1; i <= last_page; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
} else {
|
||||
pages.push(1);
|
||||
if (current_page > 4) pages.push('...');
|
||||
const startPage = Math.max(2, current_page - 2);
|
||||
const endPage = Math.min(last_page - 1, current_page + 2);
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
if (current_page < last_page - 3) pages.push('...');
|
||||
pages.push(last_page);
|
||||
}
|
||||
return pages;
|
||||
};
|
||||
|
||||
const pages = generatePageNumbers();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full gap-[10px] p-[20px] bg-fillLightBgLightContr rounded-[25px] items-center">
|
||||
<h3 className="text-textDarkt leading-[150%] text-[16px] font-[400]">Sahypalar</h3>
|
||||
<div className="flex gap-[24px] items-center justify-center">
|
||||
<svg
|
||||
className={clsx('cursor-pointer', {
|
||||
'pointer-events-none cursor-default': current_page === 1,
|
||||
})}
|
||||
onClick={() => setCurrentPage(current_page !== 1 ? current_page - 1 : current_page)}
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M8.5415 12.8333L10.9165 15.2083C11.0832 15.375 11.1632 15.5694 11.1565 15.7916C11.1498 16.0138 11.0698 16.2083 10.9165 16.375C10.7498 16.5416 10.5521 16.6286 10.3232 16.6358C10.0943 16.643 9.89623 16.563 9.729 16.3958L5.9165 12.5833C5.74984 12.4166 5.6665 12.2222 5.6665 12C5.6665 11.7777 5.74984 11.5833 5.9165 11.4166L9.729 7.60412C9.89567 7.43745 10.0937 7.35773 10.3232 7.36495C10.5526 7.37217 10.7504 7.45884 10.9165 7.62495C11.0693 7.79162 11.1493 7.98606 11.1565 8.20828C11.1637 8.43051 11.0837 8.62495 10.9165 8.79162L8.5415 11.1666H17.8332C18.0693 11.1666 18.2673 11.2466 18.4273 11.4066C18.5873 11.5666 18.6671 11.7644 18.6665 12C18.6665 12.2361 18.5865 12.4341 18.4265 12.5941C18.2665 12.7541 18.0687 12.8338 17.8332 12.8333H8.5415Z"
|
||||
fill={current_page === 1 ? '#C0C0CC' : '#878799'}
|
||||
/>
|
||||
</svg>
|
||||
{pages.map((page, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className={clsx('text-[14px] font-[500] leading-[125%]', {
|
||||
'cursor-pointer text-[#878799]': page !== '...' && page !== current_page,
|
||||
'cursor-default text-gray-400': page === '...',
|
||||
'cursor-pointer text-[#4D4D99] font-semibold': page === current_page,
|
||||
})}
|
||||
onClick={() => typeof page === 'number' && setCurrentPage(page)}>
|
||||
{page}
|
||||
</span>
|
||||
))}
|
||||
<svg
|
||||
onClick={() => setCurrentPage(current_page !== last_page ? current_page + 1 : last_page)}
|
||||
className={clsx('cursor-pointer', {
|
||||
'pointer-events-none cursor-default': current_page === last_page,
|
||||
})}
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M15.4585 12.8332H6.16683C5.93072 12.8332 5.73294 12.7532 5.5735 12.5932C5.41405 12.4332 5.33405 12.2354 5.3335 11.9999C5.3335 11.7638 5.4135 11.566 5.5735 11.4066C5.7335 11.2471 5.93127 11.1671 6.16683 11.1666H15.4585L13.0835 8.79155C12.9168 8.62489 12.8368 8.43044 12.8435 8.20822C12.8502 7.986 12.9302 7.79155 13.0835 7.62489C13.2502 7.45822 13.4482 7.37155 13.6777 7.36489C13.9071 7.35822 14.1049 7.43794 14.271 7.60405L18.0835 11.4166C18.1668 11.4999 18.226 11.5902 18.261 11.6874C18.296 11.7846 18.3132 11.8888 18.3127 11.9999C18.3127 12.111 18.2954 12.2152 18.261 12.3124C18.2266 12.4096 18.1674 12.4999 18.0835 12.5832L14.271 16.3957C14.1043 16.5624 13.9066 16.6424 13.6777 16.6357C13.4488 16.6291 13.2507 16.5421 13.0835 16.3749C12.9307 16.2082 12.8507 16.0138 12.8435 15.7916C12.8363 15.5693 12.9163 15.3749 13.0835 15.2082L15.4585 12.8332Z"
|
||||
fill={current_page === last_page ? '#C0C0CC' : '#878799'}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SmsPagination;
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
import React, { useContext, useEffect } from 'react';
|
||||
import SmsTableHead from './SmsTableHead';
|
||||
import SmsTableBody from './SmsTableBody';
|
||||
import { SmsContext } from '@/context/SmsContext';
|
||||
import Loader from '@/components/Loader';
|
||||
import SmsPagination from './SmsPagination';
|
||||
import { Queries } from '@/api/queries';
|
||||
|
||||
const SmsTable = () => {
|
||||
const smsContext = useContext(SmsContext);
|
||||
if (!smsContext) {
|
||||
throw new Error('smsContext must be used within an AuthProvider');
|
||||
}
|
||||
const {
|
||||
tableIsLoading,
|
||||
smsData,
|
||||
smsTableData,
|
||||
setSmsTableData,
|
||||
setTableIsLoading,
|
||||
setCurrentPage,
|
||||
setIsError,
|
||||
currentPage,
|
||||
activeNumber,
|
||||
searchFecth,
|
||||
activeSort,
|
||||
dateValue,
|
||||
timeDate,
|
||||
} = smsContext;
|
||||
|
||||
const getMessages = () => {
|
||||
if (smsData && activeNumber) {
|
||||
setTableIsLoading(true);
|
||||
|
||||
try {
|
||||
Queries.getMessages(activeNumber, currentPage, dateValue, activeSort, searchFecth).then(
|
||||
(res) => {
|
||||
setSmsTableData(res);
|
||||
setTableIsLoading(false);
|
||||
|
||||
if (!res.data) {
|
||||
setTableIsLoading(true);
|
||||
|
||||
setIsError(true);
|
||||
}
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
setTableIsLoading(true);
|
||||
|
||||
setIsError(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getMessages();
|
||||
}, [smsData, currentPage, activeNumber, searchFecth, activeSort, dateValue]);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
}, [activeNumber]);
|
||||
|
||||
if (tableIsLoading) {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return smsTableData?.data.length ? (
|
||||
<div className="flex flex-col w-full gap-[24px] max-w-[900px]">
|
||||
<div className="flex flex-col w-full rounded-[25px] overflow-hidden shadow-tableShadow">
|
||||
<SmsTableHead />
|
||||
<SmsTableBody />
|
||||
</div>
|
||||
{smsTableData.meta.last_page > 1 ? <SmsPagination /> : null}
|
||||
</div>
|
||||
) : (
|
||||
<h1 className="mt-[20px] text-[32px] leading-[120%] font-semibold text-textBlack">
|
||||
Нет результатов!
|
||||
</h1>
|
||||
);
|
||||
};
|
||||
|
||||
export default SmsTable;
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import SmsTableRow from './SmsTableRow';
|
||||
|
||||
import { SmsContext } from '@/context/SmsContext';
|
||||
|
||||
const SmsTableBody = () => {
|
||||
const smsContext = useContext(SmsContext);
|
||||
if (!smsContext) {
|
||||
throw new Error('smsContext must be used within an AuthProvider');
|
||||
}
|
||||
const { smsTableData, currentPage, timeDate } = smsContext;
|
||||
|
||||
console.log(smsTableData?.data[0].dt.slice(11, 15));
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full gap-[24px]">
|
||||
<div className="flex flex-col w-full rounded-b-[25px] ">
|
||||
{smsTableData?.data
|
||||
.filter((item) => (timeDate ? item.dt.slice(11, 15).includes(timeDate) : item))
|
||||
.map((row, index) => (
|
||||
<SmsTableRow
|
||||
index={currentPage !== 1 ? 30 * (currentPage - 1) + index + 1 : index + 1}
|
||||
key={row.id}
|
||||
sms={row.msg}
|
||||
date={row.dt}
|
||||
number={row.client}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SmsTableBody;
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
const SmsTableHead = () => {
|
||||
return (
|
||||
<div className="flex w-full justify-between bg-fillTableHead">
|
||||
<div className="w-[80px] px-[24px] py-[20px]">
|
||||
<span className="text-black font-[600] leading-[125%] text-[16px]">ID</span>
|
||||
</div>
|
||||
<div className="w-[200px] px-[24px] py-[20px]">
|
||||
<span className="text-black font-[600] leading-[125%] text-[16px]">Telefon belgi</span>
|
||||
</div>
|
||||
<div className="w-[380px] px-[24px] py-[20px]">
|
||||
<span className="text-black font-[600] leading-[125%] text-[16px]">SMS</span>
|
||||
</div>
|
||||
<div className="w-[180px] px-[24px] py-[20px]">
|
||||
<span className="text-black font-[600] leading-[125%] text-[16px]">Wagty</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SmsTableHead;
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import clsx from 'clsx';
|
||||
|
||||
interface IProps {
|
||||
index: number;
|
||||
number: string;
|
||||
sms: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
const SmsTableRow = ({ index, number, sms, date }: IProps) => {
|
||||
const formateDate = (date: string) => {
|
||||
const parsedDate = date.split(' ')[0].split('-').reverse().join('.');
|
||||
const parsedTime = date.split(' ')[1];
|
||||
return {
|
||||
parsedDate,
|
||||
parsedTime,
|
||||
};
|
||||
};
|
||||
|
||||
const { parsedDate, parsedTime } = formateDate(date);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(' flex justify-between', {
|
||||
'bg-fillTableRow': index % 2 !== 0,
|
||||
'bg-fillTableRow2': index % 2 === 0,
|
||||
})}>
|
||||
<div className="w-[80px] px-[24px] py-[20px]">
|
||||
<span className="text-black font-[600] leading-[125%] text-[16px]">{index}</span>
|
||||
</div>
|
||||
<div className="w-[200px] px-[24px] py-[20px]">
|
||||
<span className="text-textDarkt leading-[125%] text-[16px]">+{number}</span>
|
||||
</div>
|
||||
<div className="w-[380px] px-[24px] py-[20px] flex flex-wrap break-words text-textDarkt">
|
||||
<span className="text-textDarkt leading-[125%] text-[16px] inline-block w-[340px] break-words">
|
||||
{sms}
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-[180px] px-[24px] py-[20px] flex flex-col">
|
||||
<span className="text-textDarkt leading-[125%] text-[16px]">{parsedDate}ý.</span>
|
||||
<span className="text-textDarkt leading-[125%] text-[14px]">{parsedTime}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SmsTableRow;
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Button.displayName = "Button"
|
||||
|
||||
export { Button, buttonVariants }
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import { DayPicker } from 'react-day-picker';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import { buttonVariants } from '@/components/ui/button';
|
||||
|
||||
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
|
||||
|
||||
function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
|
||||
return (
|
||||
<DayPicker
|
||||
showOutsideDays={showOutsideDays}
|
||||
className={cn('p-3', className)}
|
||||
classNames={{
|
||||
months: 'flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0',
|
||||
month: 'space-y-4',
|
||||
caption: 'flex justify-center pt-1 relative items-center',
|
||||
caption_label: 'text-sm font-medium',
|
||||
nav: 'space-x-1 flex items-center',
|
||||
nav_button: cn(
|
||||
buttonVariants({ variant: 'outline' }),
|
||||
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
||||
),
|
||||
nav_button_previous: 'absolute left-1',
|
||||
nav_button_next: 'absolute right-1',
|
||||
table: 'w-full border-collapse space-y-1',
|
||||
head_row: 'flex',
|
||||
head_cell: 'text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]',
|
||||
row: 'flex w-full mt-2',
|
||||
cell: 'h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20',
|
||||
day: cn(
|
||||
buttonVariants({ variant: 'ghost' }),
|
||||
'h-9 w-9 p-0 font-normal aria-selected:bg-fillButtonAccentDefault hover:bg-fillButtonAccentDefault hover:text-white',
|
||||
),
|
||||
day_range_end: 'day-range-end',
|
||||
day_selected:
|
||||
'bg-fillButtonAccentDefault text-white rounded-full hover:bg-fillButtonAccentDefault hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
|
||||
day_today: 'bg-fillTableHead text-black',
|
||||
day_outside:
|
||||
'day-outside text-muted-foreground opacity-50 aria-selected:bg-black/50 aria-selected:text-muted-foreground aria-selected:opacity-30',
|
||||
day_disabled: 'text-muted-foreground opacity-50',
|
||||
day_range_middle: 'aria-selected:bg-accent aria-selected:text-accent-foreground',
|
||||
day_hidden: 'invisible',
|
||||
...classNames,
|
||||
}}
|
||||
components={{
|
||||
IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
|
||||
IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Calendar.displayName = 'Calendar';
|
||||
|
||||
export { Calendar };
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
'use client';
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
ReactNode,
|
||||
FC,
|
||||
SetStateAction,
|
||||
Dispatch,
|
||||
} from 'react';
|
||||
import axios from 'axios';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
interface AuthContextType {
|
||||
user: User | null;
|
||||
login: (login: string, password: string) => Promise<void>;
|
||||
checkUserLoggedIn: () => Promise<void>;
|
||||
logout: () => Promise<void>;
|
||||
userIsLoading: boolean;
|
||||
setUserIsLoading: Dispatch<SetStateAction<boolean>>;
|
||||
|
||||
userLogedIn: boolean | undefined;
|
||||
setUserLogedIn: Dispatch<SetStateAction<boolean | undefined>>;
|
||||
}
|
||||
|
||||
interface User {
|
||||
id: number;
|
||||
login: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
interface AuthProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
|
||||
export const AuthProvider: FC<AuthProviderProps> = ({ children }) => {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [userLogedIn, setUserLogedIn] = useState<boolean | undefined>();
|
||||
// const router = useRouter();
|
||||
const [userIsLoading, setUserIsLoading] = useState<boolean>(false);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
checkUserLoggedIn();
|
||||
}, []);
|
||||
|
||||
const checkUserLoggedIn = async () => {
|
||||
setUserIsLoading(true);
|
||||
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
if (token) {
|
||||
try {
|
||||
const res = await axios.post(
|
||||
'https://extra.turkmentv.gov.tm/api/auth/me',
|
||||
{},
|
||||
{
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
},
|
||||
);
|
||||
setUser(res.data);
|
||||
|
||||
// router.push('/sms/dashboard');
|
||||
|
||||
navigate('/dashboard');
|
||||
setUserIsLoading(false);
|
||||
} catch (error) {
|
||||
await refreshAccessToken();
|
||||
}
|
||||
} else {
|
||||
// router.push('/sms/sign_up');
|
||||
navigate('/');
|
||||
|
||||
setUserIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const login = async (login: string, password: string) => {
|
||||
try {
|
||||
const res = await axios.post('https://extra.turkmentv.gov.tm/api/auth/login', {
|
||||
login: login,
|
||||
password: password,
|
||||
});
|
||||
localStorage.setItem('access_token', res.data.access_token);
|
||||
setUser(res.data);
|
||||
// router.push('/sms/dashboard');
|
||||
navigate('/dashboard');
|
||||
|
||||
setUserIsLoading(false);
|
||||
} catch (error) {
|
||||
setUserLogedIn(false);
|
||||
}
|
||||
};
|
||||
|
||||
const refreshAccessToken = async () => {
|
||||
const token = localStorage.getItem('access_token');
|
||||
if (token) {
|
||||
try {
|
||||
const res = await axios.post(
|
||||
'https://extra.turkmentv.gov.tm/api/auth/refresh',
|
||||
{},
|
||||
{
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
},
|
||||
);
|
||||
localStorage.setItem('access_token', res.data.access_token);
|
||||
const meRes = await axios.post(
|
||||
'https://extra.turkmentv.gov.tm/api/auth/me',
|
||||
{},
|
||||
{
|
||||
headers: { Authorization: `Bearer ${res.data.access_token}` },
|
||||
},
|
||||
);
|
||||
setUser(meRes.data);
|
||||
setUserIsLoading(false);
|
||||
} catch (error) {
|
||||
localStorage.removeItem('access_token');
|
||||
setUser(null);
|
||||
// router.push('/sms/sign_up');
|
||||
navigate('/');
|
||||
|
||||
setUserIsLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
const token = localStorage.getItem('access_token');
|
||||
if (token) {
|
||||
await axios.post(
|
||||
'https://extra.turkmentv.gov.tm/api/auth/logout',
|
||||
{},
|
||||
{
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
},
|
||||
);
|
||||
localStorage.removeItem('access_token');
|
||||
setUser(null);
|
||||
// router.push('/sms/sign_up');
|
||||
navigate('/');
|
||||
|
||||
setUserIsLoading(false);
|
||||
setUserLogedIn(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
user,
|
||||
login,
|
||||
logout,
|
||||
checkUserLoggedIn,
|
||||
userIsLoading,
|
||||
setUserIsLoading,
|
||||
userLogedIn,
|
||||
setUserLogedIn,
|
||||
}}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export { AuthContext };
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
import { format } from 'date-fns';
|
||||
import { Dispatch, FC, ReactNode, SetStateAction, createContext, useState } from 'react';
|
||||
import { MessagesByTvAdmin } from '../models/messagesByTvAdmis.model';
|
||||
import { IMyTvAdmins } from '../models/my.tv.admins.model';
|
||||
|
||||
interface ISmsContext {
|
||||
activeNumber: number | undefined;
|
||||
setActiveNumber: Dispatch<SetStateAction<number | undefined>>;
|
||||
tableIsLoading: boolean | undefined;
|
||||
setTableIsLoading: Dispatch<SetStateAction<boolean | undefined>>;
|
||||
|
||||
smsData: IMyTvAdmins | undefined;
|
||||
setSmsData: Dispatch<SetStateAction<IMyTvAdmins | undefined>>;
|
||||
smsTableData: MessagesByTvAdmin | undefined;
|
||||
setSmsTableData: Dispatch<SetStateAction<MessagesByTvAdmin | undefined>>;
|
||||
|
||||
setCurrentPage: Dispatch<SetStateAction<number>>;
|
||||
currentPage: number;
|
||||
|
||||
// getAdmins: () => void;
|
||||
// getMessages: (smsData: IMyTvAdmins, currentPage: number) => void;
|
||||
|
||||
isError: boolean;
|
||||
setIsError: Dispatch<SetStateAction<boolean>>;
|
||||
|
||||
activeSort: string;
|
||||
setActiveSort: (value: string) => void;
|
||||
|
||||
searchValue: string;
|
||||
setSearchValue: (value: string) => void;
|
||||
|
||||
searchFecth: string;
|
||||
setSearchFecth: (value: string) => void;
|
||||
|
||||
datee: any;
|
||||
setDatee: (value: any) => void;
|
||||
formatedDate: any;
|
||||
dateValue: string;
|
||||
|
||||
timeDate: string;
|
||||
setTimeDate: (value: string) => void;
|
||||
}
|
||||
|
||||
interface SmsProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const SmsContext = createContext<ISmsContext | undefined>(undefined);
|
||||
|
||||
export const SmsProvider: FC<SmsProviderProps> = ({ children }) => {
|
||||
const [activeSort, setActiveSort] = useState('desc');
|
||||
const [activeNumber, setActiveNumber] = useState<number | undefined>();
|
||||
const [tableIsLoading, setTableIsLoading] = useState<boolean | undefined>(true);
|
||||
const [smsData, setSmsData] = useState<IMyTvAdmins | undefined>(undefined);
|
||||
const [smsTableData, setSmsTableData] = useState<MessagesByTvAdmin | undefined>(undefined);
|
||||
const [currentPage, setCurrentPage] = useState<number>(1);
|
||||
const [isError, setIsError] = useState<boolean>(false);
|
||||
const [datee, setDatee] = useState<Date>();
|
||||
const [searchValue, setSearchValue] = useState<string>('');
|
||||
const [searchFecth, setSearchFecth] = useState<string>('');
|
||||
const [timeDate, setTimeDate] = useState<string>('');
|
||||
|
||||
const formatedDate = datee && format(datee, 'P').split('/').reverse();
|
||||
|
||||
const dateValue = formatedDate ? `${formatedDate[0]}-${formatedDate[2]}-${formatedDate[1]}` : '';
|
||||
|
||||
// if (token) {
|
||||
// useEffect(() => {
|
||||
// console.log('first');
|
||||
// getAdmins();
|
||||
// }, [token]);
|
||||
// }
|
||||
|
||||
// useEffect(() => {
|
||||
// if (smsData) {
|
||||
// setTableIsLoading(true);
|
||||
// getMessages();
|
||||
// }
|
||||
// }, [currentPage, activeNumber]);
|
||||
|
||||
// const getAdmins = () => {
|
||||
// try {
|
||||
// Queries.getAdmins().then((res) => {
|
||||
// setSmsData(res);
|
||||
// setActiveNumber(res.data[0].id);
|
||||
// if (!res.data) {
|
||||
// setIsError(true);
|
||||
// }
|
||||
// });
|
||||
// } catch (error) {
|
||||
// setIsError(true);
|
||||
// }
|
||||
// };
|
||||
|
||||
// const getMessages = (smsData: IMyTvAdmins, currentPage: number) => {
|
||||
// try {
|
||||
// Queries.getMessages(smsData.data[0].id, currentPage, dateValue, activeSort, searchFecth).then(
|
||||
// (res) => {
|
||||
// setSmsTableData(res);
|
||||
// setTableIsLoading(false);
|
||||
|
||||
// if (!res.data) {
|
||||
// setIsError(true);
|
||||
// }
|
||||
// },
|
||||
// );
|
||||
// } catch (error) {
|
||||
// setIsError(true);
|
||||
// }
|
||||
// };
|
||||
|
||||
return (
|
||||
<SmsContext.Provider
|
||||
value={{
|
||||
activeNumber,
|
||||
tableIsLoading,
|
||||
setTableIsLoading,
|
||||
smsData,
|
||||
setSmsData,
|
||||
setActiveNumber,
|
||||
smsTableData,
|
||||
setSmsTableData,
|
||||
setCurrentPage,
|
||||
// getMessages,
|
||||
currentPage,
|
||||
isError,
|
||||
setIsError,
|
||||
activeSort,
|
||||
setActiveSort,
|
||||
datee,
|
||||
setDatee,
|
||||
formatedDate,
|
||||
dateValue,
|
||||
searchValue,
|
||||
setSearchValue,
|
||||
searchFecth,
|
||||
setSearchFecth,
|
||||
timeDate,
|
||||
setTimeDate,
|
||||
}}>
|
||||
{children}
|
||||
</SmsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export { SmsContext };
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body,
|
||||
html {
|
||||
overflow-x: hidden;
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1348px;
|
||||
padding: 0 24px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 0 0% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 0 0% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 0 0% 3.9%;
|
||||
--primary: 0 0% 9%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 0 0% 96.1%;
|
||||
--secondary-foreground: 0 0% 9%;
|
||||
--muted: 0 0% 96.1%;
|
||||
--muted-foreground: 0 0% 45.1%;
|
||||
--accent: 0 0% 96.1%;
|
||||
--accent-foreground: 0 0% 9%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 89.8%;
|
||||
--input: 0 0% 89.8%;
|
||||
--ring: 0 0% 3.9%;
|
||||
--chart-1: 12 76% 61%;
|
||||
--chart-2: 173 58% 39%;
|
||||
--chart-3: 197 37% 24%;
|
||||
--chart-4: 43 74% 66%;
|
||||
--chart-5: 27 87% 67%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
.dark {
|
||||
--background: 0 0% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 0 0% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 0 0% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 0 0% 9%;
|
||||
--secondary: 0 0% 14.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 0 0% 14.9%;
|
||||
--muted-foreground: 0 0% 63.9%;
|
||||
--accent: 0 0% 14.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 14.9%;
|
||||
--input: 0 0% 14.9%;
|
||||
--ring: 0 0% 83.1%;
|
||||
--chart-1: 220 70% 50%;
|
||||
--chart-2: 160 60% 45%;
|
||||
--chart-3: 30 80% 55%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
}
|
||||
}
|
||||
|
||||
/* @layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
} */
|
||||
|
||||
@layer utilities {
|
||||
html {
|
||||
@apply bg-lightBackground;
|
||||
}
|
||||
.input-style {
|
||||
@apply bg-lightPrimaryContainer rounded-xl py-3 px-4 placeholder:text-[#BCBCD6] outline-none;
|
||||
}
|
||||
|
||||
.calendar [aria-label='Go to next month'] {
|
||||
@apply shadow-sm transition-all;
|
||||
}
|
||||
|
||||
.calendar [aria-label='Go to previous month'] {
|
||||
@apply shadow-sm transition-all;
|
||||
}
|
||||
|
||||
.day-styles [name='day'] {
|
||||
@apply p-4 text-textDarkt leading-[140%] bg-purple-600 rounded-full;
|
||||
}
|
||||
|
||||
.btn2 {
|
||||
@apply text-textBlack leading-[140%] py-1 px-3 rounded-[8px] bg-white border border-[#E6E6FA] font-semibold shadow-sm;
|
||||
}
|
||||
|
||||
.pagination-item {
|
||||
@apply px-3 py-1 rounded-md border border-textBlack/20 text-textBlack/80 text-[12px] cursor-pointer hover:bg-textBlack transition-all hover:text-white;
|
||||
}
|
||||
|
||||
.pagination-current {
|
||||
@apply px-3 py-1 rounded-md border bg-fillButtonAccentDefault text-white text-[12px];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export default {
|
||||
// SMS
|
||||
SMS_SRC: 'https://extra.turkmentv.gov.tm/api',
|
||||
};
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export default {
|
||||
// Sms ========================================================================
|
||||
myTvAdmins: '/my-tv-admins',
|
||||
messagesByTvAdmin: (id: number) => `/messages-by-tv-admin/${id}`,
|
||||
};
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import './index.css';
|
||||
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
||||
import DashboardPage from './pages/DashboardPage';
|
||||
import SignUpPage from './pages/SignUpPage';
|
||||
import App from './App';
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: '/',
|
||||
element: <App />,
|
||||
children: [
|
||||
{ path: '', element: <SignUpPage /> },
|
||||
{ path: 'dashboard', element: <DashboardPage /> },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<RouterProvider router={router} />
|
||||
</StrictMode>,
|
||||
);
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
export interface MessagesByTvAdmin {
|
||||
data: Datum[];
|
||||
links: Links;
|
||||
meta: Meta;
|
||||
}
|
||||
|
||||
export interface Datum {
|
||||
id: number;
|
||||
client: string;
|
||||
msg: string;
|
||||
dt: string;
|
||||
}
|
||||
|
||||
export interface Links {
|
||||
first: string;
|
||||
last: string;
|
||||
prev: null;
|
||||
next: string;
|
||||
}
|
||||
|
||||
export interface Meta {
|
||||
current_page: number;
|
||||
from: number;
|
||||
last_page: number;
|
||||
links: Link[];
|
||||
path: string;
|
||||
per_page: number;
|
||||
to: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface Link {
|
||||
url: null | string;
|
||||
label: string;
|
||||
active: boolean;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
export interface IMyTvAdmins {
|
||||
data: Datum[];
|
||||
}
|
||||
|
||||
export interface Datum {
|
||||
id: number;
|
||||
login: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import Dashboard from '../components/Dashboard';
|
||||
import { SmsProvider } from '../context/SmsContext';
|
||||
|
||||
const DashboardPage = () => {
|
||||
return (
|
||||
<SmsProvider>
|
||||
<Dashboard />
|
||||
</SmsProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardPage;
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
// src/app/sms/sign_up/page.tsx
|
||||
import { useState, FormEvent, useContext, useEffect } from 'react';
|
||||
import { AuthContext } from '@/context/AuthContext';
|
||||
|
||||
const SignUpPage = () => {
|
||||
const [login, setLogin] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const authContext = useContext(AuthContext);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
if (!authContext) {
|
||||
throw new Error('AuthContext must be used within an AuthProvider');
|
||||
}
|
||||
|
||||
const { login: loginUser, userLogedIn } = authContext;
|
||||
const { checkUserLoggedIn } = authContext;
|
||||
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
loginUser(login, password);
|
||||
setLogin('');
|
||||
setPassword('');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
checkUserLoggedIn();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<main className="container flex justify-center h-screen items-center ">
|
||||
<form onSubmit={handleSubmit} id="create-course-form">
|
||||
<div className="bg-lightSurfaceContainer p-10 rounded-[25px] w-[522px] -mt-28 shadow-tableShadow">
|
||||
<h2 className="mb-10 text-[40px] leading-none text-#242429 font-semibold">SMS ulgamy</h2>
|
||||
<div className="flex flex-col gap-2 mb-5 leading-[150%]">
|
||||
<label htmlFor="login" className="text-[16px] font-semibold text-[#242429]">
|
||||
Login
|
||||
</label>
|
||||
<input
|
||||
id="login"
|
||||
type="text"
|
||||
className="px-[16px] py-[12px] rounded-[12px] outline-none text-lightOnSurfaceVariant text-textSmall leading-textSmall"
|
||||
value={login}
|
||||
onChange={(e) => setLogin(e.target.value)}
|
||||
placeholder="Login giriziň"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label htmlFor="password" className="text-[16px] font-semibold text-[#242429]">
|
||||
Açar sözi
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
className="px-[16px] py-[12px] bg-lightPrimaryContainer rounded-[12px] outline-none text-lightOnSurfaceVariant text-textSmall leading-textSmall"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Açar sözi giriziň"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="p-3 bg-[#7A7ACC] text-[18px] leading-[150%] font-medium text-white w-full rounded-xl mt-[30px]">
|
||||
Ulgama gir
|
||||
</button>
|
||||
|
||||
{userLogedIn === false ? (
|
||||
<p className="text-center pt-[16px] text-[16px] font-[600] text-red-400">
|
||||
Login ýa-da açar sözi ýalňyş
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignUpPage;
|
||||
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ['./src/**/*.{html,js,jsx,ts,tsx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
textBlack: '#242429',
|
||||
textLight: '#878799',
|
||||
textDarkt: '#636370',
|
||||
fillLightBgLightContr: '#F5F5FA',
|
||||
fillButtonAccentDefault: '#7A7ACC',
|
||||
fillTableHead: '#E1E1F5',
|
||||
fillTableRow: '#F0F0FA',
|
||||
fillTableRow2: '#F5F5FA',
|
||||
fillTableStrokeTableHead: '#dcdcfa',
|
||||
fillTableStrokeTableRow: '#E6E6FA',
|
||||
fillLinkRest: '#878799',
|
||||
fillLinkHover: '#5C5C99',
|
||||
fillLinkActive: '#4D4D99',
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
card: {
|
||||
DEFAULT: 'hsl(var(--card))',
|
||||
foreground: 'hsl(var(--card-foreground))',
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: 'hsl(var(--popover))',
|
||||
foreground: 'hsl(var(--popover-foreground))',
|
||||
},
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))',
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: 'hsl(var(--secondary))',
|
||||
foreground: 'hsl(var(--secondary-foreground))',
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: 'hsl(var(--muted))',
|
||||
foreground: 'hsl(var(--muted-foreground))',
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: 'hsl(var(--accent))',
|
||||
foreground: 'hsl(var(--accent-foreground))',
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: 'hsl(var(--destructive))',
|
||||
foreground: 'hsl(var(--destructive-foreground))',
|
||||
},
|
||||
border: 'hsl(var(--border))',
|
||||
input: 'hsl(var(--input))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
chart: {
|
||||
1: 'hsl(var(--chart-1))',
|
||||
2: 'hsl(var(--chart-2))',
|
||||
3: 'hsl(var(--chart-3))',
|
||||
4: 'hsl(var(--chart-4))',
|
||||
5: 'hsl(var(--chart-5))',
|
||||
},
|
||||
// UPDATED UI KIT =====================================================================================================
|
||||
////////////////////////////////// Light theme
|
||||
|
||||
// Background
|
||||
lightBackground: '#FCF8FF',
|
||||
// Primary
|
||||
lightPrimary: '#575992',
|
||||
lightOnPrimary: '#FFFFFF',
|
||||
lightPrimaryContainer: '#E1E0FF',
|
||||
lightOnPrimaryContainer: '#12144B',
|
||||
// Secondary
|
||||
lightSecondary: '#5D5D72',
|
||||
lightOnSecondary: '#FFFFFF',
|
||||
lightSecondaryContainer: '#E2E0F9',
|
||||
lightOnSecondaryContainer: '#191A2C',
|
||||
// Tertiary
|
||||
lightTertiary: '#79536A',
|
||||
lightOnTertiary: '#FFFFFF',
|
||||
lightTertiaryContainer: '#FFD8EC',
|
||||
lightOnTertiaryContainer: '#2E1125',
|
||||
// Error
|
||||
lightError: '#BA1A1A',
|
||||
lightOnError: '#FFFFFF',
|
||||
lightErrorContainer: '#FFDAD6',
|
||||
lightOnErrorContainer: '#410002',
|
||||
// Surface
|
||||
lightSurface: '#FCF8FF',
|
||||
lightOnSurface: '#1B1B21',
|
||||
lightOnSurfaceDisabled: 'rgba(27, 27, 33, 0.12)',
|
||||
lightSurfaceVariant: '#E4E1EC',
|
||||
lightOnSurfaceVariant: '#46464F',
|
||||
lightSurfaceContainerLowest: '#FFFFFF',
|
||||
lightSurfaceContainerLow: '#F6F2FA',
|
||||
lightSurfaceContainer: '#F0ECF4',
|
||||
lightSurfaceContainerHigh: '#EAE7EF',
|
||||
lightSurfaceContainerHigher: '#E4E1E9',
|
||||
// Outline
|
||||
lightOutline: '#777680',
|
||||
lightOutlineVariant: '#C7C5D0',
|
||||
},
|
||||
fontSize: {
|
||||
// Display
|
||||
display1: '56px',
|
||||
display2: '48px',
|
||||
display3: '44px',
|
||||
|
||||
// Heading
|
||||
heading1: '36px',
|
||||
heading2: '32px',
|
||||
heading3: '28px',
|
||||
heading4: '26px',
|
||||
heading5: '24px',
|
||||
heading6: '20px',
|
||||
|
||||
// Text
|
||||
textLarge: '18px',
|
||||
textBase: '16px',
|
||||
textSmall: '14px',
|
||||
textXSmall: '12px',
|
||||
},
|
||||
lineHeight: {
|
||||
// Display 1
|
||||
display1: '64px',
|
||||
|
||||
// Display 2
|
||||
display2: '56px',
|
||||
|
||||
// Display 3
|
||||
display3: '56px',
|
||||
|
||||
// Heading 1
|
||||
heading1: '44px',
|
||||
|
||||
// Heading 2
|
||||
heading2: '40px',
|
||||
|
||||
// Heading 3
|
||||
heading3: '36px',
|
||||
|
||||
// Heading 4
|
||||
heading4: '34px',
|
||||
|
||||
// Heading 5
|
||||
heading5: '32px',
|
||||
|
||||
// Heading 6
|
||||
heading6: '28px',
|
||||
|
||||
// Text large
|
||||
textLarge: '27px',
|
||||
|
||||
// Text base
|
||||
textBase: '24px',
|
||||
|
||||
// Text small
|
||||
textSmall: '20px',
|
||||
|
||||
// Text xSmall
|
||||
textXSmall: '16px',
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require('tailwindcss-animate')],
|
||||
};
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import path from 'path';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
});
|
||||
Loading…
Reference in New Issue