From c87ff7213e15c7b878cede8dccd5af24c6ae7041 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Sat, 31 May 2025 15:35:08 +0600 Subject: [PATCH 001/183] =?UTF-8?q?=D0=9F=D0=BE=D0=BA=D0=B0=20=D0=BD=D0=B5?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B8=D0=BB=D0=BE=D1=81=D1=8C?= =?UTF-8?q?=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B2=D0=B5=D1=81=D1=82=D0=B8=20?= =?UTF-8?q?=D1=81=D1=82=D1=83=D0=B4=D0=B5=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/{ => teacher}/page.tsx | 4 +- app/(student)/components/BaseLayout.tsx | 18 ++ app/(student)/components/CounterBanner.tsx | 61 ++++++ app/(student)/components/HomeClient.tsx | 164 ++++++++++++++ app/(student)/components/MyFontAwesome.tsx | 17 ++ app/(student)/components/VideoPlay.tsx | 49 +++++ .../components/buttons/FancyLinkBtn.tsx | 29 +++ app/(student)/components/popUp/Tiered.tsx | 57 +++++ app/(student)/layout.tsx | 13 ++ app/(student)/new/page.tsx | 11 + app/(student)/page.tsx | 8 + app/layout.tsx | 3 + package-lock.json | 94 ++++++++ package.json | 8 +- public/layout/images/home-banner-phone.png | Bin 0 -> 45155 bytes public/layout/images/logo-kg.svg | 205 ++++++++++++++++++ public/layout/images/logo-remove-white.png | Bin 0 -> 60288 bytes public/layout/images/logo-remove.png | Bin 0 -> 75195 bytes public/layout/images/man.png | Bin 0 -> 17471 bytes public/layout/images/shape.png | Bin 0 -> 2305 bytes public/layout/images/shape1.png | Bin 0 -> 382 bytes public/layout/images/shape2.png | Bin 0 -> 645 bytes public/layout/svg/red-logo.svg | 46 ++++ public/layout/svg/white-logo.svg | 47 ++++ styles/layout/_button.scss | 51 +++++ styles/layout/_topbar.scss | 17 +- styles/layout/_typography.scss | 2 +- styles/layout/layout.scss | 79 ++++++- 28 files changed, 977 insertions(+), 6 deletions(-) rename app/(main)/{ => teacher}/page.tsx (99%) create mode 100644 app/(student)/components/BaseLayout.tsx create mode 100644 app/(student)/components/CounterBanner.tsx create mode 100644 app/(student)/components/HomeClient.tsx create mode 100644 app/(student)/components/MyFontAwesome.tsx create mode 100644 app/(student)/components/VideoPlay.tsx create mode 100644 app/(student)/components/buttons/FancyLinkBtn.tsx create mode 100644 app/(student)/components/popUp/Tiered.tsx create mode 100644 app/(student)/layout.tsx create mode 100644 app/(student)/new/page.tsx create mode 100644 app/(student)/page.tsx create mode 100644 public/layout/images/home-banner-phone.png create mode 100644 public/layout/images/logo-kg.svg create mode 100644 public/layout/images/logo-remove-white.png create mode 100644 public/layout/images/logo-remove.png create mode 100644 public/layout/images/man.png create mode 100644 public/layout/images/shape.png create mode 100644 public/layout/images/shape1.png create mode 100644 public/layout/images/shape2.png create mode 100644 public/layout/svg/red-logo.svg create mode 100644 public/layout/svg/white-logo.svg create mode 100644 styles/layout/_button.scss diff --git a/app/(main)/page.tsx b/app/(main)/teacher/page.tsx similarity index 99% rename from app/(main)/page.tsx rename to app/(main)/teacher/page.tsx index afe44527..a06710d5 100644 --- a/app/(main)/page.tsx +++ b/app/(main)/teacher/page.tsx @@ -6,8 +6,8 @@ import { Column } from 'primereact/column'; import { DataTable } from 'primereact/datatable'; import { Menu } from 'primereact/menu'; import React, { useContext, useEffect, useRef, useState } from 'react'; -import { ProductService } from '../../demo/service/ProductService'; -import { LayoutContext } from '../../layout/context/layoutcontext'; +import { ProductService } from '../../../demo/service/ProductService'; +import { LayoutContext } from '../../../layout/context/layoutcontext'; import Link from 'next/link'; import { Demo } from '@/types'; import { ChartData, ChartOptions } from 'chart.js'; diff --git a/app/(student)/components/BaseLayout.tsx b/app/(student)/components/BaseLayout.tsx new file mode 100644 index 00000000..d7027d43 --- /dev/null +++ b/app/(student)/components/BaseLayout.tsx @@ -0,0 +1,18 @@ +"use client"; + +// import Footer from "./Footer"; +// import Header from "./Header"; + +export default function BaseLayout({ children }: { children: React.ReactNode }) { + return ( + <> +
+ {/*
*/} +
+ {children} +
+ {/*
*/} +
+ + ); +} \ No newline at end of file diff --git a/app/(student)/components/CounterBanner.tsx b/app/(student)/components/CounterBanner.tsx new file mode 100644 index 00000000..75b59db3 --- /dev/null +++ b/app/(student)/components/CounterBanner.tsx @@ -0,0 +1,61 @@ +import React from 'react' +import { faCircle, + faChalkboard, + faUserGraduate, + faBookOpen, + faShieldHeart, +} from "@fortawesome/free-solid-svg-icons"; +import MyFontAwesome from './MyFontAwesome'; +import CountUp from 'react-countup'; + +export default function CounterBanner() { + return ( +
+
+
+
+ + +
+
+
+
+ Курстар & видеосабактар +
+
+ +
+
+ + +
+
+
+
+ Катталган студенттер +
+
+ +
+
+ + +
+
+
+
+ Окутуучулар +
+
+ +
+
+ + +
+
+
%
+ Канааттануу деңгээли +
+
+
+
+ ) +} diff --git a/app/(student)/components/HomeClient.tsx b/app/(student)/components/HomeClient.tsx new file mode 100644 index 00000000..c57325ca --- /dev/null +++ b/app/(student)/components/HomeClient.tsx @@ -0,0 +1,164 @@ +"use client"; + +import AOS from "aos"; +import "aos/dist/aos.css"; +import { useEffect } from "react"; +import CounterBanner from "./CounterBanner"; +import Link from "next/link"; +import { faClock, faVideo,} from "@fortawesome/free-solid-svg-icons"; +import MyFontAwesome from "./MyFontAwesome"; +import VideoPlay from "./VideoPlay"; +import Image from "next/image"; + +export default function HomeClient() { + useEffect(() => { + AOS.init(); + }, []); + + return ( +
+
+
+
+
+
+ + + ЫҢГАЙЛУУ ОКУУ ҮЧҮН ОНЛАЙН МЕЙКИНДИК + +
+

+ Аралыктан окутуу порталына кош келиңиз! +

+
+ {" "} + Университеттин онлайн билим берүү жаатындагы долбоорлорун + бириктирүүдөбүз: +
    +
  • ачык онлайн курстар
  • +
  • жогорку билим берүү программалары
  • +
+
+
+
+ +
+
+ +
+
+ Shape +
+ + Пользователь + +
+ Shape +
+ +
+ Shape +
+ +
+
+ 13000 +

lorem

+
+
+ +
+
+ Куттуктайбыз! +

Сиздин кабыл алуу ийгиликтүү аяктады

+
+
+ +
+
+ User experience className +

Today at 12.00 PM

+
+ + Join now + +
+
+
+
+
+
+
+ ); +} diff --git a/app/(student)/components/MyFontAwesome.tsx b/app/(student)/components/MyFontAwesome.tsx new file mode 100644 index 00000000..e3bb24f8 --- /dev/null +++ b/app/(student)/components/MyFontAwesome.tsx @@ -0,0 +1,17 @@ +"use client"; // обязательно в app/ структуре + +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { IconProp } from "@fortawesome/fontawesome-svg-core"; +import { ComponentProps } from "react"; + +interface IconProps extends ComponentProps { + icon: IconProp; + className?: string; + size?: "xs" | "lg" | "sm" | "1x" | "2x" | "3x" | "4x" | "5x" | "6x" | "7x" | "8x" | "9x" | "10x"; +} + +export default function MyFontAwesome({ icon, className, size, ...props }:IconProps ) { + return ( + + ); +} \ No newline at end of file diff --git a/app/(student)/components/VideoPlay.tsx b/app/(student)/components/VideoPlay.tsx new file mode 100644 index 00000000..035e5c53 --- /dev/null +++ b/app/(student)/components/VideoPlay.tsx @@ -0,0 +1,49 @@ +'use client'; + +import { faPlay} from "@fortawesome/free-solid-svg-icons"; +import MyFontAwesome from './MyFontAwesome'; +import Image from "next/image"; +import { useState } from "react"; +import { Dialog } from 'primereact/dialog'; +// import 'primereact/resources/themes/lara-light-blue/theme.css'; // или другая тема +import 'primereact/resources/primereact.min.css'; +import 'primeicons/primeicons.css'; + +export default function VideoPlay() { + const [videoCall, setVideoCall] = useState(false); + + return ( +
+ {if (!videoCall) return; setVideoCall(false); }}> +
+ +
+
+
+
+
setVideoCall(true)} + > + {/* Волна */} + + + {/* Иконка-кнопка */} +
+ +
+
+
+ Логотип ОшГУ +
+
+ ) +} diff --git a/app/(student)/components/buttons/FancyLinkBtn.tsx b/app/(student)/components/buttons/FancyLinkBtn.tsx new file mode 100644 index 00000000..1073c2ce --- /dev/null +++ b/app/(student)/components/buttons/FancyLinkBtn.tsx @@ -0,0 +1,29 @@ +'use client'; + +import Link from "next/link"; +import { useState } from "react"; + +export default function FancyLinkBtn({backround, effectBackround, title}) { + const [position, setPosition] = useState(false); + + return ( +
+ +
+ ) +} \ No newline at end of file diff --git a/app/(student)/components/popUp/Tiered.tsx b/app/(student)/components/popUp/Tiered.tsx new file mode 100644 index 00000000..1260075f --- /dev/null +++ b/app/(student)/components/popUp/Tiered.tsx @@ -0,0 +1,57 @@ +'use client'; + +import { useEffect, useRef, useState } from 'react'; +import { Button } from 'primereact/button'; +import { TieredMenu } from 'primereact/tieredmenu'; +import { faBars, faClose } from "@fortawesome/free-solid-svg-icons"; +import MyFontAwesome from '../MyFontAwesome'; +import { useMediaQuery } from '@/hooks/useMediaQuery'; + +export default function Tiered({title, items, insideColor}) { + const [mobile, setMobile] = useState(false); + + const menu = useRef(null); + const media = useMediaQuery('(max-width: 1000px)'); + + const menuItems = items.map(item => ({ + ...item, + url: item.link && item.link + })); + + const toggleMenu = (e)=> { + menu.current.toggle(e); + setMobile(prev => !prev); + } + // Общий фонт который закрывает кнопку бургер меню, Close + return ( +
+ {title.name ? + } + + +
+ ); +} \ No newline at end of file diff --git a/app/(student)/layout.tsx b/app/(student)/layout.tsx new file mode 100644 index 00000000..45cb94cf --- /dev/null +++ b/app/(student)/layout.tsx @@ -0,0 +1,13 @@ +import BaseLayout from "./components/BaseLayout"; + +export default function Layout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( +
+ {children} +
+ ); +} \ No newline at end of file diff --git a/app/(student)/new/page.tsx b/app/(student)/new/page.tsx new file mode 100644 index 00000000..6b0987b4 --- /dev/null +++ b/app/(student)/new/page.tsx @@ -0,0 +1,11 @@ +'use client'; + +import React from 'react' + +export default function New() { + return ( +
+ New +
+ ) +} diff --git a/app/(student)/page.tsx b/app/(student)/page.tsx new file mode 100644 index 00000000..bd2f4a81 --- /dev/null +++ b/app/(student)/page.tsx @@ -0,0 +1,8 @@ +import HomeClient from "./components/HomeClient"; + +export default function Home() { + + return ( + + ); +} \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index 4114afcf..5e9cecc2 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,9 @@ 'use client'; import { LayoutProvider } from '../layout/context/layoutcontext'; import { PrimeReactProvider } from 'primereact/api'; +import { config } from "@fortawesome/fontawesome-svg-core"; +import "@fortawesome/fontawesome-svg-core/styles.css"; // Импорт стилей +config.autoAddCss = false; import 'primereact/resources/primereact.css'; import 'primeflex/primeflex.css'; import 'primeicons/primeicons.css'; diff --git a/package-lock.json b/package-lock.json index 332e8ef7..241b058c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,15 +8,21 @@ "name": "sakai-react", "version": "10.1.0", "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-solid-svg-icons": "^6.7.2", + "@fortawesome/react-fontawesome": "^0.2.2", "@types/node": "20.3.1", "@types/react": "18.2.12", "@types/react-dom": "18.2.5", + "aos": "^2.3.4", "chart.js": "4.2.1", + "countup": "^1.8.2", "next": "13.4.8", "primeflex": "^3.3.1", "primeicons": "^6.0.1", "primereact": "10.2.1", "react": "18.2.0", + "react-countup": "^6.5.3", "react-dom": "18.2.0", "typescript": "5.1.3" }, @@ -103,6 +109,48 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", + "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", + "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz", + "integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", + "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -561,6 +609,16 @@ "node": ">= 8" } }, + "node_modules/aos": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/aos/-/aos-2.3.4.tgz", + "integrity": "sha512-zh/ahtR2yME4I51z8IttIt4lC1Nw0ktsFtmeDzID1m9naJnWXhCoARaCgNOGXb5CLy3zm+wqmRAEgMYB5E2HUw==", + "dependencies": { + "classlist-polyfill": "^1.0.3", + "lodash.debounce": "^4.0.6", + "lodash.throttle": "^4.0.1" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -907,6 +965,11 @@ "node": ">= 6" } }, + "node_modules/classlist-polyfill": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/classlist-polyfill/-/classlist-polyfill-1.2.0.tgz", + "integrity": "sha512-GzIjNdcEtH4ieA2S8NmrSxv7DfEV5fmixQeyTmqmRmRJPGpRBaSnA2a0VrCjyT8iW8JjEdMbKzDotAJf+ajgaQ==" + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -936,6 +999,16 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/countup": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/countup/-/countup-1.8.2.tgz", + "integrity": "sha512-1qOuy+h+dgLY4TroLb4ZC2hK7HYS2Z7mnSUvdzoYAU7EZbqOse1mKZta/KYPhDPfd9lPDyP3Tb4pH6a10RkoCw==" + }, + "node_modules/countup.js": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/countup.js/-/countup.js-2.8.2.tgz", + "integrity": "sha512-UtRoPH6udaru/MOhhZhI/GZHJKAyAxuKItD2Tr7AbrqrOPBX/uejWBBJt8q86169AMqKkE9h9/24kFWbUk/Bag==" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2566,12 +2639,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -3099,6 +3182,17 @@ "node": ">=0.10.0" } }, + "node_modules/react-countup": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/react-countup/-/react-countup-6.5.3.tgz", + "integrity": "sha512-udnqVQitxC7QWADSPDOxVWULkLvKUWrDapn5i53HE4DPRVgs+Y5rr4bo25qEl8jSh+0l2cToJgGMx+clxPM3+w==", + "dependencies": { + "countup.js": "^2.8.0" + }, + "peerDependencies": { + "react": ">= 16.3.0" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", diff --git a/package.json b/package.json index 8071b50b..44eb0099 100644 --- a/package.json +++ b/package.json @@ -10,15 +10,21 @@ "lint": "next lint" }, "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-solid-svg-icons": "^6.7.2", + "@fortawesome/react-fontawesome": "^0.2.2", "@types/node": "20.3.1", "@types/react": "18.2.12", "@types/react-dom": "18.2.5", + "aos": "^2.3.4", "chart.js": "4.2.1", + "countup": "^1.8.2", "next": "13.4.8", "primeflex": "^3.3.1", "primeicons": "^6.0.1", "primereact": "10.2.1", "react": "18.2.0", + "react-countup": "^6.5.3", "react-dom": "18.2.0", "typescript": "5.1.3" }, @@ -28,4 +34,4 @@ "prettier": "^2.8.8", "sass": "^1.63.4" } -} \ No newline at end of file +} diff --git a/public/layout/images/home-banner-phone.png b/public/layout/images/home-banner-phone.png new file mode 100644 index 0000000000000000000000000000000000000000..399a19dcf39aaabbde410dda49bef65361bf272c GIT binary patch literal 45155 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 literal 0 HcmV?d00001 diff --git a/public/layout/images/logo-kg.svg b/public/layout/images/logo-kg.svg new file mode 100644 index 00000000..ac33ea12 --- /dev/null +++ b/public/layout/images/logo-kg.svg @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + В + + + + + Р + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/layout/images/logo-remove-white.png b/public/layout/images/logo-remove-white.png new file mode 100644 index 0000000000000000000000000000000000000000..3d28f7d584100fa710e5dcad4c9448c49f60319c GIT binary patch literal 60288 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 literal 0 HcmV?d00001 diff --git a/public/layout/images/man.png b/public/layout/images/man.png new file mode 100644 index 0000000000000000000000000000000000000000..0168ee167c95356cb8e67ee2cea3eb9a65478c45 GIT binary patch literal 17471 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 literal 0 HcmV?d00001 diff --git a/public/layout/images/shape.png b/public/layout/images/shape.png new file mode 100644 index 0000000000000000000000000000000000000000..a82ce023a4d747dc1f719de8472ed6cbe3815cea GIT binary patch literal 2305 zcmV+c3I6tpP)JA;Hq&=nMepQt@k!*;Vh{VB5;FM>UVT*$9J>Y5Aeo_tIok0hxIs#yPXaH`hKZb=Z`2wKK2iJaLU|}pM z0hlrL!L6Uz)p_O!8k840_Y>1C=+Lh>I!`=}J?I(~7k+T+Cw6tuqo9K_9|Pz4NryU5 zAHfvUhc>zN6T7-xeFPEZ*o)IOw2sNRXeT)V+*?E}9dY^KeePx^?;pV(xx) z?pdd(sE=d4L`~(Ovrnh2>BmoFFbNC4mME#r=GD9JYp8!F105OT#g<9|k2BQG-Xvcgc@ssts0M%5NBmfMBEY_G2BZ;YvGsi|(D&FmQbWW$1jO`Wep0zXmae)5e6+RTo?Pm1$kRcEHm z&UIrG%nlpq3Y;=oN~=o}0GdKJ#+%zRCm8(1t}aRdcnaA> zU6KH>G{!pAt!m5(=Y_0)Ii+q+O9J4$F;<+FR+l6I?h9Fyiu&w#b!Gket8+h@J_xHi zKd5T`>8s;Hc4DzzU2zT?XQ3RQKvm{(T3sB6^-^@`D1b|{1F)*|@`zXsmy9n$icF+# zf}wGOg9WQPWf*qs8d%j8c`8_ox|;jegq_Sxzzf{BChW{;s9QxbCv5c1gfbL4-NAxY zU1lB_Fm`oiimp!oAS~(@`5beiZ1h!WZI%S#hgI3VpBJg2LmdAO&!S< zh3s6bI*KDZz>LLKbtDOZU$IVPfg0c&~Y#$3kZ{brfwA}S}AH@~kZe-?z7?1j#mZCm^2SYt+ zYt;ECcY~2T!;==CAoLnXL5v!v_LLg;W*K$2BE#`aOBZ}e@qV8&brY{hU6=XPmLdp3 zCmHUgmb;t6@tRzZE@s?W6!)jCaerFs4y}I#f*_<>3uf+8mls9q3-Gys`(O{p^Qcys z-_ee!y#X;<>PIzj3xe=V{1+#XbKIU+9sx0)rcPC!W(sE#&gJ^MSKq=P8)c%Xfp9b6TGuzJf7&Pg5Vc7`+Z)$F&2*Gq|pG@Q(Hw z?%bXmhXVk2gQdqF(vISe*IMfeP+WO(8)X`}`T&3prlFE}^J;X@&I-`?c028N24-lT zzs{>KccHGC4;83=)aCgUW7N%|Nqro=sMC5Gh}q9AZ@uPqmIk8`{@ItXr*n5E4R>df zyX(A*{M%A*L61=vn79Z*_-X!|^~|F_{Gbf=edbAONA?ylFK4I{w^-!p_aAYES{-Kx zR}g-i|KMNL+`o>M+u(8={1^Eje&V@``ZhTCD_wF2Ob~hv>cs*}!3_6G%l$)YdD{73 zeM>Y;ouzQUB})F6f(H-;LHJu_xfj}5ASOetE=%3R^QhmJ;7(Hdsnv0(P&sOKYB>Z! z5dIe1z>EPiqV8W+$NfV^Zgt$Rg>b9m{>|tC2!hZkpy=Ttf#e)F)nzj2r4|EVfXYcH}qxC&WavbY( zV5jsRk~6x!?ShVs?b#1|ZpI#Zr+WgF~Rrw^1<`X`&@mnGj6c!XkC7wsmV1O4hbJ-J}l@9c?ayqSVN?sjxiHp#Z4J34;C z^G|kkM_&~m16y=-XJ^Ok*wbEUxab2V$)=Vr;};pdK+9f@W?!@R>jriL%FegjvqpBm z=RMu8kYrn)Mn-o)&0%D8e$4K^@9X-yKk+iMn@O@Iw{!&cN|!6_>B{%FfnC#C9y>#3 zKQv<(K=*W3+pY+#$4Rm&bd%POK9243@m$ieyS?4xnU0Q6t_HTJqZE5^1}_TSO_C%@ bl7Hz3mIGNh7!O9700000NkvXXu0mjf6B$k_ literal 0 HcmV?d00001 diff --git a/public/layout/images/shape1.png b/public/layout/images/shape1.png new file mode 100644 index 0000000000000000000000000000000000000000..bee24c6573993881efd3678a7b44598f614392bb GIT binary patch literal 382 zcmV-^0fGLBP){|)$kH;Y^X3`7vUwC7G8fzdu zlix6#n44j=BVD9}(U~XIiGzjR2vl@7&PAQqf<^26W5Izs4}aL{)8lc$!;Tu~e!)0X z<3bkflEOKx_@qteVZ|fDe;&IPtE-#n3+4zYykTE3Jyaf9Fk7hnk(9~7=C5>PwZY}v zHqRK?;2p5ZJvMoJ=XZXKdz;I7F}WPv!{&iJz~mLX)WYOf&Y^?J*Xvwz9BjYk-q?n<9JXf=`2PwJuf{>c)o>bEG@ ccJ)_uzu7rA;zwmh&;S4c07*qoM6N<$f<7gxu>b%7 literal 0 HcmV?d00001 diff --git a/public/layout/images/shape2.png b/public/layout/images/shape2.png new file mode 100644 index 0000000000000000000000000000000000000000..f1fe1c5b6d641f64442e6479ee8f0e2bebc56aa3 GIT binary patch literal 645 zcmV;00($+4P)3MLC)_G(CdVT0P=cim&F-Bp*9F z_GZKW5$^!Det7oHn_a_>^aRrHjQKP}uhfSR0RQ*05Wt-aFM&Vw9oz$@4FiE2b$JhN z*FHybWZ=f4JGA=x3fzPt#z%K+_g5GgwC3hIBf~KUR;kQrc5m6(sv>{o#~G{+ z5mRS51Lm?4w;LQvh$R!?Ih!SR=qcsCXOX~werjsMgD!4~Wr2ZB!9>}&!(4g>78n+7 zszuNG>2uQ*-gO@c0Mc{qcpgO(EPHye`<*30M>m^IMunul7u3PejEoEk(EVG^lWsrN zNN8rUHIu$n2%t4)w#aC{A#O30RUQ%3e1FW918iw;T%edG zq#mVjujfLq5uftxo$qbaT$2`9o=mcE=?4uH=FvwavhHWxWj-3H^II^l4j6r*!|hjS f#^|nW{TICgGb&vsttk2E00000NkvXXu0mjfbw?Z6 literal 0 HcmV?d00001 diff --git a/public/layout/svg/red-logo.svg b/public/layout/svg/red-logo.svg new file mode 100644 index 00000000..69deed08 --- /dev/null +++ b/public/layout/svg/red-logo.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/layout/svg/white-logo.svg b/public/layout/svg/white-logo.svg new file mode 100644 index 00000000..d76c8ff6 --- /dev/null +++ b/public/layout/svg/white-logo.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/layout/_button.scss b/styles/layout/_button.scss new file mode 100644 index 00000000..1d184d38 --- /dev/null +++ b/styles/layout/_button.scss @@ -0,0 +1,51 @@ +button { + outline: 0; + border: none; + + &:focus { + outline: 0; + border: 0; + } +} + +.default-btn { + padding: 15px 35px; + text-align: center; + color: var(--whiteColor); + font-size: var(--fontSize); + font-weight: 400; + transition: var(--transition); + display: inline-flex; + align-items: center; + justify-content: center; + position: relative; + z-index: 0; + box-shadow: none; + overflow: hidden; + white-space: nowrap; + cursor: pointer; +} + +.fancyEffect { + position: absolute; + top: 0; + bottom: 0; + left: 50%; + width: 550px; + height: 550px; + margin: auto; + border-radius: 50%; + z-index: -1; +} + +.fancyBefore { + transform-origin: top center; + transform: translateX(-50%) translateY(-5%) scale(0.4); + transition: transform 0.9s; +} + +.fancyAfter { + transition: transform 1s; + transform: translateX(-45%) translateY(0) scale(1); + transform-origin: bottom center; +} diff --git a/styles/layout/_topbar.scss b/styles/layout/_topbar.scss index 79402366..bf6f5d9c 100644 --- a/styles/layout/_topbar.scss +++ b/styles/layout/_topbar.scss @@ -22,7 +22,7 @@ border-radius: 12px; img { - height: 2.5rem; + // height: 2.5rem; margin-right: .5rem; } @@ -86,6 +86,21 @@ } } +.main-header { + position: fixed; + top: 50px; /* равняется высоте .top-bar */ + width: 100%; + background: #fff; + z-index: 10; /* чтобы header был над контентом */ + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + padding: 16px; + + .main-header.scrolled { + transform: translateY(50px); /* прячет шапку на высоту .top-bar */ + top: 0; + } +} + @media (max-width: 991px) { .layout-topbar { justify-content: space-between; diff --git a/styles/layout/_typography.scss b/styles/layout/_typography.scss index b9a0c8ff..a72d190a 100644 --- a/styles/layout/_typography.scss +++ b/styles/layout/_typography.scss @@ -3,7 +3,7 @@ h1, h2, h3, h4, h5, h6 { font-family: inherit; font-weight: 500; line-height: 1.2; - color: var(--surface-900); + // color: var(--surface-900); &:first-child { margin-top: 0; diff --git a/styles/layout/layout.scss b/styles/layout/layout.scss index fef2ed48..30218e29 100644 --- a/styles/layout/layout.scss +++ b/styles/layout/layout.scss @@ -1,3 +1,79 @@ + +:root { + --bodyFonts: "Jost", sans-serif; + --mainColor: #08A9E6; + --redColor: #EC272F; + --titleColor: #21225F; + --bodyColor: #555555; + --whiteColor: #ffffff; + --fontSize: 16px; + --transition: 0.5s; +} + +p { + color: var(--bodyColor); + margin-bottom: 10px; +} +p:last-child { + margin-bottom: 0; +} + +a { + display: inline-block; + transition: var(--transition); + text-decoration: none; +} +a:hover, a:focus { + text-decoration: none; +} + +h1, h2, h3, h4, h5, h6 { + color: var(--titleColor); +} + +.mainColor-hover:hover{ + color: #08A9E6; +} + +/* animate */ +.animateContent{ + animation: content linear 6s infinite; +} + +@keyframes content { + 10%{ + transform: translateX(2px); + } + 50%{ + transform: translateX(-4px); + } + 80%{ + transform: translateY(-2px); + } + 100%{ + transform: translateY(4px); + } +} + +.animateFaster{ + animation: faster linear 4s infinite forwards; +} + +@keyframes faster { + 10%{ + transform: translateX(-2px); + } + 50%{ + transform: translateX(4px); + } + 80%{ + transform: translateY(2px); + } + 100%{ + transform: translateY(-4px); + } +} + @import './_variables'; @import "./_mixins"; @import "./_main"; @@ -8,4 +84,5 @@ @import "./_footer"; @import "./_responsive"; @import "./_utils"; -@import "./_typography"; \ No newline at end of file +@import "./_typography"; +@import "./_button"; \ No newline at end of file From 8097b6c1b92cc16515df2ce440238f3e0a03b447 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Sun, 1 Jun 2025 19:38:15 +0600 Subject: [PATCH 002/183] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B2=D0=BE?= =?UTF-8?q?=D0=B4=20=D1=81=D1=82=D1=83=D0=B4=D0=B5=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/layout.tsx | 2 +- app/(student)/layout.tsx | 8 +- app/(student)/new/page.tsx | 11 - app/(student)/page.tsx | 8 - app/{(student) => }/components/BaseLayout.tsx | 0 .../components/CounterBanner.tsx | 2 +- app/{(student) => }/components/HomeClient.tsx | 2 +- .../components/MyFontAwesome.tsx | 0 app/{(student) => }/components/VideoPlay.tsx | 3 +- .../components/buttons/FancyLinkBtn.tsx | 0 .../components/popUp/Tiered.tsx | 2 +- app/favicon.ico | Bin 1225 -> 0 bytes app/layout.tsx | 1 + app/page.tsx | 8 + layout/AppTopbar.tsx | 3 +- package-lock.json | 1060 +++++++++++++++-- package.json | 6 +- postcss.config.js | 6 + styles/globals.css | 78 ++ styles/layout/_topbar.scss | 2 +- styles/layout/layout.scss | 76 -- tailwind.config.js | 10 + 22 files changed, 1081 insertions(+), 207 deletions(-) delete mode 100644 app/(student)/new/page.tsx delete mode 100644 app/(student)/page.tsx rename app/{(student) => }/components/BaseLayout.tsx (100%) rename app/{(student) => }/components/CounterBanner.tsx (98%) rename app/{(student) => }/components/HomeClient.tsx (99%) rename app/{(student) => }/components/MyFontAwesome.tsx (100%) rename app/{(student) => }/components/VideoPlay.tsx (98%) rename app/{(student) => }/components/buttons/FancyLinkBtn.tsx (100%) rename app/{(student) => }/components/popUp/Tiered.tsx (97%) delete mode 100644 app/favicon.ico create mode 100644 app/page.tsx create mode 100644 postcss.config.js create mode 100644 styles/globals.css create mode 100644 tailwind.config.js diff --git a/app/(main)/layout.tsx b/app/(main)/layout.tsx index 5cb39fdb..122640dd 100644 --- a/app/(main)/layout.tsx +++ b/app/(main)/layout.tsx @@ -19,7 +19,7 @@ export const metadata: Metadata = { ttl: 604800 }, icons: { - icon: '/favicon.ico' + // icon: '' } }; diff --git a/app/(student)/layout.tsx b/app/(student)/layout.tsx index 45cb94cf..0eec769f 100644 --- a/app/(student)/layout.tsx +++ b/app/(student)/layout.tsx @@ -1,13 +1,15 @@ -import BaseLayout from "./components/BaseLayout"; +import Layout from "../../layout/layout"; -export default function Layout({ +export default function LayoutStudent({ children, }: Readonly<{ children: React.ReactNode; }>) { + return (
- {children} + {/* {children} */} + {children};
); } \ No newline at end of file diff --git a/app/(student)/new/page.tsx b/app/(student)/new/page.tsx deleted file mode 100644 index 6b0987b4..00000000 --- a/app/(student)/new/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -'use client'; - -import React from 'react' - -export default function New() { - return ( -
- New -
- ) -} diff --git a/app/(student)/page.tsx b/app/(student)/page.tsx deleted file mode 100644 index bd2f4a81..00000000 --- a/app/(student)/page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import HomeClient from "./components/HomeClient"; - -export default function Home() { - - return ( - - ); -} \ No newline at end of file diff --git a/app/(student)/components/BaseLayout.tsx b/app/components/BaseLayout.tsx similarity index 100% rename from app/(student)/components/BaseLayout.tsx rename to app/components/BaseLayout.tsx diff --git a/app/(student)/components/CounterBanner.tsx b/app/components/CounterBanner.tsx similarity index 98% rename from app/(student)/components/CounterBanner.tsx rename to app/components/CounterBanner.tsx index 75b59db3..58a13363 100644 --- a/app/(student)/components/CounterBanner.tsx +++ b/app/components/CounterBanner.tsx @@ -5,7 +5,7 @@ import { faCircle, faBookOpen, faShieldHeart, } from "@fortawesome/free-solid-svg-icons"; -import MyFontAwesome from './MyFontAwesome'; +import MyFontAwesome from '../../components/MyFontAwesome'; import CountUp from 'react-countup'; export default function CounterBanner() { diff --git a/app/(student)/components/HomeClient.tsx b/app/components/HomeClient.tsx similarity index 99% rename from app/(student)/components/HomeClient.tsx rename to app/components/HomeClient.tsx index c57325ca..dff8bed4 100644 --- a/app/(student)/components/HomeClient.tsx +++ b/app/components/HomeClient.tsx @@ -18,7 +18,7 @@ export default function HomeClient() { return (
-
+
diff --git a/app/(student)/components/MyFontAwesome.tsx b/app/components/MyFontAwesome.tsx similarity index 100% rename from app/(student)/components/MyFontAwesome.tsx rename to app/components/MyFontAwesome.tsx diff --git a/app/(student)/components/VideoPlay.tsx b/app/components/VideoPlay.tsx similarity index 98% rename from app/(student)/components/VideoPlay.tsx rename to app/components/VideoPlay.tsx index 035e5c53..024875db 100644 --- a/app/(student)/components/VideoPlay.tsx +++ b/app/components/VideoPlay.tsx @@ -1,13 +1,14 @@ 'use client'; import { faPlay} from "@fortawesome/free-solid-svg-icons"; -import MyFontAwesome from './MyFontAwesome'; + import Image from "next/image"; import { useState } from "react"; import { Dialog } from 'primereact/dialog'; // import 'primereact/resources/themes/lara-light-blue/theme.css'; // или другая тема import 'primereact/resources/primereact.min.css'; import 'primeicons/primeicons.css'; +import MyFontAwesome from "./MyFontAwesome"; export default function VideoPlay() { const [videoCall, setVideoCall] = useState(false); diff --git a/app/(student)/components/buttons/FancyLinkBtn.tsx b/app/components/buttons/FancyLinkBtn.tsx similarity index 100% rename from app/(student)/components/buttons/FancyLinkBtn.tsx rename to app/components/buttons/FancyLinkBtn.tsx diff --git a/app/(student)/components/popUp/Tiered.tsx b/app/components/popUp/Tiered.tsx similarity index 97% rename from app/(student)/components/popUp/Tiered.tsx rename to app/components/popUp/Tiered.tsx index 1260075f..2a1718b7 100644 --- a/app/(student)/components/popUp/Tiered.tsx +++ b/app/components/popUp/Tiered.tsx @@ -4,7 +4,7 @@ import { useEffect, useRef, useState } from 'react'; import { Button } from 'primereact/button'; import { TieredMenu } from 'primereact/tieredmenu'; import { faBars, faClose } from "@fortawesome/free-solid-svg-icons"; -import MyFontAwesome from '../MyFontAwesome'; +import MyFontAwesome from '../../../components/MyFontAwesome'; import { useMediaQuery } from '@/hooks/useMediaQuery'; export default function Tiered({title, items, insideColor}) { diff --git a/app/favicon.ico b/app/favicon.ico deleted file mode 100644 index a614a31e44745fac24459b113a11e079c0e19cbe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1225 zcmV;)1UCDLP)aQP;ly#o`xZBfi3gI0H}O=_D^^b?dL&#e0&x_%X?gYdegcmgL3H zF*+mn*(Ha2@okx3Am+dIqmy9#nzI%rZ-hHedH+ zeUcX!m08-C#DNQtVS01%h+8qak*js_T{}*}FBpJUAh5h?P-baaX6YmN7I!uya24Jz zv-B{WkQXQ6X8c~U0#D!%oQS`0at8u%BW6c316Y{k#l}`h*TExM`hK)~HAZhuO`u0G zs&NR9V{F{{y!d_qWqj^!*!Kz@HCf2>=okffKH98AO`yNteBFkdF*V6Ux{hS&05)~l z;iyQK(C??j=a(A3+PkO5=Z)tZ1SK7u^jTzeOKnjN;iZjCJ->H2!{~1tBzZAWXwzp0 z36xn1bFd=Gi_uA5TqA_@KSA8g%=mLPmX=xiCyh2Nvvf<67aN3AZkkXew?FMbmW_}SW^U5Xds9_)^KzeLFCl(>V{Ip}TAjwa*yG8*4R7Ddj0iy#XuE0YEBfoCVJwUg9J9< zrg$kCL{zKA*j4dve7#B#mv-#Lj7Z`Da;z(}bXy18>G))jK$)dmu|-g;F>S`{T1;$( zqO32YeU1%lAzWxK!;XpvacL`rdJsuoj7sw20h}X<)iGh85qGyI$%}6UMFSN*EoR|+ zT#ok~d*>S)N%dkvE3{!x#eyU+rbnAz;jXzISJ!usPM6ZU-OUiRAH!ctkB4}SevWaiklzEst%;C=7csvIP8S}@(v>6eJ7NNn z!~;t}!fGvW00000NkvXXu0mjf2cAs0 diff --git a/app/layout.tsx b/app/layout.tsx index 5e9cecc2..74fedd14 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,7 @@ 'use client'; import { LayoutProvider } from '../layout/context/layoutcontext'; import { PrimeReactProvider } from 'primereact/api'; +import '../styles/globals.css'; import { config } from "@fortawesome/fontawesome-svg-core"; import "@fortawesome/fontawesome-svg-core/styles.css"; // Импорт стилей config.autoAddCss = false; diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 00000000..45a50bc6 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,8 @@ +import React from 'react' +import HomeClient from './components/HomeClient' + +export default function page() { + return ( + + ) +} diff --git a/layout/AppTopbar.tsx b/layout/AppTopbar.tsx index 9a2c8849..f63a5d92 100644 --- a/layout/AppTopbar.tsx +++ b/layout/AppTopbar.tsx @@ -21,7 +21,8 @@ const AppTopbar = forwardRef((props, ref) => { return (
- logo + {/* logo */} + logo SAKAI diff --git a/package-lock.json b/package-lock.json index 241b058c..01ec7a0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,10 +27,14 @@ "typescript": "5.1.3" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.8", + "autoprefixer": "^10.4.21", "eslint": "8.43.0", "eslint-config-next": "13.4.6", + "postcss": "^8.5.4", "prettier": "^2.8.8", - "sass": "^1.63.4" + "sass": "^1.63.4", + "tailwindcss": "^4.1.8" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -42,6 +46,31 @@ "node": ">=0.10.0" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/runtime": { "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz", @@ -184,6 +213,66 @@ "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@kurkle/color": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", @@ -387,6 +476,267 @@ "tslib": "^2.4.0" } }, + "node_modules/@tailwindcss/node": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.8.tgz", + "integrity": "sha512-OWwBsbC9BFAJelmnNcrKuf+bka2ZxCE2A4Ft53Tkg4uoiE67r/PMEYwCsourC26E+kmxfwE0hVzMdxqeW+xu7Q==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "lightningcss": "1.30.1", + "magic-string": "^0.30.17", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.8" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.8.tgz", + "integrity": "sha512-d7qvv9PsM5N3VNKhwVUhpK6r4h9wtLkJ6lz9ZY9aeZgrUWk1Z8VPyqyDT9MZlem7GTGseRQHkeB1j3tC7W1P+A==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "detect-libc": "^2.0.4", + "tar": "^7.4.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.8", + "@tailwindcss/oxide-darwin-arm64": "4.1.8", + "@tailwindcss/oxide-darwin-x64": "4.1.8", + "@tailwindcss/oxide-freebsd-x64": "4.1.8", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.8", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.8", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.8", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.8", + "@tailwindcss/oxide-linux-x64-musl": "4.1.8", + "@tailwindcss/oxide-wasm32-wasi": "4.1.8", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.8", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.8" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.8.tgz", + "integrity": "sha512-Fbz7qni62uKYceWYvUjRqhGfZKwhZDQhlrJKGtnZfuNtHFqa8wmr+Wn74CTWERiW2hn3mN5gTpOoxWKk0jRxjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.8.tgz", + "integrity": "sha512-RdRvedGsT0vwVVDztvyXhKpsU2ark/BjgG0huo4+2BluxdXo8NDgzl77qh0T1nUxmM11eXwR8jA39ibvSTbi7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.8.tgz", + "integrity": "sha512-t6PgxjEMLp5Ovf7uMb2OFmb3kqzVTPPakWpBIFzppk4JE4ix0yEtbtSjPbU8+PZETpaYMtXvss2Sdkx8Vs4XRw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.8.tgz", + "integrity": "sha512-g8C8eGEyhHTqwPStSwZNSrOlyx0bhK/V/+zX0Y+n7DoRUzyS8eMbVshVOLJTDDC+Qn9IJnilYbIKzpB9n4aBsg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.8.tgz", + "integrity": "sha512-Jmzr3FA4S2tHhaC6yCjac3rGf7hG9R6Gf2z9i9JFcuyy0u79HfQsh/thifbYTF2ic82KJovKKkIB6Z9TdNhCXQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.8.tgz", + "integrity": "sha512-qq7jXtO1+UEtCmCeBBIRDrPFIVI4ilEQ97qgBGdwXAARrUqSn/L9fUrkb1XP/mvVtoVeR2bt/0L77xx53bPZ/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.8.tgz", + "integrity": "sha512-O6b8QesPbJCRshsNApsOIpzKt3ztG35gfX9tEf4arD7mwNinsoCKxkj8TgEE0YRjmjtO3r9FlJnT/ENd9EVefQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.8.tgz", + "integrity": "sha512-32iEXX/pXwikshNOGnERAFwFSfiltmijMIAbUhnNyjFr3tmWmMJWQKU2vNcFX0DACSXJ3ZWcSkzNbaKTdngH6g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.8.tgz", + "integrity": "sha512-s+VSSD+TfZeMEsCaFaHTaY5YNj3Dri8rST09gMvYQKwPphacRG7wbuQ5ZJMIJXN/puxPcg/nU+ucvWguPpvBDg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.8.tgz", + "integrity": "sha512-CXBPVFkpDjM67sS1psWohZ6g/2/cd+cq56vPxK4JeawelxwK4YECgl9Y9TjkE2qfF+9/s1tHHJqrC4SS6cVvSg==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@emnapi/wasi-threads": "^1.0.2", + "@napi-rs/wasm-runtime": "^0.2.10", + "@tybys/wasm-util": "^0.9.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.8.tgz", + "integrity": "sha512-7GmYk1n28teDHUjPlIx4Z6Z4hHEgvP5ZW2QS9ygnDAdI/myh3HTHjDqtSqgu1BpRoI4OiLx+fThAyA1JePoENA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.8.tgz", + "integrity": "sha512-fou+U20j+Jl0EHwK92spoWISON2OBnCazIc038Xj2TdweYV33ZRkS9nwqiUi2d/Wba5xg5UoHfvynnb/UB49cQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.8.tgz", + "integrity": "sha512-vB/vlf7rIky+w94aWMw34bWW1ka6g6C3xIOdICKX2GC0VcLtL6fhlLiafF0DVIwa9V6EHz8kbWMkS2s2QvvNlw==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.8", + "@tailwindcss/oxide": "4.1.8", + "postcss": "^8.4.41", + "tailwindcss": "4.1.8" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -779,6 +1129,43 @@ "has-symbols": "^1.0.3" } }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -846,6 +1233,38 @@ "node": ">=8" } }, + "node_modules/browserslist": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -881,9 +1300,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001572", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001572.tgz", - "integrity": "sha512-1Pbh5FLmn5y4+QhNyJE9j3/7dK44dGB83/ZMjv/qJk86TvDbjk0LosiZo0i0WB0Vx607qMX9jYrn1VLHCkN4rw==", + "version": "1.0.30001720", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz", + "integrity": "sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==", "funding": [ { "type": "opencollective", @@ -965,6 +1384,15 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "engines": { + "node": ">=18" + } + }, "node_modules/classlist-polyfill": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/classlist-polyfill/-/classlist-polyfill-1.2.0.tgz", @@ -1097,6 +1525,15 @@ "node": ">=6" } }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1130,6 +1567,12 @@ "csstype": "^3.0.2" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.161", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.161.tgz", + "integrity": "sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA==", + "dev": true + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -1137,9 +1580,9 @@ "dev": true }, "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -1264,6 +1707,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1811,6 +2263,19 @@ "is-callable": "^1.1.3" } }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2522,106 +2987,343 @@ "set-function-name": "^2.0.1" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "dev": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", + "dev": true + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "dev": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "argparse": "^2.0.1" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "minimist": "^1.2.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, - "bin": { - "json5": "lib/cli.js" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/jsx-ast-utils": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", - "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=4.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "json-buffer": "3.0.1" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/language-subtag-registry": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", - "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", - "dev": true - }, - "node_modules/language-tags": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", - "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "language-subtag-registry": "^0.3.20" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=0.10" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.8.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/locate-path": { @@ -2678,6 +3380,15 @@ "node": ">=10" } }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2721,6 +3432,42 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dev": true, + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2728,9 +3475,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", @@ -2800,6 +3547,35 @@ } } }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", + "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -2809,6 +3585,15 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3037,9 +3822,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -3054,9 +3839,10 @@ } }, "node_modules/postcss": { - "version": "8.4.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", - "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", + "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", + "dev": true, "funding": [ { "type": "opencollective", @@ -3065,17 +3851,27 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3508,9 +4304,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "engines": { "node": ">=0.10.0" } @@ -3667,6 +4463,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tailwindcss": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.8.tgz", + "integrity": "sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og==", + "dev": true + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -3676,6 +4478,32 @@ "node": ">=6" } }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "engines": { + "node": ">=18" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -3848,6 +4676,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/package.json b/package.json index 44eb0099..01e1fd49 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,13 @@ "typescript": "5.1.3" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.8", + "autoprefixer": "^10.4.21", "eslint": "8.43.0", "eslint-config-next": "13.4.6", + "postcss": "^8.5.4", "prettier": "^2.8.8", - "sass": "^1.63.4" + "sass": "^1.63.4", + "tailwindcss": "^4.1.8" } } diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 00000000..8f3250b7 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + '@tailwindcss/postcss': {}, // Используем новый плагин + autoprefixer: {}, + } +} \ No newline at end of file diff --git a/styles/globals.css b/styles/globals.css new file mode 100644 index 00000000..063a1b0f --- /dev/null +++ b/styles/globals.css @@ -0,0 +1,78 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --bodyFonts: "Jost", sans-serif; + --mainColor: #08A9E6; + --redColor: #EC272F; + --titleColor: #21225F; + --bodyColor: #555555; + --whiteColor: #ffffff; + --fontSize: 16px; + --transition: 0.5s; +} + +p { + color: var(--bodyColor); + margin-bottom: 10px; +} +p:last-child { + margin-bottom: 0; +} + +a { + display: inline-block; + transition: var(--transition); + text-decoration: none; +} +a:hover, a:focus { + text-decoration: none; +} + +h1, h2, h3, h4, h5, h6 { + color: var(--titleColor); +} + +.mainColor-hover:hover{ + color: #08A9E6; +} + +/* animate */ +.animateContent{ + animation: content linear 6s infinite; +} + +@keyframes content { + 10%{ + transform: translateX(2px); + } + 50%{ + transform: translateX(-4px); + } + 80%{ + transform: translateY(-2px); + } + 100%{ + transform: translateY(4px); + } +} + +.animateFaster{ + animation: faster linear 4s infinite forwards; +} + +@keyframes faster { + 10%{ + transform: translateX(-2px); + } + 50%{ + transform: translateX(4px); + } + 80%{ + transform: translateY(2px); + } + 100%{ + transform: translateY(-4px); + } +} \ No newline at end of file diff --git a/styles/layout/_topbar.scss b/styles/layout/_topbar.scss index bf6f5d9c..3ae47918 100644 --- a/styles/layout/_topbar.scss +++ b/styles/layout/_topbar.scss @@ -1,6 +1,6 @@ .layout-topbar { position: fixed; - height: 5rem; + height: 6rem; z-index: 997; left: 0; top: 0; diff --git a/styles/layout/layout.scss b/styles/layout/layout.scss index 30218e29..0b5314a9 100644 --- a/styles/layout/layout.scss +++ b/styles/layout/layout.scss @@ -1,79 +1,3 @@ - -:root { - --bodyFonts: "Jost", sans-serif; - --mainColor: #08A9E6; - --redColor: #EC272F; - --titleColor: #21225F; - --bodyColor: #555555; - --whiteColor: #ffffff; - --fontSize: 16px; - --transition: 0.5s; -} - -p { - color: var(--bodyColor); - margin-bottom: 10px; -} -p:last-child { - margin-bottom: 0; -} - -a { - display: inline-block; - transition: var(--transition); - text-decoration: none; -} -a:hover, a:focus { - text-decoration: none; -} - -h1, h2, h3, h4, h5, h6 { - color: var(--titleColor); -} - -.mainColor-hover:hover{ - color: #08A9E6; -} - -/* animate */ -.animateContent{ - animation: content linear 6s infinite; -} - -@keyframes content { - 10%{ - transform: translateX(2px); - } - 50%{ - transform: translateX(-4px); - } - 80%{ - transform: translateY(-2px); - } - 100%{ - transform: translateY(4px); - } -} - -.animateFaster{ - animation: faster linear 4s infinite forwards; -} - -@keyframes faster { - 10%{ - transform: translateX(-2px); - } - 50%{ - transform: translateX(4px); - } - 80%{ - transform: translateY(2px); - } - 100%{ - transform: translateY(-4px); - } -} - @import './_variables'; @import "./_mixins"; @import "./_main"; diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 00000000..de7789e0 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,10 @@ +module.exports = { + content: [ + "./app/**/*.{js,ts,jsx,tsx}", + "./layout/**/*.{js,ts,jsx,tsx}", + "./components/**/*.{js,ts,jsx,tsx}", + ], + corePlugins: { + preflight: false, // Важно! + }, +} \ No newline at end of file From 15fc38e7c71e40e874b761ed1482eea5c83b9845 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Mon, 2 Jun 2025 11:32:20 +0600 Subject: [PATCH 003/183] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B2=D0=B5?= =?UTF-8?q?=D0=BB=20=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=81=D0=B5=D0=BC=D0=B0=D0=BD=D1=82=D0=B8=D0=BA=D0=B8=D0=BA?= =?UTF-8?q?=D0=B8,=20=D0=BD=D0=BE=20=D0=BD=D0=B5=20=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=D0=B5=D1=82=20tailwind?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/BaseLayout.tsx | 13 +-- app/components/CounterBanner.tsx | 8 +- app/components/HomeClient.tsx | 35 ++++--- app/components/MyFontAwesome.tsx | 2 +- app/components/VideoPlay.tsx | 22 ++--- app/components/popUp/Tiered.tsx | 19 ++-- app/lorem/page.tsx | 7 ++ app/page.tsx | 6 +- hooks/useMediaQuery.tsx | 19 ++++ layout/AppFooter.tsx | 52 +++++++++- layout/AppTopbar.tsx | 157 ++++++++++++++++++++++++++++--- styles/globals.css | 3 +- styles/layout/_topbar.scss | 53 ++++------- tailwind.config.js | 10 ++ 14 files changed, 306 insertions(+), 100 deletions(-) create mode 100644 app/lorem/page.tsx create mode 100644 hooks/useMediaQuery.tsx diff --git a/app/components/BaseLayout.tsx b/app/components/BaseLayout.tsx index d7027d43..101fd3fb 100644 --- a/app/components/BaseLayout.tsx +++ b/app/components/BaseLayout.tsx @@ -1,17 +1,18 @@ "use client"; -// import Footer from "./Footer"; -// import Header from "./Header"; +import AppTopbar from "@/layout/AppTopbar"; +import HomeClient from "./HomeClient"; +import AppFooter from "@/layout/AppFooter"; -export default function BaseLayout({ children }: { children: React.ReactNode }) { +export default function BaseLayout() { return ( <>
- {/*
*/} +
- {children} +
- {/*
*/} +
); diff --git a/app/components/CounterBanner.tsx b/app/components/CounterBanner.tsx index 58a13363..ea617fbf 100644 --- a/app/components/CounterBanner.tsx +++ b/app/components/CounterBanner.tsx @@ -5,20 +5,20 @@ import { faCircle, faBookOpen, faShieldHeart, } from "@fortawesome/free-solid-svg-icons"; -import MyFontAwesome from '../../components/MyFontAwesome'; +import MyFontAwesome from './MyFontAwesome'; import CountUp from 'react-countup'; export default function CounterBanner() { return (
-
+
-
+
+
+
Курстар & видеосабактар
@@ -55,7 +55,7 @@ export default function CounterBanner() { Канааттануу деңгээли
-
+
) } diff --git a/app/components/HomeClient.tsx b/app/components/HomeClient.tsx index dff8bed4..a66607ec 100644 --- a/app/components/HomeClient.tsx +++ b/app/components/HomeClient.tsx @@ -16,21 +16,19 @@ export default function HomeClient() { }, []); return ( -
+
-
- Shape + Shape
- Shape + Shape
13000 @@ -159,6 +157,15 @@ export default function HomeClient() {
+ + {/* Counter Statistics */} + + + {/* Oshgu Video */} +
+

Видеоэкскурсия по главному зданию ОшГУ

+
+
); } diff --git a/app/components/MyFontAwesome.tsx b/app/components/MyFontAwesome.tsx index e3bb24f8..28e58167 100644 --- a/app/components/MyFontAwesome.tsx +++ b/app/components/MyFontAwesome.tsx @@ -3,7 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { ComponentProps } from "react"; - + interface IconProps extends ComponentProps { icon: IconProp; className?: string; diff --git a/app/components/VideoPlay.tsx b/app/components/VideoPlay.tsx index 024875db..ee722e28 100644 --- a/app/components/VideoPlay.tsx +++ b/app/components/VideoPlay.tsx @@ -28,22 +28,22 @@ export default function VideoPlay() { >
-
-
-
+ {/*
*/} + {/*
setVideoCall(true)} - > + > */} {/* Волна */} - + {/* */} {/* Иконка-кнопка */} -
+ {/*
-
-
-
- Логотип ОшГУ +
*/} + {/*
+
*/} + {/* Логотип ОшГУ */}
) diff --git a/app/components/popUp/Tiered.tsx b/app/components/popUp/Tiered.tsx index 2a1718b7..b629c09f 100644 --- a/app/components/popUp/Tiered.tsx +++ b/app/components/popUp/Tiered.tsx @@ -3,8 +3,8 @@ import { useEffect, useRef, useState } from 'react'; import { Button } from 'primereact/button'; import { TieredMenu } from 'primereact/tieredmenu'; -import { faBars, faClose } from "@fortawesome/free-solid-svg-icons"; -import MyFontAwesome from '../../../components/MyFontAwesome'; +import { faBars, faClose, faEllipsisVertical } from "@fortawesome/free-solid-svg-icons"; +import MyFontAwesome from '../MyFontAwesome'; import { useMediaQuery } from '@/hooks/useMediaQuery'; export default function Tiered({title, items, insideColor}) { @@ -29,10 +29,10 @@ export default function Tiered({title, items, insideColor}) { label={title.name && title.name} icon={title.name && "pi pi-list"} onClick={(e) => toggleMenu(e)} - className={`flex gap-2 text-[17px] text-[var(${insideColor})] hover:text-[var(--mainColor)]`} + className={`gap-2 p-2 bg-inherit text-[16px] text-[var(${insideColor})] hover:text-[var(--mainColor)]`} /> : } @@ -41,17 +41,18 @@ export default function Tiered({title, items, insideColor}) { popup ref={menu} breakpoint="1000px" - style={{ width: media ? '90%' : '' , marginLeft: media ? '5%' : ''}} - className={`pointer max-h-[200px] overflow-y-scroll`} + style={{ width: media ? '90%' : '' , left:'10px'}} + className={`pointer mt-4 max-h-[200px] overflow-y-scroll`} pt={{ - root: { className: `bg-white mt-4 border border-gray-300 rounded-md shadow-md`}, + root: { className: `bg-white border border-gray-300 rounded-md shadow-md`}, menu: { className: 'transition-all' }, - menuitem: { className: 'text-[var(--titleColor)] text-[14px] px-4 py-4 border-b hover:shadow-xl border-gray-200 hover:text-white hover:bg-[var(--mainColor)]' }, - action: { className: '' }, // для иконки + текста + menuitem: { className: 'text-[var(--titleColor)] text-[14px] px-1 py-2 border-b hover:shadow-xl border-gray-200 hover:text-white hover:bg-[var(--mainColor)]' }, + action: { className: 'flex gap-1' }, // для иконки + текста icon: { className: 'text-[var(--titleColor)] mx-1 hover:text-white' }, submenuIcon: { className: 'text-gray-400 ml-auto' } }} /> +
); } \ No newline at end of file diff --git a/app/lorem/page.tsx b/app/lorem/page.tsx new file mode 100644 index 00000000..539c1ccf --- /dev/null +++ b/app/lorem/page.tsx @@ -0,0 +1,7 @@ +import React from 'react' + +export default function page() { + return ( +
page
+ ) +} diff --git a/app/page.tsx b/app/page.tsx index 45a50bc6..39c48471 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,8 +1,10 @@ import React from 'react' -import HomeClient from './components/HomeClient' +import BaseLayout from './components/BaseLayout' export default function page() { return ( - +
+ +
) } diff --git a/hooks/useMediaQuery.tsx b/hooks/useMediaQuery.tsx new file mode 100644 index 00000000..89e61dca --- /dev/null +++ b/hooks/useMediaQuery.tsx @@ -0,0 +1,19 @@ +"use client"; + +import { useState, useEffect } from "react"; + +export function useMediaQuery(query:string) { + const [matches, setMatches] = useState(false); + + useEffect(() => { + const mediaQuery = window.matchMedia(query); + setMatches(mediaQuery.matches); + + const handler = (e:MediaQueryListEvent) => setMatches(e.matches); + mediaQuery.addEventListener("change", handler); + + return () => mediaQuery.removeEventListener("change", handler); + }, [query]); + + return matches; +} diff --git a/layout/AppFooter.tsx b/layout/AppFooter.tsx index 424b9987..4b84b090 100644 --- a/layout/AppFooter.tsx +++ b/layout/AppFooter.tsx @@ -2,16 +2,60 @@ import React, { useContext } from 'react'; import { LayoutContext } from './context/layoutcontext'; +import Image from 'next/image'; +import { faChalkboard, faPhone,} from "@fortawesome/free-solid-svg-icons"; +import MyFontAwesome from '@/app/components/MyFontAwesome'; +import Link from 'next/link'; const AppFooter = () => { const { layoutConfig } = useContext(LayoutContext); + + // dark mode + // Logo return ( -
- Logo - by - PrimeReact +
+
+
+ Логотип +
+ Кыргызстан, 723500, г. Ош, ул. Ленина, 331, ОшГУ Главный корпус + Общий отдел: +996 3222 7-07-12, + факс +996 3222 7-09-15, + edu@oshsu.kg +
+
+
+
+ +

Курстар

+
+
+ lorem ipsum + lorem ipsum + lorem ipsum +
+
+
+
+ +

Байланыш

+
+
+ lorem ipsum + lorem ipsum + lorem ipsum +
+
+ +

+ © 2025 ОшГУ | 2025 OshSU - IT Academy Ошский Государственный Университет oshsu.kg +

+
); }; diff --git a/layout/AppTopbar.tsx b/layout/AppTopbar.tsx index f63a5d92..f7ffa387 100644 --- a/layout/AppTopbar.tsx +++ b/layout/AppTopbar.tsx @@ -5,6 +5,10 @@ import { classNames } from 'primereact/utils'; import React, { forwardRef, useContext, useImperativeHandle, useRef } from 'react'; import { AppTopbarRef } from '@/types'; import { LayoutContext } from './context/layoutcontext'; +import Tiered from '@/app/components/popUp/Tiered'; +import { useMediaQuery } from '@/hooks/useMediaQuery'; +import FancyLinkBtn from '@/app/components/buttons/FancyLinkBtn'; +import { usePathname } from 'next/navigation'; const AppTopbar = forwardRef((props, ref) => { const { layoutConfig, layoutState, onMenuToggle, showProfileSidebar } = useContext(LayoutContext); @@ -18,27 +22,152 @@ const AppTopbar = forwardRef((props, ref) => { topbarmenubutton: topbarmenubuttonRef.current })); + const pathName = usePathname(); + const media = useMediaQuery('(max-width: 1000px)'); + + const items = [ + { + label: 'Ачык онлайн курстар', + icon: 'pi pi-file', + items: [], + link:'/login' + }, + { + label: 'Бакалавриат', + icon: 'pi pi-file', + items: [] + }, + { + label: 'Магистратура', + icon: 'pi pi-file', + items: [] + }, + { + label: 'Кошумча билим берүү', + icon: 'pi pi-file', + items: [] + }, + ]; + + const mobileMenu = [ + { + label: 'КАТАЛОГ', + icon: 'pi pi-list', + items: [ + { + label: 'Ачык онлайн курстар', + icon: 'pi pi-file', + items: [], + link:'/login' + }, + { + label: 'Бакалавриат', + icon: 'pi pi-file', + items: [] + }, + { + label: 'Магистратура', + icon: 'pi pi-file', + items: [] + }, + { + label: 'Кошумча билим берүү', + icon: 'pi pi-file', + items: [] + }, + ], + }, + { + label: 'Бакалавриат', + icon: 'pi pi-file', + items: [] + }, + { + label: 'Магистратура', + icon: 'pi pi-file', + items: [] + }, + { + label: 'Кошумча билим берүү', + icon: 'pi pi-file', + items: [] + }, + { + label: 'Кошумча билим берүү', + icon: 'pi pi-file', + items: [] + }, + { + label: 'КАТАЛОГ', + icon: 'pi pi-list', + items: [ + { + label: 'Ачык онлайн курстар', + icon: 'pi pi-file', + items: [], + link:'/login' + }, + { + label: 'Бакалавриат', + icon: 'pi pi-file', + items: [] + }, + { + label: 'Магистратура', + icon: 'pi pi-file', + items: [] + }, + { + label: 'Кошумча билим берүү', + icon: 'pi pi-file', + items: [] + }, + ], + }, + ]; + return (
{/* logo */} - logo - SAKAI + logo +

Цифровой кампус ОшГУ

- - - + {pathName !== '/' ? + : '' + }
- +
+ {media ? + + :
+ + + + ОшМУнун сайты + Байланыш +
+ } + + +
+
+ {/* - + */}
); diff --git a/styles/globals.css b/styles/globals.css index 063a1b0f..ba85d770 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -75,4 +75,5 @@ h1, h2, h3, h4, h5, h6 { 100%{ transform: translateY(-4px); } -} \ No newline at end of file +} + diff --git a/styles/layout/_topbar.scss b/styles/layout/_topbar.scss index 3ae47918..d6a02350 100644 --- a/styles/layout/_topbar.scss +++ b/styles/layout/_topbar.scss @@ -18,12 +18,12 @@ color: var(--surface-900); font-size: 1.5rem; font-weight: 500; - width: 300px; + // width: 300px; border-radius: 12px; img { // height: 2.5rem; - margin-right: .5rem; + // margin-right: .5rem; } &:focus { @@ -63,7 +63,7 @@ } .layout-menu-button { - margin-left: 2rem; + // margin-left: 2rem; } .layout-topbar-menu-button { @@ -81,60 +81,45 @@ display: flex; .layout-topbar-button { - margin-left: 1rem; + // margin-left: 1rem; } } } -.main-header { - position: fixed; - top: 50px; /* равняется высоте .top-bar */ - width: 100%; - background: #fff; - z-index: 10; /* чтобы header был над контентом */ - box-shadow: 0 2px 4px rgba(0,0,0,0.1); - padding: 16px; - - .main-header.scrolled { - transform: translateY(50px); /* прячет шапку на высоту .top-bar */ - top: 0; - } -} - @media (max-width: 991px) { .layout-topbar { justify-content: space-between; .layout-topbar-logo { - width: auto; + // width: auto; order: 2; } .layout-menu-button { margin-left: 0; - order: 1; + order: 3; } .layout-topbar-menu-button { display: inline-flex; margin-left: 0; - order: 3; + // order: 1; } .layout-topbar-menu { margin-left: 0; - position: absolute; - flex-direction: column; - background-color: var(--surface-overlay); - box-shadow: 0px 3px 5px rgba(0,0,0,.02), 0px 0px 2px rgba(0,0,0,.05), 0px 1px 4px rgba(0,0,0,.08); - border-radius: 12px; - padding: 1rem; - right: 2rem; - top: 5rem; - min-width: 15rem; - display: none; - -webkit-animation: scalein 0.15s linear; - animation: scalein 0.15s linear; + // position: absolute; + // flex-direction: column; + // background-color: var(--surface-overlay); + // box-shadow: 0px 3px 5px rgba(0,0,0,.02), 0px 0px 2px rgba(0,0,0,.05), 0px 1px 4px rgba(0,0,0,.08); + // border-radius: 12px; + // padding: 1rem; + // right: 2rem; + // top: 5rem; + // min-width: 15rem; + // display: none; + // -webkit-animation: scalein 0.15s linear; + // animation: scalein 0.15s linear; &.layout-topbar-menu-mobile-active { display: block diff --git a/tailwind.config.js b/tailwind.config.js index de7789e0..813453ad 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -3,7 +3,17 @@ module.exports = { "./app/**/*.{js,ts,jsx,tsx}", "./layout/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}", + "./app/components/**/*.{js,ts,jsx,tsx}", ], + theme: { + screens: { + sm: '640px', + md: '768px', + lg: '1024px', + xl: '1280px', + '2xl': '1536px', + } + }, corePlugins: { preflight: false, // Важно! }, From a059336a5cc5e8abe822958683aaadd35f324abf Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Mon, 2 Jun 2025 15:22:48 +0600 Subject: [PATCH 004/183] =?UTF-8?q?=D0=91=D0=B0=D0=B3=D0=B8=20=D1=83=D1=81?= =?UTF-8?q?=D0=BF=D0=B5=D1=88=D0=BD=D0=BE=20=D0=BF=D0=BE=D1=84=D0=B8=D0=BA?= =?UTF-8?q?=D1=81=D0=B5=D0=BB=D0=B8=D1=81=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/HomeClient.tsx | 18 +++++++-------- app/components/VideoPlay.tsx | 29 +++++++++++++------------ app/components/buttons/FancyLinkBtn.tsx | 2 +- app/components/popUp/Tiered.tsx | 5 +++-- {styles => app}/globals.css | 5 ++--- app/layout.tsx | 6 ++--- layout/AppTopbar.tsx | 14 +----------- styles/layout/_typography.scss | 24 -------------------- tailwind.config.js | 20 ----------------- 9 files changed, 34 insertions(+), 89 deletions(-) rename {styles => app}/globals.css (94%) delete mode 100644 tailwind.config.js diff --git a/app/components/HomeClient.tsx b/app/components/HomeClient.tsx index a66607ec..0ecdc498 100644 --- a/app/components/HomeClient.tsx +++ b/app/components/HomeClient.tsx @@ -8,7 +8,6 @@ import Link from "next/link"; import { faClock, faVideo,} from "@fortawesome/free-solid-svg-icons"; import MyFontAwesome from "./MyFontAwesome"; import VideoPlay from "./VideoPlay"; -import Image from "next/image"; export default function HomeClient() { useEffect(() => { @@ -39,7 +38,7 @@ export default function HomeClient() { ЫҢГАЙЛУУ ОКУУ ҮЧҮН ОНЛАЙН МЕЙКИНДИК
-

Аралыктан окутуу порталына кош келиңиз! -

+
{" "} Университеттин онлайн билим берүү жаатындагы долбоорлорун бириктирүүдөбүз: -
    +
    • ачык онлайн курстар
    • жогорку билим берүү программалары
    @@ -112,15 +112,15 @@ export default function HomeClient() {
-
+
13000 -

lorem

+

Студент

@@ -131,7 +131,7 @@ export default function HomeClient() { data-aos-duration="1000" data-aos-once="true" > -
+
Куттуктайбыз!

Сиздин кабыл алуу ийгиликтүү аяктады

@@ -144,7 +144,7 @@ export default function HomeClient() { data-aos-duration="1000" data-aos-once="true" > -
+
User experience className

Today at 12.00 PM

diff --git a/app/components/VideoPlay.tsx b/app/components/VideoPlay.tsx index ee722e28..d843bcf6 100644 --- a/app/components/VideoPlay.tsx +++ b/app/components/VideoPlay.tsx @@ -15,11 +15,12 @@ export default function VideoPlay() { return (
- {if (!videoCall) return; setVideoCall(false); }}> + {if (!videoCall) return; setVideoCall(false); }}>
-
- {/*
*/} - {/*
+
+
setVideoCall(true)} - > */} + > {/* Волна */} - {/* */} + {/* Иконка-кнопка */} - {/*
+
-
*/} - {/*
-
*/} - {/* Логотип ОшГУ */} +
+
+
+ Логотип ОшГУ
) diff --git a/app/components/buttons/FancyLinkBtn.tsx b/app/components/buttons/FancyLinkBtn.tsx index 1073c2ce..9016c00f 100644 --- a/app/components/buttons/FancyLinkBtn.tsx +++ b/app/components/buttons/FancyLinkBtn.tsx @@ -17,7 +17,7 @@ export default function FancyLinkBtn({backround, effectBackround, title}) { backgroundColor:`var(${backround})`, }} > - {title} + {title} toggleMenu(e)} - className={`gap-2 p-2 bg-inherit text-[16px] text-[var(${insideColor})] hover:text-[var(--mainColor)]`} + style={{color:'var(--BodyColor)'}} + className={`gap-2 p-2 bg-white text-[16px]`} /> :
} - -
+
- {/* - - - - */}
); diff --git a/styles/layout/_typography.scss b/styles/layout/_typography.scss index a72d190a..6278de2f 100644 --- a/styles/layout/_typography.scss +++ b/styles/layout/_typography.scss @@ -10,30 +10,6 @@ h1, h2, h3, h4, h5, h6 { } } -h1 { - font-size: 2.5rem; -} - -h2 { - font-size: 2rem; -} - -h3 { - font-size: 1.75rem; -} - -h4 { - font-size: 1.5rem; -} - -h5 { - font-size: 1.25rem; -} - -h6 { - font-size: 1rem; -} - mark { background: #FFF8E1; padding: .25rem .4rem; diff --git a/tailwind.config.js b/tailwind.config.js deleted file mode 100644 index 813453ad..00000000 --- a/tailwind.config.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - content: [ - "./app/**/*.{js,ts,jsx,tsx}", - "./layout/**/*.{js,ts,jsx,tsx}", - "./components/**/*.{js,ts,jsx,tsx}", - "./app/components/**/*.{js,ts,jsx,tsx}", - ], - theme: { - screens: { - sm: '640px', - md: '768px', - lg: '1024px', - xl: '1280px', - '2xl': '1536px', - } - }, - corePlugins: { - preflight: false, // Важно! - }, -} \ No newline at end of file From 5058bdafaa0c60d052482017956b82e001cc9c46 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Mon, 2 Jun 2025 17:20:43 +0600 Subject: [PATCH 005/183] login page update --- app/(full-page)/auth/login/page.tsx | 47 ++++++++++++----------------- app/components/InfoBanner.tsx | 11 +++++++ app/globals.css | 33 ++++++++++++++++++++ 3 files changed, 63 insertions(+), 28 deletions(-) create mode 100644 app/components/InfoBanner.tsx diff --git a/app/(full-page)/auth/login/page.tsx b/app/(full-page)/auth/login/page.tsx index d3dd85ca..86647115 100644 --- a/app/(full-page)/auth/login/page.tsx +++ b/app/(full-page)/auth/login/page.tsx @@ -8,6 +8,7 @@ import { Password } from 'primereact/password'; import { LayoutContext } from '../../../../layout/context/layoutcontext'; import { InputText } from 'primereact/inputtext'; import { classNames } from 'primereact/utils'; +import InfoBanner from '@/app/components/InfoBanner'; const LoginPage = () => { const [password, setPassword] = useState(''); @@ -15,38 +16,29 @@ const LoginPage = () => { const { layoutConfig } = useContext(LayoutContext); const router = useRouter(); - const containerClassName = classNames('surface-ground flex align-items-center justify-content-center min-h-screen min-w-screen overflow-hidden', { 'p-input-filled': layoutConfig.inputStyle === 'filled' }); + // const containerClassName = classNames('surface-ground flex align-items-center justify-content-center min-h-screen min-w-screen overflow-hidden', { 'p-input-filled': layoutConfig.inputStyle === 'filled' }); return ( -
-
- Sakai logo -
-
-
- Image -
Welcome, Isabel!
- Sign in to continue -
- +
+ +
+
+ +
+ +
-
-
); }; diff --git a/app/components/InfoBanner.tsx b/app/components/InfoBanner.tsx new file mode 100644 index 00000000..c3f57cf2 --- /dev/null +++ b/app/components/InfoBanner.tsx @@ -0,0 +1,11 @@ +'use client'; + +export default function InfoBanner({title}:{title:string}) { + console.log(title); + + return ( +
+

{title}

+
+ ); +}; diff --git a/app/globals.css b/app/globals.css index 2387162d..bc4d3859 100644 --- a/app/globals.css +++ b/app/globals.css @@ -76,3 +76,36 @@ h1, h2, h3, h4, h5, h6 { } } +.user-img { + position: relative; + margin-bottom: 30px; +} +.user-img img { + /* border: solid .5px ; */ + box-shadow: 0px 1px 9px 4px; + animation: border-transform 10s linear infinite alternate forwards; +} + +@keyframes border-transform { + 0%, 100% { + border-radius: 60% 40% 56% 33%/73% 82% 18% 27%; + } + 14% { + border-radius: 40% 60% 54% 46%/49% 60% 40% 51%; + } + 28% { + border-radius: 54% 46% 38% 62%/49% 70% 30% 51%; + } + 42% { + border-radius: 61% 39% 55% 45%/61% 38% 62% 39%; + } + 56% { + border-radius: 61% 39% 67% 33%/70% 50% 50% 30%; + } + 70% { + border-radius: 50% 50% 34% 66%/56% 68% 32% 44%; + } + 84% { + border-radius: 46% 54% 50% 50%/35% 61% 39% 65%; + } +} From 0279c5d25b6aed6e4671ade26e65a28bce9ae5b9 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Tue, 3 Jun 2025 09:52:27 +0600 Subject: [PATCH 006/183] =?UTF-8?q?=D0=9B=D0=BE=D0=B3=D0=B8=D0=BD=20=D0=BF?= =?UTF-8?q?=D0=BE=D1=87=D1=82=D0=B8=20=D0=B3=D0=BE=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(full-page)/auth/login/page.tsx | 90 ++++++++++++++++++++++------- app/components/InfoBanner.tsx | 1 - app/components/VideoPlay.tsx | 2 - app/globals.css | 5 ++ package-lock.json | 73 ++++++++++++++++++++++- package.json | 5 +- schemas/schema.tsx | 22 +++++++ 7 files changed, 171 insertions(+), 27 deletions(-) create mode 100644 schemas/schema.tsx diff --git a/app/(full-page)/auth/login/page.tsx b/app/(full-page)/auth/login/page.tsx index 86647115..878d108c 100644 --- a/app/(full-page)/auth/login/page.tsx +++ b/app/(full-page)/auth/login/page.tsx @@ -10,6 +10,11 @@ import { InputText } from 'primereact/inputtext'; import { classNames } from 'primereact/utils'; import InfoBanner from '@/app/components/InfoBanner'; +import { useForm } from 'react-hook-form'; +import { schema } from '@/schemas/schema'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { Controller } from 'react-hook-form'; + const LoginPage = () => { const [password, setPassword] = useState(''); const [checked, setChecked] = useState(false); @@ -18,6 +23,33 @@ const LoginPage = () => { const router = useRouter(); // const containerClassName = classNames('surface-ground flex align-items-center justify-content-center min-h-screen min-w-screen overflow-hidden', { 'p-input-filled': layoutConfig.inputStyle === 'filled' }); + const {register, handleSubmit, formState: { errors }, control} = useForm({ + resolver:yupResolver(schema), mode: 'onChange', + }); + + const onSubmit = async (data) => { + console.log(data); + + // console.log("Данные формы:", data); + try { + const res = await fetch('https://mooc.oshsu.kg/api/v2/login', { + method:"POST", + headers: { + 'Content-Type': 'application/json', + // 'mode':'no-cors', + }, + credentials: "include", + body: JSON.stringify({ + email : "kalilov054720@oshsu.kg", + password : "054720" + }), + // body: JSON.stringify(data), + }) + } catch(err){ + console.log("Ошибка ",err); + } + }; + return (
@@ -26,29 +58,43 @@ const LoginPage = () => {
-
diff --git a/app/components/InfoBanner.tsx b/app/components/InfoBanner.tsx index c3f57cf2..862aefc0 100644 --- a/app/components/InfoBanner.tsx +++ b/app/components/InfoBanner.tsx @@ -1,7 +1,6 @@ 'use client'; export default function InfoBanner({title}:{title:string}) { - console.log(title); return (
diff --git a/app/components/VideoPlay.tsx b/app/components/VideoPlay.tsx index d843bcf6..8834a5aa 100644 --- a/app/components/VideoPlay.tsx +++ b/app/components/VideoPlay.tsx @@ -18,8 +18,6 @@ export default function VideoPlay() { {if (!videoCall) return; setVideoCall(false); }}>
diff --git a/app/components/cards/LessonCard.tsx b/app/components/cards/LessonCard.tsx index d5e5f8ff..591a25a7 100644 --- a/app/components/cards/LessonCard.tsx +++ b/app/components/cards/LessonCard.tsx @@ -4,7 +4,6 @@ import { getConfirmOptions } from '@/utils/getConfirmOptions'; import { Button } from 'primereact/button'; import { faPlay } from '@fortawesome/free-solid-svg-icons'; import MyFontAwesome from '../MyFontAwesome'; -import { Divider } from 'primereact/divider'; export default function LessonCard({ status, diff --git a/app/components/lessons/LessonTyping.tsx b/app/components/lessons/LessonTyping.tsx index 4447bed5..8f5e57b9 100644 --- a/app/components/lessons/LessonTyping.tsx +++ b/app/components/lessons/LessonTyping.tsx @@ -267,7 +267,7 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp typeColor={'var(--mainColor)'} lessonDate={'xx-xx'} urlForPDF={() => sentToPDF(item.document || '')} - urlForDownload='' + urlForDownload="" /> )) @@ -433,8 +433,8 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp type={{ typeValue: 'link', icon: 'pi pi-link' }} typeColor={'var(--mainColor)'} lessonDate={'xx-xx'} - urlForPDF={() => ('')} - urlForDownload='' + urlForPDF={() => ''} + urlForDownload="" /> )) @@ -555,8 +555,8 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp const videoSection = () => { return (
-
- + {/* { toggleVideoType(e.value); @@ -564,16 +564,19 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp options={videoSelect} optionLabel="name" placeholder="Танданыз" + style={{backgroundColor: 'var(--mainColor', color: 'white'}} + panelStyle={{color: 'white'}} className="w-[213px] sm:w-full md:w-14rem" - /> + /> */}
- {selectedCity?.status ? ( -
+ {/* {!selectedCity?.status ? ( */} +
{ setVideoValue((prev) => ({ ...prev, video_link: e.target.value })); setValue('usefulLink', e.target.value, { shouldValidate: true }); @@ -581,22 +584,22 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp /> {errors.usefulLink?.message}
- ) : ( - {}} - accept="video/" - onSelect={(e) => - setVideoValue((prev) => ({ - ...prev, - file: e.files[0] - })) - } - /> - )} + {/* // ) : ( + // {}} + // accept="video/" + // onSelect={(e) => + // setVideoValue((prev) => ({ + // ...prev, + // file: e.files[0] + // })) + // } + // /> + // )} */}
@@ -650,8 +653,8 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp type={{ typeValue: 'video', icon: 'pi pi-video' }} typeColor={'var(--mainColor)'} lessonDate={'xx-xx'} - urlForPDF={() => ('')} - urlForDownload='' + urlForPDF={() => ''} + urlForDownload="" /> )) diff --git a/app/components/popUp/Redacting.tsx b/app/components/popUp/Redacting.tsx index b42664ec..aac69991 100644 --- a/app/components/popUp/Redacting.tsx +++ b/app/components/popUp/Redacting.tsx @@ -8,7 +8,7 @@ export default function Redacting({ redactor, textSize }: {redactor: MenuItem[], return (
- menuLeft.current?.toggle(event)} aria-controls="popup_menu_left" aria-haspopup /> + menuLeft.current?.toggle(event)} aria-controls="popup_menu_left" aria-haspopup /> { edu@oshsu.kg
-
+ {/*

@@ -51,7 +51,7 @@ const AppFooter = () => { lorem ipsum lorem ipsum

-
+
*/}

© 2025 ОшГУ | 2025 OshSU - IT Academy Ошский Государственный Университет oshsu.kg diff --git a/layout/AppTopbar.tsx b/layout/AppTopbar.tsx index c5ed3c2d..90fe5626 100644 --- a/layout/AppTopbar.tsx +++ b/layout/AppTopbar.tsx @@ -145,7 +145,7 @@ const AppTopbar = forwardRef((props, ref) => { {media ? ( ) : ( -

+
{/* ((props, ref) => { ОшМУнун сайты - + {/* Байланыш - + */}
)} diff --git a/layout/StudentLayout.tsx b/layout/StudentLayout.tsx index 7ea3df42..3a078e89 100644 --- a/layout/StudentLayout.tsx +++ b/layout/StudentLayout.tsx @@ -149,7 +149,7 @@ const StudentLayout = ({ children }: ChildContainerProps) => {
{children}
{/* */}
- + {/* */}
diff --git a/layout/layout.tsx b/layout/layout.tsx index e886a689..013f6a17 100644 --- a/layout/layout.tsx +++ b/layout/layout.tsx @@ -149,7 +149,7 @@ const Layout = ({ children }: ChildContainerProps) => {
{children}
{/* */}
- + {/* */}
From ada5b1a4146aff88c37cfb7fbdc2827c3a647b11 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Mon, 25 Aug 2025 08:58:20 +0600 Subject: [PATCH 109/183] =?UTF-8?q?=D1=84=D0=BE=D1=82=D0=BE=20=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B0=D0=B5=D1=82=D1=81=D1=8F=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B8=20=D0=BD=D0=B0=20=D0=BA=D1=83=D1=80=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/course/page.tsx | 64 ++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index f32036de..86f89d0f 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -28,6 +28,9 @@ import { useMediaQuery } from '@/hooks/useMediaQuery'; export default function Course() { const { setMessage, course, setCourses, contextFetchCourse } = useContext(LayoutContext); + interface FileWithPreview extends File { + objectURL?: string; + } const [coursesValue, setValueCourses] = useState([]); const [hasCourses, setHasCourses] = useState(false); @@ -43,6 +46,7 @@ export default function Course() { perPage: 0 }); const [activeIndex, setActiveIndex] = useState(0); + const [imageState, setImageState] = useState(null); const [editingLesson, setEditingLesson] = useState({ title: '', @@ -117,11 +121,9 @@ export default function Course() { }; const handleFetchCourse = async (page = 1) => { - const data = await fetchCourses(page, 0); toggleSkeleton(); - console.log(course); - + if (course) { setHasCourses(false); setValueCourses(course.data); @@ -206,6 +208,7 @@ export default function Course() { }; const clearValues = () => { + setImageState(null); setCourseValue({ title: '', description: '', video_url: '', image: '' }); setEditingLesson({ title: '', description: '', video_url: '', image: '', created_at: '' }); setEditMode(false); @@ -236,7 +239,7 @@ export default function Course() { } }; - const onSelect = (e: FileUploadSelectEvent) => { + const onSelect = (e: FileUploadSelectEvent & { files: FileWithPreview[] }) => { editMode ? setEditingLesson((prev) => ({ ...prev, @@ -246,10 +249,13 @@ export default function Course() { ...prev, image: e.files[0] })); - - console.log('hi'); + setImageState(e.files[0].objectURL); }; + useEffect(() => { + console.log(imageState); + }, [imageState]); + const imageBodyTemplate = (product: CourseType) => { const image = product.image; @@ -290,9 +296,9 @@ export default function Course() { contextFetchCourse(); }, []); - useEffect(()=> { + useEffect(() => { handleFetchCourse(); - },[course]); + }, [course]); useEffect(() => { const title = editMode ? editingLesson.title.trim() : courseValue.title.trim(); @@ -339,11 +345,37 @@ export default function Course() { console.log('Для потока ', forStreamId); }, [forStreamId]); + const imagestateStyle = imageState ? 'flex gap-1 items-center justify-between' : ''; + return (
{/* modal window */}
+
+ {imagestateStyle && ( +
+ {typeof imageState === 'string' && + + } +
+ )} +
+ + + {courseValue.image || editingLesson.image ? ( +
+ {typeof editingLesson.image === 'string' && ( + <> + Сүрөт: {editingLesson.image} + + )} +
+ ) : ( + jpeg, png, jpg + )} +
+
{/*
*/}
@@ -405,22 +437,6 @@ export default function Course() { }} />
- -
- - - {courseValue.image || editingLesson.image ? ( -
- {typeof editingLesson.image === 'string' && ( - <> - Сүрөт: {editingLesson.image} - - )} -
- ) : ( - jpeg, png, jpg - )} -
{/*
*/}
From fb8573e24fd4f5fc7fd534fcdef723fb3c12f419 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Mon, 25 Aug 2025 13:22:26 +0600 Subject: [PATCH 110/183] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=B1=D0=B0?= =?UTF-8?q?=D0=B3=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(full-page)/auth/login/page.tsx | 5 +- app/(main)/course/[courseTheme]/page.tsx | 17 ---- app/(main)/course/page.tsx | 39 +++++----- app/(student)/teaching/page.tsx | 3 +- app/components/NotFound.tsx | 2 +- app/components/PDFBook.tsx | 1 - app/components/cards/ItemCard.tsx | 98 +++++++++--------------- app/components/lessons/LessonTyping.tsx | 23 ++---- app/components/tables/StreamList.tsx | 48 ++++++------ app/globals.css | 6 +- layout/AppMenu.tsx | 2 +- layout/StudentLayout.tsx | 5 +- layout/context/layoutcontext.tsx | 27 ++++--- public/layout/svg/login-bg.svg | 1 + types/layout.d.ts | 8 +- types/myMainCourseType.tsx | 5 +- 16 files changed, 120 insertions(+), 170 deletions(-) create mode 100644 public/layout/svg/login-bg.svg diff --git a/app/(full-page)/auth/login/page.tsx b/app/(full-page)/auth/login/page.tsx index 690d00f8..e90d1c06 100644 --- a/app/(full-page)/auth/login/page.tsx +++ b/app/(full-page)/auth/login/page.tsx @@ -19,11 +19,13 @@ import FancyLinkBtn from '@/app/components/buttons/FancyLinkBtn'; import { getToken } from '@/utils/auth'; import { logout } from '@/utils/logout'; import { LoginType } from '@/types/login'; +import { useMediaQuery } from '@/hooks/useMediaQuery'; const LoginPage = () => { const { layoutConfig, setUser, setMessage, setGlobalLoading } = useContext(LayoutContext); const router = useRouter(); + const media = useMediaQuery('(max-width: 640px)'); // const containerClassName = classNames('surface-ground flex align-items-center justify-content-center min-h-screen min-w-screen overflow-hidden', { 'p-input-filled': layoutConfig.inputStyle === 'filled' }); const { @@ -40,7 +42,6 @@ const LoginPage = () => { console.log('Данные пользователя: ', value); const user = await login(value); - console.log(user); if (user && user.success) { document.cookie = `access_token=${user.token.access_token}; path=/; Secure; SameSite=Strict; expires=${user.token.expires_at}`; @@ -91,7 +92,7 @@ const LoginPage = () => { }; return ( -
+
{/* */}
diff --git a/app/(main)/course/[courseTheme]/page.tsx b/app/(main)/course/[courseTheme]/page.tsx index 86edb58e..2e5654ce 100644 --- a/app/(main)/course/[courseTheme]/page.tsx +++ b/app/(main)/course/[courseTheme]/page.tsx @@ -50,7 +50,6 @@ export default function CourseTheme() { const data = await fetchThemes(Number(courseTheme)); toggleSkeleton(); - console.log('id', selectedCourse); contextFetchThemes(Number(courseTheme)); if (data?.lessons) { @@ -120,7 +119,6 @@ export default function CourseTheme() { }; const handleUpdateTheme = async () => { - console.log('yeee'); const data = await updateTheme(Number(courseTheme), selectedCourse.id, editingThemes.title); if (data.success) { @@ -145,7 +143,6 @@ export default function CourseTheme() { }; const edit = (rowData: number | null) => { - console.log(rowData); setEditMode(true); setSelectedCourse({id: rowData}); @@ -160,18 +157,6 @@ export default function CourseTheme() { setSelectedCourse({ id: null }); }; - // const getConfirmOptions = (id: number) => ({ - // message: 'Сиз чын эле өчүрүүнү каалайсызбы??', - // header: 'Өчүрүү', - // icon: 'pi pi-info-circle', - // defaultFocus: 'reject', - // acceptClassName: 'p-button-danger', - // acceptLabel: 'Кийинки кадам', // кастомная надпись для "Yes" - // rejectLabel: 'Артка', - // accept: () => handleDeleteCourse(id), - // // reject: () => console.log('Удаление отменено') - // }); - useEffect(() => { handleFetchInfo(); handleFetchThemes(); @@ -180,7 +165,6 @@ export default function CourseTheme() { useEffect(() => { const handleShow = async () => { const data:{lessons: {data: {id: number, title: string}[]}} = await fetchThemes(Number(courseTheme)); - console.log(data); if (data?.lessons) { console.log('rab'); @@ -226,7 +210,6 @@ export default function CourseTheme() {
{themeInfo?.created_at} - Home/theme
diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index 86f89d0f..02bfce2e 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -40,7 +40,7 @@ export default function Course() { const [formVisible, setFormVisible] = useState(false); const [forStart, setForStart] = useState(false); const [skeleton, setSkeleton] = useState(false); - const [pagination, setPagination] = useState({ + const [pagination, setPagination] = useState<{ currentPage: number; total: number; perPage: number }>({ currentPage: 1, total: 0, perPage: 0 @@ -125,13 +125,15 @@ export default function Course() { toggleSkeleton(); if (course) { + console.log(course); + setHasCourses(false); setValueCourses(course.data); - // setPagination({ - // currentPage: course.data, - // total: course?.data, - // perPage: course?.data - // }); + setPagination({ + currentPage: course.current_page, + total: course?.total, + perPage: course?.per_page + }); } else { setHasCourses(true); setMessage({ @@ -170,7 +172,7 @@ export default function Course() { if (data?.success) { toggleSkeleton(); // handleFetchCourse(); - contextFetchCourse(); + contextFetchCourse(1); setMessage({ state: true, value: { severity: 'success', summary: 'Ийгиликтүү кошулду!', detail: '' } @@ -191,7 +193,7 @@ export default function Course() { if (data?.success) { toggleSkeleton(); // handleFetchCourse(); - contextFetchCourse(); + contextFetchCourse(1); setMessage({ state: true, value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } @@ -220,7 +222,7 @@ export default function Course() { if (data?.success) { toggleSkeleton(); // handleFetchCourse(); - contextFetchCourse(); + contextFetchCourse(1); clearValues(); setEditMode(false); setSelectedCourse(null); @@ -277,6 +279,8 @@ export default function Course() { // Ручное управление пагинацией const handlePageChange = (page: number) => { // handleFetchCourse(page); + contextFetchCourse(page); + console.log(page); }; const edit = (rowData: number | null) => { @@ -293,7 +297,7 @@ export default function Course() { }; useEffect(() => { - contextFetchCourse(); + contextFetchCourse(1); }, []); useEffect(() => { @@ -353,13 +357,7 @@ export default function Course() {
- {imagestateStyle && ( -
- {typeof imageState === 'string' && - - } -
- )} + {imagestateStyle &&
{typeof imageState === 'string' && }
}
@@ -496,7 +494,7 @@ export default function Course() { />
- + rowIndex + 1} header="Номер" style={{ width: '20px' }}> { const newValue = forStreamId?.id === rowData.id ? null : { id: rowData.id, title: rowData.title }; - console.log(rowData, forStreamId); - // Устанавливаем состояние setForStreamId(newValue); }} @@ -549,7 +545,8 @@ export default function Course() { rows={pagination.perPage} totalRecords={pagination.total} onPageChange={(e) => handlePageChange(e.page + 1)} - template="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink" + // template="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink" + template={media ? 'PrevPageLink NextPageLink' : 'FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink'} />
)} diff --git a/app/(student)/teaching/page.tsx b/app/(student)/teaching/page.tsx index 1a1aad21..470e83ab 100644 --- a/app/(student)/teaching/page.tsx +++ b/app/(student)/teaching/page.tsx @@ -31,7 +31,6 @@ export default function Teaching() { // functions const toggleSortSelect = (e: sortOptType) => { - console.log(e); setSelectedSort(e); }; @@ -110,7 +109,7 @@ export default function Teaching() { const x = displayData.map((semester: any, sIdx: number) => (

{semester.semester.name_kg}

-
+
{Object.values(semester) .filter((val: any) => val.subject) // только предметы .map((subj: any, subjIdx: number) => ( diff --git a/app/components/NotFound.tsx b/app/components/NotFound.tsx index a1de19d7..74b4e906 100644 --- a/app/components/NotFound.tsx +++ b/app/components/NotFound.tsx @@ -9,7 +9,7 @@ export const NotFound = ({ titleMessage }: { titleMessage: string }) => {

{titleMessage}

- + {/* */} Башкы баракчага кайтуу diff --git a/app/components/PDFBook.tsx b/app/components/PDFBook.tsx index 4bbf9a81..abbab375 100644 --- a/app/components/PDFBook.tsx +++ b/app/components/PDFBook.tsx @@ -157,7 +157,6 @@ export default function PDFViewer({ url }: { url: string }) { // media ? ( <> -

lorm

-//
-//
-//

{lessonName}

-//
-//
-//
-//
-// Окутуучу: -// {teacherName} -// {teacherLastName} -//
-//
-//
-// Тип: -// {lessonType} -//
-// { -// //
-//
-//
-//
- -//
-// ); -// } - 'use client'; -import { itemsCourseInfo } from '@/services/studentMain'; import Link from 'next/link'; import { Button } from 'primereact/button'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; export default function ItemCard({ lessonName, @@ -48,38 +9,51 @@ export default function ItemCard({ connection }: { lessonName: string; - streams: {id: number, teacher?: { name: string; last_name?: string }; subject_type_name?: { name_kg: string, short_name_kg: string } }[]; + streams: { id: number; teacher?: { name: string; last_name?: string }; subject_type_name?: { name_kg: string; short_name_kg: string } }[]; connection: { id: number; course_id: number; id_myedu_stream: number }[]; }) { - console.log('conn',connection); - + const [activeStreamIdx, setActiveStreamIdx] = useState(null); + const matchedIdx = streams.findIndex((stream) => connection.some((item) => item.id_myedu_stream === stream.id)); + + useEffect(() => { + if (matchedIdx !== -1) { + setActiveStreamIdx(matchedIdx); + } + }, [streams, connection]); return ( -
+
-

{lessonName}

+

{lessonName}

- {streams.map((stream, idx) => ( -
-
- Окутуучу: - {stream.teacher?.name} - {stream.teacher?.last_name} -
-
+ {streams.map((stream, idx) => { + const isActive = idx === activeStreamIdx; + return ( +
- Тип: - {stream.subject_type_name?.short_name_kg} + Окутуучу: + {stream.teacher?.name} + {stream.teacher?.last_name} +
+
+
+ Тип: + {stream.subject_type_name?.short_name_kg} +
+ {connection.map((item) => { + if (item.id_myedu_stream === stream.id) { + return ( + +
- {connection.map((item) => { - if (item.id_myedu_stream === stream.id) { - return
-
- ))} + ); + })}
); diff --git a/app/components/lessons/LessonTyping.tsx b/app/components/lessons/LessonTyping.tsx index 8f5e57b9..c5424091 100644 --- a/app/components/lessons/LessonTyping.tsx +++ b/app/components/lessons/LessonTyping.tsx @@ -250,8 +250,8 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp
-
-
+
+
{docShow ? ( ) : ( @@ -417,8 +417,8 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp
-
-
+
+
{linksShow ? ( ) : ( @@ -621,19 +621,8 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp
-
-
- {/* selectedForEditing(id, type)} - onDelete={(id: number) => handleDeleteVideo(id)} - cardValue={{ title: 'item.title', id: 8, desctiption: 'item?.description', type: '', photo: ''}} - cardBg={'#fff'} - type={{ typeValue: 'Лекция', icon: 'pi pi-lecture' }} - typeColor={'var(--mainColor)'} - lessonDate={'xx-xx'} - /> */} - +
+
{videoShow ? ( ) : ( diff --git a/app/components/tables/StreamList.tsx b/app/components/tables/StreamList.tsx index cf0e4f42..955b6c23 100644 --- a/app/components/tables/StreamList.tsx +++ b/app/components/tables/StreamList.tsx @@ -14,7 +14,7 @@ import { streamsType } from '@/types/streamType'; export default function StreamList({ callIndex, courseValue, isMobile }: { callIndex: number; courseValue: { id: number | null; title: string } | null; isMobile: boolean }) { interface mainStreamsType { connect_id: number | null,stream_id:number, subject_name: {name_kg:string}, - subject_type_name:{short_name_kg:string}, teacher:{name:string}, language:{name:string}, + subject_type_name:{name_kg:string}, teacher:{name:string}, language:{name:string}, id_edu_year:number,id_period:number, semester:{name_kg:string}, edu_form:{name_kg:string} } @@ -156,7 +156,7 @@ export default function StreamList({ callIndex, courseValue, isMobile }: { callI } }, [streams]); - const itemTemplate = (item:mainStreamsType, index: number) => { + const itemTemplate = (item:mainStreamsType, index: number) => { const bgClass = item.connect_id ? 'bg-[var(--greenBgColor)] border-b border-[gray]' : index % 2 == 1 ? 'bg-[#f5f5f5]' : ''; return ( @@ -180,8 +180,8 @@ export default function StreamList({ callIndex, courseValue, isMobile }: { callI
- {item?.subject_type_name?.short_name_kg}: - {item?.teacher?.name} + {item?.subject_type_name?.name_kg} + {/* {item?.teacher?.name} */}
Үйрөтүлүү тили: @@ -189,7 +189,7 @@ export default function StreamList({ callIndex, courseValue, isMobile }: { callI
Окуу жылы: - {item?.id_edu_year} + 20{item?.id_edu_year}
Период: @@ -251,7 +251,7 @@ export default function StreamList({ callIndex, courseValue, isMobile }: { callI {hasStreams ? ( ) : ( -
+
{isMobile && (
)}
diff --git a/app/globals.css b/app/globals.css index 967962c1..fc77e25a 100644 --- a/app/globals.css +++ b/app/globals.css @@ -44,7 +44,7 @@ h1, h2, h3, h4, h5, h6 { } .login-bg{ - background: url('/layout/images/login-bg.jpg') no-repeat center/cover; + background: url('/layout/svg/login-bg.svg') no-repeat center/cover; } /* animate */ @@ -344,6 +344,10 @@ h1, h2, h3, h4, h5, h6 { } } +.select-column .my-custom-table td{ + background-color: var(--greenBgColor); +} + .my-custom-table td, .my-custom-table th { /* border: 1px solid #ccc; */ diff --git a/layout/AppMenu.tsx b/layout/AppMenu.tsx index 2e1a12a9..da735fba 100644 --- a/layout/AppMenu.tsx +++ b/layout/AppMenu.tsx @@ -48,7 +48,7 @@ const AppMenu = () => { useEffect(() => { if (user?.is_working) { - contextFetchCourse(); + contextFetchCourse(1); } if (user?.is_student) { const isTopicsChildPage = pathname.startsWith('/teaching/'); diff --git a/layout/StudentLayout.tsx b/layout/StudentLayout.tsx index 3a078e89..7175ca42 100644 --- a/layout/StudentLayout.tsx +++ b/layout/StudentLayout.tsx @@ -1,14 +1,11 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client'; -import { useRouter } from 'next/navigation'; import { useEventListener, useMountEffect, useUnmountEffect } from 'primereact/hooks'; import React, { useContext, useEffect, useRef, useState } from 'react'; import { classNames } from 'primereact/utils'; -import AppFooter from './AppFooter'; import AppSidebar from './AppSidebar'; import AppTopbar from './AppTopbar'; -import AppConfig from './AppConfig'; import { LayoutContext } from './context/layoutcontext'; import { PrimeReactContext } from 'primereact/api'; import { ChildContainerProps, LayoutState, AppTopbarRef } from '@/types'; @@ -136,7 +133,7 @@ const StudentLayout = ({ children }: ChildContainerProps) => { requireRole(); },[user]); - if(permission) return null; + // if(permission) return null; return ( diff --git a/layout/context/layoutcontext.tsx b/layout/context/layoutcontext.tsx index 810b2c23..eb3e50f3 100644 --- a/layout/context/layoutcontext.tsx +++ b/layout/context/layoutcontext.tsx @@ -66,16 +66,15 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { }; // fetch course - const [course, setCourses] = useState<{data: myMainCourseType[]}>({data: []}); + const [course, setCourses] = useState<{ current_page: number; total: number; per_page: number; data:myMainCourseType[] }>({ current_page: 1, total: 0, per_page: 10, data: [] }); - const contextFetchCourse = async (page = 1) => { + const contextFetchCourse = async (page: number) => { const data = await fetchCourses(page, 0); - + console.log(data.courses); + if (data?.courses) { // setCourses(data.courses.data); setCourses(data.courses); - } else { - } }; @@ -83,17 +82,21 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { const [contextThemes, setContextThemes] = useState([]); const contextFetchThemes = async (id: number | null) => { const data = await fetchThemes(Number(id) || null); - + setContextThemes(data); - } + }; + + useEffect(() => { + console.log('course ', course); + }, [course]); // fetch themes for student const [contextStudentThemes, setContextStudentThemes] = useState([]); const contextFetchStudentThemes = async (id: number) => { const data = await fetchStudentThemes(id); - + setContextStudentThemes(data); - } + }; const value: LayoutContextProps = { layoutConfig, @@ -108,18 +111,18 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { setGlobalLoading, message, setMessage, - + contextFetchCourse, course, setCourses, - + contextFetchThemes, contextThemes, setContextThemes, contextFetchStudentThemes, contextStudentThemes, - setContextStudentThemes, + setContextStudentThemes }; return ( diff --git a/public/layout/svg/login-bg.svg b/public/layout/svg/login-bg.svg new file mode 100644 index 00000000..5398aa5a --- /dev/null +++ b/public/layout/svg/login-bg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/types/layout.d.ts b/types/layout.d.ts index 81ae1627..f6a27da4 100644 --- a/types/layout.d.ts +++ b/types/layout.d.ts @@ -54,8 +54,12 @@ export interface LayoutContextProps { setGlobalLoading: React.Dispatch>; message: MessageType; setMessage: React.Dispatch>; - contextFetchCourse: ()=> void; - course: {data: myMainCourseType[]}; + contextFetchCourse: (id)=> void; + course: { current_page: number; + total: number; + per_page: number; + data: myMainCourseType[] + }; setCourses; contextFetchThemes: (id: number)=> void; contextThemes; diff --git a/types/myMainCourseType.tsx b/types/myMainCourseType.tsx index e14a4218..8f8280a0 100644 --- a/types/myMainCourseType.tsx +++ b/types/myMainCourseType.tsx @@ -15,7 +15,6 @@ export interface myMainCourseType { title: string; user_id: number; current_page?: number; - total?: number; - per_page?: number; - data?: test[]; + + // data?: test[]; } From 001e765fd0b6fa54d31c4aa0da428ada28b67fb4 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Mon, 25 Aug 2025 14:43:23 +0600 Subject: [PATCH 111/183] =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20?= =?UTF-8?q?=D1=81=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/course/page.tsx | 41 ++++++++++++++++--------- app/components/cards/ItemCard.tsx | 1 + app/components/lessons/LessonTyping.tsx | 7 ++++- app/globals.css | 2 +- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index 02bfce2e..b94ead3f 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -125,8 +125,6 @@ export default function Course() { toggleSkeleton(); if (course) { - console.log(course); - setHasCourses(false); setValueCourses(course.data); setPagination({ @@ -210,6 +208,8 @@ export default function Course() { }; const clearValues = () => { + console.log('clear'); + setImageState(null); setCourseValue({ title: '', description: '', video_url: '', image: '' }); setEditingLesson({ title: '', description: '', video_url: '', image: '', created_at: '' }); @@ -242,16 +242,21 @@ export default function Course() { }; const onSelect = (e: FileUploadSelectEvent & { files: FileWithPreview[] }) => { - editMode - ? setEditingLesson((prev) => ({ - ...prev, - image: e.files[0] - })) - : setCourseValue((prev) => ({ - ...prev, - image: e.files[0] - })); - setImageState(e.files[0].objectURL); + console.log('rabotaet ', e); + if (e.files.length > 0) { + editMode + ? setEditingLesson((prev) => ({ + ...prev, + image: e.files[0] + })) + : setCourseValue((prev) => ({ + ...prev, + image: e.files[0] + })); + setImageState(e.files[0].objectURL); + } else { + console.log('noo'); + } }; useEffect(() => { @@ -494,7 +499,7 @@ export default function Course() { />
- + rowIndex + 1} header="Номер" style={{ width: '20px' }}> ) : ( <> - + rowIndex + 1} header="Номер" style={{ width: '20px' }}>
diff --git a/app/components/lessons/LessonTyping.tsx b/app/components/lessons/LessonTyping.tsx index c5424091..4de3d2b0 100644 --- a/app/components/lessons/LessonTyping.tsx +++ b/app/components/lessons/LessonTyping.tsx @@ -819,6 +819,7 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp { setEditingLesson((prev) => prev && { ...prev, title: e.target.value }); @@ -833,6 +834,7 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp { setEditingLesson((prev) => prev && { ...prev, url: e.target.value }); setValue('usefulLink', e.target.value, { shouldValidate: true }); @@ -841,6 +843,7 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp { setEditingLesson((prev) => prev && { ...prev, title: e.target.value }); @@ -853,10 +856,11 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp ) : selectType === 'video' ? ( <> {editingLesson?.video_type_id ? ( -
+
{ setEditingLesson((prev) => prev && { ...prev, video_link: e.target.value }); setValue('usefulLink', e.target.value, { shouldValidate: true }); @@ -889,6 +893,7 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp { setEditingLesson((prev) => prev && { ...prev, title: e.target.value }); diff --git a/app/globals.css b/app/globals.css index fc77e25a..4babec37 100644 --- a/app/globals.css +++ b/app/globals.css @@ -345,7 +345,7 @@ h1, h2, h3, h4, h5, h6 { } .select-column .my-custom-table td{ - background-color: var(--greenBgColor); + background-color: var(--greenBgColor) !important; } .my-custom-table td, From f6a2d0c898623d8766116b41bc22505b7189e83a Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Mon, 25 Aug 2025 15:57:55 +0600 Subject: [PATCH 112/183] =?UTF-8?q?=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=20?= =?UTF-8?q?=D1=85=D1=83=D0=BA=20=D1=83=D0=BA=D0=BE=D1=80=D0=B0=D1=87=D0=B8?= =?UTF-8?q?=D0=B2=D0=B0=D1=8E=D1=89=D0=B8=D0=B9=20=D1=82=D0=B5=D0=BA=D1=81?= =?UTF-8?q?=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/course/page.tsx | 4 ++- app/components/cards/LessonCard.tsx | 9 ++++--- app/components/lessons/LessonTyping.tsx | 9 ++++--- app/globals.css | 4 +++ hooks/useShortText.tsx | 33 +++++++++++++++++++++++++ 5 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 hooks/useShortText.tsx diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index b94ead3f..4f1d1bb9 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -25,6 +25,7 @@ import StreamList from '@/app/components/tables/StreamList'; import { TabPanel, TabView } from 'primereact/tabview'; import { TabViewChange } from '@/types/tabViewChange'; import { useMediaQuery } from '@/hooks/useMediaQuery'; +import useShortText from '@/hooks/useShortText'; export default function Course() { const { setMessage, course, setCourses, contextFetchCourse } = useContext(LayoutContext); @@ -355,6 +356,7 @@ export default function Course() { }, [forStreamId]); const imagestateStyle = imageState ? 'flex gap-1 items-center justify-between' : ''; + const imageTitle = useShortText(typeof editingLesson.image === 'string' ? editingLesson.image : '', 30); return (
@@ -370,7 +372,7 @@ export default function Course() {
{typeof editingLesson.image === 'string' && ( <> - Сүрөт: {editingLesson.image} + {imageTitle} )}
diff --git a/app/components/cards/LessonCard.tsx b/app/components/cards/LessonCard.tsx index 591a25a7..fa7ff9fc 100644 --- a/app/components/cards/LessonCard.tsx +++ b/app/components/cards/LessonCard.tsx @@ -4,6 +4,7 @@ import { getConfirmOptions } from '@/utils/getConfirmOptions'; import { Button } from 'primereact/button'; import { faPlay } from '@fortawesome/free-solid-svg-icons'; import MyFontAwesome from '../MyFontAwesome'; +import useShortText from '@/hooks/useShortText'; export default function LessonCard({ status, @@ -29,6 +30,8 @@ export default function LessonCard({ urlForDownload: string }) { + const shortTitle = useShortText(cardValue.title, 10); + const toSentPDF = () => { urlForPDF(); }; @@ -99,13 +102,13 @@ export default function LessonCard({
{/*
{!cardValue.photo && }
*/} -
{cardValue.title}
+
{shortTitle}
{type.typeValue === 'link' && {cardValue?.url}}
{cardValue?.desctiption && cardValue.desctiption}
{status === 'working' && type.typeValue !== 'video' && ( -
+
- {lessonDate} + {lessonDate}
)}
diff --git a/app/components/lessons/LessonTyping.tsx b/app/components/lessons/LessonTyping.tsx index 4de3d2b0..70840ec6 100644 --- a/app/components/lessons/LessonTyping.tsx +++ b/app/components/lessons/LessonTyping.tsx @@ -422,8 +422,9 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp {linksShow ? ( ) : ( - links.map((item: lessonType) => ( - <> + links.map((item: lessonType) => { + console.log(item); + return <> selectedForEditing(id, type)} @@ -432,12 +433,12 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp cardBg={'#7bb78112'} type={{ typeValue: 'link', icon: 'pi pi-link' }} typeColor={'var(--mainColor)'} - lessonDate={'xx-xx'} + lessonDate={new Date(item.created_at).toISOString().slice(0, 10)} urlForPDF={() => ''} urlForDownload="" /> - )) + }) )}
diff --git a/app/globals.css b/app/globals.css index 4babec37..e883080e 100644 --- a/app/globals.css +++ b/app/globals.css @@ -423,6 +423,10 @@ h1, h2, h3, h4, h5, h6 { } } +.p-tooltip { + font-size: 12px; /* или любой нужный размер */ +} + /* styles/book.css */ /* Стили для контейнера всей книги */ diff --git a/hooks/useShortText.tsx b/hooks/useShortText.tsx new file mode 100644 index 00000000..e95ccce2 --- /dev/null +++ b/hooks/useShortText.tsx @@ -0,0 +1,33 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { Tooltip } from 'primereact/tooltip'; + +export default function useShortText(text: string, textLength: number) { + const [resultText, setResultText] = useState(''); + const [isLength, setIsLength] = useState(false); + + useEffect(() => { + if (text?.length > textLength) { + setIsLength(true); + return setResultText(text.slice(0, textLength)); + } else { + setResultText(text); + setIsLength(false); + } + }, [text]); + + return ( + <> + + {isLength ? ( +
+ {resultText} + +
+ ) : ( +
{resultText}
+ )} + + ); +} From 29c1ae65f5adf2da59c9340755e226a924aa708e Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Mon, 25 Aug 2025 17:23:39 +0600 Subject: [PATCH 113/183] =?UTF-8?q?=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=20=D0=BD?= =?UTF-8?q?=D0=B0=D0=B6=D0=B0=D1=82=D0=B8=D0=B8=20=D0=BA=D0=BD=D0=BE=D0=BF?= =?UTF-8?q?=D0=BE=D0=BA=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D1=81=D0=BF=D0=B8=D0=BD=D0=BD=D0=B5=D1=80=20=D0=B8=20=D0=B2=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=BC=D0=B5=D0=BD=D1=82=D0=B5=20=D0=BA=D0=BE=D0=B3?= =?UTF-8?q?=D0=B4=D0=B0=20=D0=B6=D0=B4=D0=B5=D0=BC=20=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D0=B5=20=D0=B4=D0=BB=D1=8F=20=D0=B8=D0=B7=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/course/[courseTheme]/page.tsx | 64 ++++++----- app/(main)/course/page.tsx | 129 ++++++++++----------- app/components/cards/LessonCard.tsx | 48 +++++--- app/components/lessons/LessonTyping.tsx | 136 +++++++++++++---------- 4 files changed, 212 insertions(+), 165 deletions(-) diff --git a/app/(main)/course/[courseTheme]/page.tsx b/app/(main)/course/[courseTheme]/page.tsx index 2e5654ce..30b8aa74 100644 --- a/app/(main)/course/[courseTheme]/page.tsx +++ b/app/(main)/course/[courseTheme]/page.tsx @@ -18,6 +18,7 @@ import { NotFound } from '@/app/components/NotFound'; import Redacting from '@/app/components/popUp/Redacting'; import { getRedactor } from '@/utils/getRedactor'; import { getConfirmOptions } from '@/utils/getConfirmOptions'; +import { ProgressSpinner } from 'primereact/progressspinner'; export default function CourseTheme() { const [hasThemes, setHasThemes] = useState(false); @@ -29,6 +30,7 @@ export default function CourseTheme() { const [editMode, setEditMode] = useState(false); const [forStart, setForStart] = useState(true); const [skeleton, setSkeleton] = useState(false); + const [progressSpinner, setProgressSpinner] = useState(false); const [editingThemes, setEditingThemes] = useState<{ title: string }>({ title: '' }); @@ -50,7 +52,7 @@ export default function CourseTheme() { const data = await fetchThemes(Number(courseTheme)); toggleSkeleton(); - + contextFetchThemes(Number(courseTheme)); if (data?.lessons) { setHasThemes(false); @@ -119,7 +121,6 @@ export default function CourseTheme() { }; const handleUpdateTheme = async () => { - const data = await updateTheme(Number(courseTheme), selectedCourse.id, editingThemes.title); if (data.success) { toggleSkeleton(); @@ -143,16 +144,15 @@ export default function CourseTheme() { }; const edit = (rowData: number | null) => { - setEditMode(true); - setSelectedCourse({id: rowData}); + setSelectedCourse({ id: rowData }); // setThemeValue({ title: rowData.title || '' }); setFormVisible(true); }; const clearValues = () => { setThemeValue({ title: '', description: '', video_url: '' }); - setEditingThemes({ title: ''}); + setEditingThemes({ title: '' }); setEditMode(false); setSelectedCourse({ id: null }); }; @@ -164,16 +164,18 @@ export default function CourseTheme() { useEffect(() => { const handleShow = async () => { - const data:{lessons: {data: {id: number, title: string}[]}} = await fetchThemes(Number(courseTheme)); - + setProgressSpinner(true) + const data: { lessons: { data: { id: number; title: string }[] } } = await fetchThemes(Number(courseTheme)); + if (data?.lessons) { - console.log('rab'); - - const forEditing = data.lessons.data.find(item => item.id === selectedCourse.id) - + setProgressSpinner(false); + const forEditing = data.lessons.data.find((item) => item.id === selectedCourse.id); + setEditingThemes({ - title: forEditing?.title || '', + title: forEditing?.title || '' }); + } else { + setProgressSpinner(false); } }; @@ -181,7 +183,7 @@ export default function CourseTheme() { handleShow(); } }, [editMode]); - + useEffect(() => { const title = editMode ? editingThemes.title.trim() : themeValue.title.trim(); if (title.length > 0) { @@ -235,25 +237,29 @@ export default function CourseTheme() {
- { - // setCourseTitle(e.target.value); - editMode - ? setEditingThemes((prev) => ({ - ...prev, - title: e.target.value - })) - : setThemeValue((prev) => ({ - ...prev, - title: e.target.value - })); - }} - /> +
+ { + // setCourseTitle(e.target.value); + editMode + ? setEditingThemes((prev) => ({ + ...prev, + title: e.target.value + })) + : setThemeValue((prev) => ({ + ...prev, + title: e.target.value + })); + }} + /> + {progressSpinner && } +
- + {/* table section */} {hasThemes ? ( diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index 4f1d1bb9..5e5f3ed0 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -26,6 +26,7 @@ import { TabPanel, TabView } from 'primereact/tabview'; import { TabViewChange } from '@/types/tabViewChange'; import { useMediaQuery } from '@/hooks/useMediaQuery'; import useShortText from '@/hooks/useShortText'; +import { ProgressSpinner } from 'primereact/progressspinner'; export default function Course() { const { setMessage, course, setCourses, contextFetchCourse } = useContext(LayoutContext); @@ -41,6 +42,7 @@ export default function Course() { const [formVisible, setFormVisible] = useState(false); const [forStart, setForStart] = useState(false); const [skeleton, setSkeleton] = useState(false); + const [progressSpinner, setProgressSpinner] = useState(false); const [pagination, setPagination] = useState<{ currentPage: number; total: number; perPage: number }>({ currentPage: 1, total: 0, @@ -260,10 +262,6 @@ export default function Course() { } }; - useEffect(() => { - console.log(imageState); - }, [imageState]); - const imageBodyTemplate = (product: CourseType) => { const image = product.image; @@ -286,7 +284,6 @@ export default function Course() { const handlePageChange = (page: number) => { // handleFetchCourse(page); contextFetchCourse(page); - console.log(page); }; const edit = (rowData: number | null) => { @@ -334,15 +331,19 @@ export default function Course() { useEffect(() => { const handleShow = async () => { + setProgressSpinner(true); const data = await fetchCourseInfo(selectedCourse); if (data?.success) { + setProgressSpinner(false); setEditingLesson({ title: data.course.title || '', video_url: data.course.video_url || '', description: data.course.description || '', image: data.course.image }); + } else { + setProgressSpinner(false); } }; @@ -384,63 +385,75 @@ export default function Course() { {/*
*/}
- { - editMode - ? setEditingLesson((prev) => ({ - ...prev, - title: e.target.value - })) - : setCourseValue((prev) => ({ - ...prev, - title: e.target.value - })); - }} - /> +
+ { + editMode + ? setEditingLesson((prev) => ({ + ...prev, + title: e.target.value + })) + : setCourseValue((prev) => ({ + ...prev, + title: e.target.value + })); + }} + /> + {progressSpinner && } +
- { - editMode - ? setEditingLesson((prev) => ({ - ...prev, - video_url: e.target.value - })) - : setCourseValue((prev) => ({ - ...prev, - video_url: e.target.value - })); - }} - /> +
+ { + editMode + ? setEditingLesson((prev) => ({ + ...prev, + video_url: e.target.value + })) + : setCourseValue((prev) => ({ + ...prev, + video_url: e.target.value + })); + }} + /> + {progressSpinner && } +
{/*
*/} {/*
*/}
- { - editMode - ? setEditingLesson((prev) => ({ - ...prev, - description: e.target.value - })) - : setCourseValue((prev) => ({ - ...prev, - description: e.target.value - })); - }} - /> +
+ { + editMode + ? setEditingLesson((prev) => ({ + ...prev, + description: e.target.value + })) + : setCourseValue((prev) => ({ + ...prev, + description: e.target.value + })); + }} + /> + {progressSpinner && } +
{/*
*/}
@@ -605,15 +618,7 @@ export default function Course() { ) : ( <> - + rowIndex + 1} header="Номер" style={{ width: '20px' }}> void; - urlForDownload: string + urlForDownload: string; }) { - const shortTitle = useShortText(cardValue.title, 10); + const [progressSpinner, setProgressSpinner] = useState(false); + + const toggleSpinner = () => { + setProgressSpinner(true); + setInterval(() => { + setProgressSpinner(false); + }, 2000); + }; const toSentPDF = () => { + toggleSpinner(); urlForPDF(); }; const lessonCardEvents = () => { - if(type.typeValue === 'doc'){ + if (type.typeValue === 'doc') { toSentPDF(); - } else if (type.typeValue === 'link'){ - window.location.href = cardValue?.url || '#'; + } else if (type.typeValue === 'link') { + // window.location.href = cardValue?.url || '#'; + window.open(cardValue?.url || '#', '_blank'); } }; @@ -117,15 +128,24 @@ export default function LessonCard({ {videoPreviw} {/* button */} - {btnLabel && <> - {status === 'student' && type.typeValue === 'doc' ? -
-
- :
+ + {' '} +
+ ) : ( +
); diff --git a/app/components/lessons/LessonTyping.tsx b/app/components/lessons/LessonTyping.tsx index 70840ec6..ce08534c 100644 --- a/app/components/lessons/LessonTyping.tsx +++ b/app/components/lessons/LessonTyping.tsx @@ -21,6 +21,7 @@ import { Dropdown } from 'primereact/dropdown'; import PDFViewer from '../PDFBook'; import { useMediaQuery } from '@/hooks/useMediaQuery'; import { useRouter } from 'next/navigation'; +import { ProgressSpinner } from 'primereact/progressspinner'; export default function LessonTyping({ mainType, courseId, lessonId }: { mainType: string; courseId: string | null; lessonId: string | null }) { // types @@ -101,6 +102,7 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp const [selectType, setSelectType] = useState(''); const [visible, setVisisble] = useState(false); const [editingLesson, setEditingLesson] = useState(null); + const [progressSpinner, setProgressSpinner] = useState(false); // functions const handleUpdate = () => { @@ -157,13 +159,41 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp } }; + const fileUploadRef = useRef(null); + const clearFile = () => { + fileUploadRef.current?.clear(); + if (visible) { + setEditingLesson( + (prev) => + prev && { + ...prev, + file: null + } + ); + } else { + setDocValue((prev) => ({ + ...prev, + file: null + })); + } + }; + const clearValues = () => { + clearFile(); + setDocValue({ title: '', description: '', file: null, url: '', video_link: '' }); setLinksValue({ title: '', description: '', file: null, url: '', video_link: '' }); setEditingLesson(null); setSelectId(null); setSelectType(''); }; + const toggleSpinner = () => { + setProgressSpinner(true); + setInterval(() => { + setProgressSpinner(false); + }, 1000); + }; + // DOC SECTION const sentToPDF = (url: string) => { @@ -186,25 +216,6 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp ); - const fileUploadRef = useRef(null); - const clearFile = () => { - fileUploadRef.current?.clear(); - if (visible) { - setEditingLesson( - (prev) => - prev && { - ...prev, - file: null - } - ); - } else { - setDocValue((prev) => ({ - ...prev, - file: null - })); - } - }; - const docSection = () => { return (
@@ -247,7 +258,10 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp setDocValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" />
{/*
@@ -283,7 +297,6 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp // fetch document const handleFetchDoc = async () => { // skeleton = false - const data = await fetchLesson('doc', courseId ? Number(courseId) : null, lessonId ? Number(lessonId) : null); if (data?.success) { @@ -310,6 +323,7 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp // add document const handleAddDoc = async () => { + toggleSpinner(); const token = getToken('access_token'); const data = await addLesson('doc', token, courseId ? Number(courseId) : null, lessonId ? Number(lessonId) : null, docValue, 0); @@ -413,7 +427,10 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp setLinksValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" />
-
@@ -424,20 +441,22 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp ) : ( links.map((item: lessonType) => { console.log(item); - return <> - selectedForEditing(id, type)} - onDelete={(id: number) => handleDeleteLink(id)} - cardValue={{ title: item.title, id: item.id, desctiption: item?.description, type: 'url', photo: item?.photo }} - cardBg={'#7bb78112'} - type={{ typeValue: 'link', icon: 'pi pi-link' }} - typeColor={'var(--mainColor)'} - lessonDate={new Date(item.created_at).toISOString().slice(0, 10)} - urlForPDF={() => ''} - urlForDownload="" - /> - + return ( + <> + selectedForEditing(id, type)} + onDelete={(id: number) => handleDeleteLink(id)} + cardValue={{ title: item.title, id: item.id, desctiption: item?.description, type: 'url', photo: item?.photo }} + cardBg={'#7bb78112'} + type={{ typeValue: 'link', icon: 'pi pi-link' }} + typeColor={'var(--mainColor)'} + lessonDate={new Date(item.created_at).toISOString().slice(0, 10)} + urlForPDF={() => ''} + urlForDownload="" + /> + + ); }) )}
@@ -476,10 +495,9 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp // add link const handleAddLink = async () => { + toggleSpinner(); const token = getToken('access_token'); - const data = await addLesson('url', token, courseId ? Number(courseId) : null, lessonId ? Number(lessonId) : null, linksValue, 0); - console.log(data); if (data.success) { handleFetchLink(); @@ -547,8 +565,6 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp // VIDEO SECTIONS const toggleVideoType = (e: videoType) => { - console.log(e); - setSelectedCity(e); setVideoValue({ title: '', description: '', file: null, url: '', video_link: '' }); }; @@ -571,23 +587,23 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp /> */}
{/* {!selectedCity?.status ? ( */} -
- { - setVideoValue((prev) => ({ ...prev, video_link: e.target.value })); - setValue('usefulLink', e.target.value, { shouldValidate: true }); - }} - /> - {errors.usefulLink?.message} -
+
+ { + setVideoValue((prev) => ({ ...prev, video_link: e.target.value })); + setValue('usefulLink', e.target.value, { shouldValidate: true }); + }} + /> + {errors.usefulLink?.message} +
{/* // ) : ( // setVideoValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" />
-
@@ -657,7 +676,6 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp const handleVideoType = async () => { const data = await fetchVideoType(); - console.log(data); if (data && Array.isArray(data)) { setVideoTypes(data); @@ -693,11 +711,9 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp // add vieo const handleAddVideo = async () => { + toggleSpinner(); const token = getToken('access_token'); - const data = await addLesson('video', token, courseId ? Number(courseId) : null, lessonId ? Number(lessonId) : null, videoValue, selectedCity?.id); - console.log(data); - if (data.success) { handleFetchVideo(); setMessage({ From faa046ab4c05cbf5cb4472b2d920bc98716e1270 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Mon, 25 Aug 2025 17:32:59 +0600 Subject: [PATCH 114/183] =?UTF-8?q?=D0=9D=D0=B0=D0=B2=D0=B8=D0=B3=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20=D0=B2=20=D1=81=D1=82=D1=83=D0=B4=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B5=20=D0=BF=D0=BE=D0=BA=D0=B0=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=B0=D0=B5=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- layout/AppMenu.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/layout/AppMenu.tsx b/layout/AppMenu.tsx index da735fba..0d48f725 100644 --- a/layout/AppMenu.tsx +++ b/layout/AppMenu.tsx @@ -109,8 +109,8 @@ const AppMenu = () => { forThemes.push({ label: item.title, id: item.id, - to: '/teaching/ ? ', - items: [] + to: `/teaching/lesson/${item.id}`, + // items: [] }) ); if (forThemes.length > 0) { From 8a9deefd28c02dac355f871a47fe2fcff7148230 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Mon, 25 Aug 2025 18:59:50 +0600 Subject: [PATCH 115/183] =?UTF-8?q?=D0=BC=D0=B0=D0=BB=D0=B5=D0=BD=D1=8C?= =?UTF-8?q?=D0=BA=D0=B8=D0=B5=20=D0=BF=D0=BE=D0=BF=D1=80=D0=B0=D0=B2=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B2=20=D0=B2=D0=B5=D1=80=D1=81=D1=82=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(full-page)/auth/login/page.tsx | 6 +++--- app/components/cards/LessonCard.tsx | 4 ++-- app/components/lessons/LessonTyping.tsx | 4 ++-- app/components/popUp/Redacting.tsx | 2 +- layout/AppMenu.tsx | 5 ++--- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/app/(full-page)/auth/login/page.tsx b/app/(full-page)/auth/login/page.tsx index e90d1c06..2a4846c2 100644 --- a/app/(full-page)/auth/login/page.tsx +++ b/app/(full-page)/auth/login/page.tsx @@ -25,7 +25,7 @@ const LoginPage = () => { const { layoutConfig, setUser, setMessage, setGlobalLoading } = useContext(LayoutContext); const router = useRouter(); - const media = useMediaQuery('(max-width: 640px)'); + const media = useMediaQuery('(max-width: 1030px)'); // const containerClassName = classNames('surface-ground flex align-items-center justify-content-center min-h-screen min-w-screen overflow-hidden', { 'p-input-filled': layoutConfig.inputStyle === 'filled' }); const { @@ -100,7 +100,7 @@ const LoginPage = () => {
-

Кирүү

+

Кирүү

{/*
diff --git a/app/components/cards/LessonCard.tsx b/app/components/cards/LessonCard.tsx index 6db117ed..7e7a679e 100644 --- a/app/components/cards/LessonCard.tsx +++ b/app/components/cards/LessonCard.tsx @@ -99,8 +99,8 @@ export default function LessonCard({ return (
diff --git a/app/components/lessons/LessonTyping.tsx b/app/components/lessons/LessonTyping.tsx index ce08534c..6eb7465c 100644 --- a/app/components/lessons/LessonTyping.tsx +++ b/app/components/lessons/LessonTyping.tsx @@ -275,11 +275,11 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp status="working" onSelected={(id: number, type: string) => selectedForEditing(id, type)} onDelete={(id: number) => handleDeleteDoc(id)} - cardValue={{ title: item?.title, id: item.id, type: 'doc' }} + cardValue={{ title: item?.title, id: item.id, desctiption: item?.description, type: 'doc' }} cardBg={'#ddc4f51a'} type={{ typeValue: 'doc', icon: 'pi pi-file' }} typeColor={'var(--mainColor)'} - lessonDate={'xx-xx'} + lessonDate={new Date(item.created_at).toISOString().slice(0, 10)} urlForPDF={() => sentToPDF(item.document || '')} urlForDownload="" /> diff --git a/app/components/popUp/Redacting.tsx b/app/components/popUp/Redacting.tsx index aac69991..20acd346 100644 --- a/app/components/popUp/Redacting.tsx +++ b/app/components/popUp/Redacting.tsx @@ -8,7 +8,7 @@ export default function Redacting({ redactor, textSize }: {redactor: MenuItem[], return (
- menuLeft.current?.toggle(event)} aria-controls="popup_menu_left" aria-haspopup /> + menuLeft.current?.toggle(event)} aria-controls="popup_menu_left" aria-haspopup /> { useEffect(() => { console.log('Обновился и готов'); - + if (contextStudentThemes?.lessons) { const forThemes: any = []; contextStudentThemes.lessons.data?.map((item: any) => forThemes.push({ label: item.title, id: item.id, - to: `/teaching/lesson/${item.id}`, - // items: [] + to: `/teaching/lesson/${item.id}` }) ); if (forThemes.length > 0) { From 3bceca8e21ddd4281263a13031c833493eaea43c Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Mon, 25 Aug 2025 19:25:56 +0600 Subject: [PATCH 116/183] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=BA=D0=B0=20=D1=81=D0=B5=D1=81=D1=81=D0=B8=D0=B8=20=D0=B8=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B0=D0=BA=D1=86=D0=B8=D0=B8=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/SessionManager.tsx | 13 +++++-------- layout/AppTopbar.tsx | 1 + layout/StudentLayout.tsx | 7 +++---- services/auth.tsx | 2 -- utils/axiosInstance.tsx | 16 ++++++++-------- 5 files changed, 17 insertions(+), 22 deletions(-) diff --git a/app/components/SessionManager.tsx b/app/components/SessionManager.tsx index 79dae43b..93621e9d 100644 --- a/app/components/SessionManager.tsx +++ b/app/components/SessionManager.tsx @@ -19,7 +19,6 @@ const SessionManager = () => { }, [user]); useEffect(() => { - console.log('Роутинг'); setGlobalLoading(true); setTimeout(() => { setGlobalLoading(false); @@ -33,7 +32,9 @@ const SessionManager = () => { setGlobalLoading(true); try { if (res?.success) { - setGlobalLoading(false); + setTimeout(() => { + setGlobalLoading(false); + }, 1000); console.log('Данные пользователя успешно пришли ', res); const userVisit = localStorage.getItem('userVisit'); @@ -75,12 +76,8 @@ const SessionManager = () => { if (!token && pathname !== '/' && pathname !== '/auth/login') { console.log('Перенеправляю в login'); - // logout({ setUser, setGlobalLoading }); - // window.location.href = '/auth/login'; - setTimeout(() => { - setGlobalLoading(false); - }, 1000); - + logout({ setUser, setGlobalLoading }); + window.location.href = '/auth/login'; return; } setTimeout(() => { diff --git a/layout/AppTopbar.tsx b/layout/AppTopbar.tsx index 90fe5626..fb8256bb 100644 --- a/layout/AppTopbar.tsx +++ b/layout/AppTopbar.tsx @@ -119,6 +119,7 @@ const AppTopbar = forwardRef((props, ref) => { icon: 'pi pi-sign-out', items: [], command: () => { + window.location.href = '/auth/login'; logout({ setUser, setGlobalLoading }); } } diff --git a/layout/StudentLayout.tsx b/layout/StudentLayout.tsx index 7175ca42..185278ec 100644 --- a/layout/StudentLayout.tsx +++ b/layout/StudentLayout.tsx @@ -122,18 +122,17 @@ const StudentLayout = ({ children }: ChildContainerProps) => { const requireRole = () => { if(!user?.is_student){ - console.log('Не имеете доступ! student'); + console.warn('Не имеете доступ! student'); setPermission(false); - // window.location.href = '/auth/login'; + window.location.href = '/auth/login'; } - // setPermission(true); } useEffect(()=> { requireRole(); },[user]); - // if(permission) return null; + if(permission) return null; return ( diff --git a/services/auth.tsx b/services/auth.tsx index a20fef7a..aec595d3 100644 --- a/services/auth.tsx +++ b/services/auth.tsx @@ -10,8 +10,6 @@ let url = ''; // }); export const login = async (value:LoginType) => { - console.log(value); - url = process.env.NEXT_PUBLIC_BASE_URL + '/login?'; try { diff --git a/utils/axiosInstance.tsx b/utils/axiosInstance.tsx index 17800809..8420603d 100644 --- a/utils/axiosInstance.tsx +++ b/utils/axiosInstance.tsx @@ -3,7 +3,7 @@ import { getToken } from './auth'; const axiosInstance = axios.create({ baseURL: process.env.NEXT_PUBLIC_BASE_URL, - timeout: 10000, + timeout: 20000, }); axiosInstance.interceptors.request.use((config) => { @@ -25,21 +25,21 @@ axiosInstance.interceptors.response.use( if (status === 401) { console.warn('Неавторизован. Удаляю токен...'); - // document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; - // localStorage.removeItem('userVisit'); - // window.location.href = '/auth/login'; + window.location.href = '/auth/login'; + document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; + localStorage.removeItem('userVisit'); } if (status === 403) { + window.location.href = '/'; console.warn('Не имеет доступ. Перенаправляю...'); - // window.location.href = '/'; } if (status === 404) { console.warn('404 - Перенаправляю...'); - // document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; - // localStorage.removeItem('userVisit'); - // window.location.href = '/pages/notfound'; + window.location.href = '/pages/notfound'; + document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; + localStorage.removeItem('userVisit'); } return Promise.reject(error); From 04f6fc623fee728332eeb2c49a92c07f60b3ece1 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Tue, 26 Aug 2025 09:23:08 +0600 Subject: [PATCH 117/183] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=BA=D0=B0=20=D1=81=D0=B5=D1=81=D1=81=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(full-page)/auth/login/page.tsx | 4 ++-- app/components/SessionManager.tsx | 4 ++-- layout/StudentLayout.tsx | 15 ++++++++++----- layout/layout.tsx | 19 +++++++++++-------- utils/axiosInstance.tsx | 14 +++++++------- 5 files changed, 32 insertions(+), 24 deletions(-) diff --git a/app/(full-page)/auth/login/page.tsx b/app/(full-page)/auth/login/page.tsx index 2a4846c2..a829d655 100644 --- a/app/(full-page)/auth/login/page.tsx +++ b/app/(full-page)/auth/login/page.tsx @@ -99,7 +99,7 @@ const LoginPage = () => {
-
+

Кирүү

@@ -114,7 +114,7 @@ const LoginPage = () => { name="password" control={control} defaultValue="010270Ja" - render={({ field }) => } + render={({ field }) => } /> {errors.password && {errors.password.message}}
diff --git a/app/components/SessionManager.tsx b/app/components/SessionManager.tsx index 93621e9d..7919b2ac 100644 --- a/app/components/SessionManager.tsx +++ b/app/components/SessionManager.tsx @@ -76,8 +76,8 @@ const SessionManager = () => { if (!token && pathname !== '/' && pathname !== '/auth/login') { console.log('Перенеправляю в login'); - logout({ setUser, setGlobalLoading }); - window.location.href = '/auth/login'; + // logout({ setUser, setGlobalLoading }); + // window.location.href = '/auth/login'; return; } setTimeout(() => { diff --git a/layout/StudentLayout.tsx b/layout/StudentLayout.tsx index 185278ec..1f43a25b 100644 --- a/layout/StudentLayout.tsx +++ b/layout/StudentLayout.tsx @@ -121,18 +121,23 @@ const StudentLayout = ({ children }: ChildContainerProps) => { }); const requireRole = () => { - if(!user?.is_student){ - console.warn('Не имеете доступ! student'); + console.log('Ваш статус: ', user?.is_student); + + if(user){ + if(!user?.is_student){ + console.warn('Не имеете доступ! student'); + // window.location.href = '/auth/login'; + // setPermission(true); + } setPermission(false); - window.location.href = '/auth/login'; } } useEffect(()=> { requireRole(); - },[user]); + },[user, pathname]); - if(permission) return null; + // if(permission) return null; return ( diff --git a/layout/layout.tsx b/layout/layout.tsx index 013f6a17..e40fd328 100644 --- a/layout/layout.tsx +++ b/layout/layout.tsx @@ -19,7 +19,7 @@ const Layout = ({ children }: ChildContainerProps) => { const { setRipple } = useContext(PrimeReactContext); const topbarRef = useRef(null); const sidebarRef = useRef(null); - const [permission, setPermission] = useState(false); + const [permission, setPermission] = useState(true); const [bindMenuOutsideClickListener, unbindMenuOutsideClickListener] = useEventListener({ type: 'click', listener: (event) => { @@ -124,17 +124,20 @@ const Layout = ({ children }: ChildContainerProps) => { }); const requireRole = () => { - if(!user?.is_working){ - console.log('Не имеете доступ! working'); + console.log('Ваш статус: ', user?.is_working); + if(user){ + if(!user?.is_working){ + // window.location.href = '/auth/login'; + // setPermission(true); + console.log('Не имеете доступ! working'); + } setPermission(false); - // window.location.href = '/auth/login'; - } - setPermission(true); + } } useEffect(()=> { - requireRole(); - },[user]); + requireRole(); + },[user, pathname]); // if(permission) return null; diff --git a/utils/axiosInstance.tsx b/utils/axiosInstance.tsx index 8420603d..8f5cbfc7 100644 --- a/utils/axiosInstance.tsx +++ b/utils/axiosInstance.tsx @@ -25,21 +25,21 @@ axiosInstance.interceptors.response.use( if (status === 401) { console.warn('Неавторизован. Удаляю токен...'); - window.location.href = '/auth/login'; - document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; - localStorage.removeItem('userVisit'); + // window.location.href = '/auth/login'; + // document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; + // localStorage.removeItem('userVisit'); } if (status === 403) { - window.location.href = '/'; + // window.location.href = '/'; console.warn('Не имеет доступ. Перенаправляю...'); } if (status === 404) { console.warn('404 - Перенаправляю...'); - window.location.href = '/pages/notfound'; - document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; - localStorage.removeItem('userVisit'); + // window.location.href = '/pages/notfound'; + // document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; + // localStorage.removeItem('userVisit'); } return Promise.reject(error); From e3073ebd13970fdda961331837026fcf479ed24d Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Tue, 26 Aug 2025 17:46:35 +0600 Subject: [PATCH 118/183] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D1=81=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D1=8B=20=D0=B2=20?= =?UTF-8?q?=D0=BA=D0=B0=D1=80=D1=82=D0=BE=D1=87=D0=BA=D0=B8=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=BC=D0=BE=D0=B1=D0=B8=D0=BB=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(full-page)/auth/login/page.tsx | 26 ++-- app/(full-page)/layout.tsx | 2 +- app/(main)/course/page.tsx | 175 ++++++++++++++------------- app/components/messages/Message.tsx | 4 +- app/components/tables/StreamList.tsx | 105 +++++++++++++--- app/globals.css | 43 ++++++- layout/AppTopbar.tsx | 5 - public/layout/svg/login-bg.svg | 2 +- 8 files changed, 231 insertions(+), 131 deletions(-) diff --git a/app/(full-page)/auth/login/page.tsx b/app/(full-page)/auth/login/page.tsx index a829d655..6e866044 100644 --- a/app/(full-page)/auth/login/page.tsx +++ b/app/(full-page)/auth/login/page.tsx @@ -2,13 +2,9 @@ 'use client'; import { useRouter } from 'next/navigation'; import React, { useContext, useState } from 'react'; -import { Checkbox } from 'primereact/checkbox'; -import { Button } from 'primereact/button'; import { Password } from 'primereact/password'; import { LayoutContext } from '../../../../layout/context/layoutcontext'; import { InputText } from 'primereact/inputtext'; -import { classNames } from 'primereact/utils'; -import InfoBanner from '@/app/components/InfoBanner'; import { useForm } from 'react-hook-form'; import { schema } from '@/schemas/authSchema'; @@ -16,7 +12,6 @@ import { yupResolver } from '@hookform/resolvers/yup'; import { Controller } from 'react-hook-form'; import { getUser, login } from '@/services/auth'; import FancyLinkBtn from '@/app/components/buttons/FancyLinkBtn'; -import { getToken } from '@/utils/auth'; import { logout } from '@/utils/logout'; import { LoginType } from '@/types/login'; import { useMediaQuery } from '@/hooks/useMediaQuery'; @@ -60,25 +55,25 @@ const LoginPage = () => { } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при авторизации' } - }); // messege - Ошибка при авторизации + value: { severity: 'error', summary: 'Ошибка при авторизации', detail: 'Повторите позже' } + }); // messege - Ошибка при авторизации, повторите позже logout({ setUser, setGlobalLoading }); console.log('Ошибка при получении пользователя'); } } catch (error) { setMessage({ state: true, - value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при авторизации' } - }); // messege - Ошибка при авторизации + value: { severity: 'error', summary: 'Ошибка при авторизации', detail: 'Повторите позже' } + }); // messege - Ошибка при авторизации, повторите позже logout({ setUser, setGlobalLoading }); console.log('Ошибка при получении пользователя'); } } } else { - console.log('Ошибка при авторизации'); + console.log('Ошибка при авторизации, повторите позже'); setMessage({ state: true, - value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при авторизации' } + value: { severity: 'error', summary: 'Ошибка', detail: 'Введите корректные данные' } }); // messege - Ошибка при авторизации } }; @@ -87,20 +82,21 @@ const LoginPage = () => { console.log('Ошибки формы:', errors); setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа', detail: 'ПОвтор' } + value: { severity: 'error', summary: 'Ошибка', detail: 'Введите корректные данные' } }); // messege - Ошибка при авторизации }; return ( -
+
+ {/*
*/} {/* */}
-
-

Кирүү

+
+

Кирүү

{/*
- )} -
+ <> +
+
+ + )} @@ -583,7 +594,7 @@ export default function Course() { className="p-tabview p-tabview-nav p-tabview-selected p-tabview-panels p-tabview-panel" >
- + {}} />
diff --git a/app/components/messages/Message.tsx b/app/components/messages/Message.tsx index 42baa0a2..908dd2f6 100644 --- a/app/components/messages/Message.tsx +++ b/app/components/messages/Message.tsx @@ -12,7 +12,7 @@ export default function Message() { const toast = useRef(null); const showError = () => { - toast.current?.show({ severity: message.value.severity as SeverityType, summary: message.value.summary, detail: message.value.detail, life: 3000 }); + toast.current?.show({ severity: message.value.severity as SeverityType, summary: message.value.summary, detail: message.value.detail, life: 30000 }); }; useEffect(() => { @@ -26,7 +26,7 @@ export default function Message() { return (
{/*
); } diff --git a/app/components/tables/StreamList.tsx b/app/components/tables/StreamList.tsx index 955b6c23..66de54db 100644 --- a/app/components/tables/StreamList.tsx +++ b/app/components/tables/StreamList.tsx @@ -11,16 +11,73 @@ import GroupSkeleton from '../skeleton/GroupSkeleton'; import Link from 'next/link'; import { streamsType } from '@/types/streamType'; -export default function StreamList({ callIndex, courseValue, isMobile }: { callIndex: number; courseValue: { id: number | null; title: string } | null; isMobile: boolean }) { +export default function StreamList({ callIndex, courseValue, isMobile, insideDisplayStreams }: { callIndex: number; courseValue: { id: number | null; title: string } | null; isMobile: boolean; insideDisplayStreams: ()=> void }) { interface mainStreamsType { - connect_id: number | null,stream_id:number, subject_name: {name_kg:string}, - subject_type_name:{name_kg:string}, teacher:{name:string}, language:{name:string}, - id_edu_year:number,id_period:number, semester:{name_kg:string}, edu_form:{name_kg:string} + connect_id: number | null; + stream_id: number; + subject_name: { name_kg: string }; + subject_type_name: { name_kg: string }; + teacher: { name: string }; + language: { name: string }; + id_edu_year: number; + id_period: number; + semester: { name_kg: string }; + edu_form: { name_kg: string }; } + const shablon = [ + { + connect_id: 0, + stream_id: 2, + subject_name: { name_kg: 'test' }, + subject_type_name: { name_kg: 'test' }, + teacher: { name: 'test' }, + language: { name: 'test' }, + id_edu_year: 2, + id_period: 2, + semester: { name_kg: 'test' }, + edu_form: { name_kg: 'test' } + }, + { + connect_id: 0, + stream_id: 0, + subject_name: { name_kg: 'test' }, + subject_type_name: { name_kg: 'test' }, + teacher: { name: 'test' }, + language: { name: 'test' }, + id_edu_year: 2, + id_period: 2, + semester: { name_kg: 'test' }, + edu_form: { name_kg: 'test' } + }, + { + connect_id: 2, + stream_id: 2, + subject_name: { name_kg: 'test' }, + subject_type_name: { name_kg: 'test' }, + teacher: { name: 'test' }, + language: { name: 'test' }, + id_edu_year: 2, + id_period: 2, + semester: { name_kg: 'test' }, + edu_form: { name_kg: 'test' } + }, + { + connect_id: 2, + stream_id: 2, + subject_name: { name_kg: 'test' }, + subject_type_name: { name_kg: 'test' }, + teacher: { name: 'test' }, + language: { name: 'test' }, + id_edu_year: 2, + id_period: 2, + semester: { name_kg: 'test' }, + edu_form: { name_kg: 'test' } + }, + ]; const [streams, setStreams] = useState([]); const [streamValues, setStreamValues] = useState<{ stream: streamsType[] }>({ stream: [] }); - const [displayStreams, setDisplayStreams] = useState<{ course_id: number; stream_id: number; info: string | null; stream_title: string }[]>([]); + const [displayStreams, setDisplayStreams] = useState<{ course_id: number; stream_id: number; info: string | null; stream_title: string }[] | any>([]); const [hasStreams, setHasStreams] = useState(false); const [skeleton, setSkeleton] = useState(false); @@ -35,11 +92,11 @@ export default function StreamList({ callIndex, courseValue, isMobile }: { callI }, 1000); }; - const profilactor = (data: {connect_id: number, course_id: number; stream_id: number; info: string | null}[]) => { + const profilactor = (data: { connect_id: number; course_id: number; stream_id: number; info: string | null }[]) => { const newStreams: { course_id: number; stream_id: number; info: string | null }[] = []; - + data.forEach((item) => { - if (item?.connect_id) { + if (item?.connect_id) { newStreams.push({ course_id: item?.course_id, stream_id: item.stream_id, @@ -100,8 +157,7 @@ export default function StreamList({ callIndex, courseValue, isMobile }: { callI } }; - const handleEdit = (e:{checked:boolean}, id: number, title: string) => { - + const handleEdit = (e: { checked: boolean }, id: number, title: string) => { const forSentStreams = { course_id: courseValue!.id, stream_id: id, @@ -111,10 +167,15 @@ export default function StreamList({ callIndex, courseValue, isMobile }: { callI if (e.checked) { // profilactor(); - setStreamValues((prev) => prev && ({ - ...prev, - stream: [...prev.stream, forSentStreams] - })); + setStreamValues( + (prev) => + prev && { + ...prev, + stream: [...prev.stream, forSentStreams] + } + ); + const x = {streamTitle} + insideDisplayStreams(); } else { setStreamValues( (prev) => @@ -136,7 +197,7 @@ export default function StreamList({ callIndex, courseValue, isMobile }: { callI } }); - // setDisplayStreams(forDisplay); + setDisplayStreams(forDisplay); }, [streamValues]); useEffect(() => { @@ -156,7 +217,11 @@ export default function StreamList({ callIndex, courseValue, isMobile }: { callI } }, [streams]); - const itemTemplate = (item:mainStreamsType, index: number) => { + useEffect(()=> { + console.log(displayStreams); + },[displayStreams]) + + const itemTemplate = (item: mainStreamsType, index: number) => { const bgClass = item.connect_id ? 'bg-[var(--greenBgColor)] border-b border-[gray]' : index % 2 == 1 ? 'bg-[#f5f5f5]' : ''; return ( @@ -177,7 +242,7 @@ export default function StreamList({ callIndex, courseValue, isMobile }: { callI
-
+
{item?.subject_type_name?.name_kg} @@ -211,7 +276,7 @@ export default function StreamList({ callIndex, courseValue, isMobile }: { callI ); }; - const listTemplate = (items:mainStreamsType[]) => { + const listTemplate = (items: mainStreamsType[]) => { if (!items || items.length === 0) return null; let list = items.map((product, index: number) => { @@ -248,7 +313,7 @@ export default function StreamList({ callIndex, courseValue, isMobile }: { callI )} - {hasStreams ? ( + {!hasStreams ? ( ) : (
@@ -269,7 +334,7 @@ export default function StreamList({ callIndex, courseValue, isMobile }: { callI ) : ( <> - + )}
diff --git a/app/globals.css b/app/globals.css index e883080e..343c757f 100644 --- a/app/globals.css +++ b/app/globals.css @@ -4,7 +4,8 @@ :root { --bodyFonts: "Jost", sans-serif; --mainColor: #08A9E6; - --mainBgColor: #e6eef1; + /* --mainBgColor: #e6eef1; */ + --mainBgColor: #fff; --redColor: #EC272F; --redBgColor: #EC272F1A; --greenColor: rgb(46, 192, 46); @@ -44,7 +45,7 @@ h1, h2, h3, h4, h5, h6 { } .login-bg{ - background: url('/layout/svg/login-bg.svg') no-repeat center/cover; + background: url('/layout/svg/login-bg.svg') repeat left/cover; } /* animate */ @@ -136,6 +137,7 @@ h1, h2, h3, h4, h5, h6 { height: 60px; width: 60px; } + #preloader-area .spinner { position: absolute; z-index: 9999; @@ -195,12 +197,29 @@ h1, h2, h3, h4, h5, h6 { right: 0; } +@media screen and (max-width: 640px) { + #preloader-area{ + position: absolute; + left: 60%; + top: 50%; + width: 10px; + height: 10px; + } + + #preloader-area .spinner { + position: absolute; + z-index: 9999; + /* width: 3px; Уменьшаем с 6px до 3px */ + height: 45px; /* Уменьшаем с 90px до 45px */ + margin-top: -22.5px; /* Уменьшаем с -45px до -22.5px */ + } +} + .p-password .p-password-toggle-icon { top: 50% !important; transform: translateY(-50%); } - .loaded #preloader-area { opacity: 0; visibility: hidden; @@ -313,8 +332,6 @@ h1, h2, h3, h4, h5, h6 { width: 100%; } - - /* primeReact components */ .popup_menu_left{ width: 300px; @@ -368,6 +385,10 @@ h1, h2, h3, h4, h5, h6 { background-color: #f5f5f5; } +.my-custom-mobile-table:nth-child(even) { + background-color: red; +} + /* удаление встроенного underline при активаности для таб (TabView) */ .p-tabview-ink-bar { display: none !important; @@ -427,6 +448,18 @@ h1, h2, h3, h4, h5, h6 { font-size: 12px; /* или любой нужный размер */ } +@media screen and (max-width: 640px) { + .p-toast-message{ + font-size: 14px !important; + width: 290px !important; + margin: 0 auto; + } + + .p-toast-message-content{ + padding: 10px !important; + } +} + /* styles/book.css */ /* Стили для контейнера всей книги */ diff --git a/layout/AppTopbar.tsx b/layout/AppTopbar.tsx index fb8256bb..5f8beecf 100644 --- a/layout/AppTopbar.tsx +++ b/layout/AppTopbar.tsx @@ -91,11 +91,6 @@ const AppTopbar = forwardRef((props, ref) => { icon: '', items: [], url: 'https://oshsu.kg' - }, - { - label: 'Байланыш', - icon: '', - items: [] } ]; diff --git a/public/layout/svg/login-bg.svg b/public/layout/svg/login-bg.svg index 5398aa5a..f704ca1a 100644 --- a/public/layout/svg/login-bg.svg +++ b/public/layout/svg/login-bg.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 69ed0e84c41778458b3917877e65dcfe44cf68c9 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Wed, 27 Aug 2025 11:31:03 +0600 Subject: [PATCH 119/183] =?UTF-8?q?=D0=A5=D0=BB=D0=B5=D0=B1=D0=BD=D1=8B?= =?UTF-8?q?=D0=B5=20=D0=BA=D1=80=D0=BE=D1=88=D0=BA=D0=B8=20=D0=B8=20=D0=B4?= =?UTF-8?q?=D1=80=D1=83=D0=B3=D0=B8=D0=B5=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../course/[courseTheme]/[lessons]/page.tsx | 90 ++++++++++++++----- app/(main)/course/[courseTheme]/page.tsx | 71 +++++++++++---- .../[connect_id]/[stream_id]/page.tsx | 86 ++++++++++++++---- app/components/SessionManager.tsx | 2 +- app/components/messages/Message.tsx | 2 +- app/globals.css | 3 +- hooks/useBreadCrumbs.tsx | 72 +++++++++++++++ layout/layout.tsx | 14 +-- types/breadCrumbType.tsx | 6 ++ 9 files changed, 281 insertions(+), 65 deletions(-) create mode 100644 hooks/useBreadCrumbs.tsx create mode 100644 types/breadCrumbType.tsx diff --git a/app/(main)/course/[courseTheme]/[lessons]/page.tsx b/app/(main)/course/[courseTheme]/[lessons]/page.tsx index 83fa63b4..7fe5ce0d 100644 --- a/app/(main)/course/[courseTheme]/[lessons]/page.tsx +++ b/app/(main)/course/[courseTheme]/[lessons]/page.tsx @@ -7,7 +7,7 @@ import { Button } from 'primereact/button'; import Redacting from '@/app/components/popUp/Redacting'; import { addLesson, deleteLesson, fetchLesson, updateLesson } from '@/services/courses'; import { getToken } from '@/utils/auth'; -import { useParams } from 'next/navigation'; +import { useParams, usePathname } from 'next/navigation'; import { LayoutContext } from '@/layout/context/layoutcontext'; import useErrorMessage from '@/hooks/useErrorMessage'; import { getRedactor } from '@/utils/getRedactor'; @@ -15,6 +15,7 @@ import { getConfirmOptions } from '@/utils/getConfirmOptions'; import LessonTyping from '@/app/components/lessons/LessonTyping'; import { TabViewChange } from '@/types/tabViewChange'; import { useMediaQuery } from '@/hooks/useMediaQuery'; +import useBreadCrumbs from '@/hooks/useBreadCrumbs'; export default function Lesson() { const [activeIndex, setActiveIndex] = useState(0); @@ -34,6 +35,42 @@ export default function Lesson() { const courseId = params.courseTheme; const lessonId = params.lessons; + const teachingBreadCrumb = [ + { + id: 1, + url: '/', + title: 'Башкы баракча', + parent_id: null + }, + { + id: 2, + url: '/course', + title: 'Курстар', + parent_id: 1 + }, + { + id: 3, + url: `/course/${lessonId}`, + title: 'Темалар', + parent_id: 2 + }, + { + id: 4, + url: '/course/:course_id/:lesson_id', + title: 'Сабактар', + parent_id: 3 + }, + { + id: 5, + url: '/students/:course_id/:stream_id', + title: 'Студенттер', + parent_id: 2 + } + ]; + + const pathname = usePathname(); + const breadcrumb = useBreadCrumbs(teachingBreadCrumb,pathname); + const handleText = (e: string) => { setSentValues(e); }; @@ -154,28 +191,37 @@ export default function Lesson() { setContentShow(true); }; - const lessonInfo =
-
- -
-

{'Сабактын аталышы'}

-
- - - handleTabChange({index: 1})} className='cursor-pointer hover:text-[var(--mainColor)] transition-all'>Документтердин саны: ? - - - - handleTabChange({index: 2})} className='cursor-pointer hover:text-[var(--mainColor)] transition-all'>Шилтемелердин саны: ? - - - - handleTabChange({index: 3})} className='cursor-pointer hover:text-[var(--mainColor)] transition-all'>Видеолордун саны: ? - + const lessonInfo = ( +
+
+ +
+

{'Сабактын аталышы'}

+
+ + + handleTabChange({ index: 1 })} className="cursor-pointer hover:text-[var(--mainColor)] transition-all"> + Документтердин саны: ? + + + + + handleTabChange({ index: 2 })} className="cursor-pointer hover:text-[var(--mainColor)] transition-all"> + Шилтемелердин саны: ? + + + + + handleTabChange({ index: 3 })} className="cursor-pointer hover:text-[var(--mainColor)] transition-all"> + Видеолордун саны: ? + + +
+
{breadcrumb}
-
+ ); // USE EFFECTS @@ -243,7 +289,9 @@ export default function Lesson() {
)}
- ) : lessonInfo} + ) : ( + lessonInfo + )} {/* DOC */} diff --git a/app/(main)/course/[courseTheme]/page.tsx b/app/(main)/course/[courseTheme]/page.tsx index 30b8aa74..f299aa2a 100644 --- a/app/(main)/course/[courseTheme]/page.tsx +++ b/app/(main)/course/[courseTheme]/page.tsx @@ -10,7 +10,7 @@ import { DataTable } from 'primereact/datatable'; import { InputText } from 'primereact/inputtext'; import { LayoutContext } from '@/layout/context/layoutcontext'; import React, { useContext, useEffect, useState } from 'react'; -import { useParams } from 'next/navigation'; +import { useParams, usePathname } from 'next/navigation'; import { addThemes, deleteTheme, fetchCourseInfo, fetchThemes, updateTheme } from '@/services/courses'; import useErrorMessage from '@/hooks/useErrorMessage'; import { CourseCreateType } from '@/types/courseCreateType'; @@ -19,6 +19,7 @@ import Redacting from '@/app/components/popUp/Redacting'; import { getRedactor } from '@/utils/getRedactor'; import { getConfirmOptions } from '@/utils/getConfirmOptions'; import { ProgressSpinner } from 'primereact/progressspinner'; +import useBreadCrumbs from '@/hooks/useBreadCrumbs'; export default function CourseTheme() { const [hasThemes, setHasThemes] = useState(false); @@ -39,7 +40,42 @@ export default function CourseTheme() { const { courseTheme } = useParams() as { courseTheme: string }; + const teachingBreadCrumb = [ + { + id: 1, + url: '/', + title: 'Башкы баракча', + parent_id: null + }, + { + id: 2, + url: '/course', + title: 'Курстар', + parent_id: 1 + }, + { + id: 3, + url: `/course/:id`, + title: 'Темалар', + parent_id: 2 + }, + { + id: 4, + url: '/course/:course_id/:lesson_id', + title: 'Сабактар', + parent_id: 3 + }, + { + id: 5, + url: '/students/:course_id/:stream_id', + title: 'Студенттер', + parent_id: 2 + } + ]; + const showError = useErrorMessage(); + const pathname = usePathname(); + const breadcrumb = useBreadCrumbs(teachingBreadCrumb, pathname); const toggleSkeleton = () => { setSkeleton(true); @@ -158,13 +194,13 @@ export default function CourseTheme() { }; useEffect(() => { - handleFetchInfo(); - handleFetchThemes(); + // handleFetchInfo(); + // handleFetchThemes(); }, []); useEffect(() => { const handleShow = async () => { - setProgressSpinner(true) + setProgressSpinner(true); const data: { lessons: { data: { id: number; title: string }[] } } = await fetchThemes(Number(courseTheme)); if (data?.lessons) { @@ -203,21 +239,24 @@ export default function CourseTheme() { return (
{/* title section */} -
-
-

- {themeInfo?.title} -

-

{themeInfo?.description}

-
- - {themeInfo?.created_at} +
+
+
+

+ {themeInfo?.title} +

+

{themeInfo?.description}

+
+ + {themeInfo?.created_at} +
-
-
- +
+ +
+
{breadcrumb}
{/* add button*/} diff --git a/app/(main)/students/[connect_id]/[stream_id]/page.tsx b/app/(main)/students/[connect_id]/[stream_id]/page.tsx index 320fca8e..38c455a6 100644 --- a/app/(main)/students/[connect_id]/[stream_id]/page.tsx +++ b/app/(main)/students/[connect_id]/[stream_id]/page.tsx @@ -3,11 +3,12 @@ import InfoBanner from '@/app/components/InfoBanner'; import { NotFound } from '@/app/components/NotFound'; import GroupSkeleton from '@/app/components/skeleton/GroupSkeleton'; +import useBreadCrumbs from '@/hooks/useBreadCrumbs'; import useErrorMessage from '@/hooks/useErrorMessage'; +import { useMediaQuery } from '@/hooks/useMediaQuery'; import { LayoutContext } from '@/layout/context/layoutcontext'; import { fetchStreamStudents } from '@/services/streams'; -import Link from 'next/link'; -import { useParams } from 'next/navigation'; +import { useParams, usePathname } from 'next/navigation'; import { Button } from 'primereact/button'; import { Column } from 'primereact/column'; import { DataTable } from 'primereact/datatable'; @@ -39,7 +40,7 @@ export default function StudentList() { last_visist: 'xx-xx', info: true } - ] + ]; // const [studentList, setStudentList] = useState([]); const [hasList, setHasList] = useState(false); const [skeleton, setSkeleton] = useState(false); @@ -47,9 +48,46 @@ export default function StudentList() { const { setMessage, setGlobalLoading } = useContext(LayoutContext); const showError = useErrorMessage(); - const {connect_id, stream_id} = useParams(); + const { connect_id, stream_id } = useParams(); console.log('params: ', connect_id, stream_id); - + + const media = useMediaQuery('(max-width: 640px)'); + const teachingBreadCrumb = [ + { + id: 1, + url: '/', + title: 'Башкы баракча', + parent_id: null + }, + { + id: 2, + url: '/course', + title: 'Курстар', + parent_id: 1 + }, + { + id: 3, + url: `/course/:id`, + title: 'Темалар', + parent_id: 2 + }, + { + id: 4, + url: '/course/:course_id/:lesson_id', + title: 'Сабактар', + parent_id: 3 + }, + { + id: 5, + url: `/students/${connect_id}/${stream_id}`, + title: 'Студенттер', + parent_id: 2 + } + ]; + + const pathname = usePathname(); + const breadcrumb = useBreadCrumbs(teachingBreadCrumb, pathname); + // functions const toggleSkeleton = () => { setSkeleton(true); @@ -61,7 +99,7 @@ export default function StudentList() { const handleFetchStudents = async () => { const data = await fetchStreamStudents(connect_id ? Number(connect_id) : null, stream_id ? Number(stream_id) : null); console.log(data); - + toggleSkeleton(); if (data && data.students) { setHasList(false); @@ -88,13 +126,19 @@ export default function StudentList() { return (
{skeleton ? ( - - ) : ( - <> - {/* info section */} - - - ) } + + ) : ( + <> + {/* info section */} +
+ +
+

{'Угуучулардын тизмеси'}

+
{breadcrumb}
+
+
+ + )} {/* table section */} {!hasList ? ( @@ -107,11 +151,17 @@ export default function StudentList() { <> rowIndex + 1} header="Номер" style={{ width: '20px' }}> -
- {rowData.last_name} - {rowData.name} - {rowData.father_name} -
} + ( +
+ {rowData.last_name} + {rowData.name} + {rowData.father_name} +
+ )} >
(null); const showError = () => { - toast.current?.show({ severity: message.value.severity as SeverityType, summary: message.value.summary, detail: message.value.detail, life: 30000 }); + toast.current?.show({ severity: message.value.severity as SeverityType, summary: message.value.summary, detail: message.value.detail, life: 2000 }); }; useEffect(() => { diff --git a/app/globals.css b/app/globals.css index 343c757f..18560759 100644 --- a/app/globals.css +++ b/app/globals.css @@ -41,7 +41,8 @@ h1, h2, h3, h4, h5, h6 { } .mainColor-hover:hover{ - color: #08A9E6; + color: #08A9E6 !important; + transition: .5s; } .login-bg{ diff --git a/hooks/useBreadCrumbs.tsx b/hooks/useBreadCrumbs.tsx new file mode 100644 index 00000000..2b8fc5ad --- /dev/null +++ b/hooks/useBreadCrumbs.tsx @@ -0,0 +1,72 @@ +'use client'; + +import { breadCrumbType } from '@/types/breadCrumbType'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { useEffect, useState } from 'react'; + +export default function useBreadCrumbs(insideBreadCrumb: breadCrumbType[], currentUrl: string) { + const pathname = usePathname(); + + const [breadCrumb, setBreadCrumb] = useState([]); + + const matchUrl = (pattern: string, url: string) => { + // Заменяем все ":что-то" на "([^/]+)" (регулярка: один сегмент) + const regex = new RegExp('^' + pattern.replace(/:[^/]+/g, '[^/]+') + '$'); + return regex.test(url); + }; + + const getBreadcrumbs = (data: breadCrumbType[], currentUrl: string) => { + + // 1. Находим текущую страницу по URL (с учётом динамики) + const currentPage = data.find((item) => matchUrl(item.url, currentUrl)); + console.log(currentPage); + + if (!currentPage) return []; + + const breadcrumbs = []; + let currentItem: breadCrumbType = currentPage; + const visited = new Set(); + + while (currentItem) { + if (visited.has(currentItem.id)) { + // Зацикленность → выходим + console.warn('Цикл в дереве хлебных крошек!'); + break; + } + visited.add(currentItem.id); + + breadcrumbs.unshift(currentItem); + const parent = data.find((item) => item.id === currentItem.parent_id); + if (parent) currentItem = parent; + else break; + } + + return breadcrumbs; + }; + + useEffect(() => { + const forBreadcrumb = getBreadcrumbs(insideBreadCrumb, currentUrl); + console.log(forBreadcrumb); + setBreadCrumb(forBreadcrumb); + }, [pathname]); + + return ( +
+ {breadCrumb?.map((crumb: breadCrumbType, index) => ( +
+
+ {index < breadCrumb.length - 1 ? ( + + {crumb.title} + + ) : ( + {crumb.title} + )} +
+ {index < breadCrumb.length - 1 && / } +
+ ))} +
+ ); +} diff --git a/layout/layout.tsx b/layout/layout.tsx index e40fd328..b6fc8faa 100644 --- a/layout/layout.tsx +++ b/layout/layout.tsx @@ -125,19 +125,19 @@ const Layout = ({ children }: ChildContainerProps) => { const requireRole = () => { console.log('Ваш статус: ', user?.is_working); - if(user){ - if(!user?.is_working){ + if (user) { + if (!user?.is_working) { // window.location.href = '/auth/login'; // setPermission(true); console.log('Не имеете доступ! working'); } setPermission(false); - } - } + } + }; - useEffect(()=> { - requireRole(); - },[user, pathname]); + useEffect(() => { + requireRole(); + }, [user, pathname]); // if(permission) return null; diff --git a/types/breadCrumbType.tsx b/types/breadCrumbType.tsx new file mode 100644 index 00000000..55c31ceb --- /dev/null +++ b/types/breadCrumbType.tsx @@ -0,0 +1,6 @@ +export interface breadCrumbType { + id: number; + url: string; + title: string; + parent_id: number | null; +} From e0f1fa478405507fd214e71fbf5115a041581af2 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Wed, 27 Aug 2025 13:18:31 +0600 Subject: [PATCH 120/183] =?UTF-8?q?=D0=9C=D0=BE=D0=B1=D0=B8=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D1=8B=D0=B5=20=D1=83=D0=B4=D0=BE=D0=B1=D1=81=D1=82=D0=B2?= =?UTF-8?q?=D0=B0=201.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../course/[courseTheme]/[lessons]/page.tsx | 10 +- app/(main)/course/[courseTheme]/page.tsx | 2 +- app/(main)/course/page.tsx | 3 +- app/components/HomeClient.tsx | 2 +- app/components/popUp/FormModal.tsx | 102 ++++++++++++------ app/components/tables/StreamList.tsx | 2 +- app/globals.css | 20 ++++ hooks/useBreadCrumbs.tsx | 6 +- 8 files changed, 103 insertions(+), 44 deletions(-) diff --git a/app/(main)/course/[courseTheme]/[lessons]/page.tsx b/app/(main)/course/[courseTheme]/[lessons]/page.tsx index 7fe5ce0d..c61bc859 100644 --- a/app/(main)/course/[courseTheme]/[lessons]/page.tsx +++ b/app/(main)/course/[courseTheme]/[lessons]/page.tsx @@ -193,7 +193,7 @@ export default function Lesson() { const lessonInfo = (
-
+

{'Сабактын аталышы'}

@@ -240,7 +240,7 @@ export default function Lesson() { {/* CKEDITOR */} {/* title section */} -
+

diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index db9e4f38..71041d71 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -504,6 +504,7 @@ export default function Course() { disabled={progressSpinner === true ? true : false} rows={5} cols={30} + className='w-[300px]' onChange={(e) => { editMode ? setEditingLesson((prev) => ({ @@ -690,7 +691,7 @@ export default function Course() {

{/* STREAMS SECTION */}
- + {}} />
)} diff --git a/app/components/HomeClient.tsx b/app/components/HomeClient.tsx index b3b74e96..43ccccc4 100644 --- a/app/components/HomeClient.tsx +++ b/app/components/HomeClient.tsx @@ -18,7 +18,7 @@ export default function HomeClient() { return (
- {/* {"Уроки"} */} + {"Уроки"}
diff --git a/app/components/popUp/FormModal.tsx b/app/components/popUp/FormModal.tsx index 9ec91e8d..d281290a 100644 --- a/app/components/popUp/FormModal.tsx +++ b/app/components/popUp/FormModal.tsx @@ -1,49 +1,87 @@ 'use client'; +import { useMediaQuery } from '@/hooks/useMediaQuery'; import { Button } from 'primereact/button'; import { Dialog } from 'primereact/dialog'; import { ReactNode, useEffect } from 'react'; -export default function FormModal({children, title, fetchValue, clearValues, visible, setVisible, start}: - { - children: ReactNode, - title: string, - fetchValue: ()=> void, - clearValues: ()=> void, - visible: boolean, - setVisible: (params: boolean)=> void, - start: boolean - } - ){ +export default function FormModal({ + children, + title, + fetchValue, + clearValues, + visible, + setVisible, + start +}: { + children: ReactNode; + title: string; + fetchValue: () => void; + clearValues: () => void; + visible: boolean; + setVisible: (params: boolean) => void; + start: boolean; +}) { + const media = useMediaQuery('(max-width:640px)'); const footerContent = (
-
); return (
- { - if (!visible) return; - setVisible(false); - clearValues(); - }} - footer={footerContent} - > - {children} - + {media ? ( + { + if (!visible) return; + setVisible(false); + clearValues(); + }} + footer={footerContent} + > + {children} + + ) : ( + { + if (!visible) return; + setVisible(false); + clearValues(); + }} + footer={footerContent} + > + {children} + + )}
); } diff --git a/app/components/tables/StreamList.tsx b/app/components/tables/StreamList.tsx index 66de54db..54ed7e2a 100644 --- a/app/components/tables/StreamList.tsx +++ b/app/components/tables/StreamList.tsx @@ -174,7 +174,7 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis stream: [...prev.stream, forSentStreams] } ); - const x = {streamTitle} + // const x = {streamTitle} insideDisplayStreams(); } else { setStreamValues( diff --git a/app/globals.css b/app/globals.css index 18560759..2e1512d1 100644 --- a/app/globals.css +++ b/app/globals.css @@ -438,8 +438,17 @@ h1, h2, h3, h4, h5, h6 { @media screen and (max-width: 640px) { .my-custom-dialog{ + width: 350px; + } + .my-custom-document-dialog{ width: 300px; } +} + +@media screen and (max-width: 440px) { + .my-custom-dialog{ + width:320px; + } .my-custom-document-dialog{ width: 300px; } @@ -461,6 +470,17 @@ h1, h2, h3, h4, h5, h6 { } } +@media (max-width: 480px) { + .p-confirm-dialog { + width: 90% !important; + margin: 0 5% !important; + } + .p-confirm-dialog .p-button { + width: 100%; + margin-top: 0.5rem; + } +} + /* styles/book.css */ /* Стили для контейнера всей книги */ diff --git a/hooks/useBreadCrumbs.tsx b/hooks/useBreadCrumbs.tsx index 2b8fc5ad..86d3372c 100644 --- a/hooks/useBreadCrumbs.tsx +++ b/hooks/useBreadCrumbs.tsx @@ -52,10 +52,10 @@ export default function useBreadCrumbs(insideBreadCrumb: breadCrumbType[], curre }, [pathname]); return ( -
+
{breadCrumb?.map((crumb: breadCrumbType, index) => (
-
+
{index < breadCrumb.length - 1 ? ( {crumb.title} @@ -64,7 +64,7 @@ export default function useBreadCrumbs(insideBreadCrumb: breadCrumbType[], curre {crumb.title} )}
- {index < breadCrumb.length - 1 && / } + {index < breadCrumb.length - 1 && / }
))}
From 1576e8ca0aabb130b841b4f119ca888dd1998248 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Wed, 27 Aug 2025 22:44:08 +0600 Subject: [PATCH 121/183] =?UTF-8?q?=D0=BD=D0=B5=D0=BA=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D1=8B=D0=B5=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B4=D0=BB=D1=8F=20=D0=B4=D0=B5=D0=BF=D0=BB?= =?UTF-8?q?=D0=BE=D1=8F,=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B2=20=D0=B4=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=D0=B9=D0=BD=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.gitignore | 8 ++++ .idea/codeStyles/Project.xml | 48 ++++++++++++++++++++ .idea/codeStyles/codeStyleConfig.xml | 5 ++ .idea/deployment.xml | 14 ++++++ .idea/inspectionProfiles/Project_Default.xml | 6 +++ .idea/modules.xml | 8 ++++ .idea/prettier.xml | 6 +++ .idea/sakai-react.iml | 12 +++++ .idea/vcs.xml | 6 +++ .idea/webServers.xml | 14 ++++++ app/(main)/course/page.tsx | 25 +++++++--- app/components/HomeClient.tsx | 2 +- app/components/PDFBook.tsx | 2 +- app/components/SessionManager.tsx | 4 +- app/components/VideoPlay.tsx | 2 +- app/components/lessons/LessonTyping.tsx | 1 + app/components/tables/StreamList.tsx | 4 +- app/globals.css | 25 ++++++++++ layout/AppFooter.tsx | 2 +- layout/AppTopbar.tsx | 31 +++---------- next.config.js | 4 +- package.json | 1 + services/auth.tsx | 6 ++- services/courses.tsx | 2 +- services/query-tests.http | 36 +++++++-------- 25 files changed, 212 insertions(+), 62 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/deployment.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/prettier.xml create mode 100644 .idea/sakai-react.iml create mode 100644 .idea/vcs.xml create mode 100644 .idea/webServers.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..75d97586 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..79ee123c --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/deployment.xml b/.idea/deployment.xml new file mode 100644 index 00000000..3bc60ed5 --- /dev/null +++ b/.idea/deployment.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..03d9549e --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..9d3a0203 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/prettier.xml b/.idea/prettier.xml new file mode 100644 index 00000000..b0c1c68f --- /dev/null +++ b/.idea/prettier.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/sakai-react.iml b/.idea/sakai-react.iml new file mode 100644 index 00000000..24643cc3 --- /dev/null +++ b/.idea/sakai-react.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/webServers.xml b/.idea/webServers.xml new file mode 100644 index 00000000..083d91ec --- /dev/null +++ b/.idea/webServers.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index 71041d71..7996d588 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -420,7 +420,7 @@ export default function Course() { ); }; - const imagestateStyle = imageState ? 'flex gap-1 items-center justify-between' : ''; + const imagestateStyle = imageState ? 'flex gap-1 items-center justify-between flex-col sm:flex-row' : ''; const imageTitle = useShortText(typeof editingLesson.image === 'string' ? editingLesson.image : '', 30); return ( @@ -429,10 +429,14 @@ export default function Course() {
- {imagestateStyle &&
{typeof imageState === 'string' && }
} -
+ {imagestateStyle && ( +
+ {typeof imageState === 'string' && } +
+ )} +
- + {courseValue.image || editingLesson.image ? (
{typeof editingLesson.image === 'string' && ( @@ -504,7 +508,7 @@ export default function Course() { disabled={progressSpinner === true ? true : false} rows={5} cols={30} - className='w-[300px]' + className="w-[300px]" onChange={(e) => { editMode ? setEditingLesson((prev) => ({ @@ -576,12 +580,19 @@ export default function Course() { />
+ handlePageChange(e.page + 1)} + template={media ? 'FirstPageLink PrevPageLink NextPageLink LastPageLink' : 'FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink'} + /> )} @@ -691,7 +702,7 @@ export default function Course() {
{/* STREAMS SECTION */}
- {}} /> + {}} />
)} diff --git a/app/components/HomeClient.tsx b/app/components/HomeClient.tsx index 43ccccc4..0eab5958 100644 --- a/app/components/HomeClient.tsx +++ b/app/components/HomeClient.tsx @@ -18,7 +18,7 @@ export default function HomeClient() { return (
- {"Уроки"} + {/* {"Уроки"} */}
diff --git a/app/components/PDFBook.tsx b/app/components/PDFBook.tsx index abbab375..f1f80ce9 100644 --- a/app/components/PDFBook.tsx +++ b/app/components/PDFBook.tsx @@ -32,7 +32,7 @@ export default function PDFViewer({ url }: { url: string }) { try { // Проверяем, одна ли страница в документе - const newUrl = `http://api.mooc.oshsu.kg/temprory-file/${url}`; + const newUrl = `https://api.mooc.oshsu.kg/temprory-file/${url}`; const pdf = await pdfjsLib.getDocument(newUrl).promise; const tempPages = []; const firstPage = await pdf.getPage(1); diff --git a/app/components/SessionManager.tsx b/app/components/SessionManager.tsx index 8de43d5f..91eb9ead 100644 --- a/app/components/SessionManager.tsx +++ b/app/components/SessionManager.tsx @@ -76,8 +76,8 @@ const SessionManager = () => { if (!token && pathname !== '/' && pathname !== '/auth/login') { console.log('Перенеправляю в login'); - // logout({ setUser, setGlobalLoading }); - // window.location.href = '/auth/login'; + logout({ setUser, setGlobalLoading }); + window.location.href = '/auth/login'; return; } setTimeout(() => { diff --git a/app/components/VideoPlay.tsx b/app/components/VideoPlay.tsx index 0b3f6094..e38163d8 100644 --- a/app/components/VideoPlay.tsx +++ b/app/components/VideoPlay.tsx @@ -43,7 +43,7 @@ export default function VideoPlay() {
- Логотип ОшГУ + Логотип ОшГУ
) diff --git a/app/components/lessons/LessonTyping.tsx b/app/components/lessons/LessonTyping.tsx index 6eb7465c..2a67e46f 100644 --- a/app/components/lessons/LessonTyping.tsx +++ b/app/components/lessons/LessonTyping.tsx @@ -226,6 +226,7 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp
)} - {!hasStreams ? ( + {hasStreams ? ( ) : (
@@ -334,7 +334,7 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis ) : ( <> - + )}
diff --git a/app/globals.css b/app/globals.css index 2e1512d1..2e9dec58 100644 --- a/app/globals.css +++ b/app/globals.css @@ -478,6 +478,17 @@ h1, h2, h3, h4, h5, h6 { .p-confirm-dialog .p-button { width: 100%; margin-top: 0.5rem; + padding: 4px 5px !important; + } +} + +@media (max-width: 480px) { + + .p-button-label { + font-size: 14px !important; + } + .p-button-icon{ + width: 14px !important; } } @@ -536,4 +547,18 @@ h1, h2, h3, h4, h5, h6 { border-radius: 12px; padding: 4px 12px; box-shadow: 0px 3px 5px rgba(0, 0, 0, 0.02), 0px 0px 2px rgba(0, 0, 0, 0.05), 0px 1px 4px rgba(0, 0, 0, 0.08); +} + +.p-paginator .p-paginator-first, .p-paginator .p-paginator-prev, .p-paginator .p-paginator-next, .p-paginator .p-paginator-last{ + min-width: 2rem !important; +} + +/* file upload */ +@media screen and (max-width: 640px) { + .p-button-label .p-clickable{ + font-size: 14px !important; + } + .p-fileupload-choose{ + padding: 4px 5px; + } } \ No newline at end of file diff --git a/layout/AppFooter.tsx b/layout/AppFooter.tsx index a6730783..84a2679b 100644 --- a/layout/AppFooter.tsx +++ b/layout/AppFooter.tsx @@ -18,7 +18,7 @@ const AppFooter = () => {
- Логотип + Логотип
Кыргызстан, 723500, г. Ош, ул. Ленина, 331, ОшГУ Главный корпус Общий отдел: +996 3222 7-07-12, diff --git a/layout/AppTopbar.tsx b/layout/AppTopbar.tsx index 5f8beecf..04bb88ae 100644 --- a/layout/AppTopbar.tsx +++ b/layout/AppTopbar.tsx @@ -8,7 +8,7 @@ import { classNames } from 'primereact/utils'; import { AppTopbarRef } from '@/types'; import { LayoutContext } from './context/layoutcontext'; import { useMediaQuery } from '@/hooks/useMediaQuery'; -import { usePathname } from 'next/navigation'; +import { usePathname, useRouter } from 'next/navigation'; import { logout } from '@/utils/logout'; const AppTopbar = forwardRef((props, ref) => { @@ -28,29 +28,7 @@ const AppTopbar = forwardRef((props, ref) => { const pathName = usePathname(); const media = useMediaQuery('(max-width: 1000px)'); - const items = [ - { - label: 'Ачык онлайн курстар', - icon: 'pi pi-file', - items: [], - url: '/login' - }, - { - label: 'Бакалавриат', - icon: 'pi pi-file', - items: [] - }, - { - label: 'Магистратура', - icon: 'pi pi-file', - items: [] - }, - { - label: 'Кошумча билим берүү', - icon: 'pi pi-file', - items: [] - } - ]; + const router = useRouter() const mobileMenu = [ user @@ -84,7 +62,10 @@ const AppTopbar = forwardRef((props, ref) => { label: 'Кирүү', icon: 'pi pi-sign-in', items: [], - url: '/auth/login' + // url: '/auth/login' + command: ()=> { + router.push('/auth/login'); + } }, { label: 'ОшМУнун сайты', diff --git a/next.config.js b/next.config.js index 767719fc..92539f64 100644 --- a/next.config.js +++ b/next.config.js @@ -1,4 +1,6 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {} +const nextConfig = { + output: 'export', +} module.exports = nextConfig diff --git a/package.json b/package.json index 9d95e74b..4057f700 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "scripts": { "dev": "next dev", "build": "next build", + "export": "next export", "start": "next start", "format": "prettier --write \"{app,demo,layout,types}/**/*.{js,ts,tsx,d.ts}\"", "lint": "next lint" diff --git a/services/auth.tsx b/services/auth.tsx index aec595d3..67fe48f9 100644 --- a/services/auth.tsx +++ b/services/auth.tsx @@ -11,7 +11,8 @@ let url = ''; export const login = async (value:LoginType) => { url = process.env.NEXT_PUBLIC_BASE_URL + '/login?'; - + console.log(url); + try { const res = await axiosInstance.post('/login?', value); const data = res.data; @@ -24,7 +25,8 @@ export const login = async (value:LoginType) => { export const getUser = async () => { url = process.env.NEXT_PUBLIC_BASE_URL + '/v1/user'; - + console.log(url); + try { const res = await axiosInstance.get(url); diff --git a/services/courses.tsx b/services/courses.tsx index c5be7db8..35da33e1 100644 --- a/services/courses.tsx +++ b/services/courses.tsx @@ -6,7 +6,7 @@ let url = ''; export const fetchCourses = async (page: number | null, limit: number | null) => { try { - const res = await axiosInstance.get(`/v1/teacher/courses?page=${Number(page)}&limit=${''}`); + const res = await axiosInstance.get(`/v1/teacher/courses?page=${Number(page)}&limit=${'3'}`); const data = await res.data; return data; diff --git a/services/query-tests.http b/services/query-tests.http index 8aa81c0b..94eeae42 100644 --- a/services/query-tests.http +++ b/services/query-tests.http @@ -1,24 +1,24 @@ -@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5teWVkdS5vc2hzdS5rZy9wdWJsaWMvYXBpL21vb2MvbG9naW4iLCJpYXQiOjE3NTU4NjAxNzUsImV4cCI6MTc1NTg4OTAzNSwibmJmIjoxNzU1ODYwMTc1LCJqdGkiOiJ4ZFhqRXF1UTJCUzhPM05lIiwic3ViIjoiNTAzMjciLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3In0.JsSkS7lVmjhq_9v9sGrcPUn_neb09rKfPTY4UjyXrLg +# @token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5teWVkdS5vc2hzdS5rZy9wdWJsaWMvYXBpL21vb2MvbG9naW4iLCJpYXQiOjE3NTU4NjAxNzUsImV4cCI6MTc1NTg4OTAzNSwibmJmIjoxNzU1ODYwMTc1LCJqdGkiOiJ4ZFhqRXF1UTJCUzhPM05lIiwic3ViIjoiNTAzMjciLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3In0.JsSkS7lVmjhq_9v9sGrcPUn_neb09rKfPTY4UjyXrLg -### +# ### -http://api.mooc.oshsu.kg/public/api/v1/open/video/types -Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5teWVkdS5vc2hzdS5rZy9wdWJsaWMvYXBpL21vb2MvbG9naW4iLCJpYXQiOjE3NTQzODYzMzAsImV4cCI6MTc1NDQxNTE5MCwibmJmIjoxNzU0Mzg2MzMwLCJqdGkiOiJjc0hhSkdqYVhsT1V1VkpPIiwic3ViIjoiNDY0NTYiLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3In0.E9dQ1RCISvf2psrA1iDf9CrYzExEKnZOgYq2XJ4cyXg -Content-Type: application/json +# http://api.mooc.oshsu.kg/public/api/v1/open/video/types +# Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5teWVkdS5vc2hzdS5rZy9wdWJsaWMvYXBpL21vb2MvbG9naW4iLCJpYXQiOjE3NTQzODYzMzAsImV4cCI6MTc1NDQxNTE5MCwibmJmIjoxNzU0Mzg2MzMwLCJqdGkiOiJjc0hhSkdqYVhsT1V1VkpPIiwic3ViIjoiNDY0NTYiLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3In0.E9dQ1RCISvf2psrA1iDf9CrYzExEKnZOgYq2XJ4cyXg +# Content-Type: application/json -### -# STREAMS GET +# ### +# # STREAMS GET -GET http://api.mooc.oshsu.kg/public/api/v1/teacher/stream?course_id=77 -Authorization: Bearer {{token}} -Content-Type: application/json +# GET http://api.mooc.oshsu.kg/public/api/v1/teacher/stream?course_id=77 +# Authorization: Bearer {{token}} +# Content-Type: application/json -### -# test -GET http://api.mooc.oshsu.kg/public/api/v1/student/course/lesson/show -Authorization: Bearer {{token}} -Content-Type: application/json +# ### +# # test +# GET http://api.mooc.oshsu.kg/public/api/v1/student/course/lesson/show +# Authorization: Bearer {{token}} +# Content-Type: application/json -{ - "lesson_id": 57 -} +# { +# "lesson_id": 57 +# } From 83152612e09626fdce63cc2500b927e9919729d1 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Wed, 27 Aug 2025 22:59:57 +0600 Subject: [PATCH 122/183] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=B2=D1=81=D0=B5=20=D1=81=D0=B0=D0=BA=D0=B0=D0=B8=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(full-page)/landing/page.tsx | 557 ------------- app/(main)/blocks/page.tsx | 836 -------------------- app/(main)/documentation/index.module.css | 16 - app/(main)/documentation/page.tsx | 241 ------ app/(main)/pages/crud/page.tsx | 430 ---------- app/(main)/pages/empty/page.tsx | 16 - app/(main)/pages/timeline/page.tsx | 116 --- app/(main)/teacher/page.tsx | 393 --------- app/(main)/uikit/button/index.module.scss | 151 ---- app/(main)/uikit/button/page.tsx | 248 ------ app/(main)/uikit/charts/page.tsx | 283 ------- app/(main)/uikit/file/page.tsx | 35 - app/(main)/uikit/floatlabel/page.tsx | 215 ----- app/(main)/uikit/formlayout/page.tsx | 148 ---- app/(main)/uikit/input/page.tsx | 522 ------------ app/(main)/uikit/invalidstate/page.tsx | 186 ----- app/(main)/uikit/list/page.tsx | 192 ----- app/(main)/uikit/media/page.tsx | 109 --- app/(main)/uikit/menu/confirmation/page.tsx | 16 - app/(main)/uikit/menu/page.tsx | 584 -------------- app/(main)/uikit/menu/payment/page.tsx | 15 - app/(main)/uikit/menu/seat/page.tsx | 16 - app/(main)/uikit/message/page.tsx | 131 --- app/(main)/uikit/misc/page.tsx | 220 ------ app/(main)/uikit/overlay/page.tsx | 222 ------ app/(main)/uikit/panel/page.tsx | 233 ------ app/(main)/uikit/table/page.tsx | 464 ----------- app/(main)/uikit/tree/page.tsx | 42 - app/(main)/utilities/icons/page.tsx | 121 --- 29 files changed, 6758 deletions(-) delete mode 100644 app/(full-page)/landing/page.tsx delete mode 100644 app/(main)/blocks/page.tsx delete mode 100644 app/(main)/documentation/index.module.css delete mode 100644 app/(main)/documentation/page.tsx delete mode 100644 app/(main)/pages/crud/page.tsx delete mode 100644 app/(main)/pages/empty/page.tsx delete mode 100644 app/(main)/pages/timeline/page.tsx delete mode 100644 app/(main)/teacher/page.tsx delete mode 100644 app/(main)/uikit/button/index.module.scss delete mode 100644 app/(main)/uikit/button/page.tsx delete mode 100644 app/(main)/uikit/charts/page.tsx delete mode 100644 app/(main)/uikit/file/page.tsx delete mode 100644 app/(main)/uikit/floatlabel/page.tsx delete mode 100644 app/(main)/uikit/formlayout/page.tsx delete mode 100644 app/(main)/uikit/input/page.tsx delete mode 100644 app/(main)/uikit/invalidstate/page.tsx delete mode 100644 app/(main)/uikit/list/page.tsx delete mode 100644 app/(main)/uikit/media/page.tsx delete mode 100644 app/(main)/uikit/menu/confirmation/page.tsx delete mode 100644 app/(main)/uikit/menu/page.tsx delete mode 100644 app/(main)/uikit/menu/payment/page.tsx delete mode 100644 app/(main)/uikit/menu/seat/page.tsx delete mode 100644 app/(main)/uikit/message/page.tsx delete mode 100644 app/(main)/uikit/misc/page.tsx delete mode 100644 app/(main)/uikit/overlay/page.tsx delete mode 100644 app/(main)/uikit/panel/page.tsx delete mode 100644 app/(main)/uikit/table/page.tsx delete mode 100644 app/(main)/uikit/tree/page.tsx delete mode 100644 app/(main)/utilities/icons/page.tsx diff --git a/app/(full-page)/landing/page.tsx b/app/(full-page)/landing/page.tsx deleted file mode 100644 index 9377d188..00000000 --- a/app/(full-page)/landing/page.tsx +++ /dev/null @@ -1,557 +0,0 @@ -'use client'; -/* eslint-disable @next/next/no-img-element */ -import React, { useContext, useRef, useState } from 'react'; -import Link from 'next/link'; - -import { StyleClass } from 'primereact/styleclass'; -import { Button } from 'primereact/button'; -import { Ripple } from 'primereact/ripple'; -import { Divider } from 'primereact/divider'; -import { LayoutContext } from '../../../layout/context/layoutcontext'; -import { NodeRef } from '@/types'; -import { classNames } from 'primereact/utils'; - -const LandingPage = () => { - const [isHidden, setIsHidden] = useState(false); - const { layoutConfig } = useContext(LayoutContext); - const menuRef = useRef(null); - - const toggleMenuItemClick = () => { - setIsHidden((prevState) => !prevState); - }; - - return ( -
-
-
- - Sakai Logo - SAKAI - - - - - -
- -
-
-

- Eu sem integereget magna fermentum -

-

Sed blandit libero volutpat sed cras. Fames ac turpis egestas integer. Placerat in egestas erat...

- -
-
- Hero Image -
-
- -
-
-
-

Marvelous Features

- Placerat in egestas erat... -
- -
-
-
-
- -
-
Easy to Use
- Posuere morbi leo urna molestie. -
-
-
- -
-
-
-
- -
-
Fresh Design
- Semper risus in hendrerit. -
-
-
- -
-
-
-
- -
-
Well Documented
- Non arcu risus quis varius quam quisque. -
-
-
- -
-
-
-
- -
-
Responsive Layout
- Nulla malesuada pellentesque elit. -
-
-
- -
-
-
-
- -
-
Clean Code
- Condimentum lacinia quis vel eros. -
-
-
- -
-
-
-
- -
-
Dark Mode
- Convallis tellus id interdum velit laoreet. -
-
-
- -
-
-
-
- -
-
Ready to Use
- Mauris sit amet massa vitae. -
-
-
- -
-
-
-
- -
-
Modern Practices
- Elementum nibh tellus molestie nunc non. -
-
-
- -
-
-
-
- -
-
Privacy
- Neque egestas congue quisque. -
-
-
- -
-
-

Joséphine Miller

- Peak Interactive -

- “Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est - laborum.” -

- Company logo -
-
-
-
- -
-
-

Powerful Everywhere

- Amet consectetur adipiscing elit... -
- -
-
- mockup mobile -
- -
-
- -
-

Congue Quisque Egestas

- - Lectus arcu bibendum at varius vel pharetra vel turpis nunc. Eget aliquet nibh praesent tristique magna sit amet purus gravida. Sit amet mattis vulputate enim nulla aliquet. - -
-
- -
-
-
- -
-

Celerisque Eu Ultrices

- - Adipiscing commodo elit at imperdiet dui. Viverra nibh cras pulvinar mattis nunc sed blandit libero. Suspendisse in est ante in. Mauris pharetra et ultrices neque ornare aenean euismod elementum nisi. - -
- -
- mockup -
-
-
- -
-
-

Matchless Pricing

- Amet consectetur adipiscing elit... -
- -
-
-
-

Free

- free -
- $0 - per month - -
- -
    -
  • - - Responsive Layout -
  • -
  • - - Unlimited Push Messages -
  • -
  • - - 50 Support Ticket -
  • -
  • - - Free Shipping -
  • -
-
-
- -
-
-

Startup

- startup -
- $1 - per month - -
- -
    -
  • - - Responsive Layout -
  • -
  • - - Unlimited Push Messages -
  • -
  • - - 50 Support Ticket -
  • -
  • - - Free Shipping -
  • -
-
-
- -
-
-

Enterprise

- enterprise -
- $999 - per month - -
- -
    -
  • - - Responsive Layout -
  • -
  • - - Unlimited Push Messages -
  • -
  • - - 50 Support Ticket -
  • -
  • - - Free Shipping -
  • -
-
-
-
-
- -
-
-
- ); -}; - -export default LandingPage; diff --git a/app/(main)/blocks/page.tsx b/app/(main)/blocks/page.tsx deleted file mode 100644 index ebd5a25c..00000000 --- a/app/(main)/blocks/page.tsx +++ /dev/null @@ -1,836 +0,0 @@ -'use client'; -import React, { useState } from 'react'; - -import { InputText } from 'primereact/inputtext'; -import { Chip } from 'primereact/chip'; -import { Checkbox } from 'primereact/checkbox'; -import { Button } from 'primereact/button'; -import BlockViewer from '../../../demo/components/BlockViewer'; - -const Free = () => { - const [checked, setChecked] = useState(false); - - const block1 = ` -
-
-
- Create the screens -
your visitors deserve to see
-

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

- -
-
-
- hero-1 -
-
- `; - - const block2 = ` -
-
- One Product, - Many Solutions -
-
Ac turpis egestas maecenas pharetra convallis posuere morbi leo urna.
-
-
- - - -
Built for Developers
- Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. -
-
- - - -
End-to-End Encryption
- Risus nec feugiat in fermentum posuere urna nec. Posuere sollicitudin aliquam ultrices sagittis. -
-
- - - -
Easy to Use
- Ornare suspendisse sed nisi lacus sed viverra tellus. Neque volutpat ac tincidunt vitae semper. -
-
- - - -
Fast & Global Support
- Fermentum et sollicitudin ac orci phasellus egestas tellus rutrum tellus. -
-
- - - -
Open Source
- Nec tincidunt praesent semper feugiat. Sed adipiscing diam donec adipiscing tristique risus nec feugiat. -
-
- - - -
Trusted Securitty
- Mattis rhoncus urna neque viverra justo nec ultrices. Id cursus metus aliquam eleifend. -
-
-
- `; - - const block3 = ` -
-
Pricing Plans
-
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Velit numquam eligendi quos.
- -
-
-
-
-
Basic
-
Plan description
-
-
- $9 - per month -
-
-
    -
  • - - Arcu vitae elementum -
  • -
  • - - Dui faucibus in ornare -
  • -
  • - - Morbi tincidunt augue -
  • -
-
-
-
-
- -
-
-
-
Premium
-
Plan description
-
-
- $29 - per month -
-
-
    -
  • - - Arcu vitae elementum -
  • -
  • - - Dui faucibus in ornare -
  • -
  • - - Morbi tincidunt augue -
  • -
  • - - Duis ultricies lacus sed -
  • -
-
-
-
-
- -
-
-
-
Enterprise
-
Plan description
-
-
- $49 - per month -
-
-
    -
  • - - Arcu vitae elementum -
  • -
  • - - Dui faucibus in ornare -
  • -
  • - - Morbi tincidunt augue -
  • -
  • - - Duis ultricies lacus sed -
  • -
  • - - Imperdiet proin -
  • -
  • - - Nisi scelerisque -
  • -
-
-
-
-
-
-
- `; - - const block4 = ` -
-
 POWERED BY DISCORD
-
Join Our Design Community
-
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Velit numquam eligendi quos.
-
- `; - - const block5 = ` -
-
🔥 Hot Deals!
-
- Libero voluptatum atque exercitationem praesentium provident odit. -
- - Learn More - - - - -
- `; - - const block6 = ` -
- -
-
-
Customers
-
-
- - 332 Active Users -
-
- - 9402 Sessions -
-
- - 2.32m Avg. Duration -
-
-
-
-
-
-
- `; - - const block7 = ` -
-
-
-
-
- Orders -
152
-
-
- -
-
- 24 new - since last visit -
-
-
-
-
-
- Revenue -
$2.100
-
-
- -
-
- %52+ - since last week -
-
-
-
-
-
- Customers -
28441
-
-
- -
-
- 520 - newly registered -
-
-
-
-
-
- Comments -
152 Unread
-
-
- -
-
- 85 - responded -
-
-
- `; - - const block8 = ` -
-
-
- hyper -
Welcome Back
- Don't have an account? - Create today! -
- -
- - - - - - -
-
- setChecked(e.checked)} checked={checked} className="mr-2" /> - -
- Forgot your password? -
- -
-
-
- `; - - const block9 = ` -
-
Movie Information
-
Morbi tristique blandit turpis. In viverra ligula id nulla hendrerit rutrum.
-
    -
  • -
    Title
    -
    Heat
    -
    -
    -
  • -
  • -
    Genre
    -
    - - - -
    -
    -
    -
  • -
  • -
    Director
    -
    Michael Mann
    -
    -
    -
  • -
  • -
    Actors
    -
    Robert De Niro, Al Pacino
    -
    -
    -
  • -
  • -
    Plot
    -
    - A group of professional bank robbers start to feel the heat from police - when they unknowingly leave a clue at their latest heist.
    -
    -
    -
  • -
-
- `; - - const block10 = ` -
-
Card Title
-
Vivamus id nisl interdum, blandit augue sit amet, eleifend mi.
-
-
- `; - - return ( - <> - -
-
-
- Create the screens -
your visitors deserve to see
-

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

- -
-
-
- hero-1 -
-
-
- - -
-
- One Product, - Many Solutions -
-
Ac turpis egestas maecenas pharetra convallis posuere morbi leo urna.
-
-
- - - -
Built for Developers
- Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. -
-
- - - -
End-to-End Encryption
- Risus nec feugiat in fermentum posuere urna nec. Posuere sollicitudin aliquam ultrices sagittis. -
-
- - - -
Easy to Use
- Ornare suspendisse sed nisi lacus sed viverra tellus. Neque volutpat ac tincidunt vitae semper. -
-
- - - -
Fast & Global Support
- Fermentum et sollicitudin ac orci phasellus egestas tellus rutrum tellus. -
-
- - - -
Open Source
- Nec tincidunt praesent semper feugiat. Sed adipiscing diam donec adipiscing tristique risus nec feugiat. -
-
- - - -
Trusted Securitty
- Mattis rhoncus urna neque viverra justo nec ultrices. Id cursus metus aliquam eleifend. -
-
-
-
- - -
-
Pricing Plans
-
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Velit numquam eligendi quos.
- -
-
-
-
-
Basic
-
Plan description
-
-
- $9 - per month -
-
-
    -
  • - - Arcu vitae elementum -
  • -
  • - - Dui faucibus in ornare -
  • -
  • - - Morbi tincidunt augue -
  • -
-
-
-
-
- -
-
-
-
Premium
-
Plan description
-
-
- $29 - per month -
-
-
    -
  • - - Arcu vitae elementum -
  • -
  • - - Dui faucibus in ornare -
  • -
  • - - Morbi tincidunt augue -
  • -
  • - - Duis ultricies lacus sed -
  • -
-
-
-
-
- -
-
-
-
Enterprise
-
Plan description
-
-
- $49 - per month -
-
-
    -
  • - - Arcu vitae elementum -
  • -
  • - - Dui faucibus in ornare -
  • -
  • - - Morbi tincidunt augue -
  • -
  • - - Duis ultricies lacus sed -
  • -
  • - - Imperdiet proin -
  • -
  • - - Nisi scelerisque -
  • -
-
-
-
-
-
-
-
- - -
-
-  POWERED BY DISCORD -
-
Join Our Design Community
-
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Velit numquam eligendi quos.
-
-
- - -
-
🔥 Hot Deals!
-
- Libero voluptatum atque exercitationem praesentium provident odit. -
- - Learn More - - - - -
-
- - -
- -
-
-
Customers
-
-
- - 332 Active Users -
-
- - 9402 Sessions -
-
- - 2.32m Avg. Duration -
-
-
-
-
-
-
-
- - -
-
-
-
-
- Orders -
152
-
-
- -
-
- 24 new - since last visit -
-
-
-
-
-
- Revenue -
$2.100
-
-
- -
-
- %52+ - since last week -
-
-
-
-
-
- Customers -
28441
-
-
- -
-
- 520 - newly registered -
-
-
-
-
-
- Comments -
152 Unread
-
-
- -
-
- 85 - responded -
-
-
-
- - -
-
-
- hyper -
Welcome Back
- Do not have an account? - Create today! -
- -
- - - - - - -
-
- setChecked(e.checked as boolean)} checked={checked} className="mr-2" /> - -
- Forgot your password? -
- -
-
-
-
- - -
-
Movie Information
-
Morbi tristique blandit turpis. In viverra ligula id nulla hendrerit rutrum.
-
    -
  • -
    Title
    -
    Heat
    -
    -
    -
  • -
  • -
    Genre
    -
    - - - -
    -
    -
    -
  • -
  • -
    Director
    -
    Michael Mann
    -
    -
    -
  • -
  • -
    Actors
    -
    Robert De Niro, Al Pacino
    -
    -
    -
  • -
  • -
    Plot
    -
    A group of professional bank robbers start to feel the heat from police when they unknowingly leave a clue at their latest heist.
    -
    -
    -
  • -
-
-
- - -
-
Card Title
-
Vivamus id nisl interdum, blandit augue sit amet, eleifend mi.
-
-
-
- - ); -}; - -export default Free; diff --git a/app/(main)/documentation/index.module.css b/app/(main)/documentation/index.module.css deleted file mode 100644 index acaa1030..00000000 --- a/app/(main)/documentation/index.module.css +++ /dev/null @@ -1,16 +0,0 @@ -@media screen and (max-width: 991px) { - .video-container { - position: relative; - width: 100%; - height: 0; - padding-bottom: 56.25%; - } - - .video { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - } -} \ No newline at end of file diff --git a/app/(main)/documentation/page.tsx b/app/(main)/documentation/page.tsx deleted file mode 100644 index b958c674..00000000 --- a/app/(main)/documentation/page.tsx +++ /dev/null @@ -1,241 +0,0 @@ -/* eslint-disable @next/next/no-sync-scripts */ -import React from 'react'; - -const Documentation = () => { - return ( - <> -
-
-
-

Current Version

-

Next v13, React v18, Typescript with PrimeReact v10

- -
Getting Started
-

- Sakai is an application template for React based on the popular{' '} - - Next.js - {' '} - framework with new{' '} - - App Router - - . To get started, clone the{' '} - - repository - {' '} - from GitHub and install the dependencies with npm or yarn. -

-
-                            {`"npm install" or "yarn"`}
-                        
- -

- Next step is running the application using the start script and navigate to http://localhost:3000/ to view the application. That is it, you may now start with the development of your application using the Sakai - template. -

- -
-                            {`"npm run dev" or "yarn dev"`}
-                        
- -
Dependencies
-

Dependencies of Sakai are listed below and needs to be defined at package.json.

- -
-                            {`"primereact": "^9.6.2",                    //required: PrimeReact components
-"primeicons": "^6.0.1",                    //required: Icons
-"primeflex": "^3.3.0",                     //required: Utility CSS classes
-`}
-                        
- -
Structure
-

Sakai consist of a couple of folders where demos and core layout have been separated.

-

- There are two{' '} - - route groups - {' '} - under the app folder; {`(main)`} represents the pages that reside in the main dashboard layout whereas {`(full-page)`}{' '} - groups the pages with full page content such as landing page or a login page. -

-
    -
  • - layout/: Main layout files -
  • -
  • - demo/: Contains demo related utilities and helpers -
  • -
  • - app/: Demo pages -
  • -
  • - public/demo: Assets used in demos -
  • -
  • - public/layout: Assets used in layout such as a logo -
  • -
  • - styles/demo: Styles used in demos only -
  • -
  • - styles/layout: SCSS files of the core layout -
  • -
-
Route Groups
-

- Root Layout is the main of the application and it is defined at app/layout.tsx file. It contains the style imports and layout context provider. -

-
-                            
-                                {`"use client"
-import { LayoutProvider } from "./layout/context/layoutcontext";
-import { PrimeReactProvider } from "primereact/api";
-import "primereact/resources/primereact.css";
-...
-import "../styles/layout/layout.scss";
-import "../styles/demo/Demos.scss";
-
-interface RootLayoutProps {
-  children: React.ReactNode;
-}
-
-export default function RootLayout({ children }: RootLayoutProps) {
-  return (
-    
-      
-        
-      
-      
-        
-            {children}
-        
-      
-    
-  );
-}
-
-`}
-                            
-                        
-

- The pages that are using the layout elements need to be defined under the app/{'(main)'}/ folder. Those pages use the{' '} - app/{'(main)'}/layout.tsx as the root layout. -

-
-                            
-                                {`import { Metadata } from 'next';
-import Layout from "../../layout/layout";
-
-interface MainLayoutProps {
-  children: React.ReactNode;
-}
-
-export const metadata: Metadata = {
-    title: "Sakai by PrimeReact | Free Admin Template for Next.js",
-    ...
-  };
-
-export default function MainLayout({ children }: MainLayoutProps) {
-  return {children};
-}
-`}
-                            
-                        
-

- Only the pages that are using config sidebar wihout layout elements need to be defined under the app/{'(full-page)'}/ folder. Those pages use the{' '} - app/{'(full-page)'}/layout.tsx as the root layout. -

-
-                            
-                                {`import { Metadata } from 'next';
-import AppConfig from "../../layout/AppConfig";
-import React from "react";
-
-interface FullPageLayoutProps {
-  children: React.ReactNode;
-}
-
-export const metadata: Metadata = {
-    title: "Sakai by PrimeReact | Free Admin Template for Next.js",
-    ...
-  };
-
-export default function FullPageLayout({ children }: FullPageLayoutProps) {
-  return (
-    
-      {children}
-      
-    
-  );
-}
-`}
-                            
-                        
-
Default Configuration
-

- Initial layout configuration can be defined at the layout/context/layoutcontext.js file, this step is optional and only necessary when customizing the defaults. -

- -
-                            
-                                {`"use client";
-import React, { useState } from 'react';
-import Head from 'next/head';
-export const LayoutContext = React.createContext();
-
-export const LayoutProvider = (props) => {
-    const [layoutConfig, setLayoutConfig] = useState({
-        ripple: false,                          //toggles ripple on and off
-        inputStyle: 'outlined',                 //default style for input elements
-        menuMode: 'static',                     //layout mode of the menu, valid values are "static" or "overlay"
-        colorScheme: 'light',                   //color scheme of the template, valid values are "light", "dim" and "dark"
-        theme: 'lara-light-indigo',             //default component theme for PrimeReact
-        scale: 14                               //size of the body font size to scale the whole application
-    });
-}`}
-                            
-                        
- -
Menu
-

- Main menu is defined at AppMenu.js file based on{' '} - - MenuModel API - - . -

- -
PrimeReact Theme
-

- Sakai theming is based on the PrimeReact theme being used. -

- -
SASS Variables
-

- In case you'd like to customize the main layout variables, open _variables.scss file under layout folder. Saving the changes will be reflected instantly at your browser. -

- -
layout/_variables.scss
-
-                            
-                                {`
-/* General */
-$scale:14px;                    /* initial font size */ 
-$borderRadius:12px;             /* border radius of layout element e.g. card, sidebar */ 
-$transitionDuration:.2s;        /* transition duration of layout elements e.g. sidebar */ 
-`}
-                            
-                        
-
-
-
- - ); -}; - -export default Documentation; diff --git a/app/(main)/pages/crud/page.tsx b/app/(main)/pages/crud/page.tsx deleted file mode 100644 index 52cf6e5e..00000000 --- a/app/(main)/pages/crud/page.tsx +++ /dev/null @@ -1,430 +0,0 @@ -/* eslint-disable @next/next/no-img-element */ -'use client'; -import { Button } from 'primereact/button'; -import { Column } from 'primereact/column'; -import { DataTable } from 'primereact/datatable'; -import { Dialog } from 'primereact/dialog'; -import { FileUpload } from 'primereact/fileupload'; -import { InputNumber, InputNumberValueChangeEvent } from 'primereact/inputnumber'; -import { InputText } from 'primereact/inputtext'; -import { InputTextarea } from 'primereact/inputtextarea'; -import { RadioButton, RadioButtonChangeEvent } from 'primereact/radiobutton'; -import { Rating } from 'primereact/rating'; -import { Toast } from 'primereact/toast'; -import { Toolbar } from 'primereact/toolbar'; -import { classNames } from 'primereact/utils'; -import React, { useEffect, useRef, useState } from 'react'; -import { ProductService } from '../../../../demo/service/ProductService'; -import { Demo } from '@/types'; - -/* @todo Used 'as any' for types here. Will fix in next version due to onSelectionChange event type issue. */ -const Crud = () => { - let emptyProduct: Demo.Product = { - id: '', - name: '', - image: '', - description: '', - category: '', - price: 0, - quantity: 0, - rating: 0, - inventoryStatus: 'INSTOCK' - }; - - const [products, setProducts] = useState(null); - const [productDialog, setProductDialog] = useState(false); - const [deleteProductDialog, setDeleteProductDialog] = useState(false); - const [deleteProductsDialog, setDeleteProductsDialog] = useState(false); - const [product, setProduct] = useState(emptyProduct); - const [selectedProducts, setSelectedProducts] = useState(null); - const [submitted, setSubmitted] = useState(false); - const [globalFilter, setGlobalFilter] = useState(''); - const toast = useRef(null); - const dt = useRef>(null); - - useEffect(() => { - ProductService.getProducts().then((data) => setProducts(data as any)); - }, []); - - const formatCurrency = (value: number) => { - return value.toLocaleString('en-US', { - style: 'currency', - currency: 'USD' - }); - }; - - const openNew = () => { - setProduct(emptyProduct); - setSubmitted(false); - setProductDialog(true); - }; - - const hideDialog = () => { - setSubmitted(false); - setProductDialog(false); - }; - - const hideDeleteProductDialog = () => { - setDeleteProductDialog(false); - }; - - const hideDeleteProductsDialog = () => { - setDeleteProductsDialog(false); - }; - - const saveProduct = () => { - setSubmitted(true); - - if (product.name.trim()) { - let _products = [...(products as any)]; - let _product = { ...product }; - if (product.id) { - const index = findIndexById(product.id); - - _products[index] = _product; - toast.current?.show({ - severity: 'success', - summary: 'Successful', - detail: 'Product Updated', - life: 3000 - }); - } else { - _product.id = createId(); - _product.image = 'product-placeholder.svg'; - _products.push(_product); - toast.current?.show({ - severity: 'success', - summary: 'Successful', - detail: 'Product Created', - life: 3000 - }); - } - - setProducts(_products as any); - setProductDialog(false); - setProduct(emptyProduct); - } - }; - - const editProduct = (product: Demo.Product) => { - setProduct({ ...product }); - setProductDialog(true); - }; - - const confirmDeleteProduct = (product: Demo.Product) => { - setProduct(product); - setDeleteProductDialog(true); - }; - - const deleteProduct = () => { - let _products = (products as any)?.filter((val: any) => val.id !== product.id); - setProducts(_products); - setDeleteProductDialog(false); - setProduct(emptyProduct); - toast.current?.show({ - severity: 'success', - summary: 'Successful', - detail: 'Product Deleted', - life: 3000 - }); - }; - - const findIndexById = (id: string) => { - let index = -1; - for (let i = 0; i < (products as any)?.length; i++) { - if ((products as any)[i].id === id) { - index = i; - break; - } - } - - return index; - }; - - const createId = () => { - let id = ''; - let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 5; i++) { - id += chars.charAt(Math.floor(Math.random() * chars.length)); - } - return id; - }; - - const exportCSV = () => { - dt.current?.exportCSV(); - }; - - const confirmDeleteSelected = () => { - setDeleteProductsDialog(true); - }; - - const deleteSelectedProducts = () => { - let _products = (products as any)?.filter((val: any) => !(selectedProducts as any)?.includes(val)); - setProducts(_products); - setDeleteProductsDialog(false); - setSelectedProducts(null); - toast.current?.show({ - severity: 'success', - summary: 'Successful', - detail: 'Products Deleted', - life: 3000 - }); - }; - - const onCategoryChange = (e: RadioButtonChangeEvent) => { - let _product = { ...product }; - _product['category'] = e.value; - setProduct(_product); - }; - - const onInputChange = (e: React.ChangeEvent, name: string) => { - const val = (e.target && e.target.value) || ''; - let _product = { ...product }; - _product[`${name}`] = val; - - setProduct(_product); - }; - - const onInputNumberChange = (e: InputNumberValueChangeEvent, name: string) => { - const val = e.value || 0; - let _product = { ...product }; - _product[`${name}`] = val; - - setProduct(_product); - }; - - const leftToolbarTemplate = () => { - return ( - -
-
-
- ); - }; - - const rightToolbarTemplate = () => { - return ( - - - - - ); - }; - - const customizedMarker = (item: CustomEvent) => { - return ( - - - - ); - }; - - return ( -
-
-
-
-
Left Align
- item.status} /> -
-
-
-
-
Right Align
- item.status} /> -
-
-
-
-
Alternate Align
- item.status} /> -
-
- -
-
-
Opposite Content
- item.status} content={(item) => {item.date}} /> -
-
- -
-
-
Customized
- -
-
-
-
-
Horizontal
-
Top Align
- item} /> - -
Bottom Align
- item} /> - -
Alternate Align
- item} opposite={ } /> -
-
-
-
- ); -}; - -export default TimelineDemo; diff --git a/app/(main)/teacher/page.tsx b/app/(main)/teacher/page.tsx deleted file mode 100644 index a06710d5..00000000 --- a/app/(main)/teacher/page.tsx +++ /dev/null @@ -1,393 +0,0 @@ -/* eslint-disable @next/next/no-img-element */ -'use client'; -import { Button } from 'primereact/button'; -import { Chart } from 'primereact/chart'; -import { Column } from 'primereact/column'; -import { DataTable } from 'primereact/datatable'; -import { Menu } from 'primereact/menu'; -import React, { useContext, useEffect, useRef, useState } from 'react'; -import { ProductService } from '../../../demo/service/ProductService'; -import { LayoutContext } from '../../../layout/context/layoutcontext'; -import Link from 'next/link'; -import { Demo } from '@/types'; -import { ChartData, ChartOptions } from 'chart.js'; - -const lineData: ChartData = { - labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], - datasets: [ - { - label: 'First Dataset', - data: [65, 59, 80, 81, 56, 55, 40], - fill: false, - backgroundColor: '#2f4860', - borderColor: '#2f4860', - tension: 0.4 - }, - { - label: 'Second Dataset', - data: [28, 48, 40, 19, 86, 27, 90], - fill: false, - backgroundColor: '#00bb7e', - borderColor: '#00bb7e', - tension: 0.4 - } - ] -}; - -const Dashboard = () => { - const [products, setProducts] = useState([]); - const menu1 = useRef(null); - const menu2 = useRef(null); - const [lineOptions, setLineOptions] = useState({}); - const { layoutConfig } = useContext(LayoutContext); - - const applyLightTheme = () => { - const lineOptions: ChartOptions = { - plugins: { - legend: { - labels: { - color: '#495057' - } - } - }, - scales: { - x: { - ticks: { - color: '#495057' - }, - grid: { - color: '#ebedef' - } - }, - y: { - ticks: { - color: '#495057' - }, - grid: { - color: '#ebedef' - } - } - } - }; - - setLineOptions(lineOptions); - }; - - const applyDarkTheme = () => { - const lineOptions = { - plugins: { - legend: { - labels: { - color: '#ebedef' - } - } - }, - scales: { - x: { - ticks: { - color: '#ebedef' - }, - grid: { - color: 'rgba(160, 167, 181, .3)' - } - }, - y: { - ticks: { - color: '#ebedef' - }, - grid: { - color: 'rgba(160, 167, 181, .3)' - } - } - } - }; - - setLineOptions(lineOptions); - }; - - useEffect(() => { - ProductService.getProductsSmall().then((data) => setProducts(data)); - }, []); - - useEffect(() => { - if (layoutConfig.colorScheme === 'light') { - applyLightTheme(); - } else { - applyDarkTheme(); - } - }, [layoutConfig.colorScheme]); - - const formatCurrency = (value: number) => { - return value?.toLocaleString('en-US', { - style: 'currency', - currency: 'USD' - }); - }; - - return ( -
-
-
-
-
- Orders -
152
-
-
- -
-
- 24 new - since last visit -
-
-
-
-
-
- Revenue -
$2.100
-
-
- -
-
- %52+ - since last week -
-
-
-
-
-
- Customers -
28441
-
-
- -
-
- 520 - newly registered -
-
-
-
-
-
- Comments -
152 Unread
-
-
- -
-
- 85 - responded -
-
- -
-
-
Recent Sales
- - {data.image}} /> - - formatCurrency(data.price)} /> - ( - <> -
-
-
-
Best Selling Products
-
-
-
-
    -
  • -
    - Space T-Shirt -
    Clothing
    -
    -
    -
    -
    -
    - %50 -
    -
  • -
  • -
    - Portal Sticker -
    Accessories
    -
    -
    -
    -
    -
    - %16 -
    -
  • -
  • -
    - Supernova Sticker -
    Accessories
    -
    -
    -
    -
    -
    - %67 -
    -
  • -
  • -
    - Wonders Notebook -
    Office
    -
    -
    -
    -
    -
    - %35 -
    -
  • -
  • -
    - Mat Black Case -
    Accessories
    -
    -
    -
    -
    -
    - %75 -
    -
  • -
  • -
    - Robots T-Shirt -
    Clothing
    -
    -
    -
    -
    -
    - %40 -
    -
  • -
-
-
- -
-
-
Sales Overview
- -
- -
-
-
Notifications
-
-
-
- - TODAY -
    -
  • -
    - -
    - - Richard Jones - - {' '} - has purchased a blue t-shirt for 79$ - - -
  • -
  • -
    - -
    - - Your request for withdrawal of 2500$ has been initiated. - -
  • -
- - YESTERDAY -
    -
  • -
    - -
    - - Keyser Wick - - {' '} - has purchased a black jacket for 59$ - - -
  • -
  • -
    - -
    - - Jane Davis - has posted a new questions about your product. - -
  • -
-
-
-
-
TAKE THE NEXT STEP
-
Try PrimeBlocks
-
-
- - Get Started - -
-
-
-
- ); -}; - -export default Dashboard; diff --git a/app/(main)/uikit/button/index.module.scss b/app/(main)/uikit/button/index.module.scss deleted file mode 100644 index 2ee14829..00000000 --- a/app/(main)/uikit/button/index.module.scss +++ /dev/null @@ -1,151 +0,0 @@ -.p-button{ - padding: 0%; -&.google { - background: linear-gradient(to left, var(--purple-600) 50%, var(--purple-700) 50%); - background-size: 200% 100%; - background-position: right bottom; - transition: background-position 0.5s ease-out; - border-color: var(--purple-700); - display: flex; - align-items: stretch; - padding: 0; - - - &:enabled:hover { - background: linear-gradient(to left, var(--purple-600) 50%, var(--purple-700) 50%); - background-size: 200% 100%; - background-position: left bottom; - border-color: var(--purple-700); - } - - &:focus { - box-shadow: 0 0 0 1px var(--purple-400); - } -} - -&.twitter { - background: linear-gradient(to left, var(--blue-400) 50%, var(--blue-500) 50%); - background-size: 200% 100%; - background-position: right bottom; - transition: background-position 0.5s ease-out; - border-color: var(--blue-500); - padding: 0; - display: flex; - align-items: stretch; - - &:enabled:hover { - background: linear-gradient(to left, var(--blue-400) 50%, var(--blue-500) 50%); - background-size: 200% 100%; - background-position: left bottom; - border-color: var(--blue-500); - } - - &:focus { - box-shadow: 0 0 0 1px var(--blue-200); - } -} - -&.discord { - background: linear-gradient(to left, var(--bluegray-700) 50%, var(--bluegray-800) 50%); - background-size: 200% 100%; - background-position: right bottom; - transition: background-position 0.5s ease-out; - border-color: var(--bluegray-800); - padding: 0; - display: flex; - align-items: stretch; - - &:enabled:hover { - background: linear-gradient(to left, var(--bluegray-700) 50%, var(--bluegray-800) 50%); - background-size: 200% 100%; - background-position: left bottom; - border-color: var(--bluegray-800); - } - - &:focus { - box-shadow: 0 0 0 1px var(--purple-500); - } -} - -.template-button .p-button.twitter { - background: linear-gradient(to left, var(--blue-400) 50%, var(--blue-500) 50%); - background-size: 200% 100%; - background-position: right bottom; - transition: background-position 0.5s ease-out; - color: #fff; - border-color: var(--blue-500); -} -.template-button .p-button.twitter:hover { - background-position: left bottom; -} -.template-button .p-button.twitter i { - background-color: var(--blue-500); -} -.template-button .p-button.twitter:focus { - box-shadow: 0 0 0 1px var(--blue-200); -} -.template-button .p-button.slack { - background: linear-gradient(to left, var(--orange-400) 50%, var(--orange-500) 50%); - background-size: 200% 100%; - background-position: right bottom; - transition: background-position 0.5s ease-out; - color: #fff; - border-color: var(--orange-500); -} -.template-button .p-button.slack:hover { - background-position: left bottom; -} -.template-button .p-button.slack i { - background-color: var(--orange-500); -} -.template-button .p-button.slack:focus { - box-shadow: 0 0 0 1px var(--orange-200); -} -.template-button .p-button.amazon { - background: linear-gradient(to left, var(--yellow-400) 50%, var(--yellow-500) 50%); - background-size: 200% 100%; - background-position: right bottom; - transition: background-position 0.5s ease-out; - color: #000; - border-color: var(--yellow-500); -} -.template-button .p-button.amazon:hover { - background-position: left bottom; -} -.template-button .p-button.amazon i { - background-color: var(--yellow-500); -} -.template-button .p-button.amazon:focus { - box-shadow: 0 0 0 1px var(--yellow-200); -} -.template-button .p-button.discord { - background: linear-gradient(to left, var(--bluegray-700) 50%, var(--bluegray-800) 50%); - background-size: 200% 100%; - background-position: right bottom; - transition: background-position 0.5s ease-out; - color: #fff; - border-color: var(--bluegray-800); -} -.template-button .p-button.discord:hover { - background-position: left bottom; -} -.template-button .p-button.discord i { - background-color: var(--bluegray-800); -} -.template-button .p-button.discord:focus { - box-shadow: 0 0 0 1px var(--bluegray-500); -} -@media screen and (max-width: 960px) { - -button .p-button { - margin-bottom: 0.5rem; - } - -button .p-button:not(.p-button-icon-only) { - display: flex; - flex-wrap: wrap; - - } - -button .p-buttonset .p-button { - margin-bottom: 0; - } -} -} \ No newline at end of file diff --git a/app/(main)/uikit/button/page.tsx b/app/(main)/uikit/button/page.tsx deleted file mode 100644 index 69196ed2..00000000 --- a/app/(main)/uikit/button/page.tsx +++ /dev/null @@ -1,248 +0,0 @@ -'use client'; -import React, { useState } from 'react'; -import { SplitButton } from 'primereact/splitbutton'; -import { Button } from 'primereact/button'; -import styles from './index.module.scss'; -import { classNames } from 'primereact/utils'; - -const ButtonDemo = () => { - const [loading1, setLoading1] = useState(false); - const [loading2, setLoading2] = useState(false); - const [loading3, setLoading3] = useState(false); - const [loading4, setLoading4] = useState(false); - - const onLoadingClick1 = () => { - setLoading1(true); - - setTimeout(() => { - setLoading1(false); - }, 2000); - }; - - const onLoadingClick2 = () => { - setLoading2(true); - - setTimeout(() => { - setLoading2(false); - }, 2000); - }; - - const onLoadingClick3 = () => { - setLoading3(true); - - setTimeout(() => { - setLoading3(false); - }, 2000); - }; - - const onLoadingClick4 = () => { - setLoading4(true); - - setTimeout(() => { - setLoading4(false); - }, 2000); - }; - - const items = [ - { - label: 'Update', - icon: 'pi pi-refresh' - }, - { - label: 'Delete', - icon: 'pi pi-times' - }, - { - label: 'Home', - icon: 'pi pi-home' - } - ]; - - return ( -
-
-
-
Default
-
- - - -
-
- -
-
Severities
-
-
-
- -
-
Text
-
-
-
- -
-
Outlined
-
-
-
- -
-
Button Group
- -
- -
-
SplitButton
-
- - - - - -
-
- -
-
Template
-
- - - -
-
-
- -
-
-
Icons
-
- - - -
-
- -
-
Raised
-
-
-
- -
-
Rounded
-
-
-
- -
-
Rounded Icons
-
-
-
- -
-
Rounded Text
-
-
-
- -
-
Rounded Outlined
-
-
-
- -
-
Loading
-
-
-
-
-
- ); -}; - -export default ButtonDemo; diff --git a/app/(main)/uikit/charts/page.tsx b/app/(main)/uikit/charts/page.tsx deleted file mode 100644 index 7ccc85af..00000000 --- a/app/(main)/uikit/charts/page.tsx +++ /dev/null @@ -1,283 +0,0 @@ -'use client'; -import { ChartData, ChartOptions } from 'chart.js'; -import { Chart } from 'primereact/chart'; -import React, { useContext, useEffect, useState } from 'react'; -import { LayoutContext } from '../../../../layout/context/layoutcontext'; -import type { ChartDataState, ChartOptionsState } from '@/types'; - -const ChartDemo = () => { - const [options, setOptions] = useState({}); - const [data, setChartData] = useState({}); - const { layoutConfig } = useContext(LayoutContext); - - useEffect(() => { - const documentStyle = getComputedStyle(document.documentElement); - const textColor = documentStyle.getPropertyValue('--text-color') || '#495057'; - const textColorSecondary = documentStyle.getPropertyValue('--text-color-secondary') || '#6c757d'; - const surfaceBorder = documentStyle.getPropertyValue('--surface-border') || '#dfe7ef'; - const barData: ChartData = { - labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], - datasets: [ - { - label: 'My First dataset', - backgroundColor: documentStyle.getPropertyValue('--primary-500') || '#6366f1', - borderColor: documentStyle.getPropertyValue('--primary-500') || '#6366f1', - data: [65, 59, 80, 81, 56, 55, 40] - }, - { - label: 'My Second dataset', - backgroundColor: documentStyle.getPropertyValue('--primary-200') || '#bcbdf9', - borderColor: documentStyle.getPropertyValue('--primary-200') || '#bcbdf9', - data: [28, 48, 40, 19, 86, 27, 90] - } - ] - }; - - const barOptions: ChartOptions = { - plugins: { - legend: { - labels: { - color: textColor - } - } - }, - scales: { - x: { - ticks: { - color: textColorSecondary, - font: { - weight: '500' - } - }, - grid: { - display: false - }, - border: { - display: false - } - }, - y: { - ticks: { - color: textColorSecondary - }, - grid: { - color: surfaceBorder - }, - border: { - display: false - } - } - } - }; - - const pieData: ChartData = { - labels: ['A', 'B', 'C'], - datasets: [ - { - data: [540, 325, 702], - backgroundColor: [documentStyle.getPropertyValue('--indigo-500') || '#6366f1', documentStyle.getPropertyValue('--purple-500') || '#a855f7', documentStyle.getPropertyValue('--teal-500') || '#14b8a6'], - hoverBackgroundColor: [documentStyle.getPropertyValue('--indigo-400') || '#8183f4', documentStyle.getPropertyValue('--purple-400') || '#b975f9', documentStyle.getPropertyValue('--teal-400') || '#41c5b7'] - } - ] - }; - - const pieOptions: ChartOptions = { - plugins: { - legend: { - labels: { - usePointStyle: true, - color: textColor - } - } - } - }; - - const lineData: ChartData = { - labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], - datasets: [ - { - label: 'First Dataset', - data: [65, 59, 80, 81, 56, 55, 40], - fill: false, - backgroundColor: documentStyle.getPropertyValue('--primary-500') || '#6366f1', - borderColor: documentStyle.getPropertyValue('--primary-500') || '#6366f1', - tension: 0.4 - }, - { - label: 'Second Dataset', - data: [28, 48, 40, 19, 86, 27, 90], - fill: false, - backgroundColor: documentStyle.getPropertyValue('--primary-200') || '#bcbdf9', - borderColor: documentStyle.getPropertyValue('--primary-200') || '#bcbdf9', - tension: 0.4 - } - ] - }; - - const lineOptions: ChartOptions = { - plugins: { - legend: { - labels: { - color: textColor - } - } - }, - scales: { - x: { - ticks: { - color: textColorSecondary - }, - grid: { - color: surfaceBorder - }, - border: { - display: false - } - }, - y: { - ticks: { - color: textColorSecondary - }, - grid: { - color: surfaceBorder - }, - border: { - display: false - } - } - } - }; - - const polarData: ChartData = { - datasets: [ - { - data: [11, 16, 7, 3], - backgroundColor: [ - documentStyle.getPropertyValue('--indigo-500') || '#6366f1', - documentStyle.getPropertyValue('--purple-500') || '#a855f7', - documentStyle.getPropertyValue('--teal-500') || '#14b8a6', - documentStyle.getPropertyValue('--orange-500') || '#f97316' - ], - label: 'My dataset' - } - ], - labels: ['Indigo', 'Purple', 'Teal', 'Orange'] - }; - - const polarOptions: ChartOptions = { - plugins: { - legend: { - labels: { - color: textColor - } - } - }, - scales: { - r: { - grid: { - color: surfaceBorder - } - } - } - }; - - const radarData: ChartData = { - labels: ['Eating', 'Drinking', 'Sleeping', 'Designing', 'Coding', 'Cycling', 'Running'], - datasets: [ - { - label: 'My First dataset', - borderColor: documentStyle.getPropertyValue('--indigo-400') || '#8183f4', - pointBackgroundColor: documentStyle.getPropertyValue('--indigo-400') || '#8183f4', - pointBorderColor: documentStyle.getPropertyValue('--indigo-400') || '#8183f4', - pointHoverBackgroundColor: textColor, - pointHoverBorderColor: documentStyle.getPropertyValue('--indigo-400') || '#8183f4', - data: [65, 59, 90, 81, 56, 55, 40] - }, - { - label: 'My Second dataset', - borderColor: documentStyle.getPropertyValue('--purple-400') || '#b975f9', - pointBackgroundColor: documentStyle.getPropertyValue('--purple-400') || '#b975f9', - pointBorderColor: documentStyle.getPropertyValue('--purple-400') || '#b975f9', - pointHoverBackgroundColor: textColor, - pointHoverBorderColor: documentStyle.getPropertyValue('--purple-400') || '#b975f9', - data: [28, 48, 40, 19, 96, 27, 100] - } - ] - }; - - const radarOptions: ChartOptions = { - plugins: { - legend: { - labels: { - color: textColor - } - } - }, - scales: { - r: { - grid: { - color: textColorSecondary - } - } - } - }; - - setOptions({ - barOptions, - pieOptions, - lineOptions, - polarOptions, - radarOptions - }); - setChartData({ - barData, - pieData, - lineData, - polarData, - radarData - }); - }, [layoutConfig]); - - return ( -
-
-
-
Linear Chart
- -
-
-
-
-
Bar Chart
- -
-
-
-
-
Pie Chart
- -
-
-
-
-
Doughnut Chart
- -
-
-
-
-
Polar Area Chart
- -
-
-
-
-
Radar Chart
- -
-
-
- ); -}; - -export default ChartDemo; diff --git a/app/(main)/uikit/file/page.tsx b/app/(main)/uikit/file/page.tsx deleted file mode 100644 index 9dd41f9b..00000000 --- a/app/(main)/uikit/file/page.tsx +++ /dev/null @@ -1,35 +0,0 @@ -'use client'; - -import React, { useRef } from 'react'; -import { FileUpload } from 'primereact/fileupload'; -import { Toast } from 'primereact/toast'; - -const FileDemo = () => { - const toast = useRef(null); - - const onUpload = () => { - toast.current?.show({ - severity: 'info', - summary: 'Success', - detail: 'File Uploaded', - life: 3000 - }); - }; - - return ( -
- -
-
-
Advanced
- - -
Basic
- -
-
-
- ); -}; - -export default FileDemo; diff --git a/app/(main)/uikit/floatlabel/page.tsx b/app/(main)/uikit/floatlabel/page.tsx deleted file mode 100644 index 1dec3af6..00000000 --- a/app/(main)/uikit/floatlabel/page.tsx +++ /dev/null @@ -1,215 +0,0 @@ -"use client"; -import type { Demo } from "@/types"; -import { - AutoComplete, - AutoCompleteCompleteEvent, -} from "primereact/autocomplete"; -import { Calendar } from "primereact/calendar"; -import { Chips } from "primereact/chips"; -import { Dropdown } from "primereact/dropdown"; -import { InputMask } from "primereact/inputmask"; -import { InputNumber } from "primereact/inputnumber"; -import { InputText } from "primereact/inputtext"; -import { InputTextarea } from "primereact/inputtextarea"; -import { MultiSelect } from "primereact/multiselect"; -import { useEffect, useState } from "react"; -import { CountryService } from "../../../../demo/service/CountryService"; - -const FloatLabelDemo = () => { - const [countries, setCountries] = useState([]); - const [filteredCountries, setFilteredCountries] = useState( - [] - ); - const [value1, setValue1] = useState(""); - const [value2, setValue2] = useState(null); - const [value3, setValue3] = useState(""); - const [value4, setValue4] = useState(""); - const [value5, setValue5] = useState(null); - const [value6, setValue6] = useState([]); - const [value7, setValue7] = useState(""); - const [value8, setValue8] = useState(null); - const [value9, setValue9] = useState(""); - const [value10, setValue10] = useState(null); - const [value11, setValue11] = useState(null); - const [value12, setValue12] = useState(""); - - const cities = [ - { name: "New York", code: "NY" }, - { name: "Rome", code: "RM" }, - { name: "London", code: "LDN" }, - { name: "Istanbul", code: "IST" }, - { name: "Paris", code: "PRS" }, - ]; - - useEffect(() => { - CountryService.getCountries().then((countries) => { - setCountries(countries); - }); - }, []); - - const searchCountry = (event: AutoCompleteCompleteEvent) => { - const filtered = []; - const query = event.query; - for (let i = 0; i < countries.length; i++) { - const country = countries[i]; - if (country.name.toLowerCase().indexOf(query.toLowerCase()) === 0) { - filtered.push(country); - } - } - setFilteredCountries(filtered); - }; - - return ( -
-
Float Label
-

- All input text components support floating labels by adding ( - .p-float-label) to wrapper class. -

-
-
- - setValue1(e.target.value)} - /> - - -
-
- - setValue2(e.value)} - // suggestions={filteredCountries} - completeMethod={searchCountry} - field="name" - > - - -
-
- - - setValue3(e.target.value)} - /> - - -
-
- - - setValue4(e.target.value)} - /> - - -
-
- - setValue5(e.value ?? "")} - > - - -
-
- - setValue6(e.value ?? [])} - > - - -
-
- - setValue7(e.value ?? "")} - > - - -
-
- - - setValue8(e.target.value ?? null) - } - > - - -
-
-
- - - - - setValue9(e.target.value)} - /> - - -
-
-
- - setValue10(e.value)} - optionLabel="name" - > - - -
-
- - setValue11(e.value)} - optionLabel="name" - > - - -
-
- - setValue12(e.target.value)} - > - - -
-
-
- ); -}; - -export default FloatLabelDemo; diff --git a/app/(main)/uikit/formlayout/page.tsx b/app/(main)/uikit/formlayout/page.tsx deleted file mode 100644 index 01e98052..00000000 --- a/app/(main)/uikit/formlayout/page.tsx +++ /dev/null @@ -1,148 +0,0 @@ -'use client'; - -import React, { useState, useEffect, useMemo } from 'react'; -import { InputText } from 'primereact/inputtext'; -import { Button } from 'primereact/button'; -import { InputTextarea } from 'primereact/inputtextarea'; -import { Dropdown } from 'primereact/dropdown'; - -interface DropdownItem { - name: string; - code: string; -} - -const FormLayoutDemo = () => { - const [dropdownItem, setDropdownItem] = useState(null); - const dropdownItems: DropdownItem[] = useMemo( - () => [ - { name: 'Option 1', code: 'Option 1' }, - { name: 'Option 2', code: 'Option 2' }, - { name: 'Option 3', code: 'Option 3' } - ], - [] - ); - - useEffect(() => { - setDropdownItem(dropdownItems[0]); - }, [dropdownItems]); - - return ( -
-
-
-
Vertical
-
- - -
-
- - -
-
- - -
-
- -
-
Vertical Grid
-
-
- - -
-
- - -
-
-
-
- -
-
-
Horizontal
-
- -
- -
-
-
- -
- -
-
-
- -
-
Inline
-
-
- - -
-
- - -
- -
-
- -
-
Help Text
-
- - - Enter your username to reset your password. -
-
-
- -
-
-
Advanced
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - setDropdownItem(e.value)} options={dropdownItems} optionLabel="name" placeholder="Select One"> -
-
- - -
-
-
-
-
- ); -}; - -export default FormLayoutDemo; diff --git a/app/(main)/uikit/input/page.tsx b/app/(main)/uikit/input/page.tsx deleted file mode 100644 index dd0c4688..00000000 --- a/app/(main)/uikit/input/page.tsx +++ /dev/null @@ -1,522 +0,0 @@ -"use client"; -import type { Demo, Page } from "@/types"; -import { - AutoComplete, - AutoCompleteCompleteEvent, -} from "primereact/autocomplete"; -import { Button } from "primereact/button"; -import { Calendar } from "primereact/calendar"; -import { Checkbox, CheckboxChangeEvent } from "primereact/checkbox"; -import { Chips } from "primereact/chips"; -import { - ColorPicker, - ColorPickerHSBType, - ColorPickerRGBType, -} from "primereact/colorpicker"; -import { Dropdown } from "primereact/dropdown"; -import { InputNumber } from "primereact/inputnumber"; -import { InputSwitch } from "primereact/inputswitch"; -import { InputText } from "primereact/inputtext"; -import { InputTextarea } from "primereact/inputtextarea"; -import { Knob } from "primereact/knob"; -import { ListBox } from "primereact/listbox"; -import { MultiSelect } from "primereact/multiselect"; -import { RadioButton } from "primereact/radiobutton"; -import { Rating } from "primereact/rating"; -import { SelectButton } from "primereact/selectbutton"; -import { Slider } from "primereact/slider"; -import { ToggleButton } from "primereact/togglebutton"; -import { useEffect, useState } from "react"; -import { CountryService } from "../../../../demo/service/CountryService"; - -interface InputValue { - name: string; - code: string; -} - -const InputDemo: Page = () => { - const [floatValue, setFloatValue] = useState(""); - const [autoValue, setAutoValue] = useState([]); - const [selectedAutoValue, setSelectedAutoValue] = useState(null); - const [autoFilteredValue, setAutoFilteredValue] = useState( - [] - ); - const [calendarValue, setCalendarValue] = useState(null); - const [inputNumberValue, setInputNumberValue] = useState( - null - ); - const [chipsValue, setChipsValue] = useState([]); - const [sliderValue, setSliderValue] = useState(""); - const [ratingValue, setRatingValue] = useState(null); - const [colorValue, setColorValue] = useState< - string | ColorPickerRGBType | ColorPickerHSBType - >("1976D2"); - const [knobValue, setKnobValue] = useState(20); - const [radioValue, setRadioValue] = useState(null); - const [checkboxValue, setCheckboxValue] = useState([]); - const [switchValue, setSwitchValue] = useState(false); - const [listboxValue, setListboxValue] = useState(null); - const [dropdownValue, setDropdownValue] = useState(null); - const [multiselectValue, setMultiselectValue] = useState(null); - const [toggleValue, setToggleValue] = useState(false); - const [selectButtonValue1, setSelectButtonValue1] = useState(null); - const [selectButtonValue2, setSelectButtonValue2] = useState(null); - const [inputGroupValue, setInputGroupValue] = useState(false); - - const listboxValues: InputValue[] = [ - { name: "New York", code: "NY" }, - { name: "Rome", code: "RM" }, - { name: "London", code: "LDN" }, - { name: "Istanbul", code: "IST" }, - { name: "Paris", code: "PRS" }, - ]; - - const dropdownValues: InputValue[] = [ - { name: "New York", code: "NY" }, - { name: "Rome", code: "RM" }, - { name: "London", code: "LDN" }, - { name: "Istanbul", code: "IST" }, - { name: "Paris", code: "PRS" }, - ]; - - const multiselectValues: InputValue[] = [ - { name: "Australia", code: "AU" }, - { name: "Brazil", code: "BR" }, - { name: "China", code: "CN" }, - { name: "Egypt", code: "EG" }, - { name: "France", code: "FR" }, - { name: "Germany", code: "DE" }, - { name: "India", code: "IN" }, - { name: "Japan", code: "JP" }, - { name: "Spain", code: "ES" }, - { name: "United States", code: "US" }, - ]; - - const selectButtonValues1: InputValue[] = [ - { name: "Option 1", code: "O1" }, - { name: "Option 2", code: "O2" }, - { name: "Option 3", code: "O3" }, - ]; - - const selectButtonValues2: InputValue[] = [ - { name: "Option 1", code: "O1" }, - { name: "Option 2", code: "O2" }, - { name: "Option 3", code: "O3" }, - ]; - - useEffect(() => { - CountryService.getCountries().then((data) => setAutoValue(data)); - }, []); - - const searchCountry = (event: AutoCompleteCompleteEvent) => { - setTimeout(() => { - if (!event.query.trim().length) { - setAutoFilteredValue([...autoValue]); - } else { - setAutoFilteredValue( - autoValue.filter((country) => { - return country.name - .toLowerCase() - .startsWith(event.query.toLowerCase()); - }) - ); - } - }, 250); - }; - - const onCheckboxChange = (e: CheckboxChangeEvent) => { - let selectedValue = [...checkboxValue]; - if (e.checked) selectedValue.push(e.value); - else selectedValue.splice(selectedValue.indexOf(e.value), 1); - - setCheckboxValue(selectedValue); - }; - - const itemTemplate = (option: InputValue) => { - return ( -
- {option.name} - (e.currentTarget.src = - "https://www.primefaces.org/wp-content/uploads/2020/05/placeholder.png") - } - className={`flag flag-${option.code.toLowerCase()}`} - style={{ width: "21px" }} - /> - {option.name} -
- ); - }; - - return ( -
-
-
-
InputText
-
-
- -
-
- -
-
- -
-
- -
Icons
-
-
- - - - -
-
- - - - -
-
- - - - - -
-
- -
Float Label
- - setFloatValue(e.target.value)} - /> - - - -
Textarea
- - - {/*
AutoComplete
- setSelectedAutoValue(e.value)} - suggestions={autoFilteredValue} - completeMethod={searchCountry} - field="name" - /> */} - -
Calendar
- setCalendarValue(e.value ?? null)} - /> - -
InputNumber
- - setInputNumberValue(e.value ?? null) - } - showButtons - mode="decimal" - > - -
Chips
- setChipsValue(e.value ?? [])} - /> -
- -
-
-
-
Slider
- - setSliderValue(parseInt(e.target.value, 10)) - } - /> - - setSliderValue(e.value as number) - } - /> -
-
-
Rating
- setRatingValue(e.value ?? 0)} - /> -
-
-
ColorPicker
- setColorValue(e.value ?? "")} - style={{ width: "2rem" }} - /> -
-
-
Knob
- setKnobValue(e.value)} - step={10} - min={-50} - max={50} - /> -
-
-
-
- -
-
-
RadioButton
-
-
-
- setRadioValue(e.value)} - /> - -
-
-
-
- setRadioValue(e.value)} - /> - -
-
-
-
- setRadioValue(e.value)} - /> - -
-
-
- -
Checkbox
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
- -
Input Switch
- setSwitchValue(e.value ?? false)} - /> -
- -
-
Listbox
- setListboxValue(e.value)} - options={listboxValues} - optionLabel="name" - filter - /> - -
Dropdown
- setDropdownValue(e.value)} - options={dropdownValues} - optionLabel="name" - placeholder="Select" - /> - -
MultiSelect
- setMultiselectValue(e.value)} - options={multiselectValues} - itemTemplate={itemTemplate} - optionLabel="name" - placeholder="Select Countries" - filter - className="multiselect-custom" - display="chip" - /> -
- -
-
ToggleButton
- setToggleValue(e.value)} - onLabel="Yes" - offLabel="No" - /> - -
SelectButton
- setSelectButtonValue1(e.value)} - options={selectButtonValues1} - optionLabel="name" - /> - -
SelectButton - Multiple
- setSelectButtonValue2(e.value)} - options={selectButtonValues2} - optionLabel="name" - multiple - /> -
-
- -
-
-
Input Groups
-
-
-
- - - - -
-
- -
-
- - - - - - - - $ - .00 -
-
- -
-
-
-
- -
-
- - - setInputGroupValue( - e.checked ?? false - ) - } - /> - - -
-
-
-
-
-
- ); -}; - -export default InputDemo; diff --git a/app/(main)/uikit/invalidstate/page.tsx b/app/(main)/uikit/invalidstate/page.tsx deleted file mode 100644 index 43ca3188..00000000 --- a/app/(main)/uikit/invalidstate/page.tsx +++ /dev/null @@ -1,186 +0,0 @@ -"use client"; - -import type { Demo } from "@/types"; -import { - AutoComplete, - AutoCompleteCompleteEvent, -} from "primereact/autocomplete"; -import { Calendar } from "primereact/calendar"; -import { Chips } from "primereact/chips"; -import { Dropdown } from "primereact/dropdown"; -import { InputMask } from "primereact/inputmask"; -import { InputNumber } from "primereact/inputnumber"; -import { InputText } from "primereact/inputtext"; -import { InputTextarea } from "primereact/inputtextarea"; -import { MultiSelect } from "primereact/multiselect"; -import { Password } from "primereact/password"; -import { useEffect, useState } from "react"; -import { CountryService } from "../../../../demo/service/CountryService"; - -const InvalidStateDemo = () => { - const [countries, setCountries] = useState([]); - const [filteredCountries, setFilteredCountries] = useState( - [] - ); - const [value1, setValue1] = useState(""); - const [value2, setValue2] = useState(null); - const [value3, setValue3] = useState(null); - const [value4, setValue4] = useState([]); - const [value5, setValue5] = useState(""); - const [value6, setValue6] = useState(""); - const [value7, setValue7] = useState(0); - const [value8, setValue8] = useState(null); - const [value9, setValue9] = useState(null); - const [value10, setValue10] = useState(""); - - const cities = [ - { name: "New York", code: "NY" }, - { name: "Rome", code: "RM" }, - { name: "London", code: "LDN" }, - { name: "Istanbul", code: "IST" }, - { name: "Paris", code: "PRS" }, - ]; - - useEffect(() => { - CountryService.getCountries().then((countries) => { - setCountries(countries); - }); - }, []); - - const searchCountry = (event: AutoCompleteCompleteEvent) => { - // in a real application, make a request to a remote url with the query and - // return filtered results, for demo we filter at client side - const filtered = []; - const query = event.query; - for (let i = 0; i < countries.length; i++) { - const country = countries[i]; - if (country.name.toLowerCase().indexOf(query.toLowerCase()) === 0) { - filtered.push(country); - } - } - setFilteredCountries(filtered); - }; - - const onCalendarChange = (e: any) => { - setValue3(e.value!); - }; - - return ( -
-
Invalid State
-
-
-
- - setValue1(e.target.value)} - className="p-invalid" - /> -
-
- - setValue2(e.value)} - // suggestions={filteredCountries} - completeMethod={searchCountry} - field="name" - className="p-invalid" - /> -
-
- - -
-
- - setValue4(e.value ?? [])} - className="p-invalid" - /> -
-
- - setValue5(e.target.value)} - className="p-invalid" - /> -
-
- -
-
- - setValue6(e.value ?? "")} - className="p-invalid" - /> -
-
- - - setValue7(e.target.value ?? 0) - } - className="p-invalid" - /> -
-
- - setValue8(e.value)} - optionLabel="name" - className="p-invalid" - /> -
-
- - setValue9(e.value)} - optionLabel="name" - className="p-invalid" - /> -
-
- - setValue10(e.target.value)} - className="p-invalid" - /> -
-
-
-
- ); -}; - -export default InvalidStateDemo; diff --git a/app/(main)/uikit/list/page.tsx b/app/(main)/uikit/list/page.tsx deleted file mode 100644 index ca05aa44..00000000 --- a/app/(main)/uikit/list/page.tsx +++ /dev/null @@ -1,192 +0,0 @@ -'use client'; - -import React, { useState, useEffect } from 'react'; -import { DataView, DataViewLayoutOptions } from 'primereact/dataview'; -import { Button } from 'primereact/button'; -import { Dropdown, DropdownChangeEvent } from 'primereact/dropdown'; -import { Rating } from 'primereact/rating'; -import { PickList } from 'primereact/picklist'; -import { OrderList } from 'primereact/orderlist'; -import { ProductService } from '../../../../demo/service/ProductService'; -import { InputText } from 'primereact/inputtext'; -import type { Demo } from '@/types'; - -const ListDemo = () => { - const listValue = [ - { name: 'San Francisco', code: 'SF' }, - { name: 'London', code: 'LDN' }, - { name: 'Paris', code: 'PRS' }, - { name: 'Istanbul', code: 'IST' }, - { name: 'Berlin', code: 'BRL' }, - { name: 'Barcelona', code: 'BRC' }, - { name: 'Rome', code: 'RM' } - ]; - - const [picklistSourceValue, setPicklistSourceValue] = useState(listValue); - const [picklistTargetValue, setPicklistTargetValue] = useState([]); - const [orderlistValue, setOrderlistValue] = useState(listValue); - const [dataViewValue, setDataViewValue] = useState([]); - const [globalFilterValue, setGlobalFilterValue] = useState(''); - const [filteredValue, setFilteredValue] = useState(null); - const [layout, setLayout] = useState<'grid' | 'list' | (string & Record)>('grid'); - const [sortKey, setSortKey] = useState(null); - const [sortOrder, setSortOrder] = useState<0 | 1 | -1 | null>(null); - const [sortField, setSortField] = useState(''); - - const sortOptions = [ - { label: 'Price High to Low', value: '!price' }, - { label: 'Price Low to High', value: 'price' } - ]; - - useEffect(() => { - ProductService.getProducts().then((data) => setDataViewValue(data)); - setGlobalFilterValue(''); - }, []); - - useEffect(() => { - ProductService.getProducts().then((data) => setDataViewValue(data)); - setGlobalFilterValue(''); - }, []); - - const onFilter = (e: React.ChangeEvent) => { - const value = e.target.value; - setGlobalFilterValue(value); - if (value.length === 0) { - setFilteredValue(null); - } else { - const filtered = dataViewValue?.filter((product) => { - const productNameLowercase = product.name.toLowerCase(); - const searchValueLowercase = value.toLowerCase(); - return productNameLowercase.includes(searchValueLowercase); - }); - - setFilteredValue(filtered); - } - }; - - const onSortChange = (event: DropdownChangeEvent) => { - const value = event.value; - - if (value.indexOf('!') === 0) { - setSortOrder(-1); - setSortField(value.substring(1, value.length)); - setSortKey(value); - } else { - setSortOrder(1); - setSortField(value); - setSortKey(value); - } - }; - - const dataViewHeader = ( -
- - - - - - setLayout(e.value)} /> -
- ); - - const dataviewListItem = (data: Demo.Product) => { - return ( -
-
- {data.name} -
-
{data.name}
-
{data.description}
- -
- - {data.category} -
-
-
- ${data.price} - - {data.inventoryStatus} -
-
-
- ); - }; - - const dataviewGridItem = (data: Demo.Product) => { - return ( -
-
-
-
- - {data.category} -
- {data.inventoryStatus} -
-
- {data.name} -
{data.name}
-
{data.description}
- -
-
- ${data.price} -
-
-
- ); - }; - - const itemTemplate = (data: Demo.Product, layout: 'grid' | 'list' | (string & Record)) => { - if (!data) { - return; - } - - if (layout === 'list') { - return dataviewListItem(data); - } else if (layout === 'grid') { - return dataviewGridItem(data); - } - }; - - return ( -
-
-
-
DataView
- -
-
- -
-
-
PickList
- {/*
{item.name}
} - onChange={(e) => { - setPicklistSourceValue(e.source); - setPicklistTargetValue(e.target); - }} - sourceStyle={{ height: '200px' }} - targetStyle={{ height: '200px' }} - >
*/} -
-
- -
-
-
OrderList
- {/*
{item.name}
} onChange={(e) => setOrderlistValue(e.value)}>
*/} -
-
-
- ); -}; - -export default ListDemo; diff --git a/app/(main)/uikit/media/page.tsx b/app/(main)/uikit/media/page.tsx deleted file mode 100644 index 269f5da1..00000000 --- a/app/(main)/uikit/media/page.tsx +++ /dev/null @@ -1,109 +0,0 @@ -'use client'; - -import { Button } from 'primereact/button'; -import { Carousel } from 'primereact/carousel'; -import { Galleria } from 'primereact/galleria'; -import { Image } from 'primereact/image'; -import React, { useEffect, useState } from 'react'; -import { PhotoService } from '../../../../demo/service/PhotoService'; -import { ProductService } from '../../../../demo/service/ProductService'; -import type { Demo } from '@/types'; - -const MediaDemo = () => { - const [products, setProducts] = useState([]); - const [images, setImages] = useState([]); - - const galleriaResponsiveOptions = [ - { - breakpoint: '1024px', - numVisible: 5 - }, - { - breakpoint: '960px', - numVisible: 4 - }, - { - breakpoint: '768px', - numVisible: 3 - }, - { - breakpoint: '560px', - numVisible: 1 - } - ]; - const carouselResponsiveOptions = [ - { - breakpoint: '1024px', - numVisible: 3, - numScroll: 3 - }, - { - breakpoint: '768px', - numVisible: 2, - numScroll: 2 - }, - { - breakpoint: '560px', - numVisible: 1, - numScroll: 1 - } - ]; - - useEffect(() => { - ProductService.getProductsSmall().then((products) => setProducts(products)); - - PhotoService.getImages().then((images) => setImages(images)); - }, []); - - const carouselItemTemplate = (product: Demo.Product) => { - return ( -
-
- {product.name} -
-
-

{product.name}

-
${product.price}
- {product.inventoryStatus} -
- - - -
-
-
- ); - }; - - const galleriaItemTemplate = (item: Demo.Photo) => {item.alt}; - const galleriaThumbnailTemplate = (item: Demo.Photo) => {item.alt}; - - return ( -
-
-
-
Carousel
- -
-
- -
-
-
Image
-
- Image -
-
-
- -
-
-
Galleria
- -
-
-
- ); -}; - -export default MediaDemo; diff --git a/app/(main)/uikit/menu/confirmation/page.tsx b/app/(main)/uikit/menu/confirmation/page.tsx deleted file mode 100644 index 8b5b8d2f..00000000 --- a/app/(main)/uikit/menu/confirmation/page.tsx +++ /dev/null @@ -1,16 +0,0 @@ -'use client'; - -import React from 'react'; -import Menu from '../page'; -function ConfirmationDemo() { - return ( - -
- -

Confirmation Component Content via Child Route

-
-
- ); -} - -export default ConfirmationDemo; diff --git a/app/(main)/uikit/menu/page.tsx b/app/(main)/uikit/menu/page.tsx deleted file mode 100644 index c49836bd..00000000 --- a/app/(main)/uikit/menu/page.tsx +++ /dev/null @@ -1,584 +0,0 @@ -'use client'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { Menubar } from 'primereact/menubar'; -import { InputText } from 'primereact/inputtext'; -import { BreadCrumb } from 'primereact/breadcrumb'; -import { Steps } from 'primereact/steps'; -import { TabMenu } from 'primereact/tabmenu'; -import { TieredMenu } from 'primereact/tieredmenu'; -import { Menu } from 'primereact/menu'; -import { Button } from 'primereact/button'; -import { ContextMenu } from 'primereact/contextmenu'; -import { MegaMenu } from 'primereact/megamenu'; -import { PanelMenu } from 'primereact/panelmenu'; -import { useRouter } from 'next/navigation'; -import { usePathname } from 'next/navigation'; - -const MenuDemo = ({ children }: any) => { - const [activeIndex, setActiveIndex] = useState(0); - const menu = useRef(null); - const contextMenu = useRef(null); - const router = useRouter(); - const pathname = usePathname(); - - const checkActiveIndex = useCallback(() => { - const paths = pathname.split('/'); - const currentPath = paths[paths.length - 1]; - - switch (currentPath) { - case 'seat': - setActiveIndex(1); - break; - case 'payment': - setActiveIndex(2); - break; - case 'confirmation': - setActiveIndex(3); - break; - default: - break; - } - }, [pathname]); - - useEffect(() => { - checkActiveIndex(); - }, [checkActiveIndex]); - - const nestedMenuitems = [ - { - label: 'Customers', - icon: 'pi pi-fw pi-table', - items: [ - { - label: 'New', - icon: 'pi pi-fw pi-user-plus', - items: [ - { - label: 'Customer', - icon: 'pi pi-fw pi-plus' - }, - { - label: 'Duplicate', - icon: 'pi pi-fw pi-copy' - } - ] - }, - { - label: 'Edit', - icon: 'pi pi-fw pi-user-edit' - } - ] - }, - { - label: 'Orders', - icon: 'pi pi-fw pi-shopping-cart', - items: [ - { - label: 'View', - icon: 'pi pi-fw pi-list' - }, - { - label: 'Search', - icon: 'pi pi-fw pi-search' - } - ] - }, - { - label: 'Shipments', - icon: 'pi pi-fw pi-envelope', - items: [ - { - label: 'Tracker', - icon: 'pi pi-fw pi-compass' - }, - { - label: 'Map', - icon: 'pi pi-fw pi-map-marker' - }, - { - label: 'Manage', - icon: 'pi pi-fw pi-pencil' - } - ] - }, - { - label: 'Profile', - icon: 'pi pi-fw pi-user', - items: [ - { - label: 'Settings', - icon: 'pi pi-fw pi-cog' - }, - { - label: 'Billing', - icon: 'pi pi-fw pi-file' - } - ] - }, - { - label: 'Quit', - icon: 'pi pi-fw pi-sign-out' - } - ]; - - const breadcrumbHome = { icon: 'pi pi-home', to: '/' }; - const breadcrumbItems = [{ label: 'Computer' }, { label: 'Notebook' }, { label: 'Accessories' }, { label: 'Backpacks' }, { label: 'Item' }]; - - const wizardItems = [ - { label: 'Personal', command: () => router.push('/uikit/menu') }, - { label: 'Seat', command: () => router.push('/uikit/menu/seat') }, - { label: 'Payment', command: () => router.push('/uikit/menu/payment') }, - { - label: 'Confirmation', - command: () => router.push('/uikit/menu/confirmation') - } - ]; - - const tieredMenuItems = [ - { - label: 'Customers', - icon: 'pi pi-fw pi-table', - items: [ - { - label: 'New', - icon: 'pi pi-fw pi-user-plus', - items: [ - { - label: 'Customer', - icon: 'pi pi-fw pi-plus' - }, - { - label: 'Duplicate', - icon: 'pi pi-fw pi-copy' - } - ] - }, - { - label: 'Edit', - icon: 'pi pi-fw pi-user-edit' - } - ] - }, - { - label: 'Orders', - icon: 'pi pi-fw pi-shopping-cart', - items: [ - { - label: 'View', - icon: 'pi pi-fw pi-list' - }, - { - label: 'Search', - icon: 'pi pi-fw pi-search' - } - ] - }, - { - label: 'Shipments', - icon: 'pi pi-fw pi-envelope', - items: [ - { - label: 'Tracker', - icon: 'pi pi-fw pi-compass' - }, - { - label: 'Map', - icon: 'pi pi-fw pi-map-marker' - }, - { - label: 'Manage', - icon: 'pi pi-fw pi-pencil' - } - ] - }, - { - label: 'Profile', - icon: 'pi pi-fw pi-user', - items: [ - { - label: 'Settings', - icon: 'pi pi-fw pi-cog' - }, - { - label: 'Billing', - icon: 'pi pi-fw pi-file' - } - ] - }, - { - separator: true - }, - { - label: 'Quit', - icon: 'pi pi-fw pi-sign-out' - } - ]; - - const overlayMenuItems = [ - { - label: 'Save', - icon: 'pi pi-save' - }, - { - label: 'Update', - icon: 'pi pi-refresh' - }, - { - label: 'Delete', - icon: 'pi pi-trash' - }, - { - separator: true - }, - { - label: 'Home', - icon: 'pi pi-home' - } - ]; - - const menuitems = [ - { - label: 'Customers', - items: [ - { - label: 'New', - icon: 'pi pi-fw pi-plus' - }, - { - label: 'Edit', - icon: 'pi pi-fw pi-user-edit' - } - ] - }, - { - label: 'Orders', - items: [ - { - label: 'View', - icon: 'pi pi-fw pi-list' - }, - { - label: 'Search', - icon: 'pi pi-fw pi-search' - } - ] - } - ]; - - const contextMenuItems = [ - { - label: 'Save', - icon: 'pi pi-save' - }, - { - label: 'Update', - icon: 'pi pi-refresh' - }, - { - label: 'Delete', - icon: 'pi pi-trash' - }, - { - separator: true - }, - { - label: 'Options', - icon: 'pi pi-cog' - } - ]; - - const megamenuItems = [ - { - label: 'Fashion', - icon: 'pi pi-fw pi-tag', - items: [ - [ - { - label: 'Woman', - items: [{ label: 'Woman Item' }, { label: 'Woman Item' }, { label: 'Woman Item' }] - }, - { - label: 'Men', - items: [{ label: 'Men Item' }, { label: 'Men Item' }, { label: 'Men Item' }] - } - ], - [ - { - label: 'Kids', - items: [{ label: 'Kids Item' }, { label: 'Kids Item' }] - }, - { - label: 'Luggage', - items: [{ label: 'Luggage Item' }, { label: 'Luggage Item' }, { label: 'Luggage Item' }] - } - ] - ] - }, - { - label: 'Electronics', - icon: 'pi pi-fw pi-desktop', - items: [ - [ - { - label: 'Computer', - items: [{ label: 'Computer Item' }, { label: 'Computer Item' }] - }, - { - label: 'Camcorder', - items: [{ label: 'Camcorder Item' }, { label: 'Camcorder Item' }, { label: 'Camcorder Item' }] - } - ], - [ - { - label: 'TV', - items: [{ label: 'TV Item' }, { label: 'TV Item' }] - }, - { - label: 'Audio', - items: [{ label: 'Audio Item' }, { label: 'Audio Item' }, { label: 'Audio Item' }] - } - ], - [ - { - label: 'Sports.7', - items: [{ label: 'Sports.7.1' }, { label: 'Sports.7.2' }] - } - ] - ] - }, - { - label: 'Furniture', - icon: 'pi pi-fw pi-image', - items: [ - [ - { - label: 'Living Room', - items: [{ label: 'Living Room Item' }, { label: 'Living Room Item' }] - }, - { - label: 'Kitchen', - items: [{ label: 'Kitchen Item' }, { label: 'Kitchen Item' }, { label: 'Kitchen Item' }] - } - ], - [ - { - label: 'Bedroom', - items: [{ label: 'Bedroom Item' }, { label: 'Bedroom Item' }] - }, - { - label: 'Outdoor', - items: [{ label: 'Outdoor Item' }, { label: 'Outdoor Item' }, { label: 'Outdoor Item' }] - } - ] - ] - }, - { - label: 'Sports', - icon: 'pi pi-fw pi-star', - items: [ - [ - { - label: 'Basketball', - items: [{ label: 'Basketball Item' }, { label: 'Basketball Item' }] - }, - { - label: 'Football', - items: [{ label: 'Football Item' }, { label: 'Football Item' }, { label: 'Football Item' }] - } - ], - [ - { - label: 'Tennis', - items: [{ label: 'Tennis Item' }, { label: 'Tennis Item' }] - } - ] - ] - } - ]; - - const panelMenuitems = [ - { - label: 'Customers', - icon: 'pi pi-fw pi-table', - items: [ - { - label: 'New', - icon: 'pi pi-fw pi-user-plus', - items: [ - { - label: 'Customer', - icon: 'pi pi-fw pi-plus' - }, - { - label: 'Duplicate', - icon: 'pi pi-fw pi-copy' - } - ] - }, - { - label: 'Edit', - icon: 'pi pi-fw pi-user-edit' - } - ] - }, - { - label: 'Orders', - icon: 'pi pi-fw pi-shopping-cart', - items: [ - { - label: 'View', - icon: 'pi pi-fw pi-list' - }, - { - label: 'Search', - icon: 'pi pi-fw pi-search' - } - ] - }, - { - label: 'Shipments', - icon: 'pi pi-fw pi-envelope', - items: [ - { - label: 'Tracker', - icon: 'pi pi-fw pi-compass' - }, - { - label: 'Map', - icon: 'pi pi-fw pi-map-marker' - }, - { - label: 'Manage', - icon: 'pi pi-fw pi-pencil' - } - ] - }, - { - label: 'Profile', - icon: 'pi pi-fw pi-user', - items: [ - { - label: 'Settings', - icon: 'pi pi-fw pi-cog' - }, - { - label: 'Billing', - icon: 'pi pi-fw pi-file' - } - ] - } - ]; - - const toggleMenu = (event: React.MouseEvent) => { - menu.current?.toggle(event); - }; - - const onContextRightClick = (event: React.MouseEvent) => { - contextMenu.current?.show(event); - }; - - const menubarEndTemplate = () => { - return ( - - - - - ); - }; - - return ( -
-
-
-
Menubar
- -
-
- -
-
-
Breadcrumb
- -
-
- -
-
-
Steps
- setActiveIndex(e.index)} readOnly={false} /> - {pathname === '/uikit/menu' ? ( -
- -

Personal Component Content via Child Route

-
- ) : ( - <>{children} - )} -
-
- -
-
-
TabMenu
- setActiveIndex(e.index)} /> - {pathname === '/uikit/menu' ? ( -
- -

Personal Component Content via Child Route

-
- ) : ( - <>{children} - )} -
-
- -
-
-
Tiered Menu
- -
-
- -
-
-
Plain Menu
- -
-
- -
-
-
Overlay Menu
- - -
- -
-
ContextMenu
- Right click to display. - -
-
- -
-
-
MegaMenu - Horizontal
- - -
MegaMenu - Vertical
- -
-
- -
-
-
PanelMenu
- -
-
-
- ); -}; - -export default MenuDemo; diff --git a/app/(main)/uikit/menu/payment/page.tsx b/app/(main)/uikit/menu/payment/page.tsx deleted file mode 100644 index 4f19d0b7..00000000 --- a/app/(main)/uikit/menu/payment/page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -'use client'; -import React from 'react'; -import Menu from '../page'; -function PaymentDemo() { - return ( - -
- -

Payment Component Content via Child Route

-
-
- ); -} - -export default PaymentDemo; diff --git a/app/(main)/uikit/menu/seat/page.tsx b/app/(main)/uikit/menu/seat/page.tsx deleted file mode 100644 index a4dd25cf..00000000 --- a/app/(main)/uikit/menu/seat/page.tsx +++ /dev/null @@ -1,16 +0,0 @@ -'use client'; - -import React from 'react'; -import Menu from '../page'; -function SeatDemo() { - return ( - -
- -

Seat Component Content via Child Route

-
-
- ); -} - -export default SeatDemo; diff --git a/app/(main)/uikit/message/page.tsx b/app/(main)/uikit/message/page.tsx deleted file mode 100644 index 772cb852..00000000 --- a/app/(main)/uikit/message/page.tsx +++ /dev/null @@ -1,131 +0,0 @@ -'use client'; -import React, { useRef, useState } from 'react'; -import { Toast } from 'primereact/toast'; -import { Messages } from 'primereact/messages'; -import { Message } from 'primereact/message'; -import { InputText } from 'primereact/inputtext'; -import { Button } from 'primereact/button'; - -const MessagesDemo = () => { - const [username, setUsername] = useState(''); - const [email, setEmail] = useState(''); - const toast = useRef(null); - const message = useRef(null); - - const addSuccessMessage = () => { - message.current?.show({ severity: 'success', content: 'Message Detail' }); - }; - - const addInfoMessage = () => { - message.current?.show({ severity: 'info', content: 'Message Detail' }); - }; - - const addWarnMessage = () => { - message.current?.show({ severity: 'warn', content: 'Message Detail' }); - }; - - const addErrorMessage = () => { - message.current?.show({ severity: 'error', content: 'Message Detail' }); - }; - - const showSuccess = () => { - toast.current?.show({ - severity: 'success', - summary: 'Success Message', - detail: 'Message Detail', - life: 3000 - }); - }; - - const showInfo = () => { - toast.current?.show({ - severity: 'info', - summary: 'Info Message', - detail: 'Message Detail', - life: 3000 - }); - }; - - const showWarn = () => { - toast.current?.show({ - severity: 'warn', - summary: 'Warn Message', - detail: 'Message Detail', - life: 3000 - }); - }; - - const showError = () => { - toast.current?.show({ - severity: 'error', - summary: 'Error Message', - detail: 'Message Detail', - life: 3000 - }); - }; - - return ( -
-
-
-
Toast
-
- -
-
-
- -
-
-
Messages
-
-
- -
-
- -
-
-
Inline
-
- - setUsername(e.target.value)} required className="p-invalid" /> - -
-
- - setEmail(e.target.value)} required className="p-invalid" /> - -
-
-
- -
-
-
Help Text
-
- - - - Enter your username to reset your password. - -
-
-
-
- ); -}; - -export default MessagesDemo; diff --git a/app/(main)/uikit/misc/page.tsx b/app/(main)/uikit/misc/page.tsx deleted file mode 100644 index 92c72d8b..00000000 --- a/app/(main)/uikit/misc/page.tsx +++ /dev/null @@ -1,220 +0,0 @@ -'use client'; - -import React, { useState, useEffect, useRef } from 'react'; -import { ProgressBar } from 'primereact/progressbar'; -import { Button } from 'primereact/button'; -import { Badge } from 'primereact/badge'; -import { Tag } from 'primereact/tag'; -import { Avatar } from 'primereact/avatar'; -import { AvatarGroup } from 'primereact/avatargroup'; -import { Chip } from 'primereact/chip'; -import { Skeleton } from 'primereact/skeleton'; -import { ScrollPanel } from 'primereact/scrollpanel'; -import { ScrollTop } from 'primereact/scrolltop'; - -const MiscDemo = () => { - const [value, setValue] = useState(0); - const intervalRef = useRef(null); - - useEffect(() => { - const interval = setInterval(() => { - setValue((prevValue) => { - const newVal = prevValue + Math.floor(Math.random() * 10) + 1; - return newVal >= 100 ? 100 : newVal; - }); - }, 2000); - - intervalRef.current = interval; - - return () => { - clearInterval(intervalRef.current as NodeJS.Timeout); - intervalRef.current = null; - }; - }, []); - - return ( -
-
-
-
ProgressBar
-
-
- -
-
- -
-
-
-
-
-
-

Badge

-
Numbers
-
- - - - - -
- -
Positioned Badge
-
- - - - - - - - - -
- -
Button Badge
-
- - -
-
Sizes
-
- - - -
-
- -
-

Avatar

-
Avatar Group
- - - - - - - - - -
Label - Circle
-
- - - -
- -
Icon - Badge
- - - -
- -
-

ScrollTop

- -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae et leo duis ut diam. Ultricies mi quis hendrerit dolor magna eget est lorem. Amet - consectetur adipiscing elit ut. Nam libero justo laoreet sit amet. Pharetra massa massa ultricies mi quis hendrerit dolor magna. Est ultricies integer quis auctor elit sed vulputate. Consequat ac felis donec et. Tellus - orci ac auctor augue mauris. Semper feugiat nibh sed pulvinar proin gravida hendrerit lectus a. Tincidunt arcu non sodales neque sodales. Metus aliquam eleifend mi in nulla posuere sollicitudin aliquam ultrices. Sodales ut - etiam sit amet nisl purus. Cursus sit amet dictum sit amet. Tristique senectus et netus et malesuada fames ac turpis egestas. Et tortor consequat id porta nibh venenatis cras sed. Diam maecenas ultricies mi eget mauris. - Eget egestas purus viverra accumsan in nisl nisi. Suscipit adipiscing bibendum est ultricies integer. Mattis aliquam faucibus purus in massa tempor nec. -

- -
-
-
-
-
-

Tag

-
Tags
-
- - - - - -
- -
Pills
-
- - - - - -
- -
Icons
-
- - - - - -
-
- -
-

Chip

-
Basic
-
- - - - -
- -
Icon
-
- - - - -
- -
Image
-
- - - - -
- -
Styling
-
- - - - -
-
- -
-

Skeleton

-
-
- -
- - - -
-
- -
- - -
-
-
-
-
- ); -}; - -export default MiscDemo; diff --git a/app/(main)/uikit/overlay/page.tsx b/app/(main)/uikit/overlay/page.tsx deleted file mode 100644 index 173512dd..00000000 --- a/app/(main)/uikit/overlay/page.tsx +++ /dev/null @@ -1,222 +0,0 @@ -'use client'; - -import { Button } from 'primereact/button'; -import { Column } from 'primereact/column'; -import { confirmPopup, ConfirmPopup } from 'primereact/confirmpopup'; -import { DataTable, DataTableSelectEvent } from 'primereact/datatable'; -import { Dialog } from 'primereact/dialog'; -import { InputText } from 'primereact/inputtext'; -import { OverlayPanel } from 'primereact/overlaypanel'; -import { Sidebar } from 'primereact/sidebar'; -import { Toast } from 'primereact/toast'; -import React, { useEffect, useRef, useState } from 'react'; -import { ProductService } from '../../../../demo/service/ProductService'; -import type { Demo } from '@/types'; - -type ButtonEvent = React.MouseEvent; -const OverlayDemo = () => { - const [displayBasic, setDisplayBasic] = useState(false); - const [displayConfirmation, setDisplayConfirmation] = useState(false); - const [visibleLeft, setVisibleLeft] = useState(false); - const [visibleRight, setVisibleRight] = useState(false); - const [visibleTop, setVisibleTop] = useState(false); - const [visibleBottom, setVisibleBottom] = useState(false); - const [visibleFullScreen, setVisibleFullScreen] = useState(false); - const [products, setProducts] = useState([]); - const [selectedProduct, setSelectedProduct] = useState(null); - const op = useRef(null); - const op2 = useRef(null); - const toast = useRef(null); - - const accept = () => { - toast.current?.show({ - severity: 'info', - summary: 'Confirmed', - detail: 'You have accepted', - life: 3000 - }); - }; - - const reject = () => { - toast.current?.show({ - severity: 'error', - summary: 'Rejected', - detail: 'You have rejected', - life: 3000 - }); - }; - - const confirm = (event: React.MouseEvent) => { - confirmPopup({ - target: event.currentTarget, - message: 'Are you sure you want to proceed?', - icon: 'pi pi-exclamation-triangle', - accept, - reject - }); - }; - - useEffect(() => { - ProductService.getProductsSmall().then((data) => setProducts(data)); - }, []); - - const toggle = (event: ButtonEvent) => { - op.current?.toggle(event); - }; - - const toggleDataTable = (event: ButtonEvent) => { - op2.current?.toggle(event); - }; - - const formatCurrency = (value: number) => { - return value.toLocaleString('en-US', { - style: 'currency', - currency: 'USD' - }); - }; - - const onProductSelect = (event: DataTableSelectEvent) => { - op2.current?.hide(); - toast.current?.show({ - severity: 'info', - summary: 'Product Selected', - detail: event.data.name, - life: 3000 - }); - }; - - const onSelectionChange = (e: any): void => { - setSelectedProduct(e.value as Demo.Product); - }; - - const basicDialogFooter =
-
-
-
-
Overlay Panel
-
-
-
-
-
-
-
-
- -
-
-
Confirmation
-
-
-
Sidebar
- setVisibleLeft(false)} baseZIndex={1000}> -

Left Sidebar

-
- - setVisibleRight(false)} baseZIndex={1000} position="right"> -

Right Sidebar

-
- - setVisibleTop(false)} baseZIndex={1000} position="top"> -

Top Sidebar

-
- - setVisibleBottom(false)} baseZIndex={1000} position="bottom"> -

Bottom Sidebar

-
- - setVisibleFullScreen(false)} baseZIndex={1000} fullScreen> -

Full Screen

-
- -
-
- -
-
-
Tooltip
-
- - - - -
-
-
-
- - -
-
ConfirmPopup
- - -
-
-
- - ); -}; - -export default OverlayDemo; diff --git a/app/(main)/uikit/panel/page.tsx b/app/(main)/uikit/panel/page.tsx deleted file mode 100644 index 5a852b75..00000000 --- a/app/(main)/uikit/panel/page.tsx +++ /dev/null @@ -1,233 +0,0 @@ -'use client'; - -import React, { useRef } from 'react'; -import { Toolbar } from 'primereact/toolbar'; -import { Button } from 'primereact/button'; -import { SplitButton } from 'primereact/splitbutton'; -import { Accordion, AccordionTab } from 'primereact/accordion'; -import { TabView, TabPanel } from 'primereact/tabview'; -import { Panel } from 'primereact/panel'; -import { Fieldset } from 'primereact/fieldset'; -import { Card } from 'primereact/card'; -import { Divider } from 'primereact/divider'; -import { InputText } from 'primereact/inputtext'; -import { Splitter, SplitterPanel } from 'primereact/splitter'; -import { Menu } from 'primereact/menu'; - -const PanelDemo = () => { - const menu1 = useRef(null); - const toolbarItems = [ - { - label: 'Save', - icon: 'pi pi-check' - }, - { - label: 'Update', - icon: 'pi pi-sync' - }, - { - label: 'Delete', - icon: 'pi pi-trash' - }, - { - label: 'Home Page', - icon: 'pi pi-home' - } - ]; - - const toolbarLeftTemplate = () => { - return ( - <> -
- ); - - return ( -
-
-
-
Toolbar
- -
-
-
-
-
AccordionPanel
- - -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea - commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim - id est laborum. -

-
- -

- Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. - Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Consectetur, adipisci velit, sed quia non numquam eius modi. -

-
- -

- At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt - in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo - minus. -

-
-
-
-
-
TabView
- - -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea - commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim - id est laborum. -

-
- -

- Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. - Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Consectetur, adipisci velit, sed quia non numquam eius modi. -

-
- -

- At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt - in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo - minus. -

-
-
-
-
-
-
-
Panel
- -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo - consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est - laborum. -

-
-
-
-
Fieldset
-
-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo - consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est - laborum. -

-
-
- -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo - consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. -

-
-
- -
-
-
Divider
-
-
-
-
- - -
-
- - -
- -
-
-
- - OR - -
-
-

- Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. - Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Consectetur, adipisci velit, sed quia non numquam eius modi. -

- - - Badge - - -

- At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt - in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo - minus. -

- - - - - -

- Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut - reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Donec vel volutpat ipsum. Integer nunc magna, posuere ut tincidunt eget, egestas vitae sapien. Morbi dapibus luctus odio. -

-
-
-
-
- -
-
-
Splitter
- - -
Panel 1
-
- - - -
Panel 2
-
- -
Panel 3
-
-
-
-
-
-
-
- ); -}; - -export default PanelDemo; diff --git a/app/(main)/uikit/table/page.tsx b/app/(main)/uikit/table/page.tsx deleted file mode 100644 index cb399065..00000000 --- a/app/(main)/uikit/table/page.tsx +++ /dev/null @@ -1,464 +0,0 @@ -'use client'; -import { CustomerService } from '../../../../demo/service/CustomerService'; -import { ProductService } from '../../../../demo/service/ProductService'; -import { FilterMatchMode, FilterOperator } from 'primereact/api'; -import { Button } from 'primereact/button'; -import { Calendar } from 'primereact/calendar'; -import { Column, ColumnFilterApplyTemplateOptions, ColumnFilterClearTemplateOptions, ColumnFilterElementTemplateOptions } from 'primereact/column'; -import { DataTable, DataTableExpandedRows, DataTableFilterMeta } from 'primereact/datatable'; -import { Dropdown } from 'primereact/dropdown'; -import { InputNumber } from 'primereact/inputnumber'; -import { InputText } from 'primereact/inputtext'; -import { MultiSelect } from 'primereact/multiselect'; -import { ProgressBar } from 'primereact/progressbar'; -import { Rating } from 'primereact/rating'; -import { Slider } from 'primereact/slider'; -import { ToggleButton } from 'primereact/togglebutton'; -import { TriStateCheckbox } from 'primereact/tristatecheckbox'; -import { classNames } from 'primereact/utils'; -import React, { useEffect, useState } from 'react'; -import type { Demo } from '@/types'; - -const TableDemo = () => { - const [customers1, setCustomers1] = useState([]); - const [customers2, setCustomers2] = useState([]); - const [customers3, setCustomers3] = useState([]); - const [filters1, setFilters1] = useState({}); - const [loading1, setLoading1] = useState(true); - const [loading2, setLoading2] = useState(true); - const [idFrozen, setIdFrozen] = useState(false); - const [products, setProducts] = useState([]); - const [globalFilterValue1, setGlobalFilterValue1] = useState(''); - const [expandedRows, setExpandedRows] = useState([]); - const [allExpanded, setAllExpanded] = useState(false); - - const representatives = [ - { name: 'Amy Elsner', image: 'amyelsner.png' }, - { name: 'Anna Fali', image: 'annafali.png' }, - { name: 'Asiya Javayant', image: 'asiyajavayant.png' }, - { name: 'Bernardo Dominic', image: 'bernardodominic.png' }, - { name: 'Elwin Sharvill', image: 'elwinsharvill.png' }, - { name: 'Ioni Bowcher', image: 'ionibowcher.png' }, - { name: 'Ivan Magalhaes', image: 'ivanmagalhaes.png' }, - { name: 'Onyama Limba', image: 'onyamalimba.png' }, - { name: 'Stephen Shaw', image: 'stephenshaw.png' }, - { name: 'XuXue Feng', image: 'xuxuefeng.png' } - ]; - - const statuses = ['unqualified', 'qualified', 'new', 'negotiation', 'renewal', 'proposal']; - - const clearFilter1 = () => { - initFilters1(); - }; - - const onGlobalFilterChange1 = (e: React.ChangeEvent) => { - const value = e.target.value; - let _filters1 = { ...filters1 }; - (_filters1['global'] as any).value = value; - - setFilters1(_filters1); - setGlobalFilterValue1(value); - }; - - const renderHeader1 = () => { - return ( -
-
- ); - }; - - useEffect(() => { - setLoading2(true); - - CustomerService.getCustomersLarge().then((data) => { - setCustomers1(getCustomers(data)); - setLoading1(false); - }); - CustomerService.getCustomersLarge().then((data) => { - setCustomers2(getCustomers(data)); - setLoading2(false); - }); - CustomerService.getCustomersMedium().then((data) => setCustomers3(data)); - ProductService.getProductsWithOrdersSmall().then((data) => setProducts(data)); - - initFilters1(); - }, []); - - const balanceTemplate = (rowData: Demo.Customer) => { - return ( -
- {formatCurrency(rowData.balance as number)} -
- ); - }; - - const getCustomers = (data: Demo.Customer[]) => { - return [...(data || [])].map((d) => { - d.date = new Date(d.date); - return d; - }); - }; - - const formatDate = (value: Date) => { - return value.toLocaleDateString('en-US', { - day: '2-digit', - month: '2-digit', - year: 'numeric' - }); - }; - - const formatCurrency = (value: number) => { - return value.toLocaleString('en-US', { - style: 'currency', - currency: 'USD' - }); - }; - - const initFilters1 = () => { - setFilters1({ - global: { value: null, matchMode: FilterMatchMode.CONTAINS }, - name: { - operator: FilterOperator.AND, - constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] - }, - 'country.name': { - operator: FilterOperator.AND, - constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] - }, - representative: { value: null, matchMode: FilterMatchMode.IN }, - date: { - operator: FilterOperator.AND, - constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }] - }, - balance: { - operator: FilterOperator.AND, - constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] - }, - status: { - operator: FilterOperator.OR, - constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] - }, - activity: { value: null, matchMode: FilterMatchMode.BETWEEN }, - verified: { value: null, matchMode: FilterMatchMode.EQUALS } - }); - setGlobalFilterValue1(''); - }; - - const countryBodyTemplate = (rowData: Demo.Customer) => { - return ( - - flag - {rowData.country.name} - - ); - }; - - const filterClearTemplate = (options: ColumnFilterClearTemplateOptions) => { - return ; - }; - - const filterApplyTemplate = (options: ColumnFilterApplyTemplateOptions) => { - return ; - }; - - const representativeBodyTemplate = (rowData: Demo.Customer) => { - const representative = rowData.representative; - return ( - - {representative.name} ((e.target as HTMLImageElement).src = 'https://www.primefaces.org/wp-content/uploads/2020/05/placeholder.png')} - width={32} - style={{ verticalAlign: 'middle' }} - /> - {representative.name} - - ); - }; - - const representativeFilterTemplate = (options: ColumnFilterElementTemplateOptions) => { - return ( - <> -
Agent Picker
- options.filterCallback(e.value)} optionLabel="name" placeholder="Any" className="p-column-filter" /> - - ); - }; - - const representativesItemTemplate = (option: any) => { - return ( -
- {option.name} - {option.name} -
- ); - }; - - const dateBodyTemplate = (rowData: Demo.Customer) => { - return formatDate(rowData.date); - }; - - const dateFilterTemplate = (options: ColumnFilterElementTemplateOptions) => { - return options.filterCallback(e.value, options.index)} dateFormat="mm/dd/yy" placeholder="mm/dd/yyyy" mask="99/99/9999" />; - }; - - const balanceBodyTemplate = (rowData: Demo.Customer) => { - return formatCurrency(rowData.balance as number); - }; - - const balanceFilterTemplate = (options: ColumnFilterElementTemplateOptions) => { - return options.filterCallback(e.value, options.index)} mode="currency" currency="USD" locale="en-US" />; - }; - - const statusBodyTemplate = (rowData: Demo.Customer) => { - return {rowData.status}; - }; - - const statusFilterTemplate = (options: ColumnFilterElementTemplateOptions) => { - return options.filterCallback(e.value, options.index)} itemTemplate={statusItemTemplate} placeholder="Select a Status" className="p-column-filter" showClear />; - }; - - const statusItemTemplate = (option: any) => { - return {option}; - }; - - const activityBodyTemplate = (rowData: Demo.Customer) => { - return ; - }; - - const activityFilterTemplate = (options: ColumnFilterElementTemplateOptions) => { - return ( - - options.filterCallback(e.value)} range className="m-3"> -
- {options.value ? options.value[0] : 0} - {options.value ? options.value[1] : 100} -
-
- ); - }; - - const verifiedBodyTemplate = (rowData: Demo.Customer) => { - return ( - - ); - }; - - const verifiedFilterTemplate = (options: ColumnFilterElementTemplateOptions) => { - return options.filterCallback(e.value)} />; - }; - - const toggleAll = () => { - if (allExpanded) collapseAll(); - else expandAll(); - }; - - const expandAll = () => { - let _expandedRows = {} as { [key: string]: boolean }; - products.forEach((p) => (_expandedRows[`${p.id}`] = true)); - - setExpandedRows(_expandedRows); - setAllExpanded(true); - }; - - const collapseAll = () => { - setExpandedRows([]); - setAllExpanded(false); - }; - - const amountBodyTemplate = (rowData: Demo.Customer) => { - return formatCurrency(rowData.amount as number); - }; - - const statusOrderBodyTemplate = (rowData: Demo.Customer) => { - return {rowData.status}; - }; - - const searchBodyTemplate = () => { - return
+ )} +
+
+ ); + }; + return (
{/* title section */} @@ -249,14 +286,15 @@ export default function CourseTheme() {

{themeInfo?.description}

- - {themeInfo?.created_at} + + {themeInfo?.created_at && new Date(themeInfo?.created_at).toISOString().slice(0, 10)}
- -
- -
+ {themeInfo?.image && ( +
+ +
+ )}
{breadcrumb}
@@ -309,6 +347,16 @@ export default function CourseTheme() {
{skeleton ? ( + ) : media ? ( + <> + + ) : ( rowIndex + 1} header="Номер" style={{ width: '20px' }}> diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index 7996d588..2acca02b 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -29,6 +29,7 @@ import useShortText from '@/hooks/useShortText'; import { ProgressSpinner } from 'primereact/progressspinner'; import { RadioButton } from 'primereact/radiobutton'; import { DataView } from 'primereact/dataview'; +import { displayType } from '@/types/displayType'; export default function Course() { const { setMessage, course, setCourses, contextFetchCourse } = useContext(LayoutContext); @@ -36,10 +37,6 @@ export default function Course() { objectURL?: string; } - interface forDisplayType { - streamTitle: string; - } - const [coursesValue, setValueCourses] = useState([]); const [hasCourses, setHasCourses] = useState(false); const [courseValue, setCourseValue] = useState({ title: '', description: '', video_url: '', image: '' }); @@ -56,7 +53,7 @@ export default function Course() { }); const [activeIndex, setActiveIndex] = useState(0); const [imageState, setImageState] = useState(null); - const [displayStrem, setDisplayStreams] = useState([]); + const [displayStrem, setDisplayStreams] = useState([]); const [editingLesson, setEditingLesson] = useState({ title: '', @@ -131,10 +128,6 @@ export default function Course() { }, 1000); }; - const handleDisplay = (value: forDisplayType[]) => { - setDisplayStreams(value); - }; - const handleFetchCourse = async (page = 1) => { const data = await fetchCourses(page, 0); toggleSkeleton(); @@ -308,6 +301,11 @@ export default function Course() { setActiveIndex(e.index); }; + const displayInfo = (value: displayType[]) => { + console.log('info ', value); + setDisplayStreams(value); + }; + useEffect(() => { contextFetchCourse(1); }, []); @@ -368,7 +366,7 @@ export default function Course() { const itemTemplate = (shablonData: any) => { return (
-
+
{/* Номер (rowIndex) можно добавить через внешний счетчик или props, но для DataView это сложнее */} {/* Заголовок */} @@ -530,6 +528,32 @@ export default function Course() {
+
+ {forStreamId?.title && ( +
+
+ + Тандалган курстун аталышы: {forStreamId?.title} +
+
+
+
+ {displayStrem?.length < 1 && Курска байлоо үчүн агымдарды тандаңыз} + {displayStrem.map((item, idx) => { + if (idx < 1) { + return ( + <> +
{item?.stream_title}...
+ + ); + } + // {displayStrem.length >= 3 && '...'} + })} +
+
+
+ )} +
{media ? ( <> {/* mobile table section */} {/* mobile table section */} - {false ? ( + {hasCourses ? ( <>
)} diff --git a/app/(main)/students/[connect_id]/[stream_id]/page.tsx b/app/(main)/students/[connect_id]/[stream_id]/page.tsx index 38c455a6..cdf33b14 100644 --- a/app/(main)/students/[connect_id]/[stream_id]/page.tsx +++ b/app/(main)/students/[connect_id]/[stream_id]/page.tsx @@ -56,7 +56,8 @@ export default function StudentList() { { id: 1, url: '/', - title: 'Башкы баракча', + title: '', + icon: true, parent_id: null }, { @@ -131,7 +132,6 @@ export default function StudentList() { <> {/* info section */}
-

{'Угуучулардын тизмеси'}

{breadcrumb}
diff --git a/app/components/lessons/LessonTyping.tsx b/app/components/lessons/LessonTyping.tsx index a008acf6..7d4e6efe 100644 --- a/app/components/lessons/LessonTyping.tsx +++ b/app/components/lessons/LessonTyping.tsx @@ -270,8 +270,9 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp {docShow ? ( ) : ( - documents.map((item: lessonType) => ( - <> + documents.map((item: lessonType) => { + console.log(item.created_at); + return <> selectedForEditing(id, type)} @@ -285,7 +286,7 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp urlForDownload="" /> - )) + }) )}
diff --git a/app/components/tables/StreamList.tsx b/app/components/tables/StreamList.tsx index 2972a785..10fd2519 100644 --- a/app/components/tables/StreamList.tsx +++ b/app/components/tables/StreamList.tsx @@ -10,8 +10,9 @@ import { NotFound } from '../NotFound'; import GroupSkeleton from '../skeleton/GroupSkeleton'; import Link from 'next/link'; import { streamsType } from '@/types/streamType'; +import { displayType } from '@/types/displayType'; -export default function StreamList({ callIndex, courseValue, isMobile, insideDisplayStreams }: { callIndex: number; courseValue: { id: number | null; title: string } | null; isMobile: boolean; insideDisplayStreams: ()=> void }) { +export default function StreamList({ callIndex, courseValue, isMobile, insideDisplayStreams }: { callIndex: number; courseValue: { id: number | null; title: string } | null; isMobile: boolean; insideDisplayStreams: (id:displayType[]) => void }) { interface mainStreamsType { connect_id: number | null; stream_id: number; @@ -73,13 +74,13 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis id_period: 2, semester: { name_kg: 'test' }, edu_form: { name_kg: 'test' } - }, + } ]; const [streams, setStreams] = useState([]); const [streamValues, setStreamValues] = useState<{ stream: streamsType[] }>({ stream: [] }); - const [displayStreams, setDisplayStreams] = useState<{ course_id: number; stream_id: number; info: string | null; stream_title: string }[] | any>([]); + const [displayStreams, setDisplayStreams] = useState([]); const [hasStreams, setHasStreams] = useState(false); - + const [selectedStreams, setSelectedStreams] = useState([]); const [skeleton, setSkeleton] = useState(false); const { setMessage } = useContext(LayoutContext); @@ -92,15 +93,17 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis }, 1000); }; - const profilactor = (data: { connect_id: number; course_id: number; stream_id: number; info: string | null }[]) => { - const newStreams: { course_id: number; stream_id: number; info: string | null }[] = []; - + const profilactor = (data: { connect_id: number; course_id: number; stream_id: number; info: string | null, subject_name: {name_kg:string} }[]) => { + const newStreams: { course_id: number; stream_id: number; info: string | null, stream_title: string }[] = []; + console.log(data); + data.forEach((item) => { if (item?.connect_id) { newStreams.push({ course_id: item?.course_id, stream_id: item.stream_id, - info: '' + info: '', + stream_title: item?.subject_name.name_kg }); } }); @@ -175,7 +178,6 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis } ); // const x = {streamTitle} - insideDisplayStreams(); } else { setStreamValues( (prev) => @@ -189,12 +191,10 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis useEffect(() => { console.log('отправляемый поток ', streamValues); - const forDisplay = streamValues.stream.map((item, idx) => { - if (idx <= 2) { + const forDisplay = streamValues.stream.filter((item, idx) => { + // if (idx <= 2) { return item; - } else { - return null; - } + // } }); setDisplayStreams(forDisplay); @@ -217,13 +217,15 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis } }, [streams]); - useEffect(()=> { + useEffect(() => { console.log(displayStreams); - },[displayStreams]) + insideDisplayStreams(displayStreams); + }, [displayStreams]); const itemTemplate = (item: mainStreamsType, index: number) => { const bgClass = item.connect_id ? 'bg-[var(--greenBgColor)] border-b border-[gray]' : index % 2 == 1 ? 'bg-[#f5f5f5]' : ''; - + console.log(item); + return (
@@ -339,13 +341,13 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis )}
- {courseValue?.title && ( + {/* {courseValue?.title && (
Тандалган курстун аталышы: {courseValue?.title}
- {/*
+
{streamValues.stream?.length < 1 && Курска байлоо үчүн агымдарды тандаңыз} @@ -354,9 +356,9 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis })}
{displayStreams.length >= 3 && '...'}
-
*/} +
- )} + )} */}
)}
diff --git a/hooks/useBreadCrumbs.tsx b/hooks/useBreadCrumbs.tsx index 86d3372c..923a1d92 100644 --- a/hooks/useBreadCrumbs.tsx +++ b/hooks/useBreadCrumbs.tsx @@ -57,11 +57,11 @@ export default function useBreadCrumbs(insideBreadCrumb: breadCrumbType[], curre
{index < breadCrumb.length - 1 ? ( - + {crumb.title} ) : ( - {crumb.title} + {crumb.title} )}
{index < breadCrumb.length - 1 && / } diff --git a/types/breadCrumbType.tsx b/types/breadCrumbType.tsx index 55c31ceb..a1cfb7a0 100644 --- a/types/breadCrumbType.tsx +++ b/types/breadCrumbType.tsx @@ -3,4 +3,5 @@ export interface breadCrumbType { url: string; title: string; parent_id: number | null; + icon?: boolean } diff --git a/types/displayType.tsx b/types/displayType.tsx new file mode 100644 index 00000000..1251a2b6 --- /dev/null +++ b/types/displayType.tsx @@ -0,0 +1,6 @@ +export interface displayType { + course_id: number; + stream_id: number; + info: string | null; + stream_title: string; +} From 1babdb1a49c5f7cbe841a121be3b4713b384d12f Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Fri, 29 Aug 2025 12:01:05 +0600 Subject: [PATCH 129/183] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE=D1=82=D0=BE=D0=BA?= =?UTF-8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/course/page.tsx | 16 +-- app/components/tables/StreamList.tsx | 149 ++++++++++++++++++--------- services/streams.tsx | 1 + 3 files changed, 111 insertions(+), 55 deletions(-) diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index 2acca02b..c2721867 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -528,16 +528,16 @@ export default function Course() {
-
+
{forStreamId?.title && (
-
- - Тандалган курстун аталышы: {forStreamId?.title} +
+ + Тандалган курстун аталышы: {forStreamId?.title}
-
-
-
+
+
+
{displayStrem?.length < 1 && Курска байлоо үчүн агымдарды тандаңыз} {displayStrem.map((item, idx) => { if (idx < 1) { @@ -630,7 +630,7 @@ export default function Course() { className="p-tabview p-tabview-nav p-tabview-selected p-tabview-panels p-tabview-panel" >
- {}} /> + displayInfo(value)} />
diff --git a/app/components/tables/StreamList.tsx b/app/components/tables/StreamList.tsx index 10fd2519..676ded19 100644 --- a/app/components/tables/StreamList.tsx +++ b/app/components/tables/StreamList.tsx @@ -12,7 +12,7 @@ import Link from 'next/link'; import { streamsType } from '@/types/streamType'; import { displayType } from '@/types/displayType'; -export default function StreamList({ callIndex, courseValue, isMobile, insideDisplayStreams }: { callIndex: number; courseValue: { id: number | null; title: string } | null; isMobile: boolean; insideDisplayStreams: (id:displayType[]) => void }) { +export default function StreamList({ callIndex, courseValue, isMobile, insideDisplayStreams }: { callIndex: number; courseValue: { id: number | null; title: string } | null; isMobile: boolean; insideDisplayStreams: (id: displayType[]) => void }) { interface mainStreamsType { connect_id: number | null; stream_id: number; @@ -24,6 +24,7 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis id_period: number; semester: { name_kg: string }; edu_form: { name_kg: string }; + courseValue?: number; } const shablon = [ @@ -82,6 +83,7 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis const [hasStreams, setHasStreams] = useState(false); const [selectedStreams, setSelectedStreams] = useState([]); const [skeleton, setSkeleton] = useState(false); + const [pendingChanges, setPendingChanges] = useState([]); const { setMessage } = useContext(LayoutContext); const showError = useErrorMessage(); @@ -93,10 +95,10 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis }, 1000); }; - const profilactor = (data: { connect_id: number; course_id: number; stream_id: number; info: string | null, subject_name: {name_kg:string} }[]) => { - const newStreams: { course_id: number; stream_id: number; info: string | null, stream_title: string }[] = []; + const profilactor = (data: { connect_id: number; course_id: number; stream_id: number; info: string | null; subject_name: { name_kg: string } }[]) => { + const newStreams: { course_id: number; stream_id: number; info: string | null; stream_title: string }[] = []; console.log(data); - + data.forEach((item) => { if (item?.connect_id) { newStreams.push({ @@ -108,15 +110,23 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis } }); - setStreamValues((prev) => ({ - ...prev, - stream: [...prev.stream, ...newStreams] - })); + // setStreamValues((prev) => ({ + // ...prev, + // stream: [...prev.stream, ...newStreams] + // })); + // setPendingChanges((prev)=> [...prev, ...newStreams]); + + setPendingChanges((prev) => { + const pendingIds = new Set(prev.map((p) => p.stream_id)); + const uniqueNewStreams = newStreams.filter((s) => !pendingIds.has(s.stream_id)); + return [...prev, ...uniqueNewStreams]; + }); }; const handleFetchStreams = async () => { const data = await fetchStreams(courseValue ? courseValue.id : null); - setStreamValues({ stream: [] }); + // setStreamValues({ stream: [] }); + setPendingChanges([]); if (data) { profilactor(data); @@ -140,7 +150,8 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis }; const handleConnect = async () => { - const data = await connectStreams(streamValues); + const data = await connectStreams({ stream: pendingChanges }); + // const data = await connectStreams(streamValues); if (data?.success) { toggleSkeleton(); @@ -160,48 +171,73 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis } }; - const handleEdit = (e: { checked: boolean }, id: number, title: string) => { + const handleEdit = (e: { checked: boolean }, item: mainStreamsType) => { + const { stream_id, subject_name } = item; + const isChecked = e.checked; + const forSentStreams = { course_id: courseValue!.id, - stream_id: id, + stream_id: stream_id, info: '', - stream_title: title + stream_title: subject_name.name_kg }; - if (e.checked) { - // profilactor(); - setStreamValues( - (prev) => - prev && { - ...prev, - stream: [...prev.stream, forSentStreams] - } - ); - // const x = {streamTitle} - } else { - setStreamValues( - (prev) => - prev && { - ...prev, - stream: [...prev.stream.filter((item) => item?.stream_id !== id)] - } - ); - } - }; + setPendingChanges((prev) => { + const isCurrentlyPending = prev.some((s) => s.stream_id === stream_id); - useEffect(() => { - console.log('отправляемый поток ', streamValues); - const forDisplay = streamValues.stream.filter((item, idx) => { - // if (idx <= 2) { - return item; - // } + if (isChecked) { + // Если чекбокс отмечается + if (!isCurrentlyPending) { + return [...prev, forSentStreams]; + } + } else { + // Если чекбокс снимается, удаляем объект из временного состояния + return prev.filter((s) => s.stream_id !== stream_id); + } + + return prev; }); - setDisplayStreams(forDisplay); - }, [streamValues]); + // if (e.checked) { + // // profilactor(); + // setStreamValues( + // (prev) => + // prev && { + // ...prev, + // stream: [...prev.stream, forSentStreams] + // } + // ); + // // const x = {streamTitle} + // } else { + // setStreamValues( + // (prev) => + // prev && { + // ...prev, + // stream: [...prev.stream.filter((item) => item?.stream_id !== id)] + // } + // ); + // } + }; + + // useEffect(() => { + // console.log('отправляемый поток ', streamValues); + // // const forDisplay = streamValues.stream.filter((item, idx) => { + // // // if (idx <= 2) { + // // return item; + // // // } + // // }); + + // // setDisplayStreams(forDisplay); + + // }, [streamValues]); + + useEffect(() => { + console.log('отправляемый поток ', pendingChanges); + setDisplayStreams(pendingChanges); // Или filter/map по вашему усмотрению + }, [pendingChanges]); useEffect(() => { - setStreamValues({ stream: [] }); + // setStreamValues({ stream: [] }); setDisplayStreams([]); toggleSkeleton(); if (courseValue?.id) { @@ -224,8 +260,7 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis const itemTemplate = (item: mainStreamsType, index: number) => { const bgClass = item.connect_id ? 'bg-[var(--greenBgColor)] border-b border-[gray]' : index % 2 == 1 ? 'bg-[#f5f5f5]' : ''; - console.log(item); - + return (
@@ -235,10 +270,30 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis { + // handleEdit(e.target, item.stream_id, item?.subject_name.name_kg); + // setStreams((prev) => prev.map((el) => (el.stream_id === item.stream_id ? { ...el, connect_id: el.connect_id ? null : 1 } : el))); + // }} + // vtoroy gde obratno ne ot + // checked={ + // // Проверяем, есть ли этот элемент в вашем массиве для отправки (streamValues). + // // Это отвечает за "включение" чекбокса для новых элементов. + // streamValues?.stream?.some((s) => s.stream_id === item.stream_id) || + // // ...ИЛИ если этот элемент уже пришёл с сервера (есть connect_id), НО + // // его НЕТ в вашем массиве для отправки, что означает, что вы его не "отжали". + // (Boolean(item.connect_id) && !streamValues?.stream?.some((s) => s.stream_id === item.stream_id)) + // } + // checked={ + // // Проверяем, есть ли объект в нашем буфере + // pendingChanges.some((s) => s.stream_id === item.stream_id) || + // // ...ИЛИ если его нет в буфере, проверяем, есть ли он на сервере + // (Boolean(item.connect_id) && !pendingChanges.some((s) => s.stream_id === item.stream_id)) + // } + checked={pendingChanges.some((s) => s.stream_id === item.stream_id)} onChange={(e) => { - handleEdit(e.target, item.stream_id, item?.subject_name.name_kg); - setStreams((prev) => prev.map((el) => (el.stream_id === item.stream_id ? { ...el, connect_id: el.connect_id ? null : 1 } : el))); + handleEdit(e.target, item); }} /> diff --git a/services/streams.tsx b/services/streams.tsx index fe0a1e54..b69037c4 100644 --- a/services/streams.tsx +++ b/services/streams.tsx @@ -17,6 +17,7 @@ export const fetchStreams = async (id: number | null) => { } }; +// export const connectStreams = async (value: {stream: streamsType[]}) => { export const connectStreams = async (value: {stream: streamsType[]}) => { console.log(value); From 6288b8713deeb370ebed30e68ad67e663012a40b Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Fri, 29 Aug 2025 17:12:45 +0600 Subject: [PATCH 130/183] =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=81=D1=82=D0=BA?= =?UTF-8?q?=D0=B0=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D1=8B=20?= =?UTF-8?q?=D0=BA=D1=83=D1=80=D1=81=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../course/[courseTheme]/[lessons]/page.tsx | 8 +- app/(main)/course/page.tsx | 21 +- .../[studentThemeId]/[lesson_id]/page.tsx | 6 +- app/components/PDFBook.tsx | 245 +++++++++--------- app/components/cards/LessonCard.tsx | 5 +- app/components/lessons/LessonTyping.tsx | 22 +- app/components/tables/StreamList.tsx | 17 +- app/globals.css | 11 + hooks/useShortText.tsx | 2 +- layout/AppMenu.tsx | 4 - layout/AppTopbar.tsx | 3 +- schemas/authSchema.tsx | 17 +- services/courses.tsx | 2 +- 13 files changed, 190 insertions(+), 173 deletions(-) diff --git a/app/(main)/course/[courseTheme]/[lessons]/page.tsx b/app/(main)/course/[courseTheme]/[lessons]/page.tsx index f87161c4..8c91fe8a 100644 --- a/app/(main)/course/[courseTheme]/[lessons]/page.tsx +++ b/app/(main)/course/[courseTheme]/[lessons]/page.tsx @@ -241,7 +241,7 @@ export default function Lesson() { {/* CKEDITOR */} ) : ( <> -
+
- displayInfo(value)} /> + displayInfo(value)} toggleIndex={()=> setActiveIndex(0)}/>
@@ -726,7 +737,7 @@ export default function Course() {
{/* STREAMS SECTION */}
- displayInfo(value)} /> + displayInfo(value)} toggleIndex={()=> {}}/>
)} diff --git a/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/[lesson_id]/page.tsx b/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/[lesson_id]/page.tsx index a97c4d0f..5cece8c4 100644 --- a/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/[lesson_id]/page.tsx +++ b/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/[lesson_id]/page.tsx @@ -244,7 +244,7 @@ export default function StudentLessons() { {/* DOC */} ([]); const [skeleton, setSkeleton] = useState(false); + const [hasPdf, setHasPdf] = useState(false); const containerRef = useRef(null); const [bookSize, setBookSize] = useState({ width: 0, height: 0, aspectRatio: 0 }); const FIXED_BOOK_HEIGHT = 800; // Фиксированная высота книги - const media = useMediaQuery('(max-width: 640px)'); useEffect(() => { @@ -52,6 +56,11 @@ export default function PDFViewer({ url }: { url: string }) { if (!context) { // в последний раз добавил эту строку если что + setHasPdf(true); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Документти жүктөө же көрсөтүү катасы' } + }); throw new Error('Canvas context is not supported.'); } @@ -75,16 +84,24 @@ export default function PDFViewer({ url }: { url: string }) { // Обновляем состояние с данными только после успешной загрузки setPages(tempPages); - if (containerRef.current) { // в последний раз добавил эту проверку если что + if (containerRef.current) { + // в последний раз добавил эту проверку если что setBookSize({ width: containerRef.current.offsetWidth, height: FIXED_BOOK_HEIGHT, aspectRatio: aspectRatio }); + setHasPdf(false); } setSkeleton(false); } catch (error) { console.error('Ошибка при загрузке или рендеринге PDF:', error); + setHasPdf(true); + setSkeleton(false); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Документти жүктөө же көрсөтүү катасы' } + }); } finally { setSkeleton(false); } @@ -102,37 +119,13 @@ export default function PDFViewer({ url }: { url: string }) { setBookSize({ width, height, aspectRatio: width / height }); } }; - - // Вызываем сразу после монтирования updateBookSize(); - window.addEventListener('resize', updateBookSize); return () => window.removeEventListener('resize', updateBookSize); }, [bookSize.aspectRatio]); - // useEffect(() => { - // const updateBookSize = () => { - // if (containerRef.current && bookSize.aspectRatio > 0) { - // const containerWidth = containerRef.current.offsetWidth; - // const bookHeight = containerWidth / bookSize.aspectRatio; // пропорциональная высота - - // setBookSize((prev) => ({ - // ...prev, - // width: containerWidth, - // height: bookHeight - // })); - // } - // }; - - // updateBookSize(); // сразу после монтирования - // window.addEventListener('resize', updateBookSize); - // return () => window.removeEventListener('resize', updateBookSize); - // }, [bookSize.aspectRatio]); - return ( - //
- {/* style={{ width: '100%', backgroundColor: 'red', maxWidth: 1000, margin: '0 auto', border: 'solid 1px' }} */} - {skeleton ? ( - - ) : // - // {pages.map((page, index) => ( - //
- //
- // {' '} - // {/* Добавляем класс для содержимого страницы */} - // {page} - //
- //
- // ))} - //
- media ? ( + {hasPdf ? ( + + ) : ( <> - {}} - onChangeOrientation={() => {}} - onChangeState={() => {}} - onInit={() => {}} - onUpdate={() => {}} - startPage={0} - drawShadow={true} - flippingTime={900} - showPageCorners={true} - disableFlipByClick={false} - > - {pages.map((page, index) => ( -
-
- {' '} - {/* Добавляем класс для содержимого страницы */} - {page} + {skeleton ? ( +
+ +
+ ) : media ? ( + <> + {}} + onChangeOrientation={() => {}} + onChangeState={() => {}} + onInit={() => {}} + onUpdate={() => {}} + startPage={0} + drawShadow={true} + flippingTime={900} + showPageCorners={true} + disableFlipByClick={false} + > + {pages.map((page, index) => ( +
+
+ {' '} + {/* Добавляем класс для содержимого страницы */} + {page} +
+
+ ))} +
+ + ) : ( + {}} + onChangeOrientation={() => {}} + onChangeState={() => {}} + onInit={() => {}} + onUpdate={() => {}} + startPage={0} + drawShadow={true} + flippingTime={900} + showPageCorners={true} + disableFlipByClick={false} + > + {pages.map((page, index) => ( +
+
+ {' '} + {/* Добавляем класс для содержимого страницы */} + {page} +
-
- ))} - + ))} + + )} - ) : ( - {}} - onChangeOrientation={() => {}} - onChangeState={() => {}} - onInit={() => {}} - onUpdate={() => {}} - startPage={0} - drawShadow={true} - flippingTime={900} - showPageCorners={true} - disableFlipByClick={false} - > - {pages.map((page, index) => ( -
-
- {' '} - {/* Добавляем класс для содержимого страницы */} - {page} -
-
- ))} -
)}
//
diff --git a/app/components/cards/LessonCard.tsx b/app/components/cards/LessonCard.tsx index 7e7a679e..7d55c2fd 100644 --- a/app/components/cards/LessonCard.tsx +++ b/app/components/cards/LessonCard.tsx @@ -32,6 +32,7 @@ export default function LessonCard({ urlForDownload: string; }) { const shortTitle = useShortText(cardValue.title, 10); + const shortDescription = useShortText(cardValue.desctiption ? cardValue.desctiption : '', 17); const [progressSpinner, setProgressSpinner] = useState(false); const toggleSpinner = () => { @@ -113,9 +114,9 @@ export default function LessonCard({
{/*
{!cardValue.photo && }
*/} -
{shortTitle}
+
{shortTitle}
{type.typeValue === 'link' && {cardValue?.url}} -
{cardValue?.desctiption && cardValue.desctiption}
+
{cardValue?.desctiption && cardValue?.desctiption !== 'null' && shortDescription}
{status === 'working' && type.typeValue !== 'video' && (
diff --git a/app/components/lessons/LessonTyping.tsx b/app/components/lessons/LessonTyping.tsx index 7d4e6efe..b85f1c4e 100644 --- a/app/components/lessons/LessonTyping.tsx +++ b/app/components/lessons/LessonTyping.tsx @@ -22,6 +22,7 @@ import PDFViewer from '../PDFBook'; import { useMediaQuery } from '@/hooks/useMediaQuery'; import { useRouter } from 'next/navigation'; import { ProgressSpinner } from 'primereact/progressspinner'; +import useShortText from '@/hooks/useShortText'; export default function LessonTyping({ mainType, courseId, lessonId }: { mainType: string; courseId: string | null; lessonId: string | null }) { // types @@ -103,7 +104,7 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp const [visible, setVisisble] = useState(false); const [editingLesson, setEditingLesson] = useState(null); const [progressSpinner, setProgressSpinner] = useState(false); - + // functions const handleUpdate = () => { if (selectType === 'doc') { @@ -116,8 +117,6 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp }; const selectedForEditing = (id: number, type: string) => { - console.log('Открытие окна... ', id, type); - setSelectId(id); setSelectType(type); editing(type, id); @@ -133,7 +132,7 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp const lesson: editingType = type === 'doc' - ? { key: 'documents', file: 'document_path', url: '', link: '', video_type_id: 1 } + ? { key: 'documents', file: 'document', url: '', link: '', video_type_id: 1 } : type === 'url' ? { key: 'links', file: '', url: 'url', link: '', video_type_id: 1 } : type === 'video' @@ -149,7 +148,7 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp setEditingLesson({ title: arr.title, - description: arr?.description, + description: arr?.description, document: arr[lesson.file], url: arr[lesson.url], video_link: arr[lesson.link], @@ -271,7 +270,6 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp ) : ( documents.map((item: lessonType) => { - console.log(item.created_at); return <> { + console.log(documents); + },[documents]) + // update document const handleUpdateLesson = async () => { const token = getToken('access_token'); @@ -810,16 +812,14 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp return { name: item.title, status: item.is_link, id: item.id }; }); - setVideoSelect(forSelect); - console.log(forSelect); - + setVideoSelect(forSelect); setSelectedCity(forSelect[0]); } }, [videoTypes]); return (
- +
{selectType === 'doc' ? ( @@ -851,7 +851,7 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp }} /> {errors.title?.message} - setEditingLesson((prev) => prev && { ...prev, description: e.target.value })} className="w-full" /> + setEditingLesson((prev) => prev && { ...prev, description: e.target.value })} className="w-full" /> ) : selectType === 'url' ? ( <> diff --git a/app/components/tables/StreamList.tsx b/app/components/tables/StreamList.tsx index 676ded19..5387d0d1 100644 --- a/app/components/tables/StreamList.tsx +++ b/app/components/tables/StreamList.tsx @@ -12,7 +12,7 @@ import Link from 'next/link'; import { streamsType } from '@/types/streamType'; import { displayType } from '@/types/displayType'; -export default function StreamList({ callIndex, courseValue, isMobile, insideDisplayStreams }: { callIndex: number; courseValue: { id: number | null; title: string } | null; isMobile: boolean; insideDisplayStreams: (id: displayType[]) => void }) { +export default function StreamList({ callIndex, courseValue, isMobile, insideDisplayStreams, toggleIndex }: { callIndex: number; courseValue: { id: number | null; title: string } | null; isMobile: boolean; insideDisplayStreams: (id: displayType[]) => void, toggleIndex: ()=> void }) { interface mainStreamsType { connect_id: number | null; stream_id: number; @@ -360,9 +360,6 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis icon="pi pi-link" onClick={() => { handleConnect(); - // setEditMode(false); - // clearValues(); - // setFormVisible(true); }} />
@@ -375,14 +372,24 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis ) : (
{isMobile && ( -
+
)} diff --git a/app/globals.css b/app/globals.css index 0879a4fb..122cec4b 100644 --- a/app/globals.css +++ b/app/globals.css @@ -567,4 +567,15 @@ h1, h2, h3, h4, h5, h6 { .p-fileupload-choose{ padding: 4px 5px; } +} + +/* menu item , sidebar */ +.p-menuitem-link, .p-menuitem-icon{ + font-size: 13px !important; +} + +@media screen and (max-width: 640px) { + .layout-menuitem-text{ + font-size: 13px !important; + } } \ No newline at end of file diff --git a/hooks/useShortText.tsx b/hooks/useShortText.tsx index c224d81b..2ee45c6d 100644 --- a/hooks/useShortText.tsx +++ b/hooks/useShortText.tsx @@ -21,7 +21,7 @@ export default function useShortText(text: string, textLength: number) { <> {isLength ? ( -
+
{resultText}
diff --git a/layout/AppMenu.tsx b/layout/AppMenu.tsx index 64225a25..85d1f9d0 100644 --- a/layout/AppMenu.tsx +++ b/layout/AppMenu.tsx @@ -93,11 +93,7 @@ const AppMenu = () => { label: item.title, id: item.id, to: `/course/${clickedCourseId}/${item.id}`, - command: () => { - console.log('clicked theme', item.id); - } })); - console.log(newThemes); setCourseList((prev) => prev.map((course) => diff --git a/layout/AppTopbar.tsx b/layout/AppTopbar.tsx index 04bb88ae..c2ab5c0b 100644 --- a/layout/AppTopbar.tsx +++ b/layout/AppTopbar.tsx @@ -39,7 +39,7 @@ const AppTopbar = forwardRef((props, ref) => { { label: '', template: ( -
+
{user?.last_name} {user?.name} @@ -51,6 +51,7 @@ const AppTopbar = forwardRef((props, ref) => { { label: 'Чыгуу', icon: 'pi pi-sign-out', + className: 'text-[12px]', items: [], command: () => { logout({ setUser, setGlobalLoading }); diff --git a/schemas/authSchema.tsx b/schemas/authSchema.tsx index 6d8159a5..28a7dc0b 100644 --- a/schemas/authSchema.tsx +++ b/schemas/authSchema.tsx @@ -8,15 +8,16 @@ export const schema = yup.object().shape({ /^[a-zA-Z0-9._%+-]+@oshsu\.kg$/, 'email "@oshsu.kg" форматында болуш керек' ) - .matches( - /^[a-zA-Z0-9._%+-]+@oshsu\.kg$/, // Стандартный формат email (только буквы, цифры, точки, подчеркивания и плюсы) - 'email "@oshsu.kg" форматында жана тыюу салынган символдор камтылбашы керек' - ), + // .matches( + // /^[a-zA-Z0-9._%+-]+@oshsu\.kg$/, // Стандартный формат email (только буквы, цифры, точки, подчеркивания и плюсы) + // 'email "@oshsu.kg" форматында жана тыюу салынган символдор камтылбашы керек' + // ) + , password: yup.string() .required('Сырсөз талап кылынат!') .max(20, 'Сырсөз эң көп дегенде 20 белгиден турушу керек') - .matches( - /^[^!@#$%^&*()_+={}[\]:;"'`<>,.?/\\|]*$/, - 'Сырсөздө тыюу салынган символдор камтылбашы керек' - ), + // .matches( + // /^[^!@#$%^&*()_+={}[\]:;"'`<>,.?/\\|]*$/, + // 'Сырсөздө тыюу салынган символдор камтылбашы керек' + // ), }); \ No newline at end of file diff --git a/services/courses.tsx b/services/courses.tsx index 12e7ad26..46fc15dc 100644 --- a/services/courses.tsx +++ b/services/courses.tsx @@ -281,7 +281,7 @@ export const updateLesson = async (type: string, token: string | null, course_id } else if(type === 'doc'){ url = `/v1/teacher/document/update?lesson_id=${lesson_id}&document_id=${contentId}&document=${value.file}`; formData.append('lesson_id', String(lesson_id)); - formData.append('document', value.file); + formData.append('document', value.file || ''); formData.append('document_id', String(contentId)); formData.append('title', String(value.title)); formData.append('description', String(value.description)); From 53add8334bc4fedb113149589acfcb4d1ab1582e Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Sat, 30 Aug 2025 10:41:12 +0600 Subject: [PATCH 131/183] =?UTF-8?q?=D0=92=D0=B8=D0=B4=D0=B5=D0=BE=20=D0=BA?= =?UTF-8?q?=D0=B0=D1=80=D1=82=D0=BE=D1=87=D0=BA=D0=B8=20=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D0=B6=D0=B0=D1=8E=D1=82=D1=81=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/VideoPlay.tsx | 29 +++++---- app/components/cards/LessonCard.tsx | 26 ++++---- app/components/lessons/LessonTyping.tsx | 84 ++++++++++++++++++------- app/globals.css | 5 +- services/courses.tsx | 2 +- types/lessonType.tsx | 1 + 6 files changed, 95 insertions(+), 52 deletions(-) diff --git a/app/components/VideoPlay.tsx b/app/components/VideoPlay.tsx index e38163d8..fb87d29f 100644 --- a/app/components/VideoPlay.tsx +++ b/app/components/VideoPlay.tsx @@ -1,21 +1,29 @@ 'use client'; -import { faPlay} from "@fortawesome/free-solid-svg-icons"; +import { faPlay } from '@fortawesome/free-solid-svg-icons'; -import Image from "next/image"; -import { useState } from "react"; +import Image from 'next/image'; +import { useState } from 'react'; import { Dialog } from 'primereact/dialog'; // import 'primereact/resources/themes/lara-light-blue/theme.css'; // или другая тема import 'primereact/resources/primereact.min.css'; import 'primeicons/primeicons.css'; -import MyFontAwesome from "./MyFontAwesome"; +import MyFontAwesome from './MyFontAwesome'; export default function VideoPlay() { const [videoCall, setVideoCall] = useState(false); return (
- {if (!videoCall) return; setVideoCall(false); }}> + { + if (!videoCall) return; + setVideoCall(false); + }} + >
+
+
{videoShow ? ( ) : ( @@ -670,7 +702,8 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp lessonDate={'xx-xx'} urlForPDF={() => ''} urlForDownload="" - /> + videoVisible={()=> handleVideoCall(String(item?.link))} + /> )) )} @@ -789,6 +822,8 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp if (mainType === 'doc') handleFetchDoc(); if (mainType === 'link') handleFetchLink(); if (mainType === 'video') { + console.log(video); + handleFetchVideo(); handleVideoType(); } @@ -812,7 +847,7 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp return { name: item.title, status: item.is_link, id: item.id }; }); - setVideoSelect(forSelect); + setVideoSelect(forSelect); setSelectedCity(forSelect[0]); } }, [videoTypes]); @@ -851,7 +886,12 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp }} /> {errors.title?.message} - setEditingLesson((prev) => prev && { ...prev, description: e.target.value })} className="w-full" /> + setEditingLesson((prev) => prev && { ...prev, description: e.target.value })} + className="w-full" + /> ) : selectType === 'url' ? ( <> diff --git a/app/globals.css b/app/globals.css index 122cec4b..d23f6aac 100644 --- a/app/globals.css +++ b/app/globals.css @@ -539,8 +539,9 @@ h1, h2, h3, h4, h5, h6 { -moz-border-radius: 0px 0px 200px 200px; } -.udalismelo{ - background-color: #cde7e3; +.lesson-card-border { + border: solid 2px rgb(224, 224, 224); + } .poka{ diff --git a/services/courses.tsx b/services/courses.tsx index 46fc15dc..12e7ad26 100644 --- a/services/courses.tsx +++ b/services/courses.tsx @@ -281,7 +281,7 @@ export const updateLesson = async (type: string, token: string | null, course_id } else if(type === 'doc'){ url = `/v1/teacher/document/update?lesson_id=${lesson_id}&document_id=${contentId}&document=${value.file}`; formData.append('lesson_id', String(lesson_id)); - formData.append('document', value.file || ''); + formData.append('document', value.file); formData.append('document_id', String(contentId)); formData.append('title', String(value.title)); formData.append('description', String(value.description)); diff --git a/types/lessonType.tsx b/types/lessonType.tsx index 1ed6e5dd..514cc476 100644 --- a/types/lessonType.tsx +++ b/types/lessonType.tsx @@ -12,4 +12,5 @@ export interface lessonType { document_path?: string; url?: string; photo?: string; + link?:string; } From 9a26320cf8cf2c98696d4dda7202a0702557a5d7 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Sat, 30 Aug 2025 11:07:36 +0600 Subject: [PATCH 132/183] =?UTF-8?q?=D0=92=D0=B8=D0=B4=D0=B5=D0=BE=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D0=B1=D0=B0=D1=82=D1=8B=D0=B2=D0=B0=D0=B5?= =?UTF-8?q?=D1=82=20=D1=80=D0=B0=D0=B7=D0=BD=D1=8B=D0=B5=20=D1=81=D1=81?= =?UTF-8?q?=D1=8B=D0=BB=D0=BA=D0=B8=20=D1=8E=D1=82=D1=83=D0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/lessons/LessonTyping.tsx | 39 +++++++++++++++++++++---- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/app/components/lessons/LessonTyping.tsx b/app/components/lessons/LessonTyping.tsx index ca66dbca..a6d53a8a 100644 --- a/app/components/lessons/LessonTyping.tsx +++ b/app/components/lessons/LessonTyping.tsx @@ -579,10 +579,37 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp }; const handleVideoCall = (value: string | null) => { - console.log('value', value); - setVideoLink(typeof value === 'string' ? value : ''); + if (!value) { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Видеону иштетүүдө ката', detail: '' } + }); + } + + const url = new URL(typeof value === 'string' ? value : ''); + let videoId = null; + + if (url.hostname === 'youtu.be') { + // короткая ссылка, видео ID — в пути + videoId = url.pathname.slice(1); // убираем первый слеш + } else if (url.hostname === 'www.youtube.com' || url.hostname === 'youtube.com') { + // стандартная ссылка, видео ID в параметре v + videoId = url.searchParams.get('v'); + } + + if (!videoId) { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Видеону иштетүүдө ката', detail: '' } + }); + return null; // не удалось получить ID + } + // return `https://www.youtube.com/embed/${videoId}`; + + console.log('value', videoId); + setVideoLink(`https://www.youtube.com/embed/${videoId}`); setVideoCall(true); - } + }; const videoSection = () => { return ( @@ -702,8 +729,8 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp lessonDate={'xx-xx'} urlForPDF={() => ''} urlForDownload="" - videoVisible={()=> handleVideoCall(String(item?.link))} - /> + videoVisible={() => handleVideoCall(String(item?.link))} + /> )) )} @@ -823,7 +850,7 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp if (mainType === 'link') handleFetchLink(); if (mainType === 'video') { console.log(video); - + handleFetchVideo(); handleVideoType(); } From 33877ad95665023c349ef0a931acec24f6c915ca Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Sat, 30 Aug 2025 12:53:13 +0600 Subject: [PATCH 133/183] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BE=D0=BF=D0=BE=D0=BB=D0=BD?= =?UTF-8?q?=D0=B8=D1=82=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D1=85=20=D1=84=D1=83?= =?UTF-8?q?=D0=BD=D0=BA=D1=86=D0=B8=D0=B8=20=D1=83=D1=80=D0=BE=D0=BA=D0=BE?= =?UTF-8?q?=D0=B2,=20=D0=B0=D0=B4=D0=B0=D0=BF=D1=82=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=B8=D0=BD=D0=BF=D1=83=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/course/page.tsx | 5 +- app/components/lessons/LessonTyping.tsx | 70 ++++++++++++++++++++----- app/components/popUp/Redacting.tsx | 2 +- app/components/tables/StreamList.tsx | 5 +- app/globals.css | 15 +++++- types/fileuploadPreview.tsx | 3 ++ 6 files changed, 77 insertions(+), 23 deletions(-) create mode 100644 types/fileuploadPreview.tsx diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index 41f8dd1f..fe352508 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -30,13 +30,10 @@ import { ProgressSpinner } from 'primereact/progressspinner'; import { RadioButton } from 'primereact/radiobutton'; import { DataView } from 'primereact/dataview'; import { displayType } from '@/types/displayType'; +import { FileWithPreview } from '@/types/fileuploadPreview'; export default function Course() { const { setMessage, course, setCourses, contextFetchCourse } = useContext(LayoutContext); - interface FileWithPreview extends File { - objectURL?: string; - } - const [coursesValue, setValueCourses] = useState([]); const [hasCourses, setHasCourses] = useState(false); const [courseValue, setCourseValue] = useState({ title: '', description: '', video_url: '', image: '' }); diff --git a/app/components/lessons/LessonTyping.tsx b/app/components/lessons/LessonTyping.tsx index a6d53a8a..128b92ca 100644 --- a/app/components/lessons/LessonTyping.tsx +++ b/app/components/lessons/LessonTyping.tsx @@ -2,7 +2,7 @@ import { LayoutContext } from '@/layout/context/layoutcontext'; import { Button } from 'primereact/button'; -import { FileUpload } from 'primereact/fileupload'; +import { FileUpload, FileUploadSelectEvent } from 'primereact/fileupload'; import { InputText } from 'primereact/inputtext'; import React, { useContext, useEffect, useRef, useState } from 'react'; import LessonCard from '../cards/LessonCard'; @@ -24,6 +24,7 @@ import { useRouter } from 'next/navigation'; import { ProgressSpinner } from 'primereact/progressspinner'; import useShortText from '@/hooks/useShortText'; import { Dialog } from 'primereact/dialog'; +import { FileWithPreview } from '@/types/fileuploadPreview'; export default function LessonTyping({ mainType, courseId, lessonId }: { mainType: string; courseId: string | null; lessonId: string | null }) { // types @@ -98,6 +99,7 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp video_link: '' }); const [videoShow, setVideoShow] = useState(false); + const [imageState, setImageState] = useState(null); // auxiliary const showError = useErrorMessage(); @@ -107,6 +109,7 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp const [visible, setVisisble] = useState(false); const [editingLesson, setEditingLesson] = useState(null); const [progressSpinner, setProgressSpinner] = useState(false); + const [additional, setAdditional] = useState<{ doc: boolean; link: boolean; video: boolean }>({ doc: false, link: false, video: false }); // functions const handleUpdate = () => { @@ -164,6 +167,7 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp const fileUploadRef = useRef(null); const clearFile = () => { fileUploadRef.current?.clear(); + setImageState(null); if (visible) { setEditingLesson( (prev) => @@ -257,11 +261,16 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp }} /> {errors.title?.message} + {additional.doc && setDocValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" />} - setDocValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" /> -
+
{/*
@@ -433,12 +442,20 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp }} /> {errors.title?.message} - - setLinksValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" /> -
-
-
@@ -611,6 +628,12 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp setVideoCall(true); }; + const onSelect = (e: FileUploadSelectEvent & { files: FileWithPreview[] }) => { + if (e.files.length > 0) { + setImageState(e.files[0].objectURL); + } + }; + const videoSection = () => { return (
@@ -675,10 +698,29 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp }} /> {errors.title?.message} - - setVideoValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" /> -
-
+ {additional.video && ( +
+ setVideoValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" /> +
+
+ + {imageState &&
+
+ {imageState ? : } +
+
+
+ )} + +
+ {/*
diff --git a/app/components/popUp/Redacting.tsx b/app/components/popUp/Redacting.tsx index 20acd346..39988e4f 100644 --- a/app/components/popUp/Redacting.tsx +++ b/app/components/popUp/Redacting.tsx @@ -8,7 +8,7 @@ export default function Redacting({ redactor, textSize }: {redactor: MenuItem[], return (
- menuLeft.current?.toggle(event)} aria-controls="popup_menu_left" aria-haspopup /> + menuLeft.current?.toggle(event)} aria-controls="popup_menu_left" aria-haspopup /> {item?.teacher?.name} */}
- Үйрөтүлүү тили: + Окутуу тили: {item?.language?.name}
@@ -315,7 +316,7 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis
Период: - {item?.id_period} + {item?.period.name_kg}
diff --git a/app/globals.css b/app/globals.css index d23f6aac..a7321de2 100644 --- a/app/globals.css +++ b/app/globals.css @@ -4,8 +4,10 @@ :root { --bodyFonts: "Jost", sans-serif; --mainColor: #08A9E6; - /* --mainBgColor: #e6eef1; */ - --mainBgColor: #fff; + --mainBorder: #83d6f4; + --mainBgColor: #e6eef1; + /* --mainBgColor: #fff; */ + --redColor: #EC272F; --redBgColor: #EC272F1A; --greenColor: rgb(46, 192, 46); @@ -579,4 +581,13 @@ h1, h2, h3, h4, h5, h6 { .layout-menuitem-text{ font-size: 13px !important; } +} + +/* input */ + +@media screen and (max-width: 640px) { + .p-inputtext{ + font-size: 14px !important; + padding: 8px; + } } \ No newline at end of file diff --git a/types/fileuploadPreview.tsx b/types/fileuploadPreview.tsx new file mode 100644 index 00000000..f1b5d78c --- /dev/null +++ b/types/fileuploadPreview.tsx @@ -0,0 +1,3 @@ +export interface FileWithPreview extends File { + objectURL?: string; +} From 0f15eca0d3c2c112ff458bbe4e235fe87e6e76ac Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Sat, 30 Aug 2025 16:39:12 +0600 Subject: [PATCH 134/183] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BD=D0=B0=D0=B2=D0=B8=D0=B3?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B8=20=D1=81=D1=82=D1=83=D0=B4=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[studentThemeId]/[lesson_id]/page.tsx | 5 ++++- app/(student)/teaching/page.tsx | 5 ++++- app/components/cards/ItemCard.tsx | 17 +++++++++-------- app/components/tables/StreamList.tsx | 2 +- layout/AppMenu.tsx | 4 ++-- services/streams.tsx | 2 +- 6 files changed, 21 insertions(+), 14 deletions(-) diff --git a/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/[lesson_id]/page.tsx b/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/[lesson_id]/page.tsx index 5cece8c4..6a301832 100644 --- a/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/[lesson_id]/page.tsx +++ b/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/[lesson_id]/page.tsx @@ -19,6 +19,9 @@ import { useContext, useEffect, useState } from 'react'; export default function StudentLessons() { const { lesson_id } = useParams(); + const params = useParams(); + console.log(params); + const router = useRouter(); const [activeIndex, setActiveIndex] = useState(0); @@ -119,7 +122,7 @@ export default function StudentLessons() { const navigateMainLesson = async (id: number | null) => { setSkeleton(true); - router.push(`/teaching/lesson/${id}`); + router.push(`/teaching/lesson/${lesson_id}/${id}`); }; const handleTabChange = (e: TabViewChange) => { diff --git a/app/(student)/teaching/page.tsx b/app/(student)/teaching/page.tsx index 3cffc3d0..6dfa66af 100644 --- a/app/(student)/teaching/page.tsx +++ b/app/(student)/teaching/page.tsx @@ -47,6 +47,7 @@ export default function Teaching() { toggleSkeleton(); if (data) { // валидность проверить + console.log(data); setLessons(data); setHasLessons(false); } else { @@ -104,7 +105,9 @@ export default function Teaching() { const selected = lessons[selectedSort.code]; displayData = selected && selected.semester ? [selected] : []; } - + console.log('streams', displayData); + console.log('connection', connection); + // превращаем в jsx const x = displayData.map((semester: any, sIdx: number) => (
diff --git a/app/components/cards/ItemCard.tsx b/app/components/cards/ItemCard.tsx index ecb2a13a..2bd46194 100644 --- a/app/components/cards/ItemCard.tsx +++ b/app/components/cards/ItemCard.tsx @@ -12,15 +12,14 @@ export default function ItemCard({ streams: { id: number; teacher?: { name: string; last_name?: string }; subject_type_name?: { name_kg: string; short_name_kg: string } }[]; connection: { id: number; course_id: number; id_myedu_stream: number }[]; }) { - const [activeStreamIdx, setActiveStreamIdx] = useState(null); + const [activeStreamIds, setActiveStreamIds] = useState([]); const matchedIdx = streams.findIndex((stream) => connection.some((item) => item.id_myedu_stream === stream.id)); useEffect(() => { - if (matchedIdx !== -1) { - setActiveStreamIdx(matchedIdx); - } - }, [streams, connection]); + const matchedIds = streams.filter((stream) => connection.some((item) => item.id_myedu_stream === stream.id)).map((stream) => stream.id); + setActiveStreamIds(matchedIds); + }, [streams, connection]); return (
@@ -29,12 +28,12 @@ export default function ItemCard({
{streams.map((stream, idx) => { - const isActive = idx === activeStreamIdx; + const isActive = activeStreamIds.includes(stream.id); return ( -
+
Окутуучу: -
+
{stream.teacher?.name} {stream.teacher?.last_name}
@@ -46,12 +45,14 @@ export default function ItemCard({
{connection.map((item) => { if (item.id_myedu_stream === stream.id) { + console.log('Здесь кнопка должна появиться:', item.id_myedu_stream + '=' + stream.id); return (
diff --git a/app/components/tables/StreamList.tsx b/app/components/tables/StreamList.tsx index 97c5607e..c591ad10 100644 --- a/app/components/tables/StreamList.tsx +++ b/app/components/tables/StreamList.tsx @@ -151,7 +151,7 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis }; const handleConnect = async () => { - const data = await connectStreams({ stream: pendingChanges }); + const data = await connectStreams({course_id: courseValue?.id ? courseValue?.id : null, stream: pendingChanges }); // const data = await connectStreams(streamValues); if (data?.success) { diff --git a/layout/AppMenu.tsx b/layout/AppMenu.tsx index 85d1f9d0..ce36299b 100644 --- a/layout/AppMenu.tsx +++ b/layout/AppMenu.tsx @@ -37,7 +37,7 @@ const AppMenu = () => { : user?.is_student ? [ { label: 'Окуу планы', icon: 'pi pi-fw pi-calendar-clock', to: '/teaching' }, - pathname.startsWith('/teaching/') ? { label: 'Темалар', icon: 'pi pi-fw pi-calendar-lessons', items: themesStudentList?.length > 0 ? themesStudentList : [] } : { label: '' } + pathname.startsWith('/teaching/') ? { label: 'Темалар', icon: 'pi pi-fw pi-book', items: themesStudentList?.length > 0 ? themesStudentList : [] } : { label: '' } ] : []; @@ -114,7 +114,7 @@ const AppMenu = () => { forThemes.push({ label: item.title, id: item.id, - to: `/teaching/lesson/${item.id}` + to: `/teaching/lesson/${studentThemeCourse}/${item.id}` }) ); if (forThemes.length > 0) { diff --git a/services/streams.tsx b/services/streams.tsx index b69037c4..6d6de776 100644 --- a/services/streams.tsx +++ b/services/streams.tsx @@ -18,7 +18,7 @@ export const fetchStreams = async (id: number | null) => { }; // export const connectStreams = async (value: {stream: streamsType[]}) => { -export const connectStreams = async (value: {stream: streamsType[]}) => { +export const connectStreams = async (value: {course_id: number | null, stream: streamsType[]}) => { console.log(value); // const formData = new FormData(); From 45ae7e7e3c75389f4fd042390c93bb25fc58f513 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Mon, 1 Sep 2025 14:47:36 +0600 Subject: [PATCH 135/183] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D1=80=D0=B5=D0=B2=D1=8C=D1=8E?= =?UTF-8?q?=20=D0=BA=20=D0=B2=D0=B8=D0=B4=D0=B5=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/course/page.tsx | 62 ++++-- .../[studentThemeId]/[lesson_id]/page.tsx | 203 +++++++++++++++--- .../[studentThemeId]/page.tsx | 71 ++++-- app/(student)/teaching/page.tsx | 8 +- app/components/SessionManager.tsx | 4 +- app/components/cards/ItemCard.tsx | 2 +- app/components/cards/LessonCard.tsx | 9 +- app/components/lessons/LessonTyping.tsx | 22 +- app/components/tables/StreamList.tsx | 1 - hooks/useShortText.tsx | 2 +- layout/AppMenu.tsx | 4 +- layout/AppTopbar.tsx | 9 +- layout/context/layoutcontext.tsx | 31 ++- services/courses.tsx | 8 +- services/studentLessons.tsx | 15 ++ types/layout.d.ts | 3 + types/lessonStateType.tsx | 1 + types/lessonType.tsx | 2 +- utils/axiosInstance.tsx | 4 +- 19 files changed, 368 insertions(+), 93 deletions(-) diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index fe352508..d9c7dc6c 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -51,6 +51,7 @@ export default function Course() { const [activeIndex, setActiveIndex] = useState(0); const [imageState, setImageState] = useState(null); const [displayStrem, setDisplayStreams] = useState([]); + const [visible, setVisisble] = useState(false); const [editingLesson, setEditingLesson] = useState({ title: '', @@ -125,6 +126,27 @@ export default function Course() { }, 1000); }; + const fileUploadRef = useRef(null); + const clearFile = () => { + fileUploadRef.current?.clear(); + setImageState(null); + if (editMode) { + setEditingLesson( + (prev) => + prev && { + ...prev, + image: '' + } + ); + // query + } else { + setCourseValue((prev) => ({ + ...prev, + image: '' + })); + } + }; + const handleFetchCourse = async (page = 1) => { const data = await fetchCourses(page, 0); toggleSkeleton(); @@ -299,7 +321,6 @@ export default function Course() { }; const displayInfo = (value: displayType[]) => { - console.log('info ', value); setDisplayStreams(value); }; @@ -339,6 +360,8 @@ export default function Course() { const data = await fetchCourseInfo(selectedCourse); if (data?.success) { + console.log('udali', data); + setProgressSpinner(false); setEditingLesson({ title: data.course.title || '', @@ -415,8 +438,8 @@ export default function Course() { ); }; - const imagestateStyle = imageState ? 'flex gap-1 items-center justify-between flex-col sm:flex-row' : ''; - const imageTitle = useShortText(typeof editingLesson.image === 'string' ? editingLesson.image : '', 30); + const imagestateStyle = imageState || editingLesson.image ? 'flex gap-1 items-center justify-between flex-col sm:flex-row' : ''; + const imageTitle = useShortText(typeof editingLesson.image === 'string' ? editingLesson.image : '', 20); return (
@@ -424,14 +447,18 @@ export default function Course() {
- {imagestateStyle && ( + {/* {imagestateStyle && ( */}
- {typeof imageState === 'string' && } + {/* {typeof imageState === 'string' && } */} + {typeof imageState === 'string' ? + + : + }
- )} + {/* )} */}
- + {courseValue.image || editingLesson.image ? (
{typeof editingLesson.image === 'string' && ( @@ -443,6 +470,7 @@ export default function Course() { ) : ( jpeg, png, jpg )} +
{(editingLesson.image || imageState) &&
{/*
*/} @@ -530,9 +558,11 @@ export default function Course() {
- Тандалган курстун аталышы: {forStreamId?.title} + + Тандалган курстун аталышы: {forStreamId?.title} +
-
+
{displayStrem?.length < 1 && Курска байлоо үчүн агымдарды тандаңыз} @@ -547,7 +577,7 @@ export default function Course() { // {displayStrem.length >= 3 && '...'} })}
-
+
)}
@@ -593,8 +623,8 @@ export default function Course() {
{/* STREAMS SECTION */}
- displayInfo(value)} toggleIndex={()=> {}}/> + displayInfo(value)} toggleIndex={() => {}} />
)} diff --git a/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/[lesson_id]/page.tsx b/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/[lesson_id]/page.tsx index 6a301832..bbd10872 100644 --- a/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/[lesson_id]/page.tsx +++ b/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/[lesson_id]/page.tsx @@ -3,31 +3,34 @@ import LessonCard from '@/app/components/cards/LessonCard'; import { NotFound } from '@/app/components/NotFound'; import GroupSkeleton from '@/app/components/skeleton/GroupSkeleton'; +import useBreadCrumbs from '@/hooks/useBreadCrumbs'; import useErrorMessage from '@/hooks/useErrorMessage'; import { useMediaQuery } from '@/hooks/useMediaQuery'; import useShortText from '@/hooks/useShortText'; import { LayoutContext } from '@/layout/context/layoutcontext'; -import { fetchDocuments, fetchLinks } from '@/services/studentLessons'; +import { fetchDocuments, fetchLinks, fetchVideo } from '@/services/studentLessons'; import { fetchMainLesson } from '@/services/studentMain'; import { lessonType } from '@/types/lessonType'; import { TabViewChange } from '@/types/tabViewChange'; -import { useParams, useRouter } from 'next/navigation'; +import { useParams, usePathname, useRouter } from 'next/navigation'; import { Button } from 'primereact/button'; +import { Dialog } from 'primereact/dialog'; import { ProgressBar } from 'primereact/progressbar'; import { TabPanel, TabView } from 'primereact/tabview'; import { useContext, useEffect, useState } from 'react'; export default function StudentLessons() { - const { lesson_id } = useParams(); + const { lesson_id, studentThemeId } = useParams(); const params = useParams(); - console.log(params); - const router = useRouter(); const [activeIndex, setActiveIndex] = useState(0); - const [mainLesson, setMainLesson] = useState<{title: string}>({title: ''}); + const [mainLesson, setMainLesson] = useState<{ title: string }>({ title: '' }); const [hasLessons, setHasLessons] = useState(false); const [skeleton, setSkeleton] = useState(false); + const x = localStorage.getItem('currentBreadCrumb'); + const parseX = JSON.parse(x); + const [streamId, setStreamId] = useState(parseX.studentStream || ''); // doc const [documents, setDocuments] = useState([]); @@ -37,13 +40,49 @@ export default function StudentLessons() { const [link, setLink] = useState([]); const [linkShow, setLinkShow] = useState(false); - const [navigaion, setNavigation] = useState<{prev: number | null,next: number | null}>({ prev: null, next: null }); + // video + const [video, setVideo] = useState([]); + const [videoShow, setVideoShow] = useState(false); + const [videoCall, setVideoCall] = useState(false); + const [videoLink, setVideoLink] = useState(''); + const [navigaion, setNavigation] = useState<{ prev: number | null; next: number | null }>({ prev: null, next: null }); - const { setMessage, contextFetchStudentThemes, contextStudentThemes } = useContext(LayoutContext); + const { setMessage, contextFetchStudentThemes, contextStudentThemes, crumbUrls } = useContext(LayoutContext); const showError = useErrorMessage(); + const teachingBreadCrumb = [ + { + id: 1, + url: '/', + title: '', + icon: true, + parent_id: null + }, + { + id: 2, + url: '/teaching', + title: 'Окуу план', + parent_id: 1 + }, + { + id: 3, + url: `/teaching/${studentThemeId}/${streamId && streamId}`, + title: 'Темалар', + parent_id: 2 + }, + { + id: 4, + url: '/teaching/lesson/:course_id/:lesson_id', + title: 'Сабактар', + parent_id: 3 + } + ]; + + const pathname = usePathname(); + const breadcrumb = useBreadCrumbs(teachingBreadCrumb, pathname); + const media = useMediaQuery('(max-width: 640px)'); - const titleShort = useShortText(mainLesson?.title, 25); + const titleShort = useShortText(mainLesson?.title, 35); const toggleSkeleton = () => { setSkeleton(true); @@ -52,9 +91,41 @@ export default function StudentLessons() { }, 1000); }; + const handleVideoCall = (value: string | null) => { + if (!value) { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Видеону иштетүүдө ката', detail: '' } + }); + } + + const url = new URL(typeof value === 'string' ? value : ''); + let videoId = null; + + if (url.hostname === 'youtu.be') { + // короткая ссылка, видео ID — в пути + videoId = url.pathname.slice(1); // убираем первый слеш + } else if (url.hostname === 'www.youtube.com' || url.hostname === 'youtube.com') { + // стандартная ссылка, видео ID в параметре v + videoId = url.searchParams.get('v'); + } + + if (!videoId) { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Видеону иштетүүдө ката', detail: '' } + }); + return null; // не удалось получить ID + } + // return `https://www.youtube.com/embed/${videoId}`; + + console.log('value', videoId); + setVideoLink(`https://www.youtube.com/embed/${videoId}`); + setVideoCall(true); + }; + const handleMainLesson = async () => { const data = await fetchMainLesson(lesson_id ? Number(lesson_id) : null); - console.log(data); if (data && data?.id) { if (data.next_id) { setNavigation((prev) => ({ ...prev, next: data.next_id })); @@ -120,6 +191,27 @@ export default function StudentLessons() { } }; + // fetch link + const handleFetchVideo = async () => { + const data = await fetchVideo(lesson_id ? Number(lesson_id) : null); + setSkeleton(true); + if (data && Array.isArray(data)) { + setVideo(data); + setVideoShow(false); + setSkeleton(false); + } else { + setSkeleton(false); + setVideoShow(true); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Проблема с соединением. Повторите заново' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + const navigateMainLesson = async (id: number | null) => { setSkeleton(true); router.push(`/teaching/lesson/${lesson_id}/${id}`); @@ -130,23 +222,35 @@ export default function StudentLessons() { handleFetchDoc(); } else if (e.index === 1) { handleFetchLink(); + } else if (e.index === 2) { + handleFetchVideo(); } setActiveIndex(e.index); }; + useEffect(() => { + const x = localStorage.getItem('currentBreadCrumb'); + const parseX = JSON.parse(x); + console.log('Eto x', parseX); + console.log('Eto x', x); + + setStreamId(parseX.studentStream); + }, [pathname]); + useEffect(() => { handleMainLesson(); handleFetchDoc(); }, []); useEffect(() => { - console.log('Навигация ', navigaion); - }, [navigaion]); + console.log('streamId ', streamId); + }, [streamId]); useEffect(() => { link.length < 1 ? setLinkShow(true) : setLinkShow(false); documents.length < 1 ? setDocShow(true) : setDocShow(false); - }, [link, documents]); + video.length < 1 ? setVideoShow(true) : setVideoShow(false); + }, [link, documents, video]); // doc section const sentToPDF = (url: string) => { @@ -159,7 +263,7 @@ export default function StudentLessons() {
{docShow ? ( - + ) : ( documents.map((item: lessonType) => ( <> @@ -188,8 +292,8 @@ export default function StudentLessons() {
- {docShow ? ( - + {linkShow ? ( + ) : ( link.map((item: lessonType) => ( <> @@ -197,7 +301,7 @@ export default function StudentLessons() { status="student" // onSelected={(id: number, type: string) => selectedForEditing(id, type)} // onDelete={(id: number) => handleDeleteDoc(id)} - cardValue={{ title: item?.title, id: item.id, type: 'doc', url: item?.url && item.url }} + cardValue={{ title: item?.title, id: item.id, type: 'link', url: item?.url && item.url }} cardBg={'#ddc4f51a'} type={{ typeValue: 'link', icon: 'pi pi-file' }} typeColor={'var(--mainColor)'} @@ -213,17 +317,69 @@ export default function StudentLessons() {
); + // video section + const videoSecion = ( +
+
+
+ { + if (!videoCall) return; + setVideoCall(false); + }} + > +
+ +
+
+ {videoShow ? ( + + ) : ( + video.map((item: lessonType) => ( + <> + selectedForEditing(id, type)} + // onDelete={(id: number) => handleDeleteDoc(id)} + cardValue={{ title: item?.title, id: item.id, type: 'video', url: item?.url && item.url, photo: item?.cover_url }} + cardBg={'#fff'} + type={{ typeValue: 'video', icon: 'pi pi-video' }} + typeColor={'var(--mainColor)'} + lessonDate={'xx-xx'} + urlForPDF={() => {}} + urlForDownload={item?.document_path || ''} + videoVisible={() => handleVideoCall(String(item?.link))} + /> + + )) + )} +
+
+
+ ); + return (
{/* info section */} {skeleton ? ( ) : ( -
+

{titleShort}

-
Home/theme
+
{breadcrumb}
)} @@ -274,14 +430,7 @@ export default function StudentLessons() { header="Видео" className="p-tabview p-tabview-nav p-tabview-selected p-tabview-panels p-tabview-panel" > - {/* {contentShow && } */} - {/* && ( -
-
- -
-
- ) */} + {skeleton ? : videoSecion}
diff --git a/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/page.tsx b/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/page.tsx index fe3347db..a6c0a59b 100644 --- a/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/page.tsx +++ b/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/page.tsx @@ -2,31 +2,55 @@ import { NotFound } from '@/app/components/NotFound'; import GroupSkeleton from '@/app/components/skeleton/GroupSkeleton'; +import useBreadCrumbs from '@/hooks/useBreadCrumbs'; import useErrorMessage from '@/hooks/useErrorMessage'; import { useMediaQuery } from '@/hooks/useMediaQuery'; import useShortText from '@/hooks/useShortText'; import { LayoutContext } from '@/layout/context/layoutcontext'; import { itemsCourseInfo } from '@/services/studentMain'; import Link from 'next/link'; -import { useParams } from 'next/navigation'; +import { useParams, usePathname } from 'next/navigation'; import { useContext, useEffect, useState } from 'react'; export default function StudentThemes() { - const [themes, setThemes] = useState<{image: string, description: string, title: string,}>({image: '', description: '', title: ''}); + const [themes, setThemes] = useState<{ image: string; description: string; title: string }>({ image: '', description: '', title: '' }); const [hasCourses, setHasCourses] = useState(false); const [skeleton, setSkeleton] = useState(false); const [themesStudentList, setThemesStudentList] = useState([]); const params = useParams(); - // console.log(params); const courseId = params.studentThemeCourse; const streamId = params.studentThemeId; + const teachingBreadCrumb = [ + { + id: 1, + url: '/', + title: '', + icon: true, + parent_id: null + }, + { + id: 2, + url: '/teaching', + title: 'Окуу план', + parent_id: 1 + }, + { + id: 3, + url: `/teaching/:course_id/:lesson_id`, + title: 'Темалар', + parent_id: 2 + } + ]; + + const pathname = usePathname(); + const breadcrumb = useBreadCrumbs(teachingBreadCrumb, pathname); - const { setMessage, contextFetchStudentThemes, contextStudentThemes } = useContext(LayoutContext); + const { setMessage, contextFetchStudentThemes, contextStudentThemes, crumbUrls, contextAddCrumb } = useContext(LayoutContext); const showError = useErrorMessage(); const media = useMediaQuery('(max-width: 640px)'); - const titleShort = useShortText(themes?.title, 20); + const titleShort = useShortText(themes?.title, 35); const titleInfoClass = `${themes?.image ? 'items-center justify-between' : 'justify-center'}`; const titleImageClass = `${true ? 'md:w-1/3' : ''}`; @@ -38,6 +62,15 @@ export default function StudentThemes() { }, 1000); }; + useEffect(()=> { + const isTopicsChildPage = /^\/teaching\/[^/]+\/[^/]+$/.test(pathname); + console.log(streamId); + + if(isTopicsChildPage && streamId){ + contextAddCrumb({type: 'studentStream', crumbUrl: streamId }) + } + },[]) + const handleCourseInfo = async () => { const data = await itemsCourseInfo(courseId ? Number(courseId) : null, streamId ? Number(streamId) : null); @@ -58,7 +91,7 @@ export default function StudentThemes() { useEffect(() => { handleCourseInfo(); - if(courseId){ + if (courseId) { console.log(courseId); contextFetchStudentThemes(courseId); } @@ -78,20 +111,24 @@ export default function StudentThemes() { {skeleton ? ( ) : ( -
-
+
+
-

{titleShort}

+

+ {titleShort} +

{themes?.description}

-
Home/theme
-
- Фото -
+ {themes?.image && ( +
+ Фото +
+ )}
+
{breadcrumb}
)} @@ -104,15 +141,17 @@ export default function StudentThemes() { ) : (
- {themesStudentList?.map((item: {id: number, title: string}) => ( + {themesStudentList?.map((item: { id: number; title: string }) => (
- Сабактын аталышы: + Теманын аталышы:
- {item.title} + + {item.title} +
diff --git a/app/(student)/teaching/page.tsx b/app/(student)/teaching/page.tsx index 6dfa66af..fce8e21a 100644 --- a/app/(student)/teaching/page.tsx +++ b/app/(student)/teaching/page.tsx @@ -44,12 +44,15 @@ export default function Teaching() { // fetch lessons const handleFetchLessons = async () => { const data = await fetchItemsLessons(); - toggleSkeleton(); + setSkeleton(true) + console.log(data); + if (data) { // валидность проверить console.log(data); setLessons(data); setHasLessons(false); + setSkeleton(false) } else { setHasLessons(true); setMessage({ @@ -59,6 +62,7 @@ export default function Teaching() { if (data?.response?.status) { showError(data.response.status); } + setSkeleton(false); } }; @@ -126,6 +130,8 @@ export default function Teaching() { }, [lessons, selectedSort]); useEffect(() => { + console.log('kfjlsdj;'); + handleFetchLessons(); handleFetchConnectId(); }, []); diff --git a/app/components/SessionManager.tsx b/app/components/SessionManager.tsx index 91eb9ead..baa126e3 100644 --- a/app/components/SessionManager.tsx +++ b/app/components/SessionManager.tsx @@ -69,7 +69,9 @@ const SessionManager = () => { }, []); useEffect(() => { - setGlobalLoading(true); + if(!pathname.startsWith('/teaching/lesson/')){ + setGlobalLoading(true); + } console.log('Переход в', pathname); const token = getToken('access_token'); diff --git a/app/components/cards/ItemCard.tsx b/app/components/cards/ItemCard.tsx index 2bd46194..0ff8b466 100644 --- a/app/components/cards/ItemCard.tsx +++ b/app/components/cards/ItemCard.tsx @@ -45,7 +45,7 @@ export default function ItemCard({
{connection.map((item) => { if (item.id_myedu_stream === stream.id) { - console.log('Здесь кнопка должна появиться:', item.id_myedu_stream + '=' + stream.id); + // console.log('Здесь кнопка должна появиться:', item.id_myedu_stream + '=' + stream.id); return (
+ )}
- )} - /> - + ); + })} +
)}
)} diff --git a/layout/AppMenu.tsx b/layout/AppMenu.tsx index 1aa937c9..15471116 100644 --- a/layout/AppMenu.tsx +++ b/layout/AppMenu.tsx @@ -78,7 +78,7 @@ const AppMenu = () => { to: `/course/${clickedCourseId}`, items: [], // пока пусто command: () => { - contextFetchThemes(item.id); + contextFetchThemes(item.id); setClickedCourseId(item.id); } }) From 8d3566c95fbd2f5cd94e2ca8812726d890f09033 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Fri, 5 Sep 2025 09:16:37 +0600 Subject: [PATCH 139/183] =?UTF-8?q?=D0=9D=D0=BE=D0=B2=D1=8B=D0=B5=20=D1=83?= =?UTF-8?q?=D1=80=D0=BE=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[course_id]/[lesson_id]/page.tsx | 304 +++++++++-- .../[studentThemeId]/[lesson_id]/page.tsx | 2 +- app/components/cards/LessonCard.tsx | 160 +++--- app/components/lessons/LessonDocument.tsx | 395 ++++++++++++++ app/components/lessons/LessonTyping.tsx | 4 - app/components/lessons/LessonVideo.tsx | 510 ++++++++++++++++++ app/globals.css | 26 + hooks/useBreadCrumbs.tsx | 3 +- schemas/lessonSchema.tsx | 3 +- services/courses.tsx | 2 +- services/query-tests.http | 17 +- services/steps.tsx | 221 ++++++++ styles/layout/_button.scss | 4 + styles/layout/layout.scss | 1 + styles/layout/steps.css | 11 + types/mainStepType.tsx | 10 + utils/axiosInstance.tsx | 6 +- 17 files changed, 1555 insertions(+), 124 deletions(-) create mode 100644 app/components/lessons/LessonDocument.tsx create mode 100644 app/components/lessons/LessonVideo.tsx create mode 100644 services/steps.tsx create mode 100644 styles/layout/steps.css create mode 100644 types/mainStepType.tsx diff --git a/app/(main)/course/[courseTheme]/lessonStep/[course_id]/[lesson_id]/page.tsx b/app/(main)/course/[courseTheme]/lessonStep/[course_id]/[lesson_id]/page.tsx index 8ffd5eff..915bc6c5 100644 --- a/app/(main)/course/[courseTheme]/lessonStep/[course_id]/[lesson_id]/page.tsx +++ b/app/(main)/course/[courseTheme]/lessonStep/[course_id]/[lesson_id]/page.tsx @@ -1,14 +1,52 @@ 'use client'; +import LessonDocument from '@/app/components/lessons/LessonDocument'; +import LessonVideo from '@/app/components/lessons/LessonVideo'; +import PDFViewer from '@/app/components/PDFBook'; +import FormModal from '@/app/components/popUp/FormModal'; import useBreadCrumbs from '@/hooks/useBreadCrumbs'; import useErrorMessage from '@/hooks/useErrorMessage'; import { useMediaQuery } from '@/hooks/useMediaQuery'; import { LayoutContext } from '@/layout/context/layoutcontext'; +import { lessonSchema } from '@/schemas/lessonSchema'; import { fetchLessonShow } from '@/services/courses'; -import { useParams, usePathname } from 'next/navigation'; +import { addLesson, deleteStep, fetchElement, fetchSteps, fetchTypes } from '@/services/steps'; +import { lessonStateType } from '@/types/lessonStateType'; +import { mainStepsType } from '@/types/mainStepType'; +import { getConfirmOptions } from '@/utils/getConfirmOptions'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { useParams, usePathname, useRouter } from 'next/navigation'; +import { BreadCrumb } from 'primereact/breadcrumb'; +import { Button } from 'primereact/button'; +import { confirmDialog } from 'primereact/confirmdialog'; +import { Dialog } from 'primereact/dialog'; +import { InputText } from 'primereact/inputtext'; import { useContext, useEffect, useState } from 'react'; +import { useForm } from 'react-hook-form'; export default function LessonStep() { + // types + interface editingType { + key: string; + file: string; + url: string; + link: string; + video_type_id?: number | null; + } + + interface videoType { + name: string; + status: boolean; + id: number; + } + + interface videoInsideType { + id: number; + is_link: boolean; + short_title: string; + title: string; + } + const { course_id, lesson_id } = useParams(); const [lessonInfoState, setLessonInfoState] = useState<{ title: string; documents_count: string; usefullinks_count: string; videos_count: string } | null>(null); @@ -16,6 +54,13 @@ export default function LessonStep() { const { setMessage } = useContext(LayoutContext); const showError = useErrorMessage(); + const [formVisible, setFormVisible] = useState(false); + const [types, setTypes] = useState<{ id: number; title: string; name: string; logo: string }[]>([]); + const [steps, setSteps] = useState([]); + const [element, setElement] = useState<{ content: any | null; step: mainStepsType } | null>(null); + const [selectedId, setSelectId] = useState(null); + const [hasSteps, setHasSteps] = useState(false); + const teachingBreadCrumb = [ { id: 1, @@ -30,35 +75,27 @@ export default function LessonStep() { title: 'Курстар', parent_id: 1 }, - { - id: 3, - url: `/course/${''}`, - title: 'Темалар', - parent_id: 2 - }, - { - id: 4, - url: '/course/:course_id/:lesson_id', - title: 'Сабактар', - parent_id: 3 - }, - { - id: 5, - url: '/students/:course_id/:stream_id', - title: 'Студенттер', - parent_id: 2 - } + // { + // id: 3, + // url: `/course/${''}`, + // title: 'Темалар', + // parent_id: 2 + // }, + // { + // id: 4, + // url: '/course/:course_id/:lesson_id', + // title: 'Сабактар', + // parent_id: 3 + // } ]; const pathname = usePathname(); const breadcrumb = useBreadCrumbs(teachingBreadCrumb, pathname); + + const clearValues = () => {}; const handleShow = async () => { - // skeleton = false - const data = await fetchLessonShow(lesson_id ? Number(lesson_id) : null); - console.log(data); - if (data?.lesson) { setLessonInfoState({ title: data.lesson.title, videos_count: data.lesson.videos_count, usefullinks_count: data.lesson.usefullinks_count, documents_count: data.lesson.documents_count }); } else { @@ -73,46 +110,219 @@ export default function LessonStep() { } }; + const handleFetchTypes = async () => { + const data = await fetchTypes(); + + if (data && Array.isArray(data)) { + setTypes(data); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинирээк кайталаныз' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + setFormVisible(true); + }; + + const handleFetchSteps = async () => { + const data = await fetchSteps(Number(lesson_id)); + + if (data.success) { + console.log(data.steps); + + if (data.steps.length < 1) { + setHasSteps(true); + } else { + setHasSteps(false); + setSteps(data.steps); + } + } else { + setHasSteps(false); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинирээк кайталаныз' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const handleAddLesson = async (lessonId: number, typeId: number) => { + const data = await addLesson({ lesson_id: lessonId, type_id: typeId }); + if (data.success) { + handleFetchSteps(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү кошулду!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинирээк кайталаныз' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + setFormVisible(false); + }; + + const handleFetchElement = async (stepId: number) => { + const data = await fetchElement(Number(lesson_id), stepId); + if (data.success) { + setElement({ content: data.content, step: data.step }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинирээк кайталаныз' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const handleDeleteStep = async () => { + const data = await deleteStep(Number(lesson_id), Number(selectedId)); + console.log(data); + + if (data.success) { + handleFetchSteps(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при удалении' } + }); + if (data.response.status) { + showError(data.response.status); + } + } + }; + const lessonInfo = (
-
- +
-

{lessonInfoState?.title}

-
- - - - {/* Документтердин саны: {lessonInfoState?.documents_count} */} - - - - - - {/* Шилтемелердин саны: {lessonInfoState?.usefullinks_count} */} - - - - - - {/* Видеолордун саны: {lessonInfoState?.videos_count} */} - - -
+

{lessonInfoState?.title}

{breadcrumb}
); + const step = (icon: string, step: number) => ( +
{ + setSelectId(step); + handleFetchElement(step); + }} + > +
+ +
+
+ ); + + useEffect(() => { + if (Array.isArray(steps) && steps.length > 0) { + const firstStep = steps[0]?.id; + setSelectId(steps[0]?.id); + handleFetchElement(firstStep); + } + }, [steps]); + useEffect(() => { handleShow(); + handleFetchSteps(); + // handleAddLesson(lesson_id, ); }, []); + useEffect(() => { + console.log('element', element); + }, [element]); + return ( -
+
+ {/* modal sectoin */} + { + if (!formVisible) return; + setFormVisible(false); + clearValues(); + }} + // footer={footerContent} + > +
+ {types.map((item) => { + console.log(item); + const iconClass = item.name === 'link' ? 'pi pi-link' : item.name === 'video' ? 'pi pi-video' : item.name === 'document' ? 'pi pi-folder' : ''; + + return ( +
+
+ + handleAddLesson(Number(lesson_id), item.id)}> + {item.title} + +
+
+ ); + })} +
+
+ {/* info section */} {lessonInfo} + + {/* steps section */} +
+ {hasSteps ? ( +
+

Азырынча кадамдар жок

+
+
+ ) : ( +
+ {steps.map((item, idx) => { + return ( +
+ {step(item.type.logo, item.id)} +
+ ); + })} +
+ )} + +
+ + {/* {lessonDisplay} */} + {element?.step.type.name === 'document' && {}} />} + {element?.step.type.name === 'video' && {}} />}
); } diff --git a/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/[lesson_id]/page.tsx b/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/[lesson_id]/page.tsx index bbd10872..9aac122b 100644 --- a/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/[lesson_id]/page.tsx +++ b/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/[lesson_id]/page.tsx @@ -265,7 +265,7 @@ export default function StudentLessons() { {docShow ? ( ) : ( - documents.map((item: lessonType) => ( + documents.map((item: lessonType) => ( <> void; onDelete?: (id: number) => void; - cardValue: { title: string; id: number; desctiption?: string; type?: string; photo?: string; url?: string }; + cardValue: { title: string; id: number; desctiption?: string; type?: string; photo?: string; url?: string; document?: string }; cardBg: string; type: { typeValue: string; icon: string }; typeColor: string; lessonDate: string; urlForPDF: () => void; urlForDownload: string; - videoVisible?: (id: string | null)=> void; + videoVisible?: (id: string | null) => void; }) { - const shortTitle = useShortText(cardValue.title, 20); - const shortDescription = useShortText(cardValue.desctiption ? cardValue.desctiption : '', 17); + const shortTitle = useShortText(cardValue.title, 90); + const shortDoc = useShortText(cardValue?.document || '', 70); + const shortDescription = useShortText(cardValue.desctiption ? cardValue.desctiption : '', 90); const shortUrl = useShortText(cardValue?.url ? cardValue?.url : '', 17); const [progressSpinner, setProgressSpinner] = useState(false); + // useEffect(()=> { + // console.log(cardValue.photo); + // },[cardValue]); + const media = useMediaQuery('(max-width: 640px)'); + const toggleSpinner = () => { setProgressSpinner(true); setInterval(() => { @@ -74,76 +82,102 @@ export default function LessonCard({
); - const videoPreviw = - type.typeValue === 'video' && ( -
-
-
videoVisible?.(type.typeValue)}> - {/* Волна */} - {/* */} + const videoPreviw = type.typeValue === 'video' && ( +
+
+
videoVisible?.(type.typeValue)}> + {/* Волна */} + {/* */} - {/* Иконка-кнопка */} -
- -
+ {/* Иконка-кнопка */} +
+
- Видео
- ) + Видео +
+ ); const btnLabel = type.typeValue === 'doc' && status === 'working' ? 'Көчүрүү' : type.typeValue === 'doc' && status === 'student' ? 'Ачуу' : type.typeValue === 'link' ? 'Өтүү' : ''; return ( -
- {status === 'working' && ( -
-
{status === 'working' && }
- {/* {cardHeader} */} -
- )} -
-
- {/*
{!cardValue.photo && }
*/} -
{shortTitle}
- {type.typeValue === 'link' && {shortUrl}} -
{cardValue?.desctiption && cardValue?.desctiption !== 'null' && shortDescription}
- {status === 'working' && type.typeValue !== 'video' && ( -
- - {lessonDate} -
- )} -
+
+
- {status === 'student' && type.typeValue === 'doc' ? ( -
-
-
- - {' '} -
+ + {/* video preview */} + {videoPreviw} + + {/* button */} + {( + <> + {status === 'student' && type.typeValue === 'doc' ? ( +
+
+
+ + {' '} +
+ ) : ( +
+ {btnLabel &&
+
+ )} +
+ )} + + )} +
); diff --git a/app/components/lessons/LessonDocument.tsx b/app/components/lessons/LessonDocument.tsx new file mode 100644 index 00000000..be2c4627 --- /dev/null +++ b/app/components/lessons/LessonDocument.tsx @@ -0,0 +1,395 @@ +'use client'; + +import { lessonSchema } from '@/schemas/lessonSchema'; +import { EditableLesson } from '@/types/editableLesson'; +import { lessonStateType } from '@/types/lessonStateType'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { useParams, useRouter } from 'next/navigation'; +import { Button } from 'primereact/button'; +import { FileUpload } from 'primereact/fileupload'; +import { InputText } from 'primereact/inputtext'; +import { ProgressSpinner } from 'primereact/progressspinner'; +import { useContext, useEffect, useRef, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { NotFound } from '../NotFound'; +import { lessonType } from '@/types/lessonType'; +import LessonCard from '../cards/LessonCard'; +import { useMediaQuery } from '@/hooks/useMediaQuery'; +import { log } from 'node:console'; +import { getToken } from '@/utils/auth'; +import { addDocument, deleteDocument, fetchElement, updateDocument } from '@/services/steps'; +import { mainStepsType } from '@/types/mainStepType'; +import useErrorMessage from '@/hooks/useErrorMessage'; +import { LayoutContext } from '@/layout/context/layoutcontext'; +import FormModal from '../popUp/FormModal'; + +export default function LessonDocument({ element, content, fetchPropElement, deleteElement }: { element: mainStepsType; content: any; fetchPropElement: (id: number) => void; deleteElement: (id: number) => void }) { + interface docValueType { + title: string; + description: string; + file: File | null; + document?: string; + } + + interface contentType { + course_id: number | null; + created_at: string; + description: string | null; + document: string; + id: number; + lesson_id: number; + status: true; + title: string; + updated_at: string; + user_id: number; + } + + const { course_id } = useParams(); + + const router = useRouter(); + const media = useMediaQuery('(max-width: 640px)'); + const fileUploadRef = useRef(null); + const showError = useErrorMessage(); + const { setMessage } = useContext(LayoutContext); + + const [editingLesson, setEditingLesson] = useState({ title: 'string', description: '', file: null }); + const [visible, setVisisble] = useState(false); + const [imageState, setImageState] = useState(null); + const [contentShow, setContentShow] = useState(false); + // doc + const [document, setDocuments] = useState(); + const [docValue, setDocValue] = useState({ + title: '', + description: '', + file: null + }); + const [docShow, setDocShow] = useState(false); + const [urlPDF, setUrlPDF] = useState(''); + const [PDFVisible, setPDFVisible] = useState(false); + + const [progressSpinner, setProgressSpinner] = useState(false); + const [additional, setAdditional] = useState<{ doc: boolean; link: boolean; video: boolean }>({ doc: false, link: false, video: false }); + const [selectType, setSelectType] = useState(''); + const [selectId, setSelectId] = useState(null); + + const clearFile = () => { + fileUploadRef.current?.clear(); + setAdditional((prev) => ({ ...prev, video: false })); + setImageState(null); + if (visible) { + setEditingLesson( + (prev) => + prev && { + ...prev, + file: null + } + ); + } else { + setDocValue((prev) => ({ + ...prev, + file: null + })); + } + }; + + const clearValues = () => { + clearFile(); + setDocValue({ title: '', description: '', file: null }); + setEditingLesson(null); + setSelectId(null); + setSelectType(''); + }; + + const toggleSpinner = () => { + setProgressSpinner(true); + setInterval(() => { + setProgressSpinner(false); + }, 1000); + }; + + // validate + const { + setValue, + formState: { errors } + } = useForm({ + resolver: yupResolver(lessonSchema), + mode: 'onChange' + }); + + const documentView = ( + <> +
+ setPDFVisible(false)}> +
{/* */}
+
+ + ); + const selectedForEditing = (id: number, type: string) => { + setSelectType(type); + setSelectId(id); + setVisisble(true); + editing(); + }; + + const sentToPDF = (url: string) => { + setUrlPDF(url); + if (media) { + router.push(`/pdf/${url}`); + } else { + setPDFVisible(true); + } + }; + + const editing = async () => { + const data = await fetchElement(element.lesson_id, element.id); + if (data.success) { + // setElement({ content: data.content, step: data.step }); + setEditingLesson({title: data.content.title, file:null, document: data.content.document, description: data.content.description}) + console.log(data); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинирээк кайталаныз' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const handleAddDoc = async () => { + toggleSpinner(); + const data = await addDocument(docValue, element.lesson_id, element.type_id, element.id); + console.log(data); + + if (data.success) { + fetchPropElement(element.id); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү Кошулдуу!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при добавлении' } + }); + if (data.response.status) { + showError(data.response.status); + } + } + }; + + // delete document + const handleDeleteDoc = async (id: number) => { + console.log(id); + + // const token = getToken('access_token'); + const data = await deleteDocument(Number(document?.lesson_id), id); + if (data.success) { + fetchPropElement(element.id); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при удалении' } + }); + if (data.response.status) { + showError(data.response.status); + } + } + }; + + // update document + const handleUpdateDoc = async () => { + const token = getToken('access_token'); + const data = await updateDocument(token, document?.lesson_id ? Number(document?.lesson_id) : null, Number(selectId), element.type.id, element.type.id, editingLesson); + console.log(data); + + if (data?.success) { + fetchPropElement(element.id); + clearValues(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өзгөртүлдү!', detail: '' } + }); + } else { + setDocValue({ title: '', description: '', file: null }); + setEditingLesson(null); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при изменении урока' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const documentSection = () => { + return ( +
+ {PDFVisible ? ( + documentView + ) : contentShow ? ( +
+
+ {docShow ? ( + + ) : ( + document && ( + selectedForEditing(id, type)} + onDelete={(id: number) => handleDeleteDoc(id)} + cardValue={{ title: document?.title, id: document.id, desctiption: document?.description || '', type: 'doc', document: document.document }} + cardBg={'#ddc4f51a'} + type={{ typeValue: 'doc', icon: 'pi pi-doc' }} + typeColor={'var(--mainColor)'} + lessonDate={'xx-xx'} + urlForPDF={() => sentToPDF(document.document || '')} + urlForDownload="" + /> + ) + )} +
+
+ ) : ( +
+
+ {}} + accept="application/pdf" + onSelect={(e) => + setDocValue((prev) => ({ + ...prev, + file: e.files[0] + })) + } + /> +
+ + { + setDocValue((prev) => ({ ...prev, title: e.target.value })); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} + {additional.doc && setDocValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" />} + +
+ {/*
+
+
+ )} +
+ ); + }; + + useEffect(() => { + console.log('content', content); + if (content) { + setContentShow(true); + setDocuments(content); + } else { + setContentShow(false); + } + }, [content]); + + useEffect(() => { + console.log('edititing', editingLesson); + }, [editingLesson]); + + return ( +
+ { + handleUpdateDoc(); + }} + clearValues={clearValues} + visible={visible} + setVisible={setVisisble} + start={false} + > +
+
+ {selectType === 'doc' ? ( + <> +
+ {}} + accept="application/pdf" + onSelect={(e) => { + if (e.files[0]) setEditingLesson((prev) => prev && { ...prev, file: e.files[0] }); + }} + /> +
+ {/* {String(editingLesson?.file[0].objectURL)} */} + { + console.log(editingLesson); + setEditingLesson((prev) => prev && { ...prev, title: e.target.value }); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} + setEditingLesson((prev) => prev && { ...prev, description: e.target.value })} + className="w-full" + /> + + ) : selectType === 'video' ? ( + <> + + ) : ( + '' + )} +
+
+
+ {documentSection()} +
+ ); +} diff --git a/app/components/lessons/LessonTyping.tsx b/app/components/lessons/LessonTyping.tsx index b6122a05..50af7542 100644 --- a/app/components/lessons/LessonTyping.tsx +++ b/app/components/lessons/LessonTyping.tsx @@ -641,10 +641,6 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp } }; - useEffect(() => { - console.log(imageState); - }, [imageState]); - const videoSection = () => { return (
diff --git a/app/components/lessons/LessonVideo.tsx b/app/components/lessons/LessonVideo.tsx new file mode 100644 index 00000000..cc40f3e4 --- /dev/null +++ b/app/components/lessons/LessonVideo.tsx @@ -0,0 +1,510 @@ +'use client'; + +import { lessonSchema } from '@/schemas/lessonSchema'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { useParams, useRouter } from 'next/navigation'; +import { Button } from 'primereact/button'; +import { FileUpload, FileUploadSelectEvent } from 'primereact/fileupload'; +import { InputText } from 'primereact/inputtext'; +import { ProgressSpinner } from 'primereact/progressspinner'; +import { useContext, useEffect, useRef, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { NotFound } from '../NotFound'; +import LessonCard from '../cards/LessonCard'; +import { useMediaQuery } from '@/hooks/useMediaQuery'; +import { getToken } from '@/utils/auth'; +import { addDocument, addVideo, deleteDocument, deleteVideo, fetchElement, updateDocument, updateVideo } from '@/services/steps'; +import { mainStepsType } from '@/types/mainStepType'; +import useErrorMessage from '@/hooks/useErrorMessage'; +import { LayoutContext } from '@/layout/context/layoutcontext'; +import FormModal from '../popUp/FormModal'; +import { Dropdown } from 'primereact/dropdown'; +import { Dialog } from 'primereact/dialog'; +import { FileWithPreview } from '@/types/fileuploadPreview'; + +export default function LessonVideo({ element, content, fetchPropElement, deleteElement }: { element: mainStepsType; content: any; fetchPropElement: (id: number) => void; deleteElement: (id: number) => void }) { + interface docValueType { + title: string; + description: string; + file: File | null; + document?: string; + } + + interface contentType { + course_id: number | null; + created_at: string; + description: string | null; + document: string; + id: number; + lesson_id: number; + status: true; + title: string; + updated_at: string; + user_id: number; + link: File | null; + cover: string; + cover_url: string; + } + + interface videoType { + name: string; + status: boolean; + id: number; + } + + interface videoInsideType { + id: number; + is_link: boolean; + short_title: string; + title: string; + } + + interface videoValueType { + title: string; + description?: string; + document?: File | null; + url?: string | null; + video_link: string; + video_type_id: number | null; + file?: File | null; + } + + const { course_id } = useParams(); + + const router = useRouter(); + const media = useMediaQuery('(max-width: 640px)'); + const fileUploadRef = useRef(null); + + // videos + const [video, setVideo] = useState(); + const [selectedCity, setSelectedCity] = useState({ name: '', status: true, id: 1 }); + const [videoSelect, setVideoSelect] = useState([]); + const [videoTypes, setVideoTypes] = useState([]); + const [videoCall, setVideoCall] = useState(false); + const [videoLink, setVideoLink] = useState(''); + const [videoValue, setVideoValue] = useState({ + title: '', + description: '', + file: null, + url: '', + video_link: '', + cover: null, + link: null + }); + const [videoShow, setVideoShow] = useState(false); + const [imageState, setImageState] = useState(null); + + // auxiliary + const showError = useErrorMessage(); + const { setMessage } = useContext(LayoutContext); + const [visible, setVisisble] = useState(false); + const [editingLesson, setEditingLesson] = useState(null); + const [progressSpinner, setProgressSpinner] = useState(false); + const [additional, setAdditional] = useState<{ doc: boolean; link: boolean; video: boolean }>({ doc: false, link: false, video: false }); + const [selectType, setSelectType] = useState(''); + const [selectId, setSelectId] = useState(null); + const [contentShow, setContentShow] = useState(false); + + const clearFile = () => { + fileUploadRef.current?.clear(); + setAdditional((prev) => ({ ...prev, video: false })); + setImageState(null); + setVideoValue((prev) => ({ + ...prev, + cover: null + })); + }; + + const clearValues = () => { + clearFile(); + setEditingLesson(null); + setVideoValue({ title: '', description: '', file: null, url: '', video_link: '', cover: null, link: null }); + setSelectId(null); + setSelectType(''); + }; + + const toggleSpinner = () => { + setProgressSpinner(true); + setInterval(() => { + setProgressSpinner(false); + }, 1000); + }; + + const handleVideoCall = (value: string | null) => { + console.log(value); + + if (!value) { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Видеону иштетүүдө ката', detail: '' } + }); + } + + const url = new URL(typeof value === 'string' ? value : ''); + let videoId = null; + + if (url.hostname === 'youtu.be') { + // короткая ссылка, видео ID — в пути + videoId = url.pathname.slice(1); // убираем первый слеш + } else if (url.hostname === 'www.youtube.com' || url.hostname === 'youtube.com') { + // стандартная ссылка, видео ID в параметре v + videoId = url.searchParams.get('v'); + } + + if (!videoId) { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Видеону иштетүүдө ката', detail: '' } + }); + return null; // не удалось получить ID + } + // return `https://www.youtube.com/embed/${videoId}`; + setVideoLink(`https://www.youtube.com/embed/${videoId}`); + setVideoCall(true); + }; + + // validate + const { + setValue, + formState: { errors } + } = useForm({ + resolver: yupResolver(lessonSchema), + mode: 'onChange' + }); + + const toggleVideoType = (e: videoType) => { + setSelectedCity(e); + setVideoValue({ title: '', description: '', file: null, url: '', video_link: '', cover: null, link: null }); + }; + + const selectedForEditing = (id: number, type: string) => { + console.log(id, type); + setSelectType(type); + setSelectId(id); + setVisisble(true); + editing(); + }; + + const onSelect = (e: FileUploadSelectEvent & { files: FileWithPreview[] }) => { + if (e.files.length > 0) { + setImageState(e.files[0].objectURL); + setVideoValue((prev) => ({ + ...prev, + cover: e.files[0] + })); + } + }; + + const editing = async () => { + const data = await fetchElement(element.lesson_id, element.id); + if (data.success) { + // setElement({ content: data.content, step: data.step }); + setEditingLesson({ title: data.content.title, video_type_id: data.content.video_type_id , video_link: data.content.link, description: data.content.description, }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинирээк кайталаныз' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const handleAddVideo = async () => { + toggleSpinner(); + const data = await addVideo(videoValue, element.lesson_id, 1, element.type_id, element.id); + console.log(data); + + if (data.success) { + fetchPropElement(element.id); + clearValues(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү Кошулдуу!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при добавлении' } + }); + if (data.response.status) { + showError(data.response.status); + } + } + }; + + // delete video + const handleDeleteVideo = async (id: number) => { + const data = await deleteVideo(element.lesson_id, id); + console.log(data); + + if (data.success) { + fetchPropElement(element.id); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при удалении' } + }); + if (data.response.status) { + showError(data.response.status); + } + } + }; + + // update document + const handleUpdateVideo = async () => { + const token = getToken('access_token'); + const data = await updateVideo(token, editingLesson, video?.lesson_id ? Number(video?.lesson_id) : null, Number(selectId), 1, element.type.id, element.type.id); + console.log(data); + + if (data?.success) { + fetchPropElement(element.id); + clearValues(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өзгөртүлдү!', detail: '' } + }); + } else { + setEditingLesson(null); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при изменении урока' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const videoSection = () => { + return ( +
+ {!contentShow ? ( +
+
+ { + toggleVideoType(e.value); + }} + options={videoSelect} + optionLabel="name" + placeholder="Танданыз" + // style={{backgroundColor: 'var(--mainColor', color: 'white'}} + panelStyle={{ color: 'white' }} + className="w-[213px] sm:w-full md:w-14rem" + /> +
+ {selectedCity?.status ? ( +
+ { + setVideoValue((prev) => ({ ...prev, video_link: e.target.value })); + setValue('usefulLink', e.target.value, { shouldValidate: true }); + }} + /> + {errors.usefulLink?.message} +
+ ) : ( +
+ {}} + accept="video/" + onSelect={(e) => + setVideoValue((prev) => ({ + ...prev, + file: e.files[0] + })) + } + /> +
+ )} +
+
+
+ { + setVideoValue((prev) => ({ ...prev, title: e.target.value })); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} + {additional.video && ( +
+ setVideoValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" /> +
+
+ + {imageState &&
+
+ {imageState ? : } +
+
+
+ )} + +
+ {/*
+
+
+
+ ) : ( +
+
+ { + if (!videoCall) return; + setVideoCall(false); + }} + > +
+ +
+
+ {videoShow ? ( + + ) : ( + video && ( + selectedForEditing(id, type)} + onDelete={(id: number) => handleDeleteVideo(id)} + cardValue={{ title: video.title, id: video.id, desctiption: video?.description || '', type: 'video', photo: video?.cover_url }} + cardBg={'white'} + type={{ typeValue: 'video', icon: 'pi pi-video' }} + typeColor={'var(--mainColor)'} + lessonDate={'xx-xx'} + urlForPDF={() => ''} + urlForDownload="" + videoVisible={() => handleVideoCall(String(video?.link))} + /> + ) + )} +
+
+ )} +
+ ); + }; + + useEffect(() => { + console.log('content', content); + if (content) { + setContentShow(true); + setVideo(content); + } else { + setContentShow(false); + } + }, [content]); + + useEffect(()=> { + console.log('video', video); + },[video]); + + useEffect(() => { + console.log('edititing', editingLesson); + }, [editingLesson]); + + return ( +
+ handleUpdateVideo()} clearValues={clearValues} visible={visible} setVisible={setVisisble} start={false}> +
+
+ { + <> + {/* {editingLesson?.video_type_id ? ( */} +
+ { + setEditingLesson((prev) => prev && { ...prev, video_link: e.target.value }); + setValue('usefulLink', e.target.value, { shouldValidate: true }); + }} + /> + {errors.usefulLink?.message} +
+ {/* ) : ( */} + <> + {/* {}} + accept="video/" + onSelect={(e) => + setEditingLesson( + (prev) => + prev && { + ...prev, + file: e.files[0] + } + ) + } + /> + {String(editingLesson?.video_link)} */} + + {/* )} */} + { + setEditingLesson((prev) => prev && { ...prev, title: e.target.value }); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} + setEditingLesson((prev) => prev && { ...prev, description: e.target.value })} className="w-full" /> + + } +
+
+
+ {videoSection()} +
+ ); +} diff --git a/app/globals.css b/app/globals.css index a7321de2..d758e31e 100644 --- a/app/globals.css +++ b/app/globals.css @@ -590,4 +590,30 @@ h1, h2, h3, h4, h5, h6 { font-size: 14px !important; padding: 8px; } +} + +.step{ + background-color: rgb(209, 210, 208); + transition: .5s; +} +.step:hover{ + background-color: var(--greenColor); +} + +.activeStep{ + background-color: var(--greenColor); +} + +.animate-step { + animation: bacgroundAnimate 2s ease infinite; +} + +@keyframes bacgroundAnimate { + 0%{ + background-color: rgb(209, 210, 208); + } 50%{ + background-color: var(--greenColor); + } 100%{ + background-color: rgb(209, 210, 208); + } } \ No newline at end of file diff --git a/hooks/useBreadCrumbs.tsx b/hooks/useBreadCrumbs.tsx index 8011116d..78ae2408 100644 --- a/hooks/useBreadCrumbs.tsx +++ b/hooks/useBreadCrumbs.tsx @@ -20,7 +20,6 @@ export default function useBreadCrumbs(insideBreadCrumb: breadCrumbType[], curre // 1. Находим текущую страницу по URL (с учётом динамики) const currentPage = data.find((item) => matchUrl(item.url, currentUrl)); - if (!currentPage) return []; const breadcrumbs = []; @@ -45,6 +44,8 @@ export default function useBreadCrumbs(insideBreadCrumb: breadCrumbType[], curre }; useEffect(() => { + console.log(insideBreadCrumb, currentUrl); + const forBreadcrumb = getBreadcrumbs(insideBreadCrumb, currentUrl); setBreadCrumb(forBreadcrumb); }, [pathname]); diff --git a/schemas/lessonSchema.tsx b/schemas/lessonSchema.tsx index 562bbecf..095fbdf0 100644 --- a/schemas/lessonSchema.tsx +++ b/schemas/lessonSchema.tsx @@ -9,5 +9,6 @@ export const lessonSchema = yup.object().shape({ .string() .required('Талап кылынат!') .matches(/^https?:\/\/.+/, 'Шилтеме "http://" "https://" форматында болуш керек'), - title: yup.string().required('Талап кылынат!').max(30, 'Аталыштын узундугу макс 30 тамга') + title: yup.string().required('Талап кылынат!') + // .max(50, 'Аталыштын узундугу макс 50 тамга') }); \ No newline at end of file diff --git a/services/courses.tsx b/services/courses.tsx index 54c172a2..f438662a 100644 --- a/services/courses.tsx +++ b/services/courses.tsx @@ -39,7 +39,7 @@ export const addCourse = async (value: CourseCreateType) => { } }; -export const deleteCourse = async (id: number) => { +export const deleteCourse = async (id: number) => { url = process.env.NEXT_PUBLIC_BASE_URL + `/v1/teacher/courses/delete?course_id=${id}`; try { diff --git a/services/query-tests.http b/services/query-tests.http index 4a310be7..7b6b28ce 100644 --- a/services/query-tests.http +++ b/services/query-tests.http @@ -1,4 +1,4 @@ -@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5teWVkdS5vc2hzdS5rZy9wdWJsaWMvYXBpL21vb2MvbG9naW4iLCJpYXQiOjE3NTY3MTY2MTEsImV4cCI6MTc1Njc0NTQ3MSwibmJmIjoxNzU2NzE2NjExLCJqdGkiOiJ3bkhsY0ZpQXJGVGo4a0QyIiwic3ViIjoiNDY0NTYiLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3In0.dbla0J7wZzEwZh2NnVgI8uXeXNbPX3X5U5HP69Uq8LM +@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5teWVkdS5vc2hzdS5rZy9wdWJsaWMvYXBpL21vb2MvbG9naW4iLCJpYXQiOjE3NTY5MDE3MTksImV4cCI6MTc1NjkzMDU3OSwibmJmIjoxNzU2OTAxNzE5LCJqdGkiOiJDMlRGa3hFT3RITERWWmVsIiwic3ViIjoiNDY0NTYiLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3In0.DPG-hM-6ZB1Bed7J0XmYBCT216l45PMnKUzx6Zlv4MQ # ### @@ -15,12 +15,23 @@ # ### # test -GET https://api.mooc.oshsu.kg/public/api/v1/student/course/lesson/show?lesson_id=${lessonId}` +GET https://api.mooc.oshsu.kg/public/api/v1/teacher/lessons/step?lesson_id=77 Authorization: Bearer {{token}} Content-Type: application/json { - "lesson_id": 57 + "lesson_id": 77 +} + +# test + +POST https://api.mooc.oshsu.kg/public/api/v1/teacher/lessons/step?lesson_id=77&type_id=3 +Authorization: Bearer {{token}} +Content-Type: application/json + +{ + "lesson_id": 77, + "type_id":3 } diff --git a/services/steps.tsx b/services/steps.tsx new file mode 100644 index 00000000..98ebabf5 --- /dev/null +++ b/services/steps.tsx @@ -0,0 +1,221 @@ +import { CourseCreateType } from '@/types/courseCreateType'; +import { lessonStateType } from '@/types/lessonStateType'; +import axiosInstance from '@/utils/axiosInstance'; + +let url = ''; + +export const fetchTypes = async () => { + try { + const res = await axiosInstance.get(`/v1/teacher/courses/content/types`); + const data = await res.data; + + return data; + } catch (err) { + console.log('Ошибка загрузки:', err); + return err; + } +}; + +export const fetchSteps = async (lesson_id: number) => { + try { + const res = await axiosInstance.get(`/v1/teacher/lessons/step?lesson_id=${lesson_id}`); + const data = await res.data; + + return data; + } catch (err) { + console.log('Ошибка загрузки:', err); + return err; + } +}; + +export const addLesson = async (value: { lesson_id: number; type_id: number }) => { + const formData = new FormData(); + formData.append('lesson_id', String(value.lesson_id)); + formData.append('type_id', String(value.type_id)); + + try { + const res = await axiosInstance.post(`/v1/teacher/lessons/step`, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + + return res.data; + } catch (err) { + console.log('Ошибка при добавлении курса', err); + return err; + } +}; + +// fetch steps content + +export const fetchElement = async (lesson_id: number, step_id: number) => { + console.log(lesson_id, step_id); + + try { + const res = await axiosInstance.get(`/v1/teacher/lessons/step/content?lesson_id=${lesson_id}&step_id=${step_id}`); + const data = await res.data; + + return data; + } catch (err) { + console.log('Ошибка загрузки:', err); + return err; + } +}; + +export const addDocument = async (value: { file: File | null; title: string; description: string }, lesson_id: number, type_id: number, step_id: number) => { + const formData = new FormData(); + formData.append('lesson_id', String(lesson_id)); + formData.append('type_id', String(type_id)); + formData.append('step_id', String(step_id)); + if (value.file) { + formData.append('document', value.file && value.file); + } + formData.append('title', String(value.title)); + formData.append('description', String(value?.description)); + + try { + const res = await axiosInstance.post(`/v1/teacher/document/store`, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + + return res.data; + } catch (err) { + console.log('Ошибка при добавлении курса', err); + return err; + } +}; + +export const deleteDocument = async (lesson_id: number, content_id: number) => { + console.log(lesson_id, content_id); + + try { + const res = await axiosInstance.delete(`/v1/teacher/document/delete?lesson_id=${lesson_id}&document_id=${content_id}`); + + const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка при удалении темы', err); + return err; + } +}; + +export const updateDocument = async (token: string | null, lesson_id: number | null, contentId: number | null, type_id: number, step_id: number, value: any) => { + console.log(contentId, value); + let headers: Record = token ? { Authorization: `Bearer ${token}` } : {}; + let formData = new FormData(); + let url = `/v1/teacher/document/update?lesson_id=${lesson_id}&document_id=${contentId}&document=${value.file}`; + + formData.append('lesson_id', String(lesson_id)); + formData.append('type_id', String(type_id)); + formData.append('step_id', String(step_id)); + formData.append('lesson_id', String(lesson_id)); + formData.append('document', value.file); + formData.append('document_id', String(contentId)); + formData.append('title', String(value.title)); + formData.append('description', String(value.description)); + + try { + const res = await axiosInstance.post(url, formData, { + headers + }); + + const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка при обновлении урока', err); + return err; + } +}; + +// video + +export const addVideo = async (value: { file: File | null; title: string; description: string; video_link: string; cover: string | null }, lesson_id: number, videoType: number, type_id: number, step_id: number) => { + const formData = new FormData(); + url = `v1/teacher/video/store?lesson_id=${lesson_id}&title=${value.title}&description=${value.description}&video_link=${value.video_link}&video_type_id=${videoType}`; + formData.append('type_id', String(type_id)); + formData.append('step_id', String(step_id)); + formData.append('lesson_id', String(lesson_id)); + formData.append('video_link', String(value?.video_link)); + formData.append('title', String(value?.title)); + formData.append('description', String(value?.description)); + if (value.cover) { + formData.append('cover', value?.cover && value?.cover); + } + try { + const res = await axiosInstance.post(url, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + + return res.data; + } catch (err) { + console.log('Ошибка при добавлении курса', err); + return err; + } +}; + +export const deleteVideo = async (lesson_id: number, content_id: number) => { + console.log(lesson_id, content_id); + + try { + const res = await axiosInstance.delete(`/v1/teacher/video/delete?lesson_id=${lesson_id}&video_id=${content_id}`); + + const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка при удалении темы', err); + return err; + } +}; + +export const updateVideo = async ( + token: string | null, + value: { file: File | null; title: string; description: string; video_link: string; cover: string | null, video_type_id: number }, + lesson_id: number, + contentId: number, + videoType: number, + type_id: number, + step_id: number +) => { + let headers: Record = token ? { Authorization: `Bearer ${token}` } : {}; + let formData = new FormData(); + url = `/v1/teacher/video/update?lesson_id=${lesson_id}&title=${value.title}&description=${value.description}&video_link=${value.video_link}&video_type_id=${value.video_type_id}&video_id=${contentId}`; + formData.append('type_id', String(type_id)); + formData.append('step_id', String(step_id)); + formData.append('lesson_id', String(lesson_id)); + formData.append('video_link', value.video_link); + formData.append('video_type_id', String(value.video_type_id)); + formData.append('video_id', String(contentId)); + formData.append('title', String(value.title)); + formData.append('description', String(value.description)); + + try { + const res = await axiosInstance.post(url, formData, { + headers + }); + + const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка при обновлении урока', err); + return err; + } +}; + +export const deleteStep = async (lesson_id: number, step_id: number) => { + console.log(lesson_id, step_id); + + try { + const res = await axiosInstance.delete(`/v1/teacher/lessons/step/delete?lesson_id=${lesson_id}&step_id=${step_id}`); + + const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка при удалении темы', err); + return err; + } +}; diff --git a/styles/layout/_button.scss b/styles/layout/_button.scss index 980c20d2..ae4b859c 100644 --- a/styles/layout/_button.scss +++ b/styles/layout/_button.scss @@ -61,6 +61,10 @@ button { } } +.p-button .mini-button { + padding: 5px 10px; +} + .reject-button { background-color: transparent; color: var(--bodyColor); diff --git a/styles/layout/layout.scss b/styles/layout/layout.scss index da7e8e94..7348af10 100644 --- a/styles/layout/layout.scss +++ b/styles/layout/layout.scss @@ -1,4 +1,5 @@ @import './_variables'; +@import "./steps.css"; @import "./_mixins"; @import "./_main"; @import "./_topbar"; diff --git a/styles/layout/steps.css b/styles/layout/steps.css new file mode 100644 index 00000000..fed44317 --- /dev/null +++ b/styles/layout/steps.css @@ -0,0 +1,11 @@ +.step-type-grid{ + display: grid; + grid-template-columns: 1fr 1fr; + gap: 6px; +} +@media screen and (max-width: 640px) { + .step-type-grid { + display: grid; + grid-template-columns: 1fr; + } +} diff --git a/types/mainStepType.tsx b/types/mainStepType.tsx new file mode 100644 index 00000000..bdf9a7d1 --- /dev/null +++ b/types/mainStepType.tsx @@ -0,0 +1,10 @@ +export interface mainStepsType { + id: number; + id_parent: number | null; + type_id: number; + user_id: number; + lesson_id: number; + step: number; + type: { active: true; created_at: string; id: 1; logo: string; modelName: string; name: string; title: string; updated_at: string }; + updated_at: string; +} diff --git a/utils/axiosInstance.tsx b/utils/axiosInstance.tsx index fe10a242..2aa919a3 100644 --- a/utils/axiosInstance.tsx +++ b/utils/axiosInstance.tsx @@ -37,9 +37,9 @@ axiosInstance.interceptors.response.use( if (status === 404) { console.warn('404 - Перенаправляю...'); - window.location.href = '/pages/notfound'; - document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; - localStorage.removeItem('userVisit'); + // window.location.href = '/pages/notfound'; + // document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; + // localStorage.removeItem('userVisit'); } return Promise.reject(error); From 3a7e7aa3a754616140f4ad73a9f9f37c26839bf9 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Mon, 8 Sep 2025 12:57:20 +0600 Subject: [PATCH 140/183] =?UTF-8?q?=D0=A2=D0=B5=D1=81=D1=82=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=87=D1=82=D0=B8=20=D0=B7=D0=B0=D0=B2=D0=B5=D1=80=D1=88=D0=BE?= =?UTF-8?q?=D0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[courseTheme] => }/[lessons]/page.tsx | 0 app/(main)/course/[courseTheme]/page.tsx | 399 ----------------- .../[lesson_id]/page.tsx | 151 ++++--- app/(main)/course/page.tsx | 12 +- app/components/HomeClient.tsx | 2 +- app/components/SessionManager.tsx | 4 +- app/components/cards/LessonCard.tsx | 51 ++- app/components/lessons/LessonDocument.tsx | 19 +- app/components/lessons/LessonTest.tsx | 405 ++++++++++++++++++ app/components/lessons/LessonVideo.tsx | 74 +--- app/globals.css | 8 + layout/AppMenu.tsx | 249 +++++++++-- layout/AppMenuitem.tsx | 39 +- layout/context/layoutcontext.tsx | 3 + services/auth.tsx | 1 - services/courses.tsx | 2 + services/query-tests.http | 7 +- services/steps.tsx | 69 ++- styles/layout/_menu.scss | 4 +- types/layout.d.ts | 7 + utils/axiosInstance.tsx | 8 +- 21 files changed, 935 insertions(+), 579 deletions(-) rename app/(main)/{course/[courseTheme] => }/[lessons]/page.tsx (100%) delete mode 100644 app/(main)/course/[courseTheme]/page.tsx rename app/(main)/course/{[courseTheme]/lessonStep/[course_id] => [course_Id]}/[lesson_id]/page.tsx (71%) create mode 100644 app/components/lessons/LessonTest.tsx diff --git a/app/(main)/course/[courseTheme]/[lessons]/page.tsx b/app/(main)/[lessons]/page.tsx similarity index 100% rename from app/(main)/course/[courseTheme]/[lessons]/page.tsx rename to app/(main)/[lessons]/page.tsx diff --git a/app/(main)/course/[courseTheme]/page.tsx b/app/(main)/course/[courseTheme]/page.tsx deleted file mode 100644 index ba411c07..00000000 --- a/app/(main)/course/[courseTheme]/page.tsx +++ /dev/null @@ -1,399 +0,0 @@ -'use client'; - -import ConfirmModal from '@/app/components/popUp/ConfirmModal'; -import FormModal from '@/app/components/popUp/FormModal'; -import GroupSkeleton from '@/app/components/skeleton/GroupSkeleton'; -import Link from 'next/link'; -import { Button } from 'primereact/button'; -import { Column } from 'primereact/column'; -import { DataTable } from 'primereact/datatable'; -import { InputText } from 'primereact/inputtext'; -import { LayoutContext } from '@/layout/context/layoutcontext'; -import React, { useContext, useEffect, useState } from 'react'; -import { useParams, usePathname } from 'next/navigation'; -import { addThemes, deleteTheme, fetchCourseInfo, fetchThemes, updateTheme } from '@/services/courses'; -import useErrorMessage from '@/hooks/useErrorMessage'; -import { CourseCreateType } from '@/types/courseCreateType'; -import { NotFound } from '@/app/components/NotFound'; -import Redacting from '@/app/components/popUp/Redacting'; -import { getRedactor } from '@/utils/getRedactor'; -import { getConfirmOptions } from '@/utils/getConfirmOptions'; -import { ProgressSpinner } from 'primereact/progressspinner'; -import useBreadCrumbs from '@/hooks/useBreadCrumbs'; -import { useMediaQuery } from '@/hooks/useMediaQuery'; -import { DataView } from 'primereact/dataview'; - -export default function CourseTheme() { - const [hasThemes, setHasThemes] = useState(false); - const [themes, setThemes] = useState([]); - const [themeValue, setThemeValue] = useState({ title: '', description: '', video_url: '' }); - const [themeInfo, setThemeInfo] = useState(); - const [selectedCourse, setSelectedCourse] = useState<{ id: number | null }>({ id: null }); - const [formVisible, setFormVisible] = useState(false); - const [editMode, setEditMode] = useState(false); - const [forStart, setForStart] = useState(true); - const [skeleton, setSkeleton] = useState(false); - const [progressSpinner, setProgressSpinner] = useState(false); - const [editingThemes, setEditingThemes] = useState<{ title: string }>({ - title: '' - }); - - const { setMessage, contextFetchThemes } = useContext(LayoutContext); - - const { courseTheme } = useParams() as { courseTheme: string }; - - const teachingBreadCrumb = [ - { - id: 1, - url: '/', - title: '', - icon: true, - parent_id: null - }, - { - id: 2, - url: '/course', - title: 'Курстар', - parent_id: 1 - }, - { - id: 3, - url: `/course/:id`, - title: 'Темалар', - parent_id: 2 - }, - { - id: 4, - url: '/course/:course_id/:lesson_id', - title: 'Сабактар', - parent_id: 3 - }, - { - id: 5, - url: '/students/:course_id/:stream_id', - title: 'Студенттер', - parent_id: 2 - } - ]; - - const showError = useErrorMessage(); - const pathname = usePathname(); - const breadcrumb = useBreadCrumbs(teachingBreadCrumb, pathname); - const media = useMediaQuery('(max-width: 640px)'); - const tableMedia = useMediaQuery('(max-width: 577px)'); - - const toggleSkeleton = () => { - setSkeleton(true); - setTimeout(() => { - setSkeleton(false); - }, 1000); - }; - - const handleFetchThemes = async () => { - const data = await fetchThemes(Number(courseTheme)); - - toggleSkeleton(); - - contextFetchThemes(Number(courseTheme)); - if (data?.lessons) { - setHasThemes(false); - console.log(data.lessons); - - setThemes(data.lessons.data); - } else { - setHasThemes(true); - setMessage({ - state: true, - value: { severity: 'error', summary: 'Ошибка', detail: 'Проблема с соединением. Повторите заново' } - }); // messege - Ошибка загрузки курсов - if (data?.response?.status) { - showError(data.response.status); - } - } - }; - - const handleFetchInfo = async () => { - const data = await fetchCourseInfo(Number(courseTheme)); - - if (data.success) { - setThemeInfo(data.course); - } - }; - - const handleAddTheme = async () => { - if (themeValue.title.length < 1) { - return null; - } - const data = await addThemes(Number(courseTheme), themeValue.title); - if (data.success) { - toggleSkeleton(); - handleFetchThemes(); - setMessage({ - state: true, - value: { severity: 'success', summary: 'Ийгиликтүү кошулду!', detail: '' } - }); // messege - Успех! - } else { - setMessage({ - state: true, - value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при добавлении' } - }); // messege - Ошибка при добавлении - if (data.response.status) { - showError(data.response.status); - } - } - }; - - const handleDeleteCourse = async (id: number) => { - const data = await deleteTheme(id); - if (data.success) { - toggleSkeleton(); - handleFetchThemes(); - setMessage({ - state: true, - value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } - }); // messege - Успех! - } else { - setMessage({ - state: true, - value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при удалении' } - }); // messege - Ошибка при добавлении - if (data.response.status) { - showError(data.response.status); - } - } - }; - - const handleUpdateTheme = async () => { - const data = await updateTheme(Number(courseTheme), selectedCourse.id, editingThemes.title); - if (data.success) { - toggleSkeleton(); - handleFetchThemes(); - clearValues(); - setEditMode(false); - setSelectedCourse({ id: null }); - setMessage({ - state: true, - value: { severity: 'success', summary: 'Ийгиликтүү өзгөртүлдү!', detail: '' } - }); // messege - Успех! - } else { - setMessage({ - state: true, - value: { severity: 'error', summary: 'Ошибка при при изменении темы', detail: 'Заполняйте поля правильно' } - }); // messege - Ошибка при изменении курса - if (data.response.status) { - showError(data.response.status); - } - } - }; - - const edit = (rowData: number | null) => { - setEditMode(true); - setSelectedCourse({ id: rowData }); - // setThemeValue({ title: rowData.title || '' }); - setFormVisible(true); - }; - - const clearValues = () => { - setThemeValue({ title: '', description: '', video_url: '' }); - setEditingThemes({ title: '' }); - setEditMode(false); - setSelectedCourse({ id: null }); - }; - - useEffect(() => { - handleFetchInfo(); - handleFetchThemes(); - }, []); - - useEffect(() => { - const handleShow = async () => { - setProgressSpinner(true); - const data: { lessons: { data: { id: number; title: string }[] } } = await fetchThemes(Number(courseTheme)); - - if (data?.lessons) { - setProgressSpinner(false); - const forEditing = data.lessons.data.find((item) => item.id === selectedCourse.id); - - setEditingThemes({ - title: forEditing?.title || '' - }); - } else { - setProgressSpinner(false); - } - }; - - if (editMode) { - handleShow(); - } - }, [editMode]); - - useEffect(() => { - const title = editMode ? editingThemes.title.trim() : themeValue.title.trim(); - if (title.length > 0) { - setForStart(false); - } else { - setForStart(true); - } - }, [themeValue.title, editingThemes.title]); - - useEffect(() => { - console.log(themeInfo); - themes.length < 1 ? setHasThemes(true) : setHasThemes(false); - }, [themes]); - - const titleInfoClass = `${!themeInfo?.image ? 'items-center' : 'w-full'} ${themeInfo?.image ? 'w-1/2' : 'w-full'}`; - const titleImageClass = `${themeInfo?.image ? 'md:w-1/3' : ''}`; - - const itemTemplate = (shablonData: any) => { - return ( -
-
- {/* Заголовок */} -
-
- - {shablonData.title} {/* Используем subject_name из вашего шаблона */} - -
- {tableMedia && ( -
- -
- )} -
- - {/* Кнопки действий */} - {!tableMedia && ( -
- {/*
- )} -
-
- ); - }; - - return ( -
- {/* title section */} -
-
-
-

- {themeInfo?.title} -

-

{themeInfo?.description}

-
- - {themeInfo?.created_at && new Date(themeInfo?.created_at).toISOString().slice(0, 10)} -
-
- {themeInfo?.image && ( -
- -
- )} -
-
{breadcrumb}
-
- - {/* add button*/} -
- )} -
-
- ); - })} -
- )} -
- )} -
- ); -} diff --git a/app/(main)/course/[courseTheme]/lessonStep/[course_id]/[lesson_id]/page.tsx b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx similarity index 71% rename from app/(main)/course/[courseTheme]/lessonStep/[course_id]/[lesson_id]/page.tsx rename to app/(main)/course/[course_Id]/[lesson_id]/page.tsx index 915bc6c5..ddd8e933 100644 --- a/app/(main)/course/[courseTheme]/lessonStep/[course_id]/[lesson_id]/page.tsx +++ b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx @@ -1,7 +1,9 @@ 'use client'; import LessonDocument from '@/app/components/lessons/LessonDocument'; +import LessonTest from '@/app/components/lessons/LessonTest'; import LessonVideo from '@/app/components/lessons/LessonVideo'; +import { NotFound } from '@/app/components/NotFound'; import PDFViewer from '@/app/components/PDFBook'; import FormModal from '@/app/components/popUp/FormModal'; import useBreadCrumbs from '@/hooks/useBreadCrumbs'; @@ -21,7 +23,7 @@ import { Button } from 'primereact/button'; import { confirmDialog } from 'primereact/confirmdialog'; import { Dialog } from 'primereact/dialog'; import { InputText } from 'primereact/inputtext'; -import { useContext, useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; export default function LessonStep() { @@ -47,11 +49,13 @@ export default function LessonStep() { title: string; } - const { course_id, lesson_id } = useParams(); + const param = useParams(); + const course_id = param.course_Id; + console.log('step param ', param); const [lessonInfoState, setLessonInfoState] = useState<{ title: string; documents_count: string; usefullinks_count: string; videos_count: string } | null>(null); const media = useMediaQuery('(max-width: 640px)'); - const { setMessage } = useContext(LayoutContext); + const { setMessage, contextFetchThemes, contextThemes } = useContext(LayoutContext); const showError = useErrorMessage(); const [formVisible, setFormVisible] = useState(false); @@ -60,6 +64,8 @@ export default function LessonStep() { const [element, setElement] = useState<{ content: any | null; step: mainStepsType } | null>(null); const [selectedId, setSelectId] = useState(null); const [hasSteps, setHasSteps] = useState(false); + const [themeNull, setThemeNull] = useState(false); + const [lesson_id, setLesson_id] = useState((param.lesson_id && Number(param.lesson_id)) || null); const teachingBreadCrumb = [ { @@ -74,7 +80,7 @@ export default function LessonStep() { url: '/course', title: 'Курстар', parent_id: 1 - }, + } // { // id: 3, // url: `/course/${''}`, @@ -91,11 +97,15 @@ export default function LessonStep() { const pathname = usePathname(); const breadcrumb = useBreadCrumbs(teachingBreadCrumb, pathname); - + const clearValues = () => {}; - const handleShow = async () => { - const data = await fetchLessonShow(lesson_id ? Number(lesson_id) : null); + const handleShow = async (LessonId: number) => { + // alert('lessson_id: ' + lesson_id); + + const data = await fetchLessonShow(LessonId); + console.log(data?.lesson); + if (data?.lesson) { setLessonInfoState({ title: data.lesson.title, videos_count: data.lesson.videos_count, usefullinks_count: data.lesson.usefullinks_count, documents_count: data.lesson.documents_count }); } else { @@ -111,8 +121,9 @@ export default function LessonStep() { }; const handleFetchTypes = async () => { + setFormVisible(true); const data = await fetchTypes(); - + if (data && Array.isArray(data)) { setTypes(data); } else { @@ -124,15 +135,13 @@ export default function LessonStep() { showError(data.response.status); } } - setFormVisible(true); }; - const handleFetchSteps = async () => { + const handleFetchSteps = async (lesson_id: number) => { const data = await fetchSteps(Number(lesson_id)); - - if (data.success) { - console.log(data.steps); + console.log('steps', data); + if (data.success) { if (data.steps.length < 1) { setHasSteps(true); } else { @@ -154,7 +163,7 @@ export default function LessonStep() { const handleAddLesson = async (lessonId: number, typeId: number) => { const data = await addLesson({ lesson_id: lessonId, type_id: typeId }); if (data.success) { - handleFetchSteps(); + handleFetchSteps(lessonId); setMessage({ state: true, value: { severity: 'success', summary: 'Ийгиликтүү кошулду!', detail: '' } @@ -172,26 +181,28 @@ export default function LessonStep() { }; const handleFetchElement = async (stepId: number) => { - const data = await fetchElement(Number(lesson_id), stepId); - if (data.success) { - setElement({ content: data.content, step: data.step }); - } else { - setMessage({ - state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинирээк кайталаныз' } - }); - if (data?.response?.status) { - showError(data.response.status); + console.log('sendStep 404', stepId); + + if (lesson_id) { + const data = await fetchElement(Number(lesson_id), stepId); + if (data.success) { + setElement({ content: data.content, step: data.step }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинирээк кайталаныз' } + }); + if (data?.response?.status) { + showError(data.response.status); + } } } }; const handleDeleteStep = async () => { const data = await deleteStep(Number(lesson_id), Number(selectedId)); - console.log(data); - if (data.success) { - handleFetchSteps(); + handleFetchSteps(Number(lesson_id)); setMessage({ state: true, value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } @@ -241,15 +252,51 @@ export default function LessonStep() { }, [steps]); useEffect(() => { - handleShow(); - handleFetchSteps(); - // handleAddLesson(lesson_id, ); - }, []); + if (lesson_id) { + handleShow(lesson_id); + } + }, [lesson_id]); useEffect(() => { - console.log('element', element); + // console.log('element', element); }, [element]); + useEffect(() => { + contextFetchThemes(Number(course_id)); + }, []); + + useEffect(() => { + console.log('Тема ', contextThemes); + console.log('variant 1'); + + if (contextThemes?.lessons?.data?.length > 0) { + console.log('variant 2'); + setThemeNull(false); + if (param.lesson_id == 'null') { + handleShow(contextThemes.lessons.data[0].id); + console.log('variant 4', contextThemes.lessons.data[0].id); + handleFetchSteps(contextThemes.lessons.data[0].id); + setLesson_id(contextThemes.lessons.data[0].id); + } else { + console.log('variant 5'); + handleShow(Number(param.lesson_id)); + setLesson_id((param.lesson_id && Number(param.lesson_id)) || null); + handleFetchSteps(Number(param.lesson_id)); + } + } else { + setThemeNull(true); + console.log('variant 3'); + } + }, [contextThemes]); + + // if (themeNull) { + // return ( + //
+ // + //
+ // ); + // } + return (
{/* modal sectoin */} @@ -262,22 +309,20 @@ export default function LessonStep() { setFormVisible(false); clearValues(); }} - // footer={footerContent} >
{types.map((item) => { console.log(item); - const iconClass = item.name === 'link' ? 'pi pi-link' : item.name === 'video' ? 'pi pi-video' : item.name === 'document' ? 'pi pi-folder' : ''; return ( -
+
- + handleAddLesson(Number(lesson_id), item.id)}> {item.title}
-
+ ); })}
@@ -289,8 +334,7 @@ export default function LessonStep() { {/* steps section */}
{hasSteps ? ( -
-

Азырынча кадамдар жок

+
) : ( @@ -310,19 +354,26 @@ export default function LessonStep() { > + -
- {/* {lessonDisplay} */} - {element?.step.type.name === 'document' && {}} />} - {element?.step.type.name === 'video' && {}} />} + {hasSteps && ( +
+ +
+ )} + {element?.step.type.name === 'document' && } + {element?.step.type.name === 'video' && } + {element?.step.type.name === 'test' && }
); } diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index 298518a4..914d70fb 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -33,7 +33,7 @@ import { displayType } from '@/types/displayType'; import { FileWithPreview } from '@/types/fileuploadPreview'; export default function Course() { - const { setMessage, course, setCourses, contextFetchCourse } = useContext(LayoutContext); + const { setMessage, course, setCourses, contextFetchCourse, setMainCourseId} = useContext(LayoutContext); const [coursesValue, setValueCourses] = useState([]); const [hasCourses, setHasCourses] = useState(false); const [courseValue, setCourseValue] = useState({ title: '', description: '', video_url: '', image: '' }); @@ -410,7 +410,7 @@ export default function Course() { {/* Заголовок */}
- + setMainCourseId(shablonData.id)}> {shablonData.title} {/* Используем subject_name из вашего шаблона */}
@@ -690,7 +690,7 @@ export default function Course() { ) : (
-
+
{/* info section */} {skeleton ? ( @@ -725,7 +725,7 @@ export default function Course() { header="Аталышы" style={{ width: '80%' }} body={(rowData) => ( - + setMainCourseId(rowData.id)} key={rowData.id}> {rowData.title} )} @@ -778,9 +778,9 @@ export default function Course() { )}
{/* STREAMS SECTION */} - {/*
+
displayInfo(value)} toggleIndex={() => {}} /> -
*/} +
)}
diff --git a/app/components/HomeClient.tsx b/app/components/HomeClient.tsx index 0a8e0902..09bfb4a2 100644 --- a/app/components/HomeClient.tsx +++ b/app/components/HomeClient.tsx @@ -18,7 +18,7 @@ export default function HomeClient() { return (
- {/* {"Уроки"} */} + {/* {"Уроки"} */}
diff --git a/app/components/SessionManager.tsx b/app/components/SessionManager.tsx index baa126e3..0e519760 100644 --- a/app/components/SessionManager.tsx +++ b/app/components/SessionManager.tsx @@ -78,8 +78,8 @@ const SessionManager = () => { if (!token && pathname !== '/' && pathname !== '/auth/login') { console.log('Перенеправляю в login'); - logout({ setUser, setGlobalLoading }); - window.location.href = '/auth/login'; + // logout({ setUser, setGlobalLoading }); + // window.location.href = '/auth/login'; return; } setTimeout(() => { diff --git a/app/components/cards/LessonCard.tsx b/app/components/cards/LessonCard.tsx index a1e41227..a080b1c1 100644 --- a/app/components/cards/LessonCard.tsx +++ b/app/components/cards/LessonCard.tsx @@ -21,12 +21,13 @@ export default function LessonCard({ lessonDate, urlForPDF, urlForDownload, - videoVisible + videoVisible, + answers }: { status: string; onSelected?: (id: number, type: string) => void; onDelete?: (id: number) => void; - cardValue: { title: string; id: number; desctiption?: string; type?: string; photo?: string; url?: string; document?: string }; + cardValue: { title: string; id: number; desctiption?: string; type?: string; photo?: string; url?: string; document?: string; score?: number }; cardBg: string; type: { typeValue: string; icon: string }; typeColor: string; @@ -34,6 +35,7 @@ export default function LessonCard({ urlForPDF: () => void; urlForDownload: string; videoVisible?: (id: string | null) => void; + answers?: { id?: number | null; text: string; is_correct: boolean }[]; }) { const shortTitle = useShortText(cardValue.title, 90); const shortDoc = useShortText(cardValue?.document || '', 70); @@ -99,7 +101,7 @@ export default function LessonCard({
); - const btnLabel = type.typeValue === 'doc' && status === 'working' ? 'Көчүрүү' : type.typeValue === 'doc' && status === 'student' ? 'Ачуу' : type.typeValue === 'link' ? 'Өтүү' : ''; + const btnLabel = type.typeValue === 'doc' && status === 'working' ? 'Ачуу' : type.typeValue === 'doc' && status === 'student' ? 'Ачуу' : type.typeValue === 'link' ? 'Өтүү' : ''; return (
@@ -111,6 +113,8 @@ export default function LessonCard({ ${type.typeValue === 'video' ? 'w-full' : ''} flex flex-col justify-evenly lesson-card-border rounded p-2 ${type.typeValue === 'doc' ? 'w-full min-h-[160px] bg-black' : ''} + + ${type.typeValue === 'test' ? 'w-full min-h-[160px] bg-black' : ''} `} style={{ backgroundColor: cardBg }} @@ -118,9 +122,27 @@ export default function LessonCard({
{/*
{!cardValue.photo && }
*/} - {shortTitle} +
+ {shortTitle} + {cardValue.score ? {`/ Балл: ${cardValue.score}`} : ''} +
{shortDoc}
{type.typeValue === 'link' && {shortUrl}} + {answers && ( +
+ {answers.map((item) => { + return ( +
+ +
+ ); + })} +
+ )}
{cardValue?.desctiption && cardValue?.desctiption !== 'null' && shortDescription}
{status === 'working' && (
@@ -134,7 +156,7 @@ export default function LessonCard({ {videoPreviw} {/* button */} - {( + { <> {status === 'student' && type.typeValue === 'doc' ? (
@@ -148,10 +170,21 @@ export default function LessonCard({
) : ( -
- {btnLabel &&
+ + {' '} +
+ )} {status === 'working' && ( -
+
diff --git a/app/components/lessons/LessonDocument.tsx b/app/components/lessons/LessonDocument.tsx index be2c4627..315840cc 100644 --- a/app/components/lessons/LessonDocument.tsx +++ b/app/components/lessons/LessonDocument.tsx @@ -22,8 +22,9 @@ import { mainStepsType } from '@/types/mainStepType'; import useErrorMessage from '@/hooks/useErrorMessage'; import { LayoutContext } from '@/layout/context/layoutcontext'; import FormModal from '../popUp/FormModal'; +import PDFViewer from '../PDFBook'; -export default function LessonDocument({ element, content, fetchPropElement, deleteElement }: { element: mainStepsType; content: any; fetchPropElement: (id: number) => void; deleteElement: (id: number) => void }) { +export default function LessonDocument({ element, content, fetchPropElement, clearProp }: { element: mainStepsType; content: any; fetchPropElement: (id: number) => void; clearProp: boolean}) { interface docValueType { title: string; description: string; @@ -42,6 +43,7 @@ export default function LessonDocument({ element, content, fetchPropElement, del title: string; updated_at: string; user_id: number; + document_path:string; } const { course_id } = useParams(); @@ -120,7 +122,7 @@ export default function LessonDocument({ element, content, fetchPropElement, del <>
setPDFVisible(false)}> -
{/* */}
+
); @@ -143,9 +145,7 @@ export default function LessonDocument({ element, content, fetchPropElement, del const editing = async () => { const data = await fetchElement(element.lesson_id, element.id); if (data.success) { - // setElement({ content: data.content, step: data.step }); setEditingLesson({title: data.content.title, file:null, document: data.content.document, description: data.content.description}) - console.log(data); } else { setMessage({ state: true, @@ -248,9 +248,9 @@ export default function LessonDocument({ element, content, fetchPropElement, del cardBg={'#ddc4f51a'} type={{ typeValue: 'doc', icon: 'pi pi-doc' }} typeColor={'var(--mainColor)'} - lessonDate={'xx-xx'} + lessonDate={new Date(document.created_at).toISOString().slice(0, 10)} urlForPDF={() => sentToPDF(document.document || '')} - urlForDownload="" + urlForDownload={document.document_path || ''} /> ) )} @@ -326,8 +326,9 @@ export default function LessonDocument({ element, content, fetchPropElement, del }, [content]); useEffect(() => { - console.log('edititing', editingLesson); - }, [editingLesson]); + console.log('edititing', element); + setDocValue({ title: '', description: '', file: null }); + }, [element]); return (
@@ -389,7 +390,7 @@ export default function LessonDocument({ element, content, fetchPropElement, del
- {documentSection()} + {!clearProp && documentSection()}
); } diff --git a/app/components/lessons/LessonTest.tsx b/app/components/lessons/LessonTest.tsx new file mode 100644 index 00000000..f19473d7 --- /dev/null +++ b/app/components/lessons/LessonTest.tsx @@ -0,0 +1,405 @@ +'use client'; + +import { lessonSchema } from '@/schemas/lessonSchema'; +import { EditableLesson } from '@/types/editableLesson'; +import { lessonStateType } from '@/types/lessonStateType'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { useParams, useRouter } from 'next/navigation'; +import { Button } from 'primereact/button'; +import { FileUpload } from 'primereact/fileupload'; +import { InputText } from 'primereact/inputtext'; +import { ProgressSpinner } from 'primereact/progressspinner'; +import { useContext, useEffect, useRef, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { NotFound } from '../NotFound'; +import { lessonType } from '@/types/lessonType'; +import LessonCard from '../cards/LessonCard'; +import { useMediaQuery } from '@/hooks/useMediaQuery'; +import { log } from 'node:console'; +import { getToken } from '@/utils/auth'; +import { addDocument, addTest, deleteDocument, deleteTest, fetchElement, updateDocument, updateTest } from '@/services/steps'; +import { mainStepsType } from '@/types/mainStepType'; +import useErrorMessage from '@/hooks/useErrorMessage'; +import { LayoutContext } from '@/layout/context/layoutcontext'; +import FormModal from '../popUp/FormModal'; +import PDFViewer from '../PDFBook'; +import { InputTextarea } from 'primereact/inputtextarea'; + +export default function LessonTest({ element, content, fetchPropElement, clearProp }: { element: mainStepsType; content: any; fetchPropElement: (id: number) => void; clearProp: boolean }) { + interface contentType { + course_id: number | null; + created_at: string; + description: string | null; + document: string; + id: number | null; + lesson_id: number; + status: true; + title: string; + updated_at: string; + user_id: number; + answers: { id: number | null; text: string; is_correct: boolean }[]; + content: string; + score: number; + image: null | string; + } + + const { course_id } = useParams(); + + const router = useRouter(); + const media = useMediaQuery('(max-width: 640px)'); + const fileUploadRef = useRef(null); + const showError = useErrorMessage(); + const { setMessage } = useContext(LayoutContext); + + const [editingLesson, setEditingLesson] = useState<{ title: string; score: number } | null>({ title: '', score: 0 }); + const [visible, setVisisble] = useState(false); + const [imageState, setImageState] = useState(null); + const [contentShow, setContentShow] = useState(false); + // doc + const [answer, setAnswer] = useState<{ id: number | null; text: string; is_correct: boolean }[]>([ + { text: '', is_correct: false, id: null }, + { text: '', is_correct: false, id: null } + ]); + const [test, setTests] = useState({ answers: { id: null, text: '', is_correct: false }, content: '', score: 0, image: null, title: '' }); + const [testValue, setTestValue] = useState<{ title: string; score: number }>({ title: '', score: 0 }); + const [testShow, setTestShow] = useState(false); + const [urlPDF, setUrlPDF] = useState(''); + + const [progressSpinner, setProgressSpinner] = useState(false); + const [selectType, setSelectType] = useState(''); + const [selectId, setSelectId] = useState(null); + + const clearValues = () => { + setTestValue({ title: '', score: 0 }); + setAnswer([]); + setEditingLesson(null); + setSelectId(null); + setSelectType(''); + }; + + const toggleSpinner = () => { + setProgressSpinner(true); + setInterval(() => { + setProgressSpinner(false); + }, 1000); + }; + + // validate + const { + setValue, + formState: { errors } + } = useForm({ + resolver: yupResolver(lessonSchema), + mode: 'onChange' + }); + + const selectedForEditing = (id: number) => { + setSelectId(id); + setVisisble(true); + editing(); + }; + + const editing = async () => { + const data = await fetchElement(element.lesson_id, element.id); + console.log(data); + + if (data.success) { + setEditingLesson({ title: data.content.content, score: data.content.score}); + if(data.content.answers && Array.isArray(data.content.answers)){ + console.log('DA! ', ); + + setAnswer(data.content.answers); + } + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинирээк кайталаныз' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + // update document + const handleAddTest = async () => { + console.log(answer); + + const data = await addTest(answer, testValue.title, element?.lesson_id && Number(element?.lesson_id), element.type.id, element.id, testValue.score); + console.log(data); + + if (data?.success) { + fetchPropElement(element.id); + clearValues(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү кошулду!', detail: '' } + }); + } else { + setTestValue({ title: '', score: 0 }); + setEditingLesson(null); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при изменении урока' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const addOption = () => { + setAnswer((prev) => [...prev, { text: '', is_correct: false, id: null }]); + }; + + const deleteOption = (index: number) => { + setAnswer((prev) => prev.filter((_, i) => i !== index)); + }; + + // update test + const handleUpdateTest = async () => { + const data = await updateTest(answer, editingLesson?.title || '', element.lesson_id, Number(selectId), element.type.id, element.id, 1); + console.log(data); + + // if (data?.success) { + // fetchPropElement(element.id); + // clearValues(); + // setMessage({ + // state: true, + // value: { severity: 'success', summary: 'Ийгиликтүү өзгөртүлдү!', detail: '' } + // }); + // } else { + // // setDocValue({ title: '', description: '', file: null }); + // setEditingLesson(null); + // setMessage({ + // state: true, + // value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при изменении урока' } + // }); + // if (data?.response?.status) { + // showError(data.response.status); + // } + // } + }; + + // delete document + const handleDeleteTest = async (id: number) => { + console.log(element.lesson_id, id, element.type.id, element.id); + + const data = await deleteTest(element.lesson_id, id, element.type.id, element.id); + console.log(data); + + if (data.success) { + console.log(element.id); + clearValues(); + fetchPropElement(element.id); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при удалении' } + }); + if (data.response.status) { + showError(data.response.status); + } + } + }; + + const optionAddBtn = answer.length > 2 && answer[answer.length - 1].text.length < 1; + const testSection = () => { + return ( +
+ {contentShow ? ( +
+
+ {testShow ? ( + + ) : ( + test && ( + selectedForEditing(id)} + onDelete={(id: number) => handleDeleteTest(id)} + cardValue={{ title: test?.content || '', id: Number(test!.id), desctiption: test?.description || '', type: 'test', score: test.score }} + cardBg={'#ddc4f51a'} + type={{ typeValue: 'test', icon: 'pi pi-doc' }} + typeColor={'var(--mainColor)'} + lessonDate={test.created_at && new Date(test.created_at).toISOString().slice(0, 10)} + urlForPDF={() => {}} + urlForDownload={''} + answers={test.answers} + /> + ) + )} +
+
+ ) : ( +
+
+
+
+ { + setTestValue((prev) => ({ ...prev, title: e.target.value })); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} +
+
+ Балл + { + setTestValue((prev) => ({ ...prev, score: Number(e.target.value) })); + }} + /> +
+
+
+ {answer.map((item, index) => { + return ( +
+ { + setAnswer((prev) => prev.map((ans, i) => (i === index ? { ...ans, is_correct: true } : { ...ans, is_correct: false }))); + }} + /> + { + setAnswer((prev) => prev.map((ans, i) => (i === index ? { ...ans, text: e.target.value } : ans))); + }} + /> +
+ ); + })} + +
+
+
+
+
+
+
+ )} +
+ ); + }; + + useEffect(() => { + console.log('ANSWER ', answer); + }, [answer]); + + useEffect(() => { + console.log('content', content); + if (content) { + setContentShow(true); + setTests(content); + } else { + setContentShow(false); + } + }, [content]); + + useEffect(() => { + console.log('element', element); + setTestValue({ title: '', score: 0 }); + }, [element]); + + return ( +
+ { + handleUpdateTest(); + }} + clearValues={clearValues} + visible={visible} + setVisible={setVisisble} + start={false} + > +
+
+
+
+ { + setEditingLesson((prev) => prev && ({ ...prev, title: e.target.value })); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} +
+
+ Балл + { + setEditingLesson((prev) => prev && ({ ...prev, score: Number(e.target.value) })); + }} + /> +
+
+
+ {answer.map((item, index) => { + console.log(item); + + return ( +
+ { + setAnswer((prev) => prev.map((ans, i) => (i === index ? { ...ans, is_correct: true } : { ...ans, is_correct: false }))); + }} + /> + { + setAnswer((prev) => prev.map((ans, i) => (i === index ? { ...ans, text: e.target.value } : ans))); + }} + /> +
+ ); + })} + +
+
+
+
+ {!clearProp && testSection()} +
+ ); +} diff --git a/app/components/lessons/LessonVideo.tsx b/app/components/lessons/LessonVideo.tsx index cc40f3e4..d095b219 100644 --- a/app/components/lessons/LessonVideo.tsx +++ b/app/components/lessons/LessonVideo.tsx @@ -22,7 +22,7 @@ import { Dropdown } from 'primereact/dropdown'; import { Dialog } from 'primereact/dialog'; import { FileWithPreview } from '@/types/fileuploadPreview'; -export default function LessonVideo({ element, content, fetchPropElement, deleteElement }: { element: mainStepsType; content: any; fetchPropElement: (id: number) => void; deleteElement: (id: number) => void }) { +export default function LessonVideo({ element, content, fetchPropElement, clearProp }: { element: mainStepsType; content: any; fetchPropElement: (id: number) => void; clearProp: boolean }) { interface docValueType { title: string; description: string; @@ -199,7 +199,7 @@ export default function LessonVideo({ element, content, fetchPropElement, delete const data = await fetchElement(element.lesson_id, element.id); if (data.success) { // setElement({ content: data.content, step: data.step }); - setEditingLesson({ title: data.content.title, video_type_id: data.content.video_type_id , video_link: data.content.link, description: data.content.description, }); + setEditingLesson({ title: data.content.title, video_type_id: data.content.video_type_id, video_link: data.content.link, description: data.content.description }); } else { setMessage({ state: true, @@ -287,52 +287,21 @@ export default function LessonVideo({ element, content, fetchPropElement, delete {!contentShow ? (
- { - toggleVideoType(e.value); - }} - options={videoSelect} - optionLabel="name" - placeholder="Танданыз" - // style={{backgroundColor: 'var(--mainColor', color: 'white'}} - panelStyle={{ color: 'white' }} - className="w-[213px] sm:w-full md:w-14rem" - />
- {selectedCity?.status ? ( -
- { - setVideoValue((prev) => ({ ...prev, video_link: e.target.value })); - setValue('usefulLink', e.target.value, { shouldValidate: true }); - }} - /> - {errors.usefulLink?.message} -
- ) : ( -
- {}} - accept="video/" - onSelect={(e) => - setVideoValue((prev) => ({ - ...prev, - file: e.files[0] - })) - } - /> -
- )} +
+ { + setVideoValue((prev) => ({ ...prev, video_link: e.target.value })); + setValue('usefulLink', e.target.value, { shouldValidate: true }); + }} + /> + {errors.usefulLink?.message} +
@@ -412,7 +381,7 @@ export default function LessonVideo({ element, content, fetchPropElement, delete cardBg={'white'} type={{ typeValue: 'video', icon: 'pi pi-video' }} typeColor={'var(--mainColor)'} - lessonDate={'xx-xx'} + lessonDate={new Date(video.created_at).toISOString().slice(0, 10)} urlForPDF={() => ''} urlForDownload="" videoVisible={() => handleVideoCall(String(video?.link))} @@ -436,9 +405,10 @@ export default function LessonVideo({ element, content, fetchPropElement, delete } }, [content]); - useEffect(()=> { - console.log('video', video); - },[video]); + useEffect(() => { + console.log('video', element); + setVideoValue({ title: '', description: '', file: null, url: '', video_link: '', cover: null, link: null }); + }, [element]); useEffect(() => { console.log('edititing', editingLesson); @@ -504,7 +474,7 @@ export default function LessonVideo({ element, content, fetchPropElement, delete
- {videoSection()} + {!clearProp && videoSection()}
); } diff --git a/app/globals.css b/app/globals.css index d758e31e..103579f4 100644 --- a/app/globals.css +++ b/app/globals.css @@ -616,4 +616,12 @@ h1, h2, h3, h4, h5, h6 { } 100%{ background-color: rgb(209, 210, 208); } +} + +/* texterea */ + +@media screen and (max-width: 640px){ + .p-inputtextarea{ + /* padding: 3px; */ + } } \ No newline at end of file diff --git a/layout/AppMenu.tsx b/layout/AppMenu.tsx index 15471116..f4c0e42a 100644 --- a/layout/AppMenu.tsx +++ b/layout/AppMenu.tsx @@ -5,10 +5,20 @@ import AppMenuitem from './AppMenuitem'; import { LayoutContext } from './context/layoutcontext'; import { MenuProvider } from './context/menucontext'; import { AppMenuItem } from '@/types'; -import { useParams, usePathname } from 'next/navigation'; +import { useParams, usePathname, useRouter } from 'next/navigation'; +import { getConfirmOptions } from '@/utils/getConfirmOptions'; +import { confirmDialog } from 'primereact/confirmdialog'; +import { addThemes, deleteTheme, fetchLessonShow, updateTheme } from '@/services/courses'; +import FormModal from '@/app/components/popUp/FormModal'; +import { InputText } from 'primereact/inputtext'; +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { lessonSchema } from '@/schemas/lessonSchema'; +import useErrorMessage from '@/hooks/useErrorMessage'; +import { Button } from 'primereact/button'; const AppMenu = () => { - const { layoutConfig, user, course, contextFetchCourse, contextFetchThemes, contextThemes, setContextThemes, contextFetchStudentThemes, contextStudentThemes } = useContext(LayoutContext); + const { layoutConfig, user, course, mainCourseId, setMainCourseId, contextFetchCourse, contextFetchThemes, contextThemes, setContextThemes, contextFetchStudentThemes, contextStudentThemes } = useContext(LayoutContext); interface test { label: string; id: number; @@ -17,28 +27,55 @@ const AppMenu = () => { command?: () => void; } + // validate + const { + setValue, + formState: { errors } + } = useForm({ + resolver: yupResolver(lessonSchema), + mode: 'onChange' + }); + const location = usePathname(); const pathname = location; const { studentThemeCourse } = useParams(); + const params = useParams(); + const course_Id = params.course_Id; + + const router = useRouter(); const [courseList, setCourseList] = useState([]); - const [clickedCourseId, setClickedCourseId] = useState(null); + const [selectId, setSelectId] = useState(null); + const [visible, setVisisble] = useState(false); + const [themeAddvisible, setThemeAddVisisble] = useState(false); + const [editingLesson, setEditingLesson] = useState<{ title: string } | null>(null); + const [themeValue, setThemeValue] = useState<{ title: string }>({title: ''}); const [themesStudentList, setThemesStudentList] = useState<{ label: string; id: number; to: string; items?: AppMenuItem[] }[]>([]); + const showError = useErrorMessage(); + const { setMessage } = useContext(LayoutContext); + const byStatus: AppMenuItem[] = user?.is_working - ? [ - { - label: 'Курстар', - icon: 'pi pi-fw pi-calendar-clock', - items: courseList?.length > 0 ? courseList : [] - } - ] + ? pathname.startsWith('/course/') + ? [ + { + label: 'Артка', + icon: 'pi pi-fw pi-arrow-left', + to: '/course' + }, + { + label: 'Темалар', + icon: 'pi pi-fw pi-calendar-clock', + items: courseList?.length > 0 ? courseList : [] + } + ] + : [] : user?.is_student ? [ { label: 'Окуу планы', icon: 'pi pi-fw pi-calendar-clock', to: '/teaching' }, pathname.startsWith('/teaching/lesson/') ? { label: 'Темалар', icon: 'pi pi-fw pi-book', items: themesStudentList?.length > 0 ? themesStudentList : [] } : { label: '' } - ] + ] : []; const model: AppMenuItem[] = [ @@ -52,6 +89,102 @@ const AppMenu = () => { } ]; + const selectedForEditing = (id: number) => { + setSelectId(id); + setVisisble(true); + editing(id); + }; + + const editing = async (id: number) => { + console.log(selectId); + + const data = await fetchLessonShow(id); + console.log(data); + if (data.lesson) { + setEditingLesson({ title: data.lesson.title }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинирээк кайталаныз' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const clearValues = () => { + setThemeValue({title: ''}) + setEditingLesson(null); + setSelectId(null); + }; + + // add theme + const handleAddTheme = async () => { + console.log(course_Id); + + const data = await addThemes(Number(course_Id), themeValue?.title ? themeValue?.title : ''); + console.log(data); + + if (data?.success) { + contextFetchThemes(Number(course_Id)); + clearValues(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү кошулду!', detail: '' } + }); + } else { + setEditingLesson(null); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при добавлении темы' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const handleDeleteTheme = async (id: number) => { + const data = await deleteTheme(id); + console.log(data); + if (data.success) { + contextFetchThemes(Number(course_Id)); + } + }; + + // update document + const handleUpdate = async () => { + console.log(selectId, course_Id); + + const data = await updateTheme(Number(course_Id), selectId, editingLesson?.title ? editingLesson?.title : ''); + console.log(data); + + if (data?.success) { + contextFetchThemes(Number(course_Id)); + clearValues(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өзгөртүлдү!', detail: '' } + }); + } else { + setEditingLesson(null); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при изменении урока' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const sentDelete = (item: any) => { + console.log('Delete theme ID:', item.id); + const options = getConfirmOptions(Number(), () => handleDeleteTheme(item.id)); + confirmDialog(options); + }; + useEffect(() => { if (user?.is_working) { contextFetchCourse(1); @@ -68,40 +201,19 @@ const AppMenu = () => { } }, [user, studentThemeCourse]); - useEffect(() => { - if (course) { - const forCourse: test[] = [{ label: 'Курс', id: 0, to: '/course' }]; - course.data?.map((item) => - forCourse.push({ - label: item.title, - id: item.id, - to: `/course/${clickedCourseId}`, - items: [], // пока пусто - command: () => { - contextFetchThemes(item.id); - setClickedCourseId(item.id); - } - }) - ); - setCourseList(forCourse); - } - }, [course]); - useEffect(() => { if (contextThemes && contextThemes.lessons) { const newThemes = contextThemes.lessons.data.map((item: any) => ({ label: item.title, id: item.id, - to: `/course/${clickedCourseId}/${item.id}`, + to: `/course/${course_Id}/${item.id}`, + onEdit: () => { + selectedForEditing(item.id); + }, + onDelete: () => sentDelete(item) })); - setCourseList((prev) => - prev.map((course) => - course.id === clickedCourseId - ? { ...course, items: newThemes } // добавляем темы - : course - ) - ); + setCourseList(newThemes); } }, [contextThemes]); @@ -125,11 +237,72 @@ const AppMenu = () => { return ( + { + handleUpdate(); + }} + clearValues={clearValues} + visible={visible} + setVisible={setVisisble} + start={false} + > +
+
+ { + console.log(editingLesson); + setEditingLesson((prev) => prev && { ...prev, title: e.target.value }); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} +
+
+
+ + { + handleAddTheme(); + }} + clearValues={clearValues} + visible={themeAddvisible} + setVisible={setThemeAddVisisble} + start={false} + > +
+
+ { + console.log(e.target.value, themeValue); + setThemeValue((prev) => prev && { ...prev, title: e.target.value }); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} +
+
+
+
    {model.map((item, i) => { return !item?.seperator ? :
  • ; })}
+ {pathname.startsWith('/course/') &&
+ +
}
); }; diff --git a/layout/AppMenuitem.tsx b/layout/AppMenuitem.tsx index de7c7630..2e3bc7ca 100644 --- a/layout/AppMenuitem.tsx +++ b/layout/AppMenuitem.tsx @@ -67,13 +67,50 @@ const AppMenuitem = (props: AppMenuItemProps) => { ) : null} - {item!.to && !item!.items && item!.visible !== false ? ( + {/* {item!.to && !item!.items && item!.visible !== false ? ( itemClick(e)} className={classNames(item!.class, 'p-ripple', { 'active-route': isActiveRoute })} tabIndex={0}> {item!.label} {item!.items && } + ) : null} */} + {item!.to && !item!.items && item!.visible !== false ? ( +
+ itemClick(e)} className={classNames(item!.class, 'p-ripple', { 'active-route': isActiveRoute })} tabIndex={0} style={{ flexGrow: 1 }}> + + {item!.label} + + + +
+ {/* Кнопки редактирования и удаления */} + {item!.onEdit && ( + + )} + {item!.onDelete && ( + + )} +
+
) : null} {subMenu} diff --git a/layout/context/layoutcontext.tsx b/layout/context/layoutcontext.tsx index 8edf3d8f..fbae4f8d 100644 --- a/layout/context/layoutcontext.tsx +++ b/layout/context/layoutcontext.tsx @@ -83,6 +83,7 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { },[crumbUrls]); // fetch course + const [mainCourseId, setMainCourseId] = useState(null); const [course, setCourses] = useState<{ current_page: number; total: number; per_page: number; data: myMainCourseType[] }>({ current_page: 1, total: 0, per_page: 10, data: [] }); const contextFetchCourse = async (page: number) => { @@ -138,6 +139,8 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { crumbUrls, contextAddCrumb, + mainCourseId, + setMainCourseId }; return ( diff --git a/services/auth.tsx b/services/auth.tsx index 67fe48f9..d549e43d 100644 --- a/services/auth.tsx +++ b/services/auth.tsx @@ -11,7 +11,6 @@ let url = ''; export const login = async (value:LoginType) => { url = process.env.NEXT_PUBLIC_BASE_URL + '/login?'; - console.log(url); try { const res = await axiosInstance.post('/login?', value); diff --git a/services/courses.tsx b/services/courses.tsx index f438662a..1b00890a 100644 --- a/services/courses.tsx +++ b/services/courses.tsx @@ -116,6 +116,8 @@ export const addThemes = async (id: number, value: string) => { }; export const fetchThemes = async (id: number | null) => { + console.log('Вызываемая тема ',id); + try { const res = await axiosInstance(`/v1/teacher/lessons?course_id=${id}`); diff --git a/services/query-tests.http b/services/query-tests.http index 7b6b28ce..588454e0 100644 --- a/services/query-tests.http +++ b/services/query-tests.http @@ -15,12 +15,12 @@ # ### # test -GET https://api.mooc.oshsu.kg/public/api/v1/teacher/lessons/step?lesson_id=77 -Authorization: Bearer {{token}} +POST https://api.mooc.oshsu.kg/public/api/v1/login?email=ajaparkulov@oshsu.kg&password=010270Ja Content-Type: application/json { - "lesson_id": 77 + "email": "ajaparkulov@oshsu.kg", + "password": "010270Ja" } # test @@ -35,3 +35,4 @@ Content-Type: application/json } + diff --git a/services/steps.tsx b/services/steps.tsx index 98ebabf5..8ce8d69d 100644 --- a/services/steps.tsx +++ b/services/steps.tsx @@ -51,7 +51,7 @@ export const addLesson = async (value: { lesson_id: number; type_id: number }) = export const fetchElement = async (lesson_id: number, step_id: number) => { console.log(lesson_id, step_id); - + try { const res = await axiosInstance.get(`/v1/teacher/lessons/step/content?lesson_id=${lesson_id}&step_id=${step_id}`); const data = await res.data; @@ -174,7 +174,7 @@ export const deleteVideo = async (lesson_id: number, content_id: number) => { export const updateVideo = async ( token: string | null, - value: { file: File | null; title: string; description: string; video_link: string; cover: string | null, video_type_id: number }, + value: { file: File | null; title: string; description: string; video_link: string; cover: string | null; video_type_id: number }, lesson_id: number, contentId: number, videoType: number, @@ -219,3 +219,68 @@ export const deleteStep = async (lesson_id: number, step_id: number) => { return err; } }; + +// test +export const addTest = async (answers: { text: string; is_correct: boolean }[], title: string, lesson_id: number, type_id: number, step_id: number, score: number) => { + console.log(score); + + const payload = { + lesson_id, + type_id, + step_id, + answers, // массив уходит как есть + content: title, + img: null, // если файла нет, можно null или пустую строку + score + }; + + try { + const res = await axiosInstance.post(`/v1/teacher/test/store`, payload); + + return res.data; + } catch (err) { + console.log('Ошибка при добавлении курса', err); + return err; + } +}; + +export const updateTest = async ( + answers: { text: string; is_correct: boolean }[], title: string, lesson_id: number, test_id:number, type_id: number, step_id: number, score: number +) => { + url = `/v1/teacher/test/update`; + + const payload = { + lesson_id, + test_id, + type_id, + step_id, + answers, // массив уходит как есть + content: title, + img: null, // если файла нет, можно null или пустую строку + score + }; + + try { + const res = await axiosInstance.post(url, payload); + + const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка при обновлении урока', err); + return err; + } +}; + +export const deleteTest = async (lesson_id: number, test_id: number, type_id: number, step_id: number) => { + console.log(lesson_id, test_id, type_id,step_id); + + try { + const res = await axiosInstance.delete(`/v1/teacher/test/delete?lesson_id=${lesson_id}&test_id=${test_id}&type_id=${type_id}&step_id=${step_id}`); + + const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка при удалении темы', err); + return err; + } +}; \ No newline at end of file diff --git a/styles/layout/_menu.scss b/styles/layout/_menu.scss index cb7bef2f..8266dbe7 100644 --- a/styles/layout/_menu.scss +++ b/styles/layout/_menu.scss @@ -1,6 +1,6 @@ .layout-sidebar { position: fixed; - width: 300px; + width: 320px; height: calc(100vh - 9rem); z-index: 999; overflow-y: auto; @@ -10,7 +10,7 @@ transition: transform $transitionDuration, left $transitionDuration; background-color: var(--surface-overlay); border-radius: $borderRadius; - padding: 0.5rem 1.5rem; + padding: 0.5rem 1.2rem; box-shadow: 0px 3px 5px rgba(0, 0, 0, .02), 0px 0px 2px rgba(0, 0, 0, .05), 0px 1px 4px rgba(0, 0, 0, .08); } diff --git a/types/layout.d.ts b/types/layout.d.ts index 80a81c4c..468e2973 100644 --- a/types/layout.d.ts +++ b/types/layout.d.ts @@ -71,6 +71,9 @@ export interface LayoutContextProps { crumbUrls: {type: string; crumbUrl: string }[]; contextAddCrumb: (id)=> void + + mainCourseId: number | null, + setMainCourseId // message: { state: boolean; value: MessageType }; // setMessage: React.Dispatch>; } @@ -123,6 +126,10 @@ export interface AppMenuItem extends MenuModel { disabled?: boolean; replaceUrl?: boolean; command?: ({ originalEvent, item }: CommandProps) => void; + + // + onEdit?: () => void; + onDelete?: () => void; } export interface AppMenuItemProps { diff --git a/utils/axiosInstance.tsx b/utils/axiosInstance.tsx index 2aa919a3..739fc847 100644 --- a/utils/axiosInstance.tsx +++ b/utils/axiosInstance.tsx @@ -25,14 +25,14 @@ axiosInstance.interceptors.response.use( if (status === 401) { console.warn('Неавторизован. Удаляю токен...'); - window.location.href = '/auth/login'; - document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; - localStorage.removeItem('userVisit'); + // window.location.href = '/auth/login'; + // document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; + // localStorage.removeItem('userVisit'); } if (status === 403) { - window.location.href = '/'; console.warn('Не имеет доступ. Перенаправляю...'); + // window.location.href = '/'; } if (status === 404) { From 3e8346a8613422b64b8ff433ab62731dd045847c Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Mon, 8 Sep 2025 13:30:19 +0600 Subject: [PATCH 141/183] =?UTF-8?q?=D0=9E=D1=88=D0=B8=D0=B1=D0=BA=D0=B0=20?= =?UTF-8?q?=D1=81=20=D0=BF=D0=B4=D1=84=20=D0=BF=D0=BE=D0=BA=D0=B0=20=D0=BD?= =?UTF-8?q?=D0=B5=20=D0=BF=D0=BE=D1=8F=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=D1=81?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/pdf/[pdfUrl]/page.tsx | 9 ++++++++- app/components/lessons/LessonDocument.tsx | 11 ++++++----- app/components/lessons/LessonTest.tsx | 1 - app/components/lessons/LessonTyping.tsx | 18 ++---------------- 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/app/(main)/pdf/[pdfUrl]/page.tsx b/app/(main)/pdf/[pdfUrl]/page.tsx index 6fb1a851..02c8e582 100644 --- a/app/(main)/pdf/[pdfUrl]/page.tsx +++ b/app/(main)/pdf/[pdfUrl]/page.tsx @@ -1,6 +1,13 @@ 'use client'; -import PDFViewer from '@/app/components/PDFBook'; +// import PDFViewer from '@/app/components/PDFBook'; +// import PDFViewer from '../PDFBook'; +import dynamic from "next/dynamic"; + +const PDFViewer = dynamic(()=> import('@/app/components/PDFBook'), { + ssr: false, +}) + import { useParams } from 'next/navigation'; export default function PdfUrlViewer() { diff --git a/app/components/lessons/LessonDocument.tsx b/app/components/lessons/LessonDocument.tsx index 315840cc..e907a6c1 100644 --- a/app/components/lessons/LessonDocument.tsx +++ b/app/components/lessons/LessonDocument.tsx @@ -1,8 +1,6 @@ 'use client'; import { lessonSchema } from '@/schemas/lessonSchema'; -import { EditableLesson } from '@/types/editableLesson'; -import { lessonStateType } from '@/types/lessonStateType'; import { yupResolver } from '@hookform/resolvers/yup'; import { useParams, useRouter } from 'next/navigation'; import { Button } from 'primereact/button'; @@ -12,17 +10,20 @@ import { ProgressSpinner } from 'primereact/progressspinner'; import { useContext, useEffect, useRef, useState } from 'react'; import { useForm } from 'react-hook-form'; import { NotFound } from '../NotFound'; -import { lessonType } from '@/types/lessonType'; import LessonCard from '../cards/LessonCard'; import { useMediaQuery } from '@/hooks/useMediaQuery'; -import { log } from 'node:console'; import { getToken } from '@/utils/auth'; import { addDocument, deleteDocument, fetchElement, updateDocument } from '@/services/steps'; import { mainStepsType } from '@/types/mainStepType'; import useErrorMessage from '@/hooks/useErrorMessage'; import { LayoutContext } from '@/layout/context/layoutcontext'; import FormModal from '../popUp/FormModal'; -import PDFViewer from '../PDFBook'; +// import PDFViewer from '../PDFBook'; +import dynamic from "next/dynamic"; + +const PDFViewer = dynamic(()=> import('../PDFBook'), { + ssr: false, +}) export default function LessonDocument({ element, content, fetchPropElement, clearProp }: { element: mainStepsType; content: any; fetchPropElement: (id: number) => void; clearProp: boolean}) { interface docValueType { diff --git a/app/components/lessons/LessonTest.tsx b/app/components/lessons/LessonTest.tsx index f19473d7..2b78b7c7 100644 --- a/app/components/lessons/LessonTest.tsx +++ b/app/components/lessons/LessonTest.tsx @@ -22,7 +22,6 @@ import { mainStepsType } from '@/types/mainStepType'; import useErrorMessage from '@/hooks/useErrorMessage'; import { LayoutContext } from '@/layout/context/layoutcontext'; import FormModal from '../popUp/FormModal'; -import PDFViewer from '../PDFBook'; import { InputTextarea } from 'primereact/inputtextarea'; export default function LessonTest({ element, content, fetchPropElement, clearProp }: { element: mainStepsType; content: any; fetchPropElement: (id: number) => void; clearProp: boolean }) { diff --git a/app/components/lessons/LessonTyping.tsx b/app/components/lessons/LessonTyping.tsx index 50af7542..3b4acb42 100644 --- a/app/components/lessons/LessonTyping.tsx +++ b/app/components/lessons/LessonTyping.tsx @@ -18,7 +18,6 @@ import { lessonType } from '@/types/lessonType'; import { NotFound } from '../NotFound'; import { EditableLesson } from '@/types/editableLesson'; import { Dropdown } from 'primereact/dropdown'; -import PDFViewer from '../PDFBook'; import { useMediaQuery } from '@/hooks/useMediaQuery'; import { useRouter } from 'next/navigation'; import { ProgressSpinner } from 'primereact/progressspinner'; @@ -213,23 +212,10 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp } }; - const documentView = ( - <> -
- setPDFVisible(false)}> -
- -
-
- - ); - const docSection = () => { return (
- {PDFVisible ? ( - documentView - ) : ( + ( <>
@@ -305,7 +291,7 @@ export default function LessonTyping({ mainType, courseId, lessonId }: { mainTyp
- )} + )
); }; From d9d3c4e56d31c18e81331ec60e7502ac0d356725 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Mon, 8 Sep 2025 15:06:25 +0600 Subject: [PATCH 142/183] =?UTF-8?q?=D0=BA=D1=80=D1=83=D0=B4=20=D1=82=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=B0=20=D0=B7=D0=B0=D0=B2=D0=B5=D1=80=D1=88=D0=B5?= =?UTF-8?q?=D0=BD,=20=D0=B0=D0=BA=D1=82=D0=B8=D0=B2=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=88=D0=B0=D0=B3=D0=B8=20=D0=BD=D0=B0=D1=87=D0=B8=D0=BD=D0=B0?= =?UTF-8?q?=D1=8E=D1=82=D1=81=D1=8F=20=D0=BF=D0=BE=20=D1=83=D0=BC=D0=BE?= =?UTF-8?q?=D0=BB=D1=87=D0=B0=D0=BD=D0=B8=D1=8E=20=D1=81=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BD=D1=86=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../course/[course_Id]/[lesson_id]/page.tsx | 29 ++++++---- .../[studentThemeId]/[lesson_id]/page.tsx | 10 ++-- app/components/cards/LessonCard.tsx | 5 +- app/components/lessons/LessonTest.tsx | 56 ++++++++----------- app/components/lessons/LessonVideo.tsx | 15 +++-- services/steps.tsx | 16 +++--- 6 files changed, 65 insertions(+), 66 deletions(-) diff --git a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx index ddd8e933..6b896455 100644 --- a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx +++ b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx @@ -63,6 +63,7 @@ export default function LessonStep() { const [steps, setSteps] = useState([]); const [element, setElement] = useState<{ content: any | null; step: mainStepsType } | null>(null); const [selectedId, setSelectId] = useState(null); + const [lastSelectedId, setLastSelectId] = useState(null); const [hasSteps, setHasSteps] = useState(false); const [themeNull, setThemeNull] = useState(false); const [lesson_id, setLesson_id] = useState((param.lesson_id && Number(param.lesson_id)) || null); @@ -162,6 +163,8 @@ export default function LessonStep() { const handleAddLesson = async (lessonId: number, typeId: number) => { const data = await addLesson({ lesson_id: lessonId, type_id: typeId }); + console.log(data); + if (data.success) { handleFetchSteps(lessonId); setMessage({ @@ -243,10 +246,16 @@ export default function LessonStep() {
); + useEffect(()=> { + console.log('last', lastSelectedId); + + },[lastSelectedId]) + useEffect(() => { if (Array.isArray(steps) && steps.length > 0) { - const firstStep = steps[0]?.id; - setSelectId(steps[0]?.id); + // const firstStep = steps[0]?.id; + const firstStep = steps[steps.length - 1]?.id; + setSelectId(firstStep); handleFetchElement(firstStep); } }, [steps]); @@ -289,13 +298,13 @@ export default function LessonStep() { } }, [contextThemes]); - // if (themeNull) { - // return ( - //
- // - //
- // ); - // } + if (themeNull) { + return ( +
+ +
+ ); + } return (
@@ -312,8 +321,6 @@ export default function LessonStep() { >
{types.map((item) => { - console.log(item); - return (
diff --git a/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/[lesson_id]/page.tsx b/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/[lesson_id]/page.tsx index 9aac122b..ab54083e 100644 --- a/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/[lesson_id]/page.tsx +++ b/app/(student)/teaching/[studentThemeCourse]/[studentThemeId]/[lesson_id]/page.tsx @@ -29,7 +29,7 @@ export default function StudentLessons() { const [hasLessons, setHasLessons] = useState(false); const [skeleton, setSkeleton] = useState(false); const x = localStorage.getItem('currentBreadCrumb'); - const parseX = JSON.parse(x); + const parseX = JSON.parse('x'); const [streamId, setStreamId] = useState(parseX.studentStream || ''); // doc @@ -229,10 +229,10 @@ export default function StudentLessons() { }; useEffect(() => { - const x = localStorage.getItem('currentBreadCrumb'); - const parseX = JSON.parse(x); - console.log('Eto x', parseX); - console.log('Eto x', x); + // const x = localStorage.getItem('currentBreadCrumb'); + // const parseX = JSON.parse(x); + // console.log('Eto x', parseX); + // console.log('Eto x', x); setStreamId(parseX.studentStream); }, [pathname]); diff --git a/app/components/cards/LessonCard.tsx b/app/components/cards/LessonCard.tsx index a080b1c1..af8d6b2c 100644 --- a/app/components/cards/LessonCard.tsx +++ b/app/components/cards/LessonCard.tsx @@ -124,7 +124,10 @@ export default function LessonCard({ {/*
{!cardValue.photo && }
*/}
{shortTitle} - {cardValue.score ? {`/ Балл: ${cardValue.score}`} : ''} + {cardValue.score ?
+ / Балл: + {`${cardValue.score}`} +
: ''}
{shortDoc}
{type.typeValue === 'link' && {shortUrl}} diff --git a/app/components/lessons/LessonTest.tsx b/app/components/lessons/LessonTest.tsx index 2b78b7c7..51d99e7a 100644 --- a/app/components/lessons/LessonTest.tsx +++ b/app/components/lessons/LessonTest.tsx @@ -42,27 +42,24 @@ export default function LessonTest({ element, content, fetchPropElement, clearPr image: null | string; } + interface testType { answers: { id: number | null, text: string, is_correct: boolean }[], id: number | null, content: string, score: number, image: string | null, title: string, created_at: string } + const { course_id } = useParams(); - - const router = useRouter(); const media = useMediaQuery('(max-width: 640px)'); - const fileUploadRef = useRef(null); const showError = useErrorMessage(); const { setMessage } = useContext(LayoutContext); const [editingLesson, setEditingLesson] = useState<{ title: string; score: number } | null>({ title: '', score: 0 }); const [visible, setVisisble] = useState(false); - const [imageState, setImageState] = useState(null); const [contentShow, setContentShow] = useState(false); // doc const [answer, setAnswer] = useState<{ id: number | null; text: string; is_correct: boolean }[]>([ { text: '', is_correct: false, id: null }, { text: '', is_correct: false, id: null } ]); - const [test, setTests] = useState({ answers: { id: null, text: '', is_correct: false }, content: '', score: 0, image: null, title: '' }); + const [test, setTests] = useState({ answers: [{ id: null, text: '', is_correct: false }], id: null, content: '', score: 0, image: null, title: '', created_at:'' }); const [testValue, setTestValue] = useState<{ title: string; score: number }>({ title: '', score: 0 }); const [testShow, setTestShow] = useState(false); - const [urlPDF, setUrlPDF] = useState(''); const [progressSpinner, setProgressSpinner] = useState(false); const [selectType, setSelectType] = useState(''); @@ -100,13 +97,9 @@ export default function LessonTest({ element, content, fetchPropElement, clearPr const editing = async () => { const data = await fetchElement(element.lesson_id, element.id); - console.log(data); - if (data.success) { setEditingLesson({ title: data.content.content, score: data.content.score}); - if(data.content.answers && Array.isArray(data.content.answers)){ - console.log('DA! ', ); - + if(data.content.answers && Array.isArray(data.content.answers)){ setAnswer(data.content.answers); } } else { @@ -125,8 +118,6 @@ export default function LessonTest({ element, content, fetchPropElement, clearPr console.log(answer); const data = await addTest(answer, testValue.title, element?.lesson_id && Number(element?.lesson_id), element.type.id, element.id, testValue.score); - console.log(data); - if (data?.success) { fetchPropElement(element.id); clearValues(); @@ -160,24 +151,24 @@ export default function LessonTest({ element, content, fetchPropElement, clearPr const data = await updateTest(answer, editingLesson?.title || '', element.lesson_id, Number(selectId), element.type.id, element.id, 1); console.log(data); - // if (data?.success) { - // fetchPropElement(element.id); - // clearValues(); - // setMessage({ - // state: true, - // value: { severity: 'success', summary: 'Ийгиликтүү өзгөртүлдү!', detail: '' } - // }); - // } else { - // // setDocValue({ title: '', description: '', file: null }); - // setEditingLesson(null); - // setMessage({ - // state: true, - // value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при изменении урока' } - // }); - // if (data?.response?.status) { - // showError(data.response.status); - // } - // } + if (data?.success) { + fetchPropElement(element.id); + clearValues(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өзгөртүлдү!', detail: '' } + }); + } else { + // setDocValue({ title: '', description: '', file: null }); + setEditingLesson(null); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при изменении урока' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } }; // delete document @@ -188,7 +179,6 @@ export default function LessonTest({ element, content, fetchPropElement, clearPr console.log(data); if (data.success) { - console.log(element.id); clearValues(); fetchPropElement(element.id); setMessage({ @@ -221,7 +211,7 @@ export default function LessonTest({ element, content, fetchPropElement, clearPr status="working" onSelected={(id: number, type: string) => selectedForEditing(id)} onDelete={(id: number) => handleDeleteTest(id)} - cardValue={{ title: test?.content || '', id: Number(test!.id), desctiption: test?.description || '', type: 'test', score: test.score }} + cardValue={{ title: test?.content || '', id: Number(test!.id), desctiption: '', type: 'test', score: test.score }} cardBg={'#ddc4f51a'} type={{ typeValue: 'test', icon: 'pi pi-doc' }} typeColor={'var(--mainColor)'} diff --git a/app/components/lessons/LessonVideo.tsx b/app/components/lessons/LessonVideo.tsx index d095b219..67e70ed1 100644 --- a/app/components/lessons/LessonVideo.tsx +++ b/app/components/lessons/LessonVideo.tsx @@ -42,7 +42,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP updated_at: string; user_id: number; link: File | null; - cover: string; + cover: string | null; cover_url: string; } @@ -61,12 +61,12 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP interface videoValueType { title: string; - description?: string; + description: string | null; // вместо ? → строго string | null document?: File | null; url?: string | null; video_link: string; - video_type_id: number | null; - file?: File | null; + video_type_id: number; // без null + cover: File | null; } const { course_id } = useParams(); @@ -82,7 +82,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP const [videoTypes, setVideoTypes] = useState([]); const [videoCall, setVideoCall] = useState(false); const [videoLink, setVideoLink] = useState(''); - const [videoValue, setVideoValue] = useState({ + const [videoValue, setVideoValue] = useState<{ title: string; description: string; file: null; url: string; video_link: string; cover: File | null; link: null }>({ title: '', description: '', file: null, @@ -199,7 +199,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP const data = await fetchElement(element.lesson_id, element.id); if (data.success) { // setElement({ content: data.content, step: data.step }); - setEditingLesson({ title: data.content.title, video_type_id: data.content.video_type_id, video_link: data.content.link, description: data.content.description }); + setEditingLesson({ title: data.content.title, video_type_id: data.content.video_type_id, video_link: data.content.link, cover: null, description: data.content.description }); } else { setMessage({ state: true, @@ -260,7 +260,6 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP const handleUpdateVideo = async () => { const token = getToken('access_token'); const data = await updateVideo(token, editingLesson, video?.lesson_id ? Number(video?.lesson_id) : null, Number(selectId), 1, element.type.id, element.type.id); - console.log(data); if (data?.success) { fetchPropElement(element.id); @@ -426,7 +425,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP { setEditingLesson((prev) => prev && { ...prev, video_link: e.target.value }); diff --git a/services/steps.tsx b/services/steps.tsx index 8ce8d69d..6e41341f 100644 --- a/services/steps.tsx +++ b/services/steps.tsx @@ -132,7 +132,7 @@ export const updateDocument = async (token: string | null, lesson_id: number | n // video -export const addVideo = async (value: { file: File | null; title: string; description: string; video_link: string; cover: string | null }, lesson_id: number, videoType: number, type_id: number, step_id: number) => { +export const addVideo = async (value: { file: File | null; title: string; description: string; video_link: string; cover: File | null }, lesson_id: number, videoType: number, type_id: number, step_id: number) => { const formData = new FormData(); url = `v1/teacher/video/store?lesson_id=${lesson_id}&title=${value.title}&description=${value.description}&video_link=${value.video_link}&video_type_id=${videoType}`; formData.append('type_id', String(type_id)); @@ -174,8 +174,8 @@ export const deleteVideo = async (lesson_id: number, content_id: number) => { export const updateVideo = async ( token: string | null, - value: { file: File | null; title: string; description: string; video_link: string; cover: string | null; video_type_id: number }, - lesson_id: number, + value: { title: string; description: string | null; video_link: string; cover: File | null; video_type_id: number } | null, + lesson_id: number | null, contentId: number, videoType: number, type_id: number, @@ -183,15 +183,15 @@ export const updateVideo = async ( ) => { let headers: Record = token ? { Authorization: `Bearer ${token}` } : {}; let formData = new FormData(); - url = `/v1/teacher/video/update?lesson_id=${lesson_id}&title=${value.title}&description=${value.description}&video_link=${value.video_link}&video_type_id=${value.video_type_id}&video_id=${contentId}`; + url = `/v1/teacher/video/update?lesson_id=${lesson_id}&title=${value?.title}&description=${value?.description}&video_link=${value?.video_link}&video_type_id=${value?.video_type_id}&video_id=${contentId}`; formData.append('type_id', String(type_id)); formData.append('step_id', String(step_id)); formData.append('lesson_id', String(lesson_id)); - formData.append('video_link', value.video_link); - formData.append('video_type_id', String(value.video_type_id)); + formData.append('video_link', value?.video_link || ''); + formData.append('video_type_id', String(value?.video_type_id)); formData.append('video_id', String(contentId)); - formData.append('title', String(value.title)); - formData.append('description', String(value.description)); + formData.append('title', String(value?.title)); + formData.append('description', String(value?.description)); try { const res = await axiosInstance.post(url, formData, { From c793a3af6d8eb9ff8067270fc0299f0bfc6af35e Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Mon, 8 Sep 2025 18:01:42 +0600 Subject: [PATCH 143/183] =?UTF-8?q?=D0=A3=D1=81=D0=BF=D0=B5=D1=88=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D0=BA=D1=80=D1=83=D0=B4=20=D1=82=D0=B5=D0=BC.?= =?UTF-8?q?=20=D0=94=D0=B8=D0=B7=D0=B0=D0=B9=D0=BD=20=D1=82=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../course/[course_Id]/[lesson_id]/page.tsx | 45 ++---- app/(main)/course/page.tsx | 4 +- app/components/cards/LessonCard.tsx | 14 +- app/components/lessons/LessonTest.tsx | 136 ++++++++++-------- layout/AppMenu.tsx | 33 +++-- layout/context/layoutcontext.tsx | 5 +- styles/layout/forms.css | 2 +- types/layout.d.ts | 2 + 8 files changed, 128 insertions(+), 113 deletions(-) diff --git a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx index 6b896455..c8380e91 100644 --- a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx +++ b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx @@ -11,7 +11,7 @@ import useErrorMessage from '@/hooks/useErrorMessage'; import { useMediaQuery } from '@/hooks/useMediaQuery'; import { LayoutContext } from '@/layout/context/layoutcontext'; import { lessonSchema } from '@/schemas/lessonSchema'; -import { fetchLessonShow } from '@/services/courses'; +import { deleteLesson, fetchLessonShow } from '@/services/courses'; import { addLesson, deleteStep, fetchElement, fetchSteps, fetchTypes } from '@/services/steps'; import { lessonStateType } from '@/types/lessonStateType'; import { mainStepsType } from '@/types/mainStepType'; @@ -27,35 +27,13 @@ import React, { useContext, useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; export default function LessonStep() { - // types - interface editingType { - key: string; - file: string; - url: string; - link: string; - video_type_id?: number | null; - } - - interface videoType { - name: string; - status: boolean; - id: number; - } - - interface videoInsideType { - id: number; - is_link: boolean; - short_title: string; - title: string; - } - const param = useParams(); const course_id = param.course_Id; console.log('step param ', param); const [lessonInfoState, setLessonInfoState] = useState<{ title: string; documents_count: string; usefullinks_count: string; videos_count: string } | null>(null); const media = useMediaQuery('(max-width: 640px)'); - const { setMessage, contextFetchThemes, contextThemes } = useContext(LayoutContext); + const { setMessage, contextFetchThemes, contextThemes, setDeleteQuery, deleteQuery } = useContext(LayoutContext); const showError = useErrorMessage(); const [formVisible, setFormVisible] = useState(false); @@ -96,14 +74,17 @@ export default function LessonStep() { // } ]; + const router = useRouter(); const pathname = usePathname(); const breadcrumb = useBreadCrumbs(teachingBreadCrumb, pathname); const clearValues = () => {}; - const handleShow = async (LessonId: number) => { - // alert('lessson_id: ' + lesson_id); + const changeUrl = (lessonId: number) => { + router.replace(`/course/${course_id}/${lessonId ? lessonId : null}`); + }; + const handleShow = async (LessonId: number) => { const data = await fetchLessonShow(LessonId); console.log(data?.lesson); @@ -246,11 +227,6 @@ export default function LessonStep() {
); - useEffect(()=> { - console.log('last', lastSelectedId); - - },[lastSelectedId]) - useEffect(() => { if (Array.isArray(steps) && steps.length > 0) { // const firstStep = steps[0]?.id; @@ -263,6 +239,7 @@ export default function LessonStep() { useEffect(() => { if (lesson_id) { handleShow(lesson_id); + changeUrl(lesson_id); } }, [lesson_id]); @@ -275,17 +252,17 @@ export default function LessonStep() { }, []); useEffect(() => { - console.log('Тема ', contextThemes); - console.log('variant 1'); + console.log('Тема ', contextThemes, deleteQuery); if (contextThemes?.lessons?.data?.length > 0) { console.log('variant 2'); setThemeNull(false); - if (param.lesson_id == 'null') { + if (param.lesson_id == 'null' || deleteQuery) { handleShow(contextThemes.lessons.data[0].id); console.log('variant 4', contextThemes.lessons.data[0].id); handleFetchSteps(contextThemes.lessons.data[0].id); setLesson_id(contextThemes.lessons.data[0].id); + setDeleteQuery(false); } else { console.log('variant 5'); handleShow(Number(param.lesson_id)); diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index 914d70fb..508b1b20 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -410,7 +410,7 @@ export default function Course() { {/* Заголовок */}
- setMainCourseId(shablonData.id)}> + setMainCourseId(shablonData.id)}> {shablonData.title} {/* Используем subject_name из вашего шаблона */}
@@ -725,7 +725,7 @@ export default function Course() { header="Аталышы" style={{ width: '80%' }} body={(rowData) => ( - setMainCourseId(rowData.id)} key={rowData.id}> + setMainCourseId(rowData.id)} key={rowData.id}> {rowData.title} )} diff --git a/app/components/cards/LessonCard.tsx b/app/components/cards/LessonCard.tsx index af8d6b2c..11dd2f14 100644 --- a/app/components/cards/LessonCard.tsx +++ b/app/components/cards/LessonCard.tsx @@ -122,12 +122,16 @@ export default function LessonCard({
{/*
{!cardValue.photo && }
*/} -
+
{shortTitle} - {cardValue.score ?
- / Балл: - {`${cardValue.score}`} -
: ''} + {cardValue.score ? ( +
+ {!media && '/'} Балл: + {`${cardValue.score}`} +
+ ) : ( + '' + )}
{shortDoc}
{type.typeValue === 'link' && {shortUrl}} diff --git a/app/components/lessons/LessonTest.tsx b/app/components/lessons/LessonTest.tsx index 51d99e7a..a5bf7610 100644 --- a/app/components/lessons/LessonTest.tsx +++ b/app/components/lessons/LessonTest.tsx @@ -42,8 +42,16 @@ export default function LessonTest({ element, content, fetchPropElement, clearPr image: null | string; } - interface testType { answers: { id: number | null, text: string, is_correct: boolean }[], id: number | null, content: string, score: number, image: string | null, title: string, created_at: string } - + interface testType { + answers: { id: number | null; text: string; is_correct: boolean }[]; + id: number | null; + content: string; + score: number; + image: string | null; + title: string; + created_at: string; + } + const { course_id } = useParams(); const media = useMediaQuery('(max-width: 640px)'); const showError = useErrorMessage(); @@ -57,20 +65,21 @@ export default function LessonTest({ element, content, fetchPropElement, clearPr { text: '', is_correct: false, id: null }, { text: '', is_correct: false, id: null } ]); - const [test, setTests] = useState({ answers: [{ id: null, text: '', is_correct: false }], id: null, content: '', score: 0, image: null, title: '', created_at:'' }); + const [test, setTests] = useState({ answers: [{ id: null, text: '', is_correct: false }], id: null, content: '', score: 0, image: null, title: '', created_at: '' }); const [testValue, setTestValue] = useState<{ title: string; score: number }>({ title: '', score: 0 }); const [testShow, setTestShow] = useState(false); const [progressSpinner, setProgressSpinner] = useState(false); - const [selectType, setSelectType] = useState(''); const [selectId, setSelectId] = useState(null); const clearValues = () => { setTestValue({ title: '', score: 0 }); - setAnswer([]); + setAnswer([ + { text: '', is_correct: false, id: null }, + { text: '', is_correct: false, id: null } + ]); setEditingLesson(null); setSelectId(null); - setSelectType(''); }; const toggleSpinner = () => { @@ -98,8 +107,8 @@ export default function LessonTest({ element, content, fetchPropElement, clearPr const editing = async () => { const data = await fetchElement(element.lesson_id, element.id); if (data.success) { - setEditingLesson({ title: data.content.content, score: data.content.score}); - if(data.content.answers && Array.isArray(data.content.answers)){ + setEditingLesson({ title: data.content.content, score: data.content.score }); + if (data.content.answers && Array.isArray(data.content.answers)) { setAnswer(data.content.answers); } } else { @@ -226,57 +235,64 @@ export default function LessonTest({ element, content, fetchPropElement, clearPr
) : (
-
-
-
- { - setTestValue((prev) => ({ ...prev, title: e.target.value })); - setValue('title', e.target.value, { shouldValidate: true }); - }} - /> - {errors.title?.message} -
-
- Балл - { - setTestValue((prev) => ({ ...prev, score: Number(e.target.value) })); - }} - /> +
+
+
+
+ { + setTestValue((prev) => ({ ...prev, title: e.target.value })); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} +
+
+ Балл + { + setTestValue((prev) => ({ ...prev, score: Number(e.target.value) })); + }} + /> +
-
-
- {answer.map((item, index) => { - return ( -
- { - setAnswer((prev) => prev.map((ans, i) => (i === index ? { ...ans, is_correct: true } : { ...ans, is_correct: false }))); - }} - /> - { - setAnswer((prev) => prev.map((ans, i) => (i === index ? { ...ans, text: e.target.value } : ans))); - }} - /> -
- ); - })} +
+ {answer.map((item, index) => { + return ( +
+ + { + setAnswer((prev) => prev.map((ans, i) => (i === index ? { ...ans, text: e.target.value } : ans))); + }} + /> +
+ ); + })} -
@@ -338,7 +354,7 @@ export default function LessonTest({ element, content, fetchPropElement, clearPr value={editingLesson?.title && editingLesson.title} style={{ resize: 'none', width: '100%' }} onChange={(e) => { - setEditingLesson((prev) => prev && ({ ...prev, title: e.target.value })); + setEditingLesson((prev) => prev && { ...prev, title: e.target.value }); setValue('title', e.target.value, { shouldValidate: true }); }} /> @@ -351,7 +367,7 @@ export default function LessonTest({ element, content, fetchPropElement, clearPr className="w-[70px]" value={String(editingLesson?.score)} onChange={(e) => { - setEditingLesson((prev) => prev && ({ ...prev, score: Number(e.target.value) })); + setEditingLesson((prev) => prev && { ...prev, score: Number(e.target.value) }); }} />
@@ -359,7 +375,7 @@ export default function LessonTest({ element, content, fetchPropElement, clearPr
{answer.map((item, index) => { console.log(item); - + return (
{ - const { layoutConfig, user, course, mainCourseId, setMainCourseId, contextFetchCourse, contextFetchThemes, contextThemes, setContextThemes, contextFetchStudentThemes, contextStudentThemes } = useContext(LayoutContext); + const { layoutConfig, user, setDeleteQuery, course, mainCourseId, setMainCourseId, contextFetchCourse, contextFetchThemes, contextThemes, setContextThemes, contextFetchStudentThemes, contextStudentThemes } = useContext(LayoutContext); interface test { label: string; id: number; @@ -49,7 +50,7 @@ const AppMenu = () => { const [visible, setVisisble] = useState(false); const [themeAddvisible, setThemeAddVisisble] = useState(false); const [editingLesson, setEditingLesson] = useState<{ title: string } | null>(null); - const [themeValue, setThemeValue] = useState<{ title: string }>({title: ''}); + const [themeValue, setThemeValue] = useState<{ title: string }>({ title: '' }); const [themesStudentList, setThemesStudentList] = useState<{ label: string; id: number; to: string; items?: AppMenuItem[] }[]>([]); @@ -114,12 +115,12 @@ const AppMenu = () => { }; const clearValues = () => { - setThemeValue({title: ''}) + setThemeValue({ title: '' }); setEditingLesson(null); setSelectId(null); }; - // add theme + // add theme const handleAddTheme = async () => { console.log(course_Id); @@ -147,9 +148,21 @@ const AppMenu = () => { const handleDeleteTheme = async (id: number) => { const data = await deleteTheme(id); - console.log(data); if (data.success) { contextFetchThemes(Number(course_Id)); + setDeleteQuery(true); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при удалении темы' } + }); + if (data?.response?.status) { + showError(data.response.status); + } } }; @@ -298,11 +311,11 @@ const AppMenu = () => { return !item?.seperator ? :
  • ; })} - {pathname.startsWith('/course/') &&
    - -
    } + {pathname.startsWith('/course/') && ( +
    + +
    + )} ); }; diff --git a/layout/context/layoutcontext.tsx b/layout/context/layoutcontext.tsx index fbae4f8d..c7f1e832 100644 --- a/layout/context/layoutcontext.tsx +++ b/layout/context/layoutcontext.tsx @@ -96,6 +96,7 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { }; // fetch themes + const [deleteQuery, setDeleteQuery] = useState(false); const [contextThemes, setContextThemes] = useState([]); const contextFetchThemes = async (id: number | null) => { const data = await fetchThemes(Number(id) || null); @@ -132,6 +133,8 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { contextFetchThemes, contextThemes, setContextThemes, + deleteQuery, + setDeleteQuery, contextFetchStudentThemes, contextStudentThemes, @@ -140,7 +143,7 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { crumbUrls, contextAddCrumb, mainCourseId, - setMainCourseId + setMainCourseId, }; return ( diff --git a/styles/layout/forms.css b/styles/layout/forms.css index d20cf888..a457ddc6 100644 --- a/styles/layout/forms.css +++ b/styles/layout/forms.css @@ -16,7 +16,7 @@ font-size: 14px; gap: 8px; position: relative; - padding: 6px 10px; + padding: 4px 7px; transition: color 0.3s; } diff --git a/types/layout.d.ts b/types/layout.d.ts index 468e2973..fc111287 100644 --- a/types/layout.d.ts +++ b/types/layout.d.ts @@ -64,6 +64,8 @@ export interface LayoutContextProps { contextFetchThemes: (id: number)=> void; contextThemes; setContextThemes; + deleteQuery: boolean; + setDeleteQuery; contextFetchStudentThemes: (id)=> void; contextStudentThemes; From 6a012d2afbb7a2df18ab5b91a167302708a2d5aa Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Tue, 9 Sep 2025 08:53:56 +0600 Subject: [PATCH 144/183] =?UTF-8?q?=D0=A1=D0=B2=D1=8F=D0=B7=D1=8C=20=D0=BF?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=B2=20=D0=BA=20=D0=BA=D1=83?= =?UTF-8?q?=D1=80=D1=81=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/tables/StreamList.tsx | 19 +++++++++++++++---- types/layout.d.ts | 2 +- types/streamType.tsx | 8 ++++++-- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/app/components/tables/StreamList.tsx b/app/components/tables/StreamList.tsx index c30277c5..d97138bf 100644 --- a/app/components/tables/StreamList.tsx +++ b/app/components/tables/StreamList.tsx @@ -16,8 +16,9 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis interface mainStreamsType { connect_id: number | null; stream_id: number; - subject_name: { name_kg: string }; - subject_type_name: { name_kg: string }; + id_curricula: number; + subject_name: { name_kg: string, id: number }; + subject_type_name: { name_kg: string, id: number }; teacher: { name: string }; language: { name: string }; id_edu_year: number; @@ -26,6 +27,7 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis edu_form: { name_kg: string }; period: {name_kg: string}, courseValue?: number; + speciality: {id: number, id_faculty: number}; } const shablon = [ @@ -151,7 +153,6 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis const handleConnect = async () => { const data = await connectStreams({course_id: courseValue?.id ? courseValue?.id : null, stream: pendingChanges }); - // const data = await connectStreams(streamValues); if (data?.success) { toggleSkeleton(); @@ -171,13 +172,23 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis } }; - const handleEdit = (e: { checked: boolean }, item: mainStreamsType) => { + const handleEdit = (e: { checked: boolean }, item: mainStreamsType) => { + console.log(item); + const { stream_id, subject_name } = item; const isChecked = e.checked; const forSentStreams = { course_id: courseValue!.id, stream_id: stream_id, + id_curricula: item.id_curricula, + id_subject: item.subject_name.id, + subject_type: item.subject_type_name.name_kg, + id_subject_type: item.subject_type_name.id, + id_edu_year: item.id_edu_year, + id_period: item.id_period, + id_speciality: item.speciality.id, + id_faculty: item.speciality.id_faculty, info: '', stream_title: subject_name.name_kg }; diff --git a/types/layout.d.ts b/types/layout.d.ts index fc111287..eca3b1be 100644 --- a/types/layout.d.ts +++ b/types/layout.d.ts @@ -71,7 +71,7 @@ export interface LayoutContextProps { contextStudentThemes; setContextStudentThemes; - crumbUrls: {type: string; crumbUrl: string }[]; + crumbUrls: {type: string; crumbUrl: string }; contextAddCrumb: (id)=> void mainCourseId: number | null, diff --git a/types/streamType.tsx b/types/streamType.tsx index 5cf141cf..8a8c2e9e 100644 --- a/types/streamType.tsx +++ b/types/streamType.tsx @@ -1,2 +1,6 @@ -export interface streamsType { course_id: number | null; stream_id: number; info: string | null, stream_title?:string } - \ No newline at end of file +export interface streamsType { + course_id: number | null; + stream_id: number; + info: string | null; + stream_title?: string; +} From 6e9f3dad94220bd69fcc638c7603145d7fc67e62 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Tue, 9 Sep 2025 10:18:23 +0600 Subject: [PATCH 145/183] =?UTF-8?q?=D0=A3=D0=B1=D1=80=D0=B0=D0=BB=20=D1=81?= =?UTF-8?q?=D0=B0=D0=B9=D0=B4=D0=B1=D0=B0=D1=80=20=D0=B2=20=D0=BA=D1=83?= =?UTF-8?q?=D1=80=D1=81=D0=B5.=20=D0=A4=D0=B8=D0=BA=D1=81=20=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=B1=D0=BA=D0=B8=20=D0=B2=20=D1=83=D1=80=D0=BE=D0=BA?= =?UTF-8?q?=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../course/[course_Id]/[lesson_id]/page.tsx | 13 ++++----- layout/AppMenu.tsx | 2 ++ layout/context/layoutcontext.tsx | 29 +++++++++++++++---- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx index c8380e91..329689a7 100644 --- a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx +++ b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx @@ -238,24 +238,20 @@ export default function LessonStep() { useEffect(() => { if (lesson_id) { + console.warn('LESSONID ', lesson_id); handleShow(lesson_id); changeUrl(lesson_id); } }, [lesson_id]); - useEffect(() => { - // console.log('element', element); - }, [element]); - useEffect(() => { contextFetchThemes(Number(course_id)); - }, []); + }, [course_id]); useEffect(() => { - console.log('Тема ', contextThemes, deleteQuery); + console.log('Тема ', contextThemes, lesson_id); if (contextThemes?.lessons?.data?.length > 0) { - console.log('variant 2'); setThemeNull(false); if (param.lesson_id == 'null' || deleteQuery) { handleShow(contextThemes.lessons.data[0].id); @@ -263,7 +259,8 @@ export default function LessonStep() { handleFetchSteps(contextThemes.lessons.data[0].id); setLesson_id(contextThemes.lessons.data[0].id); setDeleteQuery(false); - } else { + } + else { console.log('variant 5'); handleShow(Number(param.lesson_id)); setLesson_id((param.lesson_id && Number(param.lesson_id)) || null); diff --git a/layout/AppMenu.tsx b/layout/AppMenu.tsx index db5f1d52..00130b72 100644 --- a/layout/AppMenu.tsx +++ b/layout/AppMenu.tsx @@ -215,6 +215,8 @@ const AppMenu = () => { }, [user, studentThemeCourse]); useEffect(() => { + console.log(contextThemes); + if (contextThemes && contextThemes.lessons) { const newThemes = contextThemes.lessons.data.map((item: any) => ({ label: item.title, diff --git a/layout/context/layoutcontext.tsx b/layout/context/layoutcontext.tsx index c7f1e832..6076ee00 100644 --- a/layout/context/layoutcontext.tsx +++ b/layout/context/layoutcontext.tsx @@ -68,19 +68,36 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { return window.innerWidth > 991; }; + useEffect(() => { + if (pathname === '/course') { + setLayoutState((prev) => ({ + ...prev, + staticMenuDesktopInactive: true, + staticMenuMobileActive: false, + overlayMenuActive: false, + profileSidebarVisible: false + })); + } else { + setLayoutState((prev) => ({ + ...prev, + staticMenuDesktopInactive: false + })); + } + }, [pathname]); + // breadCrumb urls const isTopicsChildPage = /^\/teaching\/[^/]+\/[^/]+$/.test(pathname); - const [crumbUrls, setCrumbUrls] = useState<{type: string; crumbUrl: string }>({type: '', crumbUrl: ''}); + const [crumbUrls, setCrumbUrls] = useState<{ type: string; crumbUrl: string }>({ type: '', crumbUrl: '' }); const contextAddCrumb = (url: { type: string; crumbUrl: string }) => { const urlName = url.type === 'studentStream' ? 'studentStream' : ''; setCrumbUrls((prev) => ({ ...prev, [urlName]: url.crumbUrl })); }; - useEffect(()=> { - if(isTopicsChildPage && crumbUrls){ + useEffect(() => { + if (isTopicsChildPage && crumbUrls) { localStorage.setItem('currentBreadCrumb', JSON.stringify(crumbUrls)); - } - },[crumbUrls]); + } + }, [crumbUrls]); // fetch course const [mainCourseId, setMainCourseId] = useState(null); @@ -143,7 +160,7 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { crumbUrls, contextAddCrumb, mainCourseId, - setMainCourseId, + setMainCourseId }; return ( From 78251571d821aaaf0bc41b9e50ec5916841b7656 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Tue, 9 Sep 2025 10:31:09 +0600 Subject: [PATCH 146/183] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D0=B5=20=D0=B7=D0=B0=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/course/[course_Id]/[lesson_id]/page.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx index 329689a7..f631222b 100644 --- a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx +++ b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx @@ -236,6 +236,10 @@ export default function LessonStep() { } }, [steps]); + useEffect(()=> { + + },[]); + useEffect(() => { if (lesson_id) { console.warn('LESSONID ', lesson_id); From 8e6fabe05e159638384df91c0c0706417e673342 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Tue, 9 Sep 2025 10:56:14 +0600 Subject: [PATCH 147/183] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20=D0=BF=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/tables/StreamList.tsx | 72 ++++++++++++++-------------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/app/components/tables/StreamList.tsx b/app/components/tables/StreamList.tsx index d97138bf..44a41b89 100644 --- a/app/components/tables/StreamList.tsx +++ b/app/components/tables/StreamList.tsx @@ -12,22 +12,34 @@ import Link from 'next/link'; import { streamsType } from '@/types/streamType'; import { displayType } from '@/types/displayType'; -export default function StreamList({ callIndex, courseValue, isMobile, insideDisplayStreams, toggleIndex }: { callIndex: number; courseValue: { id: number | null; title: string } | null; isMobile: boolean; insideDisplayStreams: (id: displayType[]) => void, toggleIndex: ()=> void }) { +export default function StreamList({ + callIndex, + courseValue, + isMobile, + insideDisplayStreams, + toggleIndex +}: { + callIndex: number; + courseValue: { id: number | null; title: string } | null; + isMobile: boolean; + insideDisplayStreams: (id: displayType[]) => void; + toggleIndex: () => void; +}) { interface mainStreamsType { connect_id: number | null; stream_id: number; id_curricula: number; - subject_name: { name_kg: string, id: number }; - subject_type_name: { name_kg: string, id: number }; + subject_name: { name_kg: string; id: number }; + subject_type_name: { name_kg: string; id: number }; teacher: { name: string }; language: { name: string }; id_edu_year: number; id_period: number; semester: { name_kg: string }; edu_form: { name_kg: string }; - period: {name_kg: string}, + period: { name_kg: string }; courseValue?: number; - speciality: {id: number, id_faculty: number}; + speciality: { id: number; id_faculty: number }; } const shablon = [ @@ -107,17 +119,19 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis course_id: item?.course_id, stream_id: item.stream_id, info: '', - stream_title: item?.subject_name.name_kg + id_curricula: item.id_curricula, + id_subject: item.subject_name.id, + subject_type: item.subject_type_name.name_kg, + id_subject_type: item.subject_type_name.id, + id_edu_year: item.id_edu_year, + id_period: item.id_period, + id_speciality: item.speciality.id, + id_faculty: item.speciality.id_faculty, + stream_title: item?.subject_name.name_kg // }); } }); - // setStreamValues((prev) => ({ - // ...prev, - // stream: [...prev.stream, ...newStreams] - // })); - // setPendingChanges((prev)=> [...prev, ...newStreams]); - setPendingChanges((prev) => { const pendingIds = new Set(prev.map((p) => p.stream_id)); const uniqueNewStreams = newStreams.filter((s) => !pendingIds.has(s.stream_id)); @@ -131,6 +145,8 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis setPendingChanges([]); if (data) { + console.log(data); + profilactor(data); setHasStreams(false); setStreams(data); @@ -152,7 +168,9 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis }; const handleConnect = async () => { - const data = await connectStreams({course_id: courseValue?.id ? courseValue?.id : null, stream: pendingChanges }); + console.log(pendingChanges); + + const data = await connectStreams({ course_id: courseValue?.id ? courseValue?.id : null, stream: pendingChanges }); if (data?.success) { toggleSkeleton(); @@ -172,9 +190,9 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis } }; - const handleEdit = (e: { checked: boolean }, item: mainStreamsType) => { + const handleEdit = (e: { checked: boolean }, item: mainStreamsType) => { console.log(item); - + const { stream_id, subject_name } = item; const isChecked = e.checked; @@ -208,26 +226,6 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis return prev; }); - - // if (e.checked) { - // // profilactor(); - // setStreamValues( - // (prev) => - // prev && { - // ...prev, - // stream: [...prev.stream, forSentStreams] - // } - // ); - // // const x = {streamTitle} - // } else { - // setStreamValues( - // (prev) => - // prev && { - // ...prev, - // stream: [...prev.stream.filter((item) => item?.stream_id !== id)] - // } - // ); - // } }; // useEffect(() => { @@ -386,7 +384,7 @@ export default function StreamList({ callIndex, courseValue, isMobile, insideDis
    ); - const step = (icon: string, step: number) => ( -
    { - setSelectId(step); - handleFetchElement(step); - }} - > -
    - + const step = (icon: string, step: number, idx: number) => { + return ( +
    { + setSelectId(step); + handleFetchElement(step); + }} + > + {idx + 1} +
    + +
    -
    - ); + ); + }; useEffect(() => { if (Array.isArray(steps) && steps.length > 0) { @@ -252,21 +258,20 @@ export default function LessonStep() { useEffect(() => { console.log('Тема ', contextThemes, lesson_id); - if(testovy || updateQuery || deleteQuery){ + if (testovy || updateQuery || deleteQuery) { setTestovy(false); setUpdateeQuery(false); if (contextThemes?.lessons?.data?.length > 0) { setThemeNull(false); if (param.lesson_id == 'null' || deleteQuery) { console.log(contextThemes.lessons.data[0].id); - + handleShow(contextThemes.lessons.data[0].id); console.log('variant 4', contextThemes.lessons.data[0].id); handleFetchSteps(contextThemes.lessons.data[0].id); setLesson_id(contextThemes.lessons.data[0].id); setDeleteQuery(false); - } - else { + } else { console.log('variant 5'); handleShow(Number(param.lesson_id)); setLesson_id((param.lesson_id && Number(param.lesson_id)) || null); @@ -320,7 +325,7 @@ export default function LessonStep() { {lessonInfo} {/* steps section */} -
    +
    {hasSteps ? (
    @@ -330,28 +335,30 @@ export default function LessonStep() { {steps.map((item, idx) => { return (
    - {step(item.type.logo, item.id)} + {step(item.type.logo, item.id, idx)}
    ); })}
    )} - - {!hasSteps && ( - + {!hasSteps && ( +
    {hasSteps && ( @@ -362,6 +369,7 @@ export default function LessonStep() { {element?.step.type.name === 'document' && } {element?.step.type.name === 'video' && } {element?.step.type.name === 'test' && } + {element?.step.type.name === 'practical' && }
    ); } diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index 508b1b20..785c66b1 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -1,7 +1,7 @@ 'use client'; import FormModal from '@/app/components/popUp/FormModal'; -import { addCourse, deleteCourse, fetchCourseInfo, fetchCourses, updateCourse } from '@/services/courses'; +import { addCourse, deleteCourse, fetchCourseInfo, fetchCourses, publishCourse, updateCourse } from '@/services/courses'; import { Button } from 'primereact/button'; import { FileUpload, FileUploadSelectEvent } from 'primereact/fileupload'; import { InputText } from 'primereact/inputtext'; @@ -262,6 +262,25 @@ export default function Course() { setSelectedCourse(null); }; + const publish = async (id: number) => { + const data = await publishCourse(id); + if(data.success){ + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү кошулду!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Катаа', detail: 'Кийинирээк кайталаныз' } + }); // messege - Ошибка при добавлении + if (data?.response?.status) { + showError(data.response.status); + } + } + + } + const handleUpdateCourse = async () => { const data = await updateCourse(selectedCourse, editingLesson); if (data?.success) { @@ -732,7 +751,13 @@ export default function Course() { > - + ( +
    ); - const btnLabel = type.typeValue === 'doc' && status === 'working' ? 'Ачуу' : type.typeValue === 'doc' && status === 'student' ? 'Ачуу' : type.typeValue === 'link' ? 'Өтүү' : ''; + const btnLabel = type.typeValue === 'doc' || type.typeValue === 'practica' ? 'Ачуу' : type.typeValue === 'link' ? 'Өтүү' : ''; return (
    @@ -115,15 +116,17 @@ export default function LessonCard({ ${type.typeValue === 'doc' ? 'w-full min-h-[160px] bg-black' : ''} ${type.typeValue === 'test' ? 'w-full min-h-[160px] bg-black' : ''} + + ${type.typeValue === 'practica' ? 'w-full min-h-[160px] bg-black' : ''} `} style={{ backgroundColor: cardBg }} > -
    -
    +
    +
    {/*
    {!cardValue.photo && }
    */} -
    - {shortTitle} +
    + {shortTitle} {cardValue.score ? (
    {!media && '/'} Балл: @@ -133,8 +136,23 @@ export default function LessonCard({ '' )}
    -
    {shortDoc}
    - {type.typeValue === 'link' && {shortUrl}} + + {type.typeValue !== 'practica' &&
    {shortDoc}
    } + + {type.typeValue === 'practica' && cardValue.url ? ( +
    + + Шилтеме: + + {shortUrl} +
    + ) : ( + type.typeValue === 'link' && ( + <> + {shortUrl} + + ) + )} {answers && (
    {answers.map((item) => { @@ -150,15 +168,17 @@ export default function LessonCard({ })}
    )} -
    {cardValue?.desctiption && cardValue?.desctiption !== 'null' && shortDescription}
    +
    + {cardValue?.desctiption && cardValue?.desctiption !== 'null' ? shortDescription : cardValue?.desctiption && cardValue?.desctiption !== 'null' && type.typeValue === 'practica' ?
    {shortDescription}
    : ''} +
    + {status === 'working' && ( -
    +
    {lessonDate}
    )}
    - {/* video preview */} {videoPreviw} @@ -168,7 +188,6 @@ export default function LessonCard({ {status === 'student' && type.typeValue === 'doc' ? ( )} + {type.typeValue === 'practica' && ( + + )} {status === 'working' && (
    diff --git a/app/components/lessons/LessonPractica.tsx b/app/components/lessons/LessonPractica.tsx new file mode 100644 index 00000000..ecfb0151 --- /dev/null +++ b/app/components/lessons/LessonPractica.tsx @@ -0,0 +1,441 @@ +'use client'; + +import { lessonSchema } from '@/schemas/lessonSchema'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { useParams, useRouter } from 'next/navigation'; +import { Button } from 'primereact/button'; +import { FileUpload } from 'primereact/fileupload'; +import { InputText } from 'primereact/inputtext'; +import { ProgressSpinner } from 'primereact/progressspinner'; +import { useContext, useEffect, useRef, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { NotFound } from '../NotFound'; +import LessonCard from '../cards/LessonCard'; +import { useMediaQuery } from '@/hooks/useMediaQuery'; +import { getToken } from '@/utils/auth'; +import { addDocument, addPractica, deleteDocument, deletePractica, fetchElement, updateDocument, updatePractica } from '@/services/steps'; +import { mainStepsType } from '@/types/mainStepType'; +import useErrorMessage from '@/hooks/useErrorMessage'; +import { LayoutContext } from '@/layout/context/layoutcontext'; +import FormModal from '../popUp/FormModal'; +import { InputTextarea } from 'primereact/inputtextarea'; +import dynamic from 'next/dynamic'; + +const PDFViewer = dynamic(() => import('../PDFBook'), { + ssr: false +}); + +export default function LessonPractica({ element, content, fetchPropElement, clearProp }: { element: mainStepsType; content: any; fetchPropElement: (id: number) => void; clearProp: boolean }) { + interface docValueType { + title: string; + description: string; + document: File | null; + url: string; + } + + interface contentType { + course_id: number | null; + created_at: string; + description: string | null; + document: string; + id: number; + lesson_id: number; + status: true; + title: string; + updated_at: string; + user_id: number; + document_path: string; + url: string; + } + + const { course_id } = useParams(); + + const router = useRouter(); + const media = useMediaQuery('(max-width: 640px)'); + const fileUploadRef = useRef(null); + const showError = useErrorMessage(); + const { setMessage } = useContext(LayoutContext); + + const [editingLesson, setEditingLesson] = useState({ title: '', description: '', document: null, url: '' }); + const [visible, setVisisble] = useState(false); + const [imageState, setImageState] = useState(null); + const [contentShow, setContentShow] = useState(false); + // doc + const [document, setDocuments] = useState(); + const [docValue, setDocValue] = useState({ + title: '', + description: '', + document: null, + url: '' + }); + const [docShow, setDocShow] = useState(false); + const [urlPDF, setUrlPDF] = useState(''); + const [PDFVisible, setPDFVisible] = useState(false); + + const [progressSpinner, setProgressSpinner] = useState(false); + const [additional, setAdditional] = useState<{ doc: boolean; link: boolean; video: boolean }>({ doc: false, link: false, video: false }); + const [selectType, setSelectType] = useState(''); + const [selectId, setSelectId] = useState(null); + + const clearFile = () => { + fileUploadRef.current?.clear(); + setAdditional((prev) => ({ ...prev, video: false })); + setImageState(null); + if (visible) { + setEditingLesson( + (prev) => + prev && { + ...prev, + document: null + } + ); + } else { + setDocValue((prev) => ({ + ...prev, + document: null + })); + } + }; + + const documentView = ( + <> +
    + setPDFVisible(false)}> +
    + +
    +
    + + ); + + const clearValues = () => { + clearFile(); + setDocValue({ title: '', description: '', document: null, url: '' }); + setEditingLesson({ title: '', description: '', document: null, url: '' }); + setSelectId(null); + setSelectType(''); + }; + + const toggleSpinner = () => { + setProgressSpinner(true); + setInterval(() => { + setProgressSpinner(false); + }, 1000); + }; + + // validate + const { + setValue, + formState: { errors } + } = useForm({ + resolver: yupResolver(lessonSchema), + mode: 'onChange' + }); + + const selectedForEditing = (id: number, type: string) => { + setSelectType(type); + setSelectId(id); + setVisisble(true); + editing(); + }; + + const sentToPDF = (url: string) => { + console.log(url); + + setUrlPDF(url); + if (media) { + router.push(`/pdf/${url}`); + } else { + setPDFVisible(true); + } + }; + + const editing = async () => { + + const data = await fetchElement(element.lesson_id, element.id); + console.log('send ', data.content); + if (data.success) { + setEditingLesson({ title: data.content.title, document: null, description: data.content.description, url: '' }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинирээк кайталаныз' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const handleAddPracica = async () => { + toggleSpinner(); + const data = await addPractica(docValue, element.lesson_id, element.type_id, element.id); + console.log(data); + + if (data.success) { + fetchPropElement(element.id); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү Кошулдуу!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при добавлении' } + }); + if (data.response.status) { + showError(data.response.status); + } + } + }; + + // delete document + const handleDeleteDoc = async (id: number) => { + console.log(id); + + const data = await deletePractica(Number(document?.lesson_id), id, element.type.id, element.id); + if (data.success) { + fetchPropElement(element.id); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } + }); + } else { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при удалении' } + }); + if (data.response.status) { + showError(data.response.status); + } + } + }; + + useEffect(() => { + console.log('editinglesson', editingLesson); + }, [editingLesson]); + + // update document + const handleUpdateDoc = async () => { + const data = await updatePractica(editingLesson, document?.lesson_id ? Number(document?.lesson_id) : null, Number(selectId), element.type.id, element.id); + console.log(data); + + if (data?.success) { + fetchPropElement(element.id); + clearValues(); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Ийгиликтүү өзгөртүлдү!', detail: '' } + }); + } else { + setDocValue({ title: '', description: '', document: null, url: '' }); + setEditingLesson({ title: '', description: '', document: null, url: '' }); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при изменении урока' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const practicaSection = () => { + return ( +
    + {PDFVisible ? ( + documentView + ) : contentShow ? ( +
    +
    + {docShow ? ( + + ) : ( + document && ( + selectedForEditing(id, type)} + onDelete={(id: number) => handleDeleteDoc(id)} + cardValue={{ title: document?.title, id: document.id, desctiption: document?.description || '', type: 'practica', url: document.url, document: document.document }} + cardBg={'#ddc4f51a'} + type={{ typeValue: 'practica', icon: 'pi pi-list' }} + typeColor={'var(--mainColor)'} + lessonDate={new Date(document.created_at).toISOString().slice(0, 10)} + urlForPDF={() => sentToPDF(document.document_path || '')} + urlForDownload={document.document_path || ''} + /> + ) + )} +
    +
    + ) : ( +
    +
    + setDocValue((prev) => ({ ...prev, title: e.target.value }))} /> +
    + {errors.title?.message} + {additional.doc && ( +
    + {}} + accept="application/pdf" + onSelect={(e) => + setDocValue((prev) => ({ + ...prev, + document: e.files[0] + })) + } + /> +
    + )} + {additional.doc && ( +
    + { + setDocValue((prev) => ({ ...prev, url: e.target.value })); + setValue('usefulLink', e.target.value, { shouldValidate: true }); + }} + /> + {errors.usefulLink?.message} +
    + )} + {additional.doc && setDocValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" />} + +
    + {/*
    +
    +
    + )} +
    + ); + }; + + useEffect(() => { + console.log('content', content); + if (content) { + setContentShow(true); + setDocuments(content); + } else { + setContentShow(false); + } + }, [content]); + + useEffect(() => { + console.log('edititing', element); + setDocValue({ title: '', description: '', document: null, url: '' }); + }, [element]); + + useEffect(() => { + console.log('value ', docValue); + }, [docValue]); + + return ( +
    + { + handleUpdateDoc(); + }} + clearValues={clearValues} + visible={visible} + setVisible={setVisisble} + start={false} + > +
    +
    +
    + { + setEditingLesson((prev) => prev && { ...prev, title: e.target.value }) + setValue('title', e.target.value, { shouldValidate: true }); + }} /> +
    + {errors.title?.message} + {additional.doc && ( +
    +
    + {}} + accept="application/pdf" + onSelect={(e) => + setEditingLesson( + (prev) => + prev && { + ...prev, + document: e.files[0] + } + ) + } + /> +
    + {typeof editingLesson?.document === 'string' && String(editingLesson?.document)} +
    + )} + {additional.doc && ( +
    + { + setEditingLesson((prev) => prev && { ...prev, url: e.target.value }); + setValue('usefulLink', e.target.value, { shouldValidate: true }); + }} + /> +
    + )} + { + setEditingLesson((prev) => ({ ...prev, description: e.target.value })) + setValue('title', e.target.value, { shouldValidate: true }); + }} className="w-full" /> + {errors.title?.message} + +
    +
    + setAdditional((prev) => ({ ...prev, doc: !prev.doc }))}> + Кошумча {additional.doc ? '-' : '+'} + +
    +
    +
    +
    +
    + {!clearProp && practicaSection()} +
    + ); +} diff --git a/hooks/useShortText.tsx b/hooks/useShortText.tsx index fa3dd27c..b85403b5 100644 --- a/hooks/useShortText.tsx +++ b/hooks/useShortText.tsx @@ -20,13 +20,13 @@ export default function useShortText(text: string, textLength: number) { return ( <> - {isLength ? ( + {isLength ? (
    {resultText}
    ) : ( -
    {resultText}
    +
    {resultText}
    )} ); diff --git a/layout/AppMenu.tsx b/layout/AppMenu.tsx index bfb90007..ec6d83fd 100644 --- a/layout/AppMenu.tsx +++ b/layout/AppMenu.tsx @@ -219,8 +219,8 @@ const AppMenu = () => { console.log(contextThemes); if (contextThemes && contextThemes.lessons) { - const newThemes = contextThemes.lessons.data.map((item: any) => ({ - label: item.title, + const newThemes = contextThemes.lessons.data.map((item: any, idx:number) => ({ + label:
    {idx+1}. {item.title}
    , id: item.id, to: `/course/${course_Id}/${item.id}`, onEdit: () => { diff --git a/services/courses.tsx b/services/courses.tsx index 1b00890a..95927a65 100644 --- a/services/courses.tsx +++ b/services/courses.tsx @@ -345,3 +345,20 @@ export const fetchVideoType = async () => { } }; +export const publishCourse = async (id: number) => { + const formData = new FormData(); + formData.append('course_id', String(id)); + + try { + const res = await axiosInstance.post(`/v1/teacher/courses/published`, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + + return res.data; + } catch (err) { + console.log('Ошибка при добавлении курса', err); + return err; + } +}; diff --git a/services/steps.tsx b/services/steps.tsx index 6e41341f..985205eb 100644 --- a/services/steps.tsx +++ b/services/steps.tsx @@ -244,11 +244,9 @@ export const addTest = async (answers: { text: string; is_correct: boolean }[], } }; -export const updateTest = async ( - answers: { text: string; is_correct: boolean }[], title: string, lesson_id: number, test_id:number, type_id: number, step_id: number, score: number -) => { +export const updateTest = async (answers: { text: string; is_correct: boolean }[], title: string, lesson_id: number, test_id: number, type_id: number, step_id: number, score: number) => { url = `/v1/teacher/test/update`; - + const payload = { lesson_id, test_id, @@ -272,7 +270,7 @@ export const updateTest = async ( }; export const deleteTest = async (lesson_id: number, test_id: number, type_id: number, step_id: number) => { - console.log(lesson_id, test_id, type_id,step_id); + console.log(lesson_id, test_id, type_id, step_id); try { const res = await axiosInstance.delete(`/v1/teacher/test/delete?lesson_id=${lesson_id}&test_id=${test_id}&type_id=${type_id}&step_id=${step_id}`); @@ -283,4 +281,77 @@ export const deleteTest = async (lesson_id: number, test_id: number, type_id: nu console.log('Ошибка при удалении темы', err); return err; } -}; \ No newline at end of file +}; + +// practica +export const addPractica = async (value: { url: string | null; title: string; description: string | null; document: File | null }, lesson_id: number, type_id: number, step_id: number) => { + console.log(value); + + const formData = new FormData(); + url = `/v1/teacher/practice-lesson/store?lesson_id=${lesson_id}&title=${value.title}&description=${value.description}&url=${value.url}&document=${value.document}&video_type_id=${type_id}&step_id=${step_id}`; + formData.append('type_id', String(type_id)); + formData.append('step_id', String(step_id)); + formData.append('lesson_id', String(lesson_id)); + formData.append('url', String(value?.url)); + formData.append('title', String(value?.title)); + formData.append('description', String(value?.description)); + if (value.document) formData.append('document', value?.document && value?.document); + else formData.append('document', ''); + + try { + const res = await axiosInstance.post(url, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + + return res.data; + } catch (err) { + console.log('Ошибка при добавлении курса', err); + return err; + } +}; + +export const updatePractica = async (value: { url: string | null; title: string; description: string | null; document: File | null }, lesson_id: number | null, practice_id: number, type_id: number, step_id: number) => { + console.log(value); + + let formData = new FormData(); + url = `/v1/teacher/practice-lesson/update?lesson_id=${lesson_id}&title=${value.title}&description=${value.description}&url=${value.url}&document=${value.document}&practice_id=${practice_id}&video_type_id=${type_id}&step_id=${step_id}`; + formData.append('type_id', String(type_id)); + formData.append('step_id', String(step_id)); + formData.append('lesson_id', String(lesson_id)); + formData.append('practice_id', String(practice_id)); + formData.append('url', value?.url || ''); + formData.append('title', String(value?.title)); + formData.append('description', String(value?.description)); + if (value.document) formData.append('document', value?.document && value?.document); + else formData.append('document', ''); + + try { + const res = await axiosInstance.post(url, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + + const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка при обновлении урока', err); + return err; + } +}; + +export const deletePractica = async (lesson_id: number, practice_id: number, type_id: number, step_id: number) => { + console.log(lesson_id, practice_id); + + try { + const res = await axiosInstance.delete(`/v1/teacher/practice-lesson/delete?lesson_id=${lesson_id}&practice_id=${practice_id}&step_id=${step_id}&type_id=${type_id}`); + + const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка при удалении темы', err); + return err; + } +}; From b0821c67ea702a801faf2fceb663ddd996de5002 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Wed, 10 Sep 2025 17:01:40 +0600 Subject: [PATCH 151/183] build --- app/(full-page)/auth/login/page.tsx | 2 - app/(main)/course/page.tsx | 44 ++++++--------------- app/components/cards/LessonCard.tsx | 3 +- app/components/lessons/LessonTest.tsx | 4 +- app/components/tables/StreamList.tsx | 57 ++------------------------- layout/AppMenu.tsx | 4 -- services/auth.tsx | 1 - types/streamType.tsx | 10 ++++- 8 files changed, 29 insertions(+), 96 deletions(-) diff --git a/app/(full-page)/auth/login/page.tsx b/app/(full-page)/auth/login/page.tsx index aaeefe43..8a5679dc 100644 --- a/app/(full-page)/auth/login/page.tsx +++ b/app/(full-page)/auth/login/page.tsx @@ -34,8 +34,6 @@ const LoginPage = () => { }); const onSubmit = async (value: LoginType) => { - console.log('Данные пользователя: ', value); - const user = await login(value); if (user && user.success) { document.cookie = `access_token=${user.token.access_token}; path=/; Secure; SameSite=Strict; expires=${user.token.expires_at}`; diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index 785c66b1..fc1a7e11 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -31,9 +31,11 @@ import { RadioButton } from 'primereact/radiobutton'; import { DataView } from 'primereact/dataview'; import { displayType } from '@/types/displayType'; import { FileWithPreview } from '@/types/fileuploadPreview'; +import { InputSwitch, InputSwitchChangeEvent } from 'primereact/inputswitch'; +import { SelectButton, SelectButtonChangeEvent } from 'primereact/selectbutton'; export default function Course() { - const { setMessage, course, setCourses, contextFetchCourse, setMainCourseId} = useContext(LayoutContext); + const { setMessage, course, setCourses, contextFetchCourse, setMainCourseId } = useContext(LayoutContext); const [coursesValue, setValueCourses] = useState([]); const [hasCourses, setHasCourses] = useState(false); const [courseValue, setCourseValue] = useState({ title: '', description: '', video_url: '', image: '' }); @@ -52,25 +54,6 @@ export default function Course() { const [imageState, setImageState] = useState(null); const [displayStrem, setDisplayStreams] = useState([]); const [visible, setVisisble] = useState(false); - - [ - { - title: 'dfdj', - topic_id: 1, - lessons: [] - }, - { - title: 'r', - topic_id: 2, - lessons: [] - }, - { - title: 't', - topic_id: 3, - lessons: [] - } - ]; - const [editingLesson, setEditingLesson] = useState({ title: '', description: '', @@ -79,6 +62,8 @@ export default function Course() { created_at: '' }); + const [active, setActive] = useState(false); + const shablon = [ { created_at: '', @@ -264,7 +249,7 @@ export default function Course() { const publish = async (id: number) => { const data = await publishCourse(id); - if(data.success){ + if (data.success) { setMessage({ state: true, value: { severity: 'success', summary: 'Ийгиликтүү кошулду!', detail: '' } @@ -278,8 +263,7 @@ export default function Course() { showError(data.response.status); } } - - } + }; const handleUpdateCourse = async () => { const data = await updateCourse(selectedCourse, editingLesson); @@ -367,6 +351,7 @@ export default function Course() { useEffect(() => { handleFetchCourse(); + console.log('Приходящие', course); }, [course]); useEffect(() => { @@ -429,7 +414,7 @@ export default function Course() { {/* Заголовок */}
    - setMainCourseId(shablonData.id)}> + setMainCourseId(shablonData.id)}> {shablonData.title} {/* Используем subject_name из вашего шаблона */}
    @@ -475,6 +460,8 @@ export default function Course() { ); }; + + const imagestateStyle = imageState || editingLesson.image ? 'flex gap-1 items-center justify-between flex-col sm:flex-row' : ''; const imageTitle = useShortText(typeof editingLesson.image === 'string' ? editingLesson.image : '', 20); @@ -744,20 +731,13 @@ export default function Course() { header="Аталышы" style={{ width: '80%' }} body={(rowData) => ( - setMainCourseId(rowData.id)} key={rowData.id}> + setMainCourseId(rowData.id)} key={rowData.id}> {rowData.title} )} > - ( - From b13efde8905cdae802329663aa443df128652600 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Thu, 11 Sep 2025 10:53:24 +0600 Subject: [PATCH 156/183] =?UTF-8?q?=D0=BF=D0=BE=D0=B8=D1=81=D0=BA=20=D0=BA?= =?UTF-8?q?=D0=B5=D0=B9=20=D0=BF=D1=80=D0=BE=D0=BF=20=D0=BE=D1=88=D0=B8?= =?UTF-8?q?=D0=B1=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/course/[course_Id]/[lesson_id]/page.tsx | 7 +++---- app/components/lessons/LessonTest.tsx | 2 -- layout/AppMenu.tsx | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx index 1d406af1..1abeeb92 100644 --- a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx +++ b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx @@ -106,8 +106,7 @@ export default function LessonStep() { const handleFetchTypes = async () => { setFormVisible(true); - const data = await fetchTypes(); - + const data = await fetchTypes(); if (data && Array.isArray(data)) { setTypes(data); } else { @@ -313,11 +312,11 @@ export default function LessonStep() { }} />
    -
    +
    {types.map((item) => { return ( -
    +
    handleAddLesson(Number(lesson_id), item.id)}> {item.title} diff --git a/app/components/lessons/LessonTest.tsx b/app/components/lessons/LessonTest.tsx index 58729332..a1fc11c7 100644 --- a/app/components/lessons/LessonTest.tsx +++ b/app/components/lessons/LessonTest.tsx @@ -373,8 +373,6 @@ export default function LessonTest({ element, content, fetchPropElement, clearPr
    {answer.map((item, index) => { - console.log(item); - return (
    { if (contextThemes && contextThemes.lessons) { const newThemes = contextThemes.lessons.data.map((item: any, idx: number) => ({ label: ( -
    +
    {idx + 1}. {item.title}
    ), From cb6a4c6cb83e3acc1c914958eb0796538ade4322 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Thu, 11 Sep 2025 11:06:35 +0600 Subject: [PATCH 157/183] =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=81=D1=82=D0=BA?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/lessons/LessonPractica.tsx | 20 +++++++++++++------- schemas/lessonSchema.tsx | 3 +++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/app/components/lessons/LessonPractica.tsx b/app/components/lessons/LessonPractica.tsx index ecfb0151..1d407b02 100644 --- a/app/components/lessons/LessonPractica.tsx +++ b/app/components/lessons/LessonPractica.tsx @@ -12,7 +12,6 @@ import { useForm } from 'react-hook-form'; import { NotFound } from '../NotFound'; import LessonCard from '../cards/LessonCard'; import { useMediaQuery } from '@/hooks/useMediaQuery'; -import { getToken } from '@/utils/auth'; import { addDocument, addPractica, deleteDocument, deletePractica, fetchElement, updateDocument, updatePractica } from '@/services/steps'; import { mainStepsType } from '@/types/mainStepType'; import useErrorMessage from '@/hooks/useErrorMessage'; @@ -271,7 +270,10 @@ export default function LessonPractica({ element, content, fetchPropElement, cle ) : (
    - setDocValue((prev) => ({ ...prev, title: e.target.value }))} /> + { + setDocValue((prev) => ({ ...prev, title: e.target.value })); + setValue('title', e.target.value, { shouldValidate: true }); + }} />
    {errors.title?.message} {additional.doc && ( @@ -298,20 +300,24 @@ export default function LessonPractica({ element, content, fetchPropElement, cle {additional.doc && (
    { setDocValue((prev) => ({ ...prev, url: e.target.value })); - setValue('usefulLink', e.target.value, { shouldValidate: true }); + setValue('usefulLinkNotReq', e.target.value, { shouldValidate: true }); }} /> - {errors.usefulLink?.message} + {errors.usefulLinkNotReq?.message}
    )} - {additional.doc && setDocValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" />} + { + setDocValue((prev) => ({ ...prev, description: e.target.value })) + setValue('title', e.target.value, { shouldValidate: true }); + }} className="w-full" /> + {errors.title?.message}
    {/*
    @@ -353,7 +332,7 @@ export default function StreamList({
    )} -
    +
    {skeleton ? ( ) : ( diff --git a/layout/AppMenu.tsx b/layout/AppMenu.tsx index dea6c356..7882471d 100644 --- a/layout/AppMenu.tsx +++ b/layout/AppMenu.tsx @@ -101,7 +101,7 @@ const AppMenu = () => { } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинирээк кайталаныз' } + value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинчерээк кайталаныз' } }); if (data?.response?.status) { showError(data.response.status); @@ -133,7 +133,7 @@ const AppMenu = () => { setEditingLesson(null); setMessage({ state: true, - value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при добавлении темы' } + value: { severity: 'error', summary: 'Катаа!', detail: 'Кошуу учурунда катаа кетти' } }); if (data?.response?.status) { showError(data.response.status); @@ -153,7 +153,7 @@ const AppMenu = () => { } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при удалении темы' } + value: { severity: 'error', summary: 'Катаа!', detail: 'Өчүрүүдө ката кетти' } }); if (data?.response?.status) { showError(data.response.status); @@ -176,7 +176,7 @@ const AppMenu = () => { setEditingLesson(null); setMessage({ state: true, - value: { severity: 'error', summary: 'Ошибка', detail: 'Ошибка при при изменении урока' } + value: { severity: 'error', summary: 'Катаа!', detail: 'Өзгөртүүдө ката кетти' } }); if (data?.response?.status) { showError(data.response.status); @@ -207,8 +207,6 @@ const AppMenu = () => { }, [user, studentThemeCourse]); useEffect(() => { - console.log(contextThemes); - if (contextThemes && contextThemes.lessons) { const newThemes = contextThemes.lessons.data.map((item: any, idx: number) => ({ label: ( @@ -228,13 +226,7 @@ const AppMenu = () => { } }, [contextThemes]); - useEffect(()=> { - console.log(themeValue); - },[themeValue]); - useEffect(() => { - console.log('Обновился и готов'); - if (contextStudentThemes?.lessons) { const forThemes: any = []; contextStudentThemes.lessons.data?.map((item: any) => diff --git a/utils/axiosInstance.tsx b/utils/axiosInstance.tsx index 739fc847..88decc90 100644 --- a/utils/axiosInstance.tsx +++ b/utils/axiosInstance.tsx @@ -21,18 +21,16 @@ axiosInstance.interceptors.response.use( (error) => { const status = error.response?.status; - console.log('Ошибка из axiosInstance ', error); - if (status === 401) { console.warn('Неавторизован. Удаляю токен...'); - // window.location.href = '/auth/login'; - // document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; - // localStorage.removeItem('userVisit'); + window.location.href = '/auth/login'; + document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; + localStorage.removeItem('userVisit'); } if (status === 403) { console.warn('Не имеет доступ. Перенаправляю...'); - // window.location.href = '/'; + window.location.href = '/'; } if (status === 404) { From dffb2d1c818fe6c4111b0a8db2e3e5f0162cd2f2 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Thu, 11 Sep 2025 14:24:13 +0600 Subject: [PATCH 159/183] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BF=D0=BE=D0=BB=D0=B5=D0=B7=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=81=D1=81=D1=8B=D0=BB=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../course/[course_Id]/[lesson_id]/page.tsx | 2 + app/components/cards/LessonCard.tsx | 20 +- app/components/lessons/LessonLink.tsx | 324 ++++++++++++++++++ schemas/lessonSchema.tsx | 6 +- services/steps.tsx | 67 +++- 5 files changed, 404 insertions(+), 15 deletions(-) create mode 100644 app/components/lessons/LessonLink.tsx diff --git a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx index e7d9979b..8dc940e9 100644 --- a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx +++ b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx @@ -1,6 +1,7 @@ 'use client'; import LessonDocument from '@/app/components/lessons/LessonDocument'; +import LessonLink from '@/app/components/lessons/LessonLink'; import LessonPractica from '@/app/components/lessons/LessonPractica'; import LessonTest from '@/app/components/lessons/LessonTest'; import LessonVideo from '@/app/components/lessons/LessonVideo'; @@ -344,6 +345,7 @@ export default function LessonStep() { {element?.step.type.name === 'video' && } {element?.step.type.name === 'test' && } {element?.step.type.name === 'practical' && } + {element?.step.type.name === 'link' && }
    ); } diff --git a/app/components/cards/LessonCard.tsx b/app/components/cards/LessonCard.tsx index 6e795ad5..9ebaa3ab 100644 --- a/app/components/cards/LessonCard.tsx +++ b/app/components/cards/LessonCard.tsx @@ -112,13 +112,7 @@ export default function LessonCard({ ${type.typeValue === 'video' && status === 'working' ? 'min-h-[200px]' : type.typeValue !== 'video' && status === 'working' ? 'min-h-[160px]' : ''} ${status === 'student' && type.typeValue !== 'video' ? 'min-h-[160px]' : status === 'student' && type.typeValue === 'video' ? 'min-h-[200px]' : ''} - ${type.typeValue === 'video' ? 'w-full' : ''} flex flex-col justify-evenly lesson-card-border rounded p-2 - - ${type.typeValue === 'doc' ? 'w-full min-h-[160px] bg-black' : ''} - - ${type.typeValue === 'test' ? 'w-full min-h-[160px] bg-black' : ''} - - ${type.typeValue === 'practica' ? 'w-full min-h-[160px] bg-black' : ''} + ${type.typeValue === 'video' ? 'w-full' : 'w-full'} flex flex-col justify-evenly lesson-card-border rounded p-2 `} style={{ backgroundColor: cardBg }} @@ -188,9 +182,7 @@ export default function LessonCard({ <> {status === 'student' && type.typeValue === 'doc' ? (
    -
    - {progressSpinner && } -
    +
    {progressSpinner && }
    {' '}
    )} + {type.typeValue === 'link' && ( +
    +
    + )} {status === 'working' && (
    -
    +
    +
    +
    + )} +
    + ); + }; + + useEffect(() => { + console.log('content', content); + if (content) { + setContentShow(true); + setLink(content); + } else { + setContentShow(false); + } + }, [content]); + + useEffect(() => { + console.log('edititing', element); + setLinkValue({ title: '', description: '', url: '' }); + }, [element]); + + useEffect(() => { + console.log('value ', linkValue); + }, [linkValue]); + + return ( +
    + handleUpdateLink()} + clearValues={clearValues} + visible={visible} + setVisible={setVisisble} + start={false} + > +
    +
    + { + setEditingLesson((prev) => ({ ...prev, url: e.target.value })); + setValue('usefulLink', e.target.value, { shouldValidate: true }); + }} + /> + {errors.usefulLink?.message} +
    + { + setEditingLesson((prev) => ({ ...prev, title: e.target.value })); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} + {additional.link && setEditingLesson((prev) => ({ ...prev, description: e.target.value }))} className="w-full" />} + +
    + {/*
    +
    +
    + {!clearProp && linkSection()} +
    + ); +} diff --git a/schemas/lessonSchema.tsx b/schemas/lessonSchema.tsx index 72ec023d..5f661de2 100644 --- a/schemas/lessonSchema.tsx +++ b/schemas/lessonSchema.tsx @@ -4,11 +4,13 @@ export const lessonSchema = yup.object().shape({ videoReq: yup .string() .required('Талап кылынат!') - .matches(/^https?:\/\/.+/, 'Видео шилтеме "http://" "https://" форматында болуш керек'), + .matches(/^https?:\/\/.+/, 'Видео шилтеме "http://" "https://" форматында болуш керек') + .max(200, 'Узундугу макс 200 тамга'), usefulLink: yup .string() .required('Талап кылынат!') - .matches(/^https?:\/\/.+/, 'Шилтеме "http://" "https://" форматында болуш керек'), + .matches(/^https?:\/\/.+/, 'Шилтеме "http://" "https://" форматында болуш керек') + .max(200, 'Узундугу макс 200 тамга'), usefulLinkNotReq: yup .string() .matches(/^https?:\/\/.+/, 'Шилтеме "http://" "https://" форматында болуш керек'), diff --git a/services/steps.tsx b/services/steps.tsx index 0c4f454b..6e9c327b 100644 --- a/services/steps.tsx +++ b/services/steps.tsx @@ -30,7 +30,7 @@ export const fetchSteps = async (lesson_id: number) => { export const addLesson = async (value: { lesson_id: number; type_id: number }, step: number | null) => { console.log(step); - + const formData = new FormData(); formData.append('lesson_id', String(value.lesson_id)); formData.append('type_id', String(value.type_id)); @@ -317,7 +317,7 @@ export const addPractica = async (value: { url: string | null; title: string; de export const updatePractica = async (value: { url: string | null; title: string; description: string | null; document: File | null }, lesson_id: number | null, practice_id: number, type_id: number, step_id: number) => { console.log(value); - + let formData = new FormData(); url = `/v1/teacher/practice-lesson/update?lesson_id=${lesson_id}&title=${value.title}&description=${value.description}&url=${value.url}&document=${value.document}&practice_id=${practice_id}&video_type_id=${type_id}&step_id=${step_id}`; formData.append('type_id', String(type_id)); @@ -358,3 +358,66 @@ export const deletePractica = async (lesson_id: number, practice_id: number, typ return err; } }; + +export const addLink = async (value: { url: string; title: string; description: string }, lesson_id: number, type_id: number, step_id: number) => { + const formData = new FormData(); + formData.append('lesson_id', String(lesson_id)); + formData.append('type_id', String(type_id)); + formData.append('step_id', String(step_id)); + formData.append('url', String(value.url)); + formData.append('title', String(value.title)); + formData.append('description', String(value?.description)); + + try { + const res = await axiosInstance.post(`/v1/teacher/usefullinks/store`, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + + return res.data; + } catch (err) { + console.log('Ошибка при добавлении курса', err); + return err; + } +}; + +export const deleteLink = async (lesson_id: number, link_id: number, type_id: number, step_id: number) => { + try { + const res = await axiosInstance.delete(`/v1/teacher/usefullinks/delete?lesson_id=${lesson_id}&link_id=${link_id}&step_id=${step_id}&type_id=${type_id}`); + + const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка при удалении темы', err); + return err; + } +}; + +export const updateLink = async (value: { url: string | null; title: string; description: string | null }, lesson_id: number | null, link_id: number, type_id: number, step_id: number) => { + console.log(value); + + let formData = new FormData(); + url = `/v1/teacher/usefullinks/update?lesson_id=${lesson_id}&title=${value.title}&description=${value.description}&url=${value.url}&link_id=${link_id}&type_id=${type_id}&step_id=${step_id}`; + formData.append('type_id', String(type_id)); + formData.append('step_id', String(step_id)); + formData.append('lesson_id', String(lesson_id)); + formData.append('link_id', String(link_id)); + formData.append('url', String(value.url)); + formData.append('title', String(value?.title)); + formData.append('description', String(value?.description)); + + try { + const res = await axiosInstance.post(url, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + + const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка при обновлении урока', err); + return err; + } +}; \ No newline at end of file From 3bd2077b0122b26fc1ce530161ff54456c9a53ea Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Thu, 11 Sep 2025 14:34:33 +0600 Subject: [PATCH 160/183] =?UTF-8?q?Skeleton=20=D0=B4=D0=BB=D1=8F=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=B1=D0=BE=D1=80=D0=B0=20=D0=B2=D0=B0=D1=80=D0=B8=D0=B0?= =?UTF-8?q?=D0=BD=D1=82=D0=BE=D0=B2=20=D1=88=D0=B0=D0=B3=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../course/[course_Id]/[lesson_id]/page.tsx | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx index 8dc940e9..55e36a60 100644 --- a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx +++ b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx @@ -8,6 +8,7 @@ import LessonVideo from '@/app/components/lessons/LessonVideo'; import { NotFound } from '@/app/components/NotFound'; import PDFViewer from '@/app/components/PDFBook'; import FormModal from '@/app/components/popUp/FormModal'; +import GroupSkeleton from '@/app/components/skeleton/GroupSkeleton'; import useBreadCrumbs from '@/hooks/useBreadCrumbs'; import useErrorMessage from '@/hooks/useErrorMessage'; import { useMediaQuery } from '@/hooks/useMediaQuery'; @@ -46,7 +47,7 @@ export default function LessonStep() { const [themeNull, setThemeNull] = useState(false); const [lesson_id, setLesson_id] = useState((param.lesson_id && Number(param.lesson_id)) || null); const [sequence_number, setSequence_number] = useState(null); - + const [skeleton, setSkeleton] = useState(false); const [testovy, setTestovy] = useState(false); const router = useRouter(); const pathname = usePathname(); @@ -76,10 +77,13 @@ export default function LessonStep() { const handleFetchTypes = async () => { setFormVisible(true); + setSkeleton(true); const data = await fetchTypes(); if (data && Array.isArray(data)) { setTypes(data); + setSkeleton(false); } else { + setSkeleton(false); setMessage({ state: true, value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинчерээк кайталаныз' } @@ -279,20 +283,24 @@ export default function LessonStep() { }} />
    -
    - {types.map((item) => { - return ( - -
    - - handleAddLesson(Number(lesson_id), item.id)}> - {item.title} - -
    -
    - ); - })} -
    + {skeleton ? ( + + ) : ( +
    + {types.map((item) => { + return ( + +
    + + handleAddLesson(Number(lesson_id), item.id)}> + {item.title} + +
    +
    + ); + })} +
    + )}
    From 853d53c03c37115d35aaa703b7708a967bac639a Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Fri, 12 Sep 2025 12:08:43 +0600 Subject: [PATCH 161/183] =?UTF-8?q?=D0=A3=D0=B1=D1=80=D0=B0=D0=BB=20=D0=BA?= =?UTF-8?q?=D0=BD=D0=BE=D0=BF=D0=BA=D1=83=20=D0=B2=D1=85=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(full-page)/auth/login/page.tsx | 3 +- .../course/[course_Id]/[lesson_id]/page.tsx | 5 +- app/components/BaseLayout.tsx | 2 +- app/components/PDFBook.tsx | 6 +-- app/components/SessionManager.tsx | 50 ++----------------- app/components/cards/LessonCard.tsx | 20 ++++---- app/components/lessons/LessonDocument.tsx | 3 -- app/components/lessons/LessonPractica.tsx | 12 ++--- app/components/lessons/LessonVideo.tsx | 2 +- layout/AppMenu.tsx | 8 +-- layout/AppTopbar.tsx | 7 ++- services/steps.tsx | 6 ++- utils/axiosInstance.tsx | 33 +++++++----- 13 files changed, 59 insertions(+), 98 deletions(-) diff --git a/app/(full-page)/auth/login/page.tsx b/app/(full-page)/auth/login/page.tsx index 8a5679dc..c1a1e60d 100644 --- a/app/(full-page)/auth/login/page.tsx +++ b/app/(full-page)/auth/login/page.tsx @@ -41,9 +41,9 @@ const LoginPage = () => { const token = user.token.access_token; if (token) { const res = await getUser(); + try { if (res?.success) { - console.log(res); if (res?.user.is_working) { window.location.href = '/course'; } @@ -108,7 +108,6 @@ const LoginPage = () => { } /> {errors.password && {errors.password.message}} diff --git a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx index 55e36a60..d2f177bc 100644 --- a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx +++ b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx @@ -52,8 +52,6 @@ export default function LessonStep() { const router = useRouter(); const pathname = usePathname(); - const clearValues = () => {}; - const changeUrl = (lessonId: number) => { router.replace(`/course/${course_id}/${lessonId ? lessonId : null}`); }; @@ -236,7 +234,7 @@ export default function LessonStep() { console.log('variant 4', contextThemes.lessons.data[0].id); handleFetchSteps(contextThemes.lessons.data[0].id); setLesson_id(contextThemes.lessons.data[0].id); - setDeleteQuery(false); + setDeleteQuery(false); } else { console.log('variant 5'); handleShow(Number(param.lesson_id)); @@ -268,7 +266,6 @@ export default function LessonStep() { onHide={() => { if (!formVisible) return; setFormVisible(false); - clearValues(); }} >
    diff --git a/app/components/BaseLayout.tsx b/app/components/BaseLayout.tsx index 78eb0b27..4faf15fc 100644 --- a/app/components/BaseLayout.tsx +++ b/app/components/BaseLayout.tsx @@ -7,7 +7,7 @@ import { useEffect } from 'react'; export default function BaseLayout() { // useEffect(() => { - // window.location.href = 'http://oldmooc.oshsu.kg/'; // твоя ссылка + // window.location.href = 'http://oldmooc.oshsu.kg/'; // }, []); // return null; // ничего не показываем diff --git a/app/components/PDFBook.tsx b/app/components/PDFBook.tsx index f6b056ca..938c4807 100644 --- a/app/components/PDFBook.tsx +++ b/app/components/PDFBook.tsx @@ -30,16 +30,14 @@ export default function PDFViewer({ url }: { url: string }) { useEffect(() => { const renderPDF = async () => { + console.log(url); + if (!url) return; setSkeleton(true); try { // Проверяем, одна ли страница в документе - const forUrl = new RegExp('https:'); let newUrl = `https://api.mooc.oshsu.kg/temprory-file/${url}`; - if(forUrl.test(url)){ - newUrl = url; - } console.log(newUrl); const pdf = await pdfjsLib.getDocument(newUrl).promise; const tempPages = []; diff --git a/app/components/SessionManager.tsx b/app/components/SessionManager.tsx index 0e519760..44c3883e 100644 --- a/app/components/SessionManager.tsx +++ b/app/components/SessionManager.tsx @@ -15,15 +15,6 @@ const SessionManager = () => { const pathname = usePathname(); useEffect(() => { - console.log('Пользователь ', user); - }, [user]); - - useEffect(() => { - setGlobalLoading(true); - setTimeout(() => { - setGlobalLoading(false); - }, 2000); - const init = async () => { console.log('проверяем токен...'); const token = getToken('access_token'); @@ -69,55 +60,24 @@ const SessionManager = () => { }, []); useEffect(() => { - if(!pathname.startsWith('/teaching/lesson/')){ + if(!pathname.startsWith('/teaching/lesson/') && !pathname.startsWith('/course/')){ setGlobalLoading(true); } - console.log('Переход в', pathname); + const token = getToken('access_token'); if (!token && pathname !== '/' && pathname !== '/auth/login') { console.log('Перенеправляю в login'); - // logout({ setUser, setGlobalLoading }); - // window.location.href = '/auth/login'; + logout({ setUser, setGlobalLoading }); + window.location.href = '/auth/login'; return; } setTimeout(() => { setGlobalLoading(false); - }, 1000); + }, 900); }, [pathname]); - // useEffect(() => { - // const checkToken = () => { - // const token = getToken('access_token'); - - // if (!token) { - // const userVisit = localStorage.getItem('userVisit'); - // if(userVisit){ - // setMessage({ - // state: true, - // value: { severity: 'error', summary: 'Сессия завершилось', detail: 'Войдите заново' } - // }); // messege - Время сесси завершилось - // } - // logout({setUser, setGlobalLoading}); - // console.log('Токен отсутствует - завершаем сессию'); - // return false; // сигнал для остановки интервала - // } - // return true; - // }; - - // // немедленная проверка - // if (!checkToken()) return; - - // const interval = setInterval(() => { - // if (!checkToken()) { - // clearInterval(interval); - // } - // }, 5000); - - // return () => clearInterval(interval); - // }, []); - return null; }; diff --git a/app/components/cards/LessonCard.tsx b/app/components/cards/LessonCard.tsx index 9ebaa3ab..eb7ec105 100644 --- a/app/components/cards/LessonCard.tsx +++ b/app/components/cards/LessonCard.tsx @@ -46,8 +46,8 @@ export default function LessonCard({ const [progressSpinner, setProgressSpinner] = useState(false); // useEffect(()=> { - // console.log(cardValue.photo); - // },[cardValue]); + // console.log(urlForDownload); + // },[urlForDownload]); const media = useMediaQuery('(max-width: 640px)'); const toggleSpinner = () => { @@ -117,11 +117,11 @@ export default function LessonCard({ `} style={{ backgroundColor: cardBg }} > -
    +
    {/*
    {!cardValue.photo && }
    */} -
    - {shortTitle} +
    + {shortTitle} {cardValue.score ? (
    {!media && '/'} Балл: @@ -139,12 +139,12 @@ export default function LessonCard({ Шилтеме: - {shortUrl} + {cardValue?.url}
    ) : ( type.typeValue === 'link' && ( <> - {shortUrl} + {shortUrl} ) )} @@ -163,12 +163,12 @@ export default function LessonCard({ })}
    )} -
    +
    {cardValue?.desctiption && cardValue?.desctiption !== 'null' ? shortDescription : cardValue?.desctiption && cardValue?.desctiption !== 'null' && type.typeValue === 'practica' ?
    {shortDescription}
    : ''}
    {status === 'working' && ( -
    )} - {type.typeValue === 'practica' && ( + {type.typeValue === 'practica' && urlForDownload.length > 1 && (
    {' '} diff --git a/app/components/lessons/LessonDocument.tsx b/app/components/lessons/LessonDocument.tsx index ac9f2424..8be4951d 100644 --- a/app/components/lessons/LessonDocument.tsx +++ b/app/components/lessons/LessonDocument.tsx @@ -182,8 +182,6 @@ export default function LessonDocument({ element, content, fetchPropElement, cle // delete document const handleDeleteDoc = async (id: number) => { - console.log(id); - // const token = getToken('access_token'); const data = await deleteDocument(Number(document?.lesson_id), id); if (data.success) { @@ -369,7 +367,6 @@ export default function LessonDocument({ element, content, fetchPropElement, cle className="w-full" value={editingLesson?.title && editingLesson?.title} onChange={(e) => { - console.log(editingLesson); setEditingLesson((prev) => prev && { ...prev, title: e.target.value }); setValue('title', e.target.value, { shouldValidate: true }); }} diff --git a/app/components/lessons/LessonPractica.tsx b/app/components/lessons/LessonPractica.tsx index 85ce7846..12848ec1 100644 --- a/app/components/lessons/LessonPractica.tsx +++ b/app/components/lessons/LessonPractica.tsx @@ -246,8 +246,8 @@ export default function LessonPractica({ element, content, fetchPropElement, cle type={{ typeValue: 'practica', icon: 'pi pi-list' }} typeColor={'var(--mainColor)'} lessonDate={new Date(document.created_at).toISOString().slice(0, 10)} - urlForPDF={() => sentToPDF(document.document_path || '')} - urlForDownload={document.document_path || ''} + urlForPDF={() => sentToPDF('')} + urlForDownload={document.document ? document.document_path : ''} /> ) )} @@ -345,8 +345,8 @@ export default function LessonPractica({ element, content, fetchPropElement, cle }, [element]); useEffect(() => { - console.log('value ', docValue); - }, [docValue]); + console.log('value ', editingLesson); + }, [editingLesson]); return (
    @@ -362,8 +362,8 @@ export default function LessonPractica({ element, content, fetchPropElement, cle >
    -
    - { +
    + { setEditingLesson((prev) => prev && { ...prev, title: e.target.value }) setValue('title', e.target.value, { shouldValidate: true }); }} /> diff --git a/app/components/lessons/LessonVideo.tsx b/app/components/lessons/LessonVideo.tsx index 1efebd26..ae276bff 100644 --- a/app/components/lessons/LessonVideo.tsx +++ b/app/components/lessons/LessonVideo.tsx @@ -377,7 +377,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP onSelected={(id: number, type: string) => selectedForEditing(id, type)} onDelete={(id: number) => handleDeleteVideo(id)} cardValue={{ title: video.title, id: video.id, desctiption: video?.description || '', type: 'video', photo: video?.cover_url }} - cardBg={'white'} + cardBg={'#ddc4f51a'} type={{ typeValue: 'video', icon: 'pi pi-video' }} typeColor={'var(--mainColor)'} lessonDate={new Date(video.created_at).toISOString().slice(0, 10)} diff --git a/layout/AppMenu.tsx b/layout/AppMenu.tsx index 7882471d..862ab2d7 100644 --- a/layout/AppMenu.tsx +++ b/layout/AppMenu.tsx @@ -209,11 +209,7 @@ const AppMenu = () => { useEffect(() => { if (contextThemes && contextThemes.lessons) { const newThemes = contextThemes.lessons.data.map((item: any, idx: number) => ({ - label: ( -
    - {idx + 1}. {item.title} -
    - ), + label: `${idx + 1}. ${item.title}`, id: item.id, to: `/course/${course_Id}/${item.id}`, onEdit: () => { @@ -231,7 +227,7 @@ const AppMenu = () => { const forThemes: any = []; contextStudentThemes.lessons.data?.map((item: any) => forThemes.push({ - label: item.title, + label: item.title || '', id: item.id, to: `/teaching/${studentThemeCourse}/${item.id}` }) diff --git a/layout/AppTopbar.tsx b/layout/AppTopbar.tsx index 7712c7e2..f3e3259d 100644 --- a/layout/AppTopbar.tsx +++ b/layout/AppTopbar.tsx @@ -110,6 +110,7 @@ const AppTopbar = forwardRef((props, ref) => { logo

    Цифровой кампус ОшГУ

    + (в разработке) {pathName !== '/' && pathName !== '/course' ? (
    )} - {user && user ? ( + {/* {user && user ? (
    @@ -161,9 +162,11 @@ const AppTopbar = forwardRef((props, ref) => {
    - )} + )} */}
    + + (в разработке)
    ); }); diff --git a/services/steps.tsx b/services/steps.tsx index 6e9c327b..d65b7f2c 100644 --- a/services/steps.tsx +++ b/services/steps.tsx @@ -115,7 +115,11 @@ export const updateDocument = async (token: string | null, lesson_id: number | n formData.append('type_id', String(type_id)); formData.append('step_id', String(step_id)); formData.append('lesson_id', String(lesson_id)); - formData.append('document', value.file); + if(value.file){ + formData.append('document', value.file) + } else { + formData.append('document', ''); + } formData.append('document_id', String(contentId)); formData.append('title', String(value.title)); formData.append('description', String(value.description)); diff --git a/utils/axiosInstance.tsx b/utils/axiosInstance.tsx index 88decc90..3a83ad58 100644 --- a/utils/axiosInstance.tsx +++ b/utils/axiosInstance.tsx @@ -3,15 +3,15 @@ import { getToken } from './auth'; const axiosInstance = axios.create({ baseURL: process.env.NEXT_PUBLIC_BASE_URL, - timeout: 30000, + timeout: 30000 }); axiosInstance.interceptors.request.use((config) => { - const token = getToken('access_token'); - if (token) { - config.headers.Authorization = `Bearer ${token}`; - } - return config; + const token = getToken('access_token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; }); axiosInstance.interceptors.response.use( @@ -19,25 +19,32 @@ axiosInstance.interceptors.response.use( return response; }, (error) => { - const status = error.response?.status; if (status === 401) { console.warn('Неавторизован. Удаляю токен...'); - window.location.href = '/auth/login'; + if (typeof window !== 'undefined') { + window.location.href = '/auth/login'; + localStorage.removeItem('userVisit'); + } else { + console.log('SSR — window недоступен'); + } document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; - localStorage.removeItem('userVisit'); } if (status === 403) { console.warn('Не имеет доступ. Перенаправляю...'); - window.location.href = '/'; + if (typeof window !== 'undefined') { + window.location.href = '/'; + } } - + if (status === 404) { console.warn('404 - Перенаправляю...'); - // window.location.href = '/pages/notfound'; + if (typeof window !== 'undefined') { + // window.location.href = '/pages/notfound'; + // localStorage.removeItem('userVisit'); + } // document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; - // localStorage.removeItem('userVisit'); } return Promise.reject(error); From d74a879b2fd4069205d37fee4e8beac4e11bfac9 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Fri, 12 Sep 2025 12:10:54 +0600 Subject: [PATCH 162/183] =?UTF-8?q?=D0=A3=D0=B1=D1=80=D0=B0=D0=BB=20=D0=BA?= =?UTF-8?q?=D0=BD=D0=BE=D0=BF=D0=BA=D1=83=20=D0=B2=D1=85=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- layout/AppTopbar.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/layout/AppTopbar.tsx b/layout/AppTopbar.tsx index f3e3259d..303d72fc 100644 --- a/layout/AppTopbar.tsx +++ b/layout/AppTopbar.tsx @@ -123,7 +123,8 @@ const AppTopbar = forwardRef((props, ref) => {
    {media ? ( - + <> + // ) : (
    {/* From d5f4039ca156ffc4679761fa28c3b39e2caea43d Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Fri, 12 Sep 2025 15:32:00 +0600 Subject: [PATCH 163/183] =?UTF-8?q?=D0=92=D0=B5=D1=80=D1=81=D1=82=D0=BA?= =?UTF-8?q?=D0=B0=20=D1=88=D0=B0=D0=B3=D0=BE=D0=B2,=20=D1=84=D0=B8=D0=BA?= =?UTF-8?q?=D1=81=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D1=8B=20?= =?UTF-8?q?=D1=81=20=D0=BF=D0=B4=D1=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../course/[course_Id]/[lesson_id]/page.tsx | 26 ++-- app/(main)/course/page.tsx | 72 +++++---- app/(main)/pdf/[pdfUrl]/page.tsx | 26 +++- app/components/PDFBook.tsx | 141 ++++++++++-------- app/components/SessionManager.tsx | 4 +- app/components/cards/LessonCard.tsx | 4 +- app/components/lessons/LessonDocument.tsx | 5 +- app/components/lessons/LessonLink.tsx | 4 +- app/components/lessons/LessonPractica.tsx | 4 +- app/components/lessons/LessonTest.tsx | 4 +- app/components/lessons/LessonVideo.tsx | 4 +- app/components/popUp/FormModal.tsx | 45 ++---- app/globals.css | 8 +- utils/axiosInstance.tsx | 20 ++- 14 files changed, 195 insertions(+), 172 deletions(-) diff --git a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx index d2f177bc..211ceb0e 100644 --- a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx +++ b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx @@ -234,7 +234,7 @@ export default function LessonStep() { console.log('variant 4', contextThemes.lessons.data[0].id); handleFetchSteps(contextThemes.lessons.data[0].id); setLesson_id(contextThemes.lessons.data[0].id); - setDeleteQuery(false); + setDeleteQuery(false); } else { console.log('variant 5'); handleShow(Number(param.lesson_id)); @@ -328,16 +328,6 @@ export default function LessonStep() { > + - {!hasSteps && ( -
    @@ -346,6 +336,20 @@ export default function LessonStep() {
    )} +
    + {element?.step.type.title} + {!hasSteps && ( +
    {element?.step.type.name === 'document' && } {element?.step.type.name === 'video' && } {element?.step.type.name === 'test' && } diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index 0144110e..0b51eb34 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -389,38 +389,15 @@ export default function Course() { {/* modal window */}
    -
    - {/* {imagestateStyle && ( */} -
    - {/* {typeof imageState === 'string' && } */} - {typeof imageState === 'string' ? : } -
    - {/* )} */} -
    - - - {courseValue.image || editingLesson.image ? ( -
    - {typeof editingLesson.image === 'string' && ( - <> - {imageTitle} - - )} -
    - ) : ( - jpeg, png, jpg - )} -
    {(editingLesson.image || imageState) &&
    -
    -
    {/*
    */}
    -
    +
    { editMode ? setEditingLesson((prev) => ({ @@ -438,12 +415,13 @@ export default function Course() {
    -
    +
    { editMode ? setEditingLesson((prev) => ({ @@ -464,14 +442,14 @@ export default function Course() { {/*
    */}
    -
    +
    { editMode ? setEditingLesson((prev) => ({ @@ -488,6 +466,42 @@ export default function Course() {
    {/*
    */} + +
    + {/* {imagestateStyle && ( */} +
    + {/* {typeof imageState === 'string' && } */} + {typeof imageState === 'string' ? : } +
    + {/* )} */} +
    + + + {courseValue.image || editingLesson.image ? ( +
    + {typeof editingLesson.image === 'string' && ( + <> + {imageTitle} + + )} +
    + ) : ( + jpeg, png, jpg + )} +
    {(editingLesson.image || imageState) &&
    +
    +
    diff --git a/app/(main)/pdf/[pdfUrl]/page.tsx b/app/(main)/pdf/[pdfUrl]/page.tsx index 02c8e582..da48f26e 100644 --- a/app/(main)/pdf/[pdfUrl]/page.tsx +++ b/app/(main)/pdf/[pdfUrl]/page.tsx @@ -2,18 +2,28 @@ // import PDFViewer from '@/app/components/PDFBook'; // import PDFViewer from '../PDFBook'; -import dynamic from "next/dynamic"; +import dynamic from 'next/dynamic'; +import Link from 'next/link'; -const PDFViewer = dynamic(()=> import('@/app/components/PDFBook'), { - ssr: false, -}) +const PDFViewer = dynamic(() => import('@/app/components/PDFBook'), { + ssr: false +}); -import { useParams } from 'next/navigation'; +import { useParams, useRouter } from 'next/navigation'; export default function PdfUrlViewer() { const { pdfUrl } = useParams(); + const router = useRouter(); - return
    - -
    ; + return ( +
    + + +
    + +
    +
    + ); } diff --git a/app/components/PDFBook.tsx b/app/components/PDFBook.tsx index 938c4807..c2b070a9 100644 --- a/app/components/PDFBook.tsx +++ b/app/components/PDFBook.tsx @@ -31,7 +31,7 @@ export default function PDFViewer({ url }: { url: string }) { useEffect(() => { const renderPDF = async () => { console.log(url); - + if (!url) return; setSkeleton(true); @@ -128,30 +128,84 @@ export default function PDFViewer({ url }: { url: string }) { return (
    - {hasPdf ? ( - - ) : ( - <> - {skeleton ? ( -
    - -
    - ) : media ? ( - <> +
    + {hasPdf ? ( + + ) : ( + <> + {skeleton ? ( +
    + +
    + ) : media ? ( + <> + {}} + onChangeOrientation={() => {}} + onChangeState={() => {}} + onInit={() => {}} + onUpdate={() => {}} + startPage={0} + drawShadow={true} + flippingTime={900} + showPageCorners={true} + disableFlipByClick={false} + > + {pages.map((page, index) => ( +
    +
    + {' '} + {/* Добавляем класс для содержимого страницы */} + {page} +
    +
    + ))} +
    + + ) : ( ))} - - ) : ( - {}} - onChangeOrientation={() => {}} - onChangeState={() => {}} - onInit={() => {}} - onUpdate={() => {}} - startPage={0} - drawShadow={true} - flippingTime={900} - showPageCorners={true} - disableFlipByClick={false} - > - {pages.map((page, index) => ( -
    -
    - {' '} - {/* Добавляем класс для содержимого страницы */} - {page} -
    -
    - ))} -
    - )} - - )} + )} + + )} +
    - //
    ); } diff --git a/app/components/SessionManager.tsx b/app/components/SessionManager.tsx index 44c3883e..c149813e 100644 --- a/app/components/SessionManager.tsx +++ b/app/components/SessionManager.tsx @@ -69,8 +69,8 @@ const SessionManager = () => { if (!token && pathname !== '/' && pathname !== '/auth/login') { console.log('Перенеправляю в login'); - logout({ setUser, setGlobalLoading }); - window.location.href = '/auth/login'; + // logout({ setUser, setGlobalLoading }); + // window.location.href = '/auth/login'; return; } setTimeout(() => { diff --git a/app/components/cards/LessonCard.tsx b/app/components/cards/LessonCard.tsx index eb7ec105..db7879e4 100644 --- a/app/components/cards/LessonCard.tsx +++ b/app/components/cards/LessonCard.tsx @@ -193,7 +193,7 @@ export default function LessonCard({ {type.typeValue === 'doc' && (
    -
    @@ -212,7 +212,7 @@ export default function LessonCard({ )} {type.typeValue === 'link' && (
    -
    )} diff --git a/app/components/lessons/LessonDocument.tsx b/app/components/lessons/LessonDocument.tsx index 8be4951d..86b9e5bb 100644 --- a/app/components/lessons/LessonDocument.tsx +++ b/app/components/lessons/LessonDocument.tsx @@ -229,11 +229,11 @@ export default function LessonDocument({ element, content, fetchPropElement, cle const documentSection = () => { return ( -
    +
    {PDFVisible ? ( documentView ) : contentShow ? ( -
    +
    {docShow ? ( @@ -325,7 +325,6 @@ export default function LessonDocument({ element, content, fetchPropElement, cle }, [content]); useEffect(() => { - console.log('edititing', element); setDocValue({ title: '', description: '', file: null }); }, [element]); diff --git a/app/components/lessons/LessonLink.tsx b/app/components/lessons/LessonLink.tsx index a0ded0f2..e6f671df 100644 --- a/app/components/lessons/LessonLink.tsx +++ b/app/components/lessons/LessonLink.tsx @@ -176,9 +176,9 @@ export default function LessonLink({ element, content, fetchPropElement, clearPr const linkSection = () => { return ( -
    +
    {contentShow ? ( -
    +
    {docShow ? ( diff --git a/app/components/lessons/LessonPractica.tsx b/app/components/lessons/LessonPractica.tsx index 12848ec1..e85269df 100644 --- a/app/components/lessons/LessonPractica.tsx +++ b/app/components/lessons/LessonPractica.tsx @@ -227,11 +227,11 @@ export default function LessonPractica({ element, content, fetchPropElement, cle const practicaSection = () => { return ( -
    +
    {PDFVisible ? ( documentView ) : contentShow ? ( -
    +
    {docShow ? ( diff --git a/app/components/lessons/LessonTest.tsx b/app/components/lessons/LessonTest.tsx index 8dfdb492..bb1ec435 100644 --- a/app/components/lessons/LessonTest.tsx +++ b/app/components/lessons/LessonTest.tsx @@ -207,9 +207,9 @@ export default function LessonTest({ element, content, fetchPropElement, clearPr const optionAddBtn = answer.length > 2 && answer[answer.length - 1].text.length < 1; const testSection = () => { return ( -
    +
    {contentShow ? ( -
    +
    {testShow ? ( diff --git a/app/components/lessons/LessonVideo.tsx b/app/components/lessons/LessonVideo.tsx index ae276bff..b1e0130a 100644 --- a/app/components/lessons/LessonVideo.tsx +++ b/app/components/lessons/LessonVideo.tsx @@ -282,7 +282,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP const videoSection = () => { return ( -
    +
    {!contentShow ? (
    @@ -345,7 +345,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP
    ) : ( -
    +
    {
    - - ) : ( - <> - -
    - )} -
    - ) : ( - lessonInfo - )} - */} - - {/* DOC */} - - { } - {/* */} - - - {/* USEFUL LINKS */} - - {contentShow && } - - - {/* VIDEO */} - - {contentShow && } - {/* && ( -
    -
    - -
    -
    - ) */} -
    - -
    - ); -} diff --git a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx index 211ceb0e..3279ab58 100644 --- a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx +++ b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx @@ -337,7 +337,7 @@ export default function LessonStep() {
    )}
    - {element?.step.type.title} + {!hasSteps && {element?.step.type.title}} {!hasSteps && (
    @@ -525,7 +526,7 @@ export default function Course() { pt={{ headerAction: { className: 'font-italic ' } }} - header="Курстар" + header="Курсы" className=" p-tabview p-tabview-nav p-tabview-selected p-tabview-panels p-tabview-panel" > {/* mobile table section */} @@ -534,7 +535,7 @@ export default function Course() { <>
    +
    */} + { + console.log(e.target.files); + const file = e.target.files?.[0]; + if (file) { + setDocValue((prev) => ({ + ...prev, + file: file + })); + } + }} + /> {selectType === 'doc' ? ( <> -
    + {/*
    +
    */} + { + console.log(e.target.files); + const file = e.target.files?.[0]; + if (file) { + setEditingLesson( + (prev) => + prev && { + ...prev, + file: file + } + ); + } + }} + /> {/* {String(editingLesson?.file[0].objectURL)} */} ) : selectType === 'video' ? ( - <> - + <> ) : ( '' )} diff --git a/app/components/lessons/LessonPractica.tsx b/app/components/lessons/LessonPractica.tsx index e85269df..6798444d 100644 --- a/app/components/lessons/LessonPractica.tsx +++ b/app/components/lessons/LessonPractica.tsx @@ -263,25 +263,40 @@ export default function LessonPractica({ element, content, fetchPropElement, cle
    {errors.title?.message} {additional.doc && ( -
    - {}} - accept="application/pdf" - onSelect={(e) => - setDocValue((prev) => ({ - ...prev, - document: e.files[0] - })) - } - /> -
    + //
    + // {}} + // accept="application/pdf" + // onSelect={(e) => + // setDocValue((prev) => ({ + // ...prev, + // document: e.files[0] + // })) + // } + // /> + //
    + { + console.log(e.target.files); + const file = e.target.files?.[0]; + if(file){ + setDocValue((prev) => ({ + ...prev, + document: file + })) + } + }} + /> )} {additional.doc && (
    diff --git a/app/components/lessons/LessonVideo.tsx b/app/components/lessons/LessonVideo.tsx index b1e0130a..7272b317 100644 --- a/app/components/lessons/LessonVideo.tsx +++ b/app/components/lessons/LessonVideo.tsx @@ -104,6 +104,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP const [selectType, setSelectType] = useState(''); const [selectId, setSelectId] = useState(null); const [contentShow, setContentShow] = useState(false); + const [videoVisible, setVideoVisible] = useState(false); const clearFile = () => { fileUploadRef.current?.clear(); @@ -161,6 +162,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP // return `https://www.youtube.com/embed/${videoId}`; setVideoLink(`https://www.youtube.com/embed/${videoId}`); setVideoCall(true); + // setVisisble(true); }; // validate @@ -347,7 +349,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP ) : (
    -
    - + */} {videoShow ? ( ) : ( - video && ( - selectedForEditing(id, type)} - onDelete={(id: number) => handleDeleteVideo(id)} - cardValue={{ title: video.title, id: video.id, desctiption: video?.description || '', type: 'video', photo: video?.cover_url }} - cardBg={'#ddc4f51a'} - type={{ typeValue: 'video', icon: 'pi pi-video' }} - typeColor={'var(--mainColor)'} - lessonDate={new Date(video.created_at).toISOString().slice(0, 10)} - urlForPDF={() => ''} - urlForDownload="" - videoVisible={() => handleVideoCall(String(video?.link))} - /> - ) + <> + {!videoCall ? ( + video && ( + selectedForEditing(id, type)} + onDelete={(id: number) => handleDeleteVideo(id)} + cardValue={{ title: video.title, id: video.id, desctiption: video?.description || '', type: 'video', photo: video?.cover_url }} + cardBg={'#ddc4f51a'} + type={{ typeValue: 'video', icon: 'pi pi-video' }} + typeColor={'va r(--mainColor)'} + lessonDate={new Date(video.created_at).toISOString().slice(0, 10)} + urlForPDF={() => ''} + urlForDownload="" + videoVisible={() => handleVideoCall(String(video?.link))} + /> + ) + ) : ( +
    +
    + setVideoCall(false)}> +
    + +
    + )} + )}
    diff --git a/app/components/popUp/FormModal.tsx b/app/components/popUp/FormModal.tsx index cdf245b3..bc25ce7e 100644 --- a/app/components/popUp/FormModal.tsx +++ b/app/components/popUp/FormModal.tsx @@ -27,7 +27,7 @@ export default function FormModal({ const footerContent = (
    + ); + return ( <> + { + if (!visible) return; + setVisible(false); + // clearValues(); + }} + footer={footerContent} + > + { + + rowIndex + 1} header="Номер"> + {/* */} + +

    {rowData.subject_name.name_ru}

    }>
    + +

    {rowData.language.name}

    }>
    + +

    20{rowData.id_edu_year}

    }>
    +

    {rowData.period.name_ru}

    }>
    + +

    {rowData.semester.name_ru}

    }>
    + +

    {rowData.subject_type_name.short_name_ru}

    }>
    + + ( + <> + + + )} + > +
    + } +
    {callIndex === 1 && (
    {skeleton ? ( @@ -294,17 +358,21 @@ export default function StreamList({ <> {/* info section */} {!isMobile && ( -
    - - Курс связанный с потокам: {courseValue?.title} - -
    )} @@ -317,11 +385,12 @@ export default function StreamList({ {isMobile && (
    )} -
    +
    {skeleton ? ( ) : ( <> - +
    + +
    )}
    diff --git a/styles/layout/forms.css b/styles/layout/forms.css index a457ddc6..4d93cefb 100644 --- a/styles/layout/forms.css +++ b/styles/layout/forms.css @@ -1,4 +1,3 @@ -/* Сброс стандартного radio */ .custom-radio input[type="radio"] { appearance: none; /* Убираем нативный стиль */ -webkit-appearance: none; @@ -58,6 +57,70 @@ /* box-shadow: inset 4px 4px 7px 9px var(--mainColor) ; */ } + /* Для курсого инпута */ + +.custom-course-radio input[type="radio"] { + appearance: none; /* Убираем нативный стиль */ + -webkit-appearance: none; + position: absolute; + opacity: 0; + pointer-events: none; +} + +/* Контейнер */ +.custom-course-radio { + display: inline-flex; + align-items: center; + cursor: pointer; + font-family: sans-serif; + font-size: 14px; + gap: 8px; + position: relative; + padding: 4px 7px; + transition: color 0.3s; +} + +/* Сам кружок */ +.radio-course-mark { + /* width: 18px; + height: 18px; */ + border: 2px solid var(--mainColor); + /* border-radius: 50%; */ + display: inline-block; + position: relative; + transition: all 0.3s ease; +} + +/* Внутренняя точка при выборе */ +.radio-course-mark::after { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 100%; + height: 100%; + background-color: var(--mainColor); + /* box-shadow: inset 4px 4px 7px 9px var(--mainColor) ; */ + + border-radius: 50%; + transform: translate(-50%, -50%) scale(0); + transition: transform 0.2s ease; + z-index: -2; +} + +/* Когда radio-course выбран */ +.custom-course-radio input[type="radio"]:checked + .radio-course-mark { + /* transform: translate(-50%, -50%) scale(1); */ + background-color: var(--mainColor); + color: white; +} + +/* Фокус */ +.custom-course-radio input[type="radio"]:focus + .radio-course-mark { + border: solid 2px rgba(48, 169, 193, 0.3); + box-shadow: inset 4px 4px 7px 9px var(--mainColor) ; +} + /* checkbox */ .customCheckbox { diff --git a/types/mainStreamsType.tsx b/types/mainStreamsType.tsx new file mode 100644 index 00000000..fefeedce --- /dev/null +++ b/types/mainStreamsType.tsx @@ -0,0 +1,17 @@ +export interface mainStreamsType { + connect_id: number | null; + stream_id: number; + id_curricula: number; + subject_name: { name_ru: string; id: number }; + subject_type_name: { name_ru: string; short_name_ru: string; id: number }; + teacher: { name: string }; + language: { name: string }; + id_edu_year: number; + id_period: number; + semester: { name_ru: string }; + edu_form: { name_ru: string }; + period: { name_ru: string }; + courseValue?: number; + speciality: { id: number; id_faculty: number }; + course_id?: number | null; +} diff --git a/utils/axiosInstance.tsx b/utils/axiosInstance.tsx index ea7be095..0441957b 100644 --- a/utils/axiosInstance.tsx +++ b/utils/axiosInstance.tsx @@ -22,27 +22,27 @@ axiosInstance.interceptors.response.use( const status = error.response?.status; if (status === 401) { console.warn('Неавторизован. Удаляю токен...'); - if (typeof window !== 'undefined') { - window.location.href = '/auth/login'; - localStorage.removeItem('userVisit'); - } - document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; + // if (typeof window !== 'undefined') { + // window.location.href = '/auth/login'; + // localStorage.removeItem('userVisit'); + // } + // document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; } if (status === 403) { console.warn('Не имеет доступ. Перенаправляю...'); - if (typeof window !== 'undefined') { - window.location.href = '/'; - } + // if (typeof window !== 'undefined') { + // window.location.href = '/'; + // } } if (status === 404) { console.warn('404 - Перенаправляю...'); - if (typeof window !== 'undefined') { - window.location.href = '/pages/notfound'; - localStorage.removeItem('userVisit'); - } - document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; + // if (typeof window !== 'undefined') { + // window.location.href = '/pages/notfound'; + // localStorage.removeItem('userVisit'); + // } + // document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; } return Promise.reject(error); From 6dc5ecc66f7480899a3d8bf507cd34ac5368106f Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Mon, 15 Sep 2025 17:08:27 +0600 Subject: [PATCH 167/183] =?UTF-8?q?=D0=9F=D1=83=D0=B1=D0=BB=D0=B8=D0=BA?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B7=D0=B0=D0=B2=20?= =?UTF-8?q?=D0=BA=D0=B0=D1=84=D0=B5=D0=B4=D1=80=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(full-page)/auth/login/page.tsx | 23 +- .../course/[course_Id]/[lesson_id]/page.tsx | 50 +++-- app/(main)/course/page.tsx | 61 ++--- app/(main)/faculty/[id_kafedra]/page.tsx | 211 ++++++++++++++++++ app/(main)/faculty/page.tsx | 115 ++++++++++ app/components/NotFound.tsx | 2 +- app/components/SessionManager.tsx | 15 +- app/components/tables/StreamList.tsx | 20 +- app/globals.css | 160 ++++++++++++- layout/AppMenu.tsx | 53 +++-- layout/AppTopbar.tsx | 37 +-- layout/context/layoutcontext.tsx | 8 +- services/courses.tsx | 115 +++++----- services/faculty.tsx | 41 ++++ styles/layout/forms.css | 8 +- types/layout.d.ts | 7 +- utils/axiosInstance.tsx | 26 +-- 17 files changed, 772 insertions(+), 180 deletions(-) create mode 100644 app/(main)/faculty/[id_kafedra]/page.tsx create mode 100644 app/(main)/faculty/page.tsx create mode 100644 services/faculty.tsx diff --git a/app/(full-page)/auth/login/page.tsx b/app/(full-page)/auth/login/page.tsx index 65476cdd..c71d1c70 100644 --- a/app/(full-page)/auth/login/page.tsx +++ b/app/(full-page)/auth/login/page.tsx @@ -1,7 +1,7 @@ /* eslint-disable @next/next/no-img-element */ 'use client'; import { useRouter } from 'next/navigation'; -import React, { useContext, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { Password } from 'primereact/password'; import { LayoutContext } from '../../../../layout/context/layoutcontext'; import { InputText } from 'primereact/inputtext'; @@ -17,7 +17,7 @@ import { LoginType } from '@/types/login'; import { useMediaQuery } from '@/hooks/useMediaQuery'; const LoginPage = () => { - const { layoutConfig, setUser, setMessage, setGlobalLoading } = useContext(LayoutContext); + const { layoutConfig, setUser, setMessage, setGlobalLoading, setDepartament, departament } = useContext(LayoutContext); const router = useRouter(); const media = useMediaQuery('(max-width: 1030px)'); @@ -39,13 +39,23 @@ const LoginPage = () => { document.cookie = `access_token=${user.token.access_token}; path=/; Secure; SameSite=Strict; expires=${user.token.expires_at}`; const token = user.token.access_token; if (token) { - const res = await getUser(); + const res = await getUser(); console.log(res); try { if (res?.success) { if (res?.user.is_working) { - window.location.href = '/course'; + if(res.roles && res.roles.length > 0){ + const roleCheck = res.roles.find((i:{id_role: number})=> i.id_role) + if(roleCheck){ + setDepartament({info: roleCheck.roles_name.info_ru, last_name:res.user?.last_name, name:res?.user.name, father_name:res.user?.father_name}); + window.location.href = '/faculty'; + } else { + window.location.href = '/course'; + } + } else { + window.location.href = '/course'; + } } if(res?.user.is_student){ window.location.href = '/'; @@ -84,6 +94,11 @@ const LoginPage = () => { }); // messege - Ошибка при авторизации }; + useEffect(()=> { + console.log(departament); + + },[departament]); + return (
    {/*
    */} diff --git a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx index 3279ab58..219259fa 100644 --- a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx +++ b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx @@ -26,7 +26,7 @@ import { Button } from 'primereact/button'; import { confirmDialog } from 'primereact/confirmdialog'; import { Dialog } from 'primereact/dialog'; import { InputText } from 'primereact/inputtext'; -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext, useEffect, useRef, useState } from 'react'; import { useForm } from 'react-hook-form'; export default function LessonStep() { @@ -93,10 +93,12 @@ export default function LessonStep() { }; const handleFetchSteps = async (lesson_id: number) => { + setSkeleton(true); const data = await fetchSteps(Number(lesson_id)); console.log('steps', data); if (data.success) { + setSkeleton(false); if (data.steps.length < 1) { setHasSteps(true); } else { @@ -104,6 +106,7 @@ export default function LessonStep() { setSteps(data.steps); } } else { + setSkeleton(false); setHasSteps(false); setMessage({ state: true, @@ -116,6 +119,7 @@ export default function LessonStep() { }; const handleAddLesson = async (lessonId: number, typeId: number) => { + setFormVisible(false); const data = await addLesson({ lesson_id: lessonId, type_id: typeId }, sequence_number); console.log(data); @@ -134,7 +138,6 @@ export default function LessonStep() { showError(data.response.status); } } - setFormVisible(false); }; const handleFetchElement = async (stepId: number) => { @@ -256,6 +259,23 @@ export default function LessonStep() { ); } + const scrollRef = useRef(null); + + useEffect(() => { + const el = scrollRef.current; + if (!el) return; + + const onWheel = (e: WheelEvent) => { + if (e.deltaY !== 0) { + e.preventDefault(); + el.scrollLeft += e.deltaY; // прокрутка по горизонтали + } + }; + + el.addEventListener('wheel', onWheel, { passive: false }); + return () => el.removeEventListener('wheel', onWheel); + }, []); + return (
    {/* modal sectoin */} @@ -311,14 +331,18 @@ export default function LessonStep() {
    ) : ( -
    - {steps.map((item, idx) => { - return ( -
    - {step(item.type.logo, item.id, idx)} -
    - ); - })} +
    + {skeleton ? ( +
    + ) : ( + steps.map((item, idx) => { + return ( +
    + {step(item.type.logo, item.id, idx)} +
    + ); + }) + )}
    )}
    @@ -336,12 +360,12 @@ export default function LessonStep() {
    )} -
    - {!hasSteps && {element?.step.type.title}} +
    + {!hasSteps && {element?.step.type.title}} {!hasSteps && (
    @@ -561,7 +539,7 @@ export default function Course() { }} /> + ) : ( + + )} + {progressSpinner && } +
    + )} + > + +
    + )} +
    + ); + })} + + {notCourse.length > 0 && ( +
    +

    Курсы отсутствуют:

    + + rowIndex + 1} header="#"> + ( +

    + {rowData.last_name} {rowData.name} {rowData.father_name} +

    + )} + >
    +
    +
    + )} + + )} +
    + ); +} diff --git a/app/(main)/faculty/page.tsx b/app/(main)/faculty/page.tsx new file mode 100644 index 00000000..64486ff3 --- /dev/null +++ b/app/(main)/faculty/page.tsx @@ -0,0 +1,115 @@ +'use client'; + +import { NotFound } from '@/app/components/NotFound'; +import useErrorMessage from '@/hooks/useErrorMessage'; +import { LayoutContext } from '@/layout/context/layoutcontext'; +import { fetchFaculty, fetchKafedra } from '@/services/faculty'; +import Link from 'next/link'; +import { Column } from 'primereact/column'; +import { DataTable } from 'primereact/datatable'; +import { Dropdown, DropdownChangeEvent } from 'primereact/dropdown'; +import { useContext, useEffect, useState } from 'react'; + +export default function Faculty() { + interface City { + id: number | null; + name_ru: string; + id_faculty?: number | null; + } + + const showError = useErrorMessage(); + const { setMessage } = useContext(LayoutContext); + + const [selected, setSelected] = useState(null); + const [faculty, setFaculty] = useState([{ name_ru: '', id: null }]); + const [kafedra, setKafedra] = useState([{ name_ru: '', id: null }]); + const [selectShow, setSelectShow] = useState(false); + const [facultyShow, setFacultyShow] = useState(false); + + const handleFetchFaculty = async () => { + const data = await fetchFaculty(); + if (data && Array.isArray(data)) { + const newFaculty = data.map((item) => { + return { name_ru: item.name_ru, id: item.id }; + }); + setFaculty(newFaculty); + + if (newFaculty.length > 0) { + setSelected(newFaculty[0]); + setSelectShow(false); + } else { + setSelectShow(true); + } + } else { + setSelectShow(true); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка!', detail: 'Повторите позже' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const handleFetchKafedra = async () => { + if (selected) { + const data = await fetchKafedra(selected && selected.id); + console.log(data); + + if (data && Array.isArray(data)) { + setKafedra(data); + setFacultyShow(false); + } else { + setFacultyShow(true); + } + } + }; + + useEffect(() => { + handleFetchFaculty(); + }, []); + + useEffect(() => { + handleFetchKafedra(); + }, [selected]); + + useEffect(() => { + console.log(kafedra); + }, [kafedra]); + + return ( +
    +
    + {selectShow ? ( +

    Факультеты временно не доступны

    + ) : ( +
    + setSelected(e.value)} options={faculty} optionLabel="name_ru" className="w-[90%] overflow-x-auto" panelClassName="w-[50%] overflow-x-scroll" /> +
    + )} +
    + {/* data table */} +
    +

    Кафедры

    + {facultyShow ? ( + + ) : ( + + rowIndex + 1} header="#" style={{ width: '20px' }}> + ( + + {rowData.name_ru} + + )} + > + + )} +
    +
    + ); +} diff --git a/app/components/NotFound.tsx b/app/components/NotFound.tsx index 74b4e906..6eafe78c 100644 --- a/app/components/NotFound.tsx +++ b/app/components/NotFound.tsx @@ -11,7 +11,7 @@ export const NotFound = ({ titleMessage }: { titleMessage: string }) => {

    {titleMessage}

    {/* */} - Башкы баракчага кайтуу + Перейти в главную страницу
    diff --git a/app/components/SessionManager.tsx b/app/components/SessionManager.tsx index 695a1a0d..0d8bf0fe 100644 --- a/app/components/SessionManager.tsx +++ b/app/components/SessionManager.tsx @@ -9,7 +9,7 @@ import { usePathname } from 'next/navigation'; const SessionManager = () => { const { setMessage } = useContext(LayoutContext); - const { user, setUser } = useContext(LayoutContext); + const { user, setUser, departament, setDepartament } = useContext(LayoutContext); const { setGlobalLoading } = useContext(LayoutContext); const pathname = usePathname(); @@ -27,7 +27,12 @@ const SessionManager = () => { setGlobalLoading(false); }, 1000); console.log('Данные пользователя успешно пришли ', res); - + if (res.roles && res.roles.length > 0) { + const roleCheck = res.roles.find((i: { id_role: number }) => i.id_role); + if (roleCheck) { + setDepartament({ info: roleCheck.roles_name.info_ru, last_name: res.user?.last_name, name: res?.user.name, father_name: res.user?.father_name }); + } + } const userVisit = localStorage.getItem('userVisit'); if (!userVisit) { localStorage.setItem('userVisit', JSON.stringify(true)); @@ -60,7 +65,7 @@ const SessionManager = () => { }, []); useEffect(() => { - if(!pathname.startsWith('/teaching/lesson/') && !pathname.startsWith('/course/')){ + if (!pathname.startsWith('/teaching/lesson/') && !pathname.startsWith('/course/')) { setGlobalLoading(true); } @@ -69,8 +74,8 @@ const SessionManager = () => { if (!token && pathname !== '/' && pathname !== '/auth/login') { console.log('Перенеправляю в login'); - // logout({ setUser, setGlobalLoading }); - // window.location.href = '/auth/login'; + logout({ setUser, setGlobalLoading }); + window.location.href = '/auth/login'; setGlobalLoading(false); return; } diff --git a/app/components/tables/StreamList.tsx b/app/components/tables/StreamList.tsx index ea81d14b..9cd5f0c3 100644 --- a/app/components/tables/StreamList.tsx +++ b/app/components/tables/StreamList.tsx @@ -28,7 +28,6 @@ export default function StreamList({ insideDisplayStreams: (id: mainStreamsType[]) => void; toggleIndex: () => void; }) { - const [streams, setStreams] = useState([]); const [displayStreams, setDisplayStreams] = useState([]); const [hasStreams, setHasStreams] = useState(false); @@ -170,7 +169,7 @@ export default function StreamList({ }, [courseValue]); useEffect(() => { - console.log('обнова'); + console.log('streams', streams); if (streams.length < 1) { insideDisplayStreams(streams); @@ -250,7 +249,6 @@ export default function StreamList({ if (!items || items.length === 0) return null; const hasData = items.some((item) => item.connect_id !== null); - // console.warn('HAS', hasData); if (hasData) { let list = items.map((product, index: number) => { if (product.connect_id !== null) { @@ -305,7 +303,7 @@ export default function StreamList({ }} footer={footerContent} > - { + {streams && streams.length > 0 ? ( rowIndex + 1} header="Номер"> {/* */} @@ -348,7 +346,9 @@ export default function StreamList({ )} > - } + ) : ( +

    Курс не опубликован или данные временно не доступны

    + )} {callIndex === 1 && (
    @@ -362,11 +362,11 @@ export default function StreamList({ {/* */} {courseValue?.title} {/* */} -
    +
    @@ -297,7 +318,7 @@ const AppMenu = () => { // className="w-[50px] sm:w-[70px]" className="w-[90%]" onChange={(e) => { - setThemeValue((prev) => prev && ({ ...prev, sequence_number: Number(e.target.value) })); + setThemeValue((prev) => prev && { ...prev, sequence_number: Number(e.target.value) }); }} />
    diff --git a/layout/AppTopbar.tsx b/layout/AppTopbar.tsx index 303d72fc..c5e997e4 100644 --- a/layout/AppTopbar.tsx +++ b/layout/AppTopbar.tsx @@ -1,7 +1,7 @@ /* eslint-disable @next/next/no-img-element */ import Link from 'next/link'; -import React, { forwardRef, useContext, useImperativeHandle, useRef } from 'react'; +import React, { forwardRef, useContext, useEffect, useImperativeHandle, useRef } from 'react'; import Tiered from '@/app/components/popUp/Tiered'; import FancyLinkBtn from '@/app/components/buttons/FancyLinkBtn'; import { classNames } from 'primereact/utils'; @@ -12,7 +12,7 @@ import { usePathname, useRouter } from 'next/navigation'; import { logout } from '@/utils/logout'; const AppTopbar = forwardRef((props, ref) => { - const { layoutConfig, layoutState, onMenuToggle, showProfileSidebar, user, setUser, setGlobalLoading } = useContext(LayoutContext); + const { layoutConfig, layoutState, onMenuToggle, showProfileSidebar, user, setUser, setGlobalLoading, departament } = useContext(LayoutContext); const menubuttonRef = useRef(null); const topbarmenuRef = useRef(null); @@ -38,7 +38,7 @@ const AppTopbar = forwardRef((props, ref) => { { label: '', template: ( -
    +
    {user?.last_name} {user?.name} @@ -53,8 +53,8 @@ const AppTopbar = forwardRef((props, ref) => { className: 'text-[12px]', items: [], command: () => { - window.location.href = '/auth/login'; - logout({ setUser, setGlobalLoading }); + window.location.href = '/auth/login'; + logout({ setUser, setGlobalLoading }); } } ] @@ -63,10 +63,10 @@ const AppTopbar = forwardRef((props, ref) => { label: 'Кирүү', icon: 'pi pi-sign-in', items: [], - // url: '/auth/login' - command: ()=> { - // router.push('/auth/login'); - window.location.href = '/auth/login'; + // url: '/auth/login' + command: () => { + // router.push('/auth/login'); + window.location.href = '/auth/login'; } }, { @@ -90,7 +90,6 @@ const AppTopbar = forwardRef((props, ref) => { {user?.email}
    ) - }, { label: 'Чыгуу', @@ -103,6 +102,10 @@ const AppTopbar = forwardRef((props, ref) => { } ]; + useEffect(() => { + console.log('dep', departament); + }, [departament]); + return (
    @@ -110,9 +113,13 @@ const AppTopbar = forwardRef((props, ref) => { logo

    Цифровой кампус ОшГУ

    - (в разработке) + (в разработке) - {pathName !== '/' && pathName !== '/course' ? ( + {pathName !== '/' && departament.name.length > 0 ? ( + + ) : pathName !== '/course' ? ( @@ -124,9 +131,9 @@ const AppTopbar = forwardRef((props, ref) => {
    {media ? ( <> - // ) : ( -
    + // +
    {/* ((props, ref) => {
    - (в разработке) + (в разработке)
    ); }); diff --git a/layout/context/layoutcontext.tsx b/layout/context/layoutcontext.tsx index 1db690c9..81e3593f 100644 --- a/layout/context/layoutcontext.tsx +++ b/layout/context/layoutcontext.tsx @@ -132,6 +132,9 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { setContextStudentThemes(data); }; + // departament + const [departament, setDepartament] = useState<{last_name:string, name:string, father_name:string, info:string}>({last_name:'', name:'', father_name:'', info: ''}); + const value: LayoutContextProps = { layoutConfig, setLayoutConfig, @@ -165,7 +168,10 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { crumbUrls, contextAddCrumb, mainCourseId, - setMainCourseId + setMainCourseId, + + departament, + setDepartament, }; return ( diff --git a/services/courses.tsx b/services/courses.tsx index 89e0473a..07ff583c 100644 --- a/services/courses.tsx +++ b/services/courses.tsx @@ -24,7 +24,7 @@ export const addCourse = async (value: CourseCreateType) => { formData.append('image', value.image); } formData.append('video_url', value.video_url); - + try { const res = await axiosInstance.post(`/v1/teacher/courses/store`, formData, { headers: { @@ -39,12 +39,12 @@ export const addCourse = async (value: CourseCreateType) => { } }; -export const deleteCourse = async (id: number) => { +export const deleteCourse = async (id: number) => { url = process.env.NEXT_PUBLIC_BASE_URL + `/v1/teacher/courses/delete?course_id=${id}`; try { const res = await axiosInstance.delete(`/v1/teacher/courses/delete?course_id=${id}`, { - headers: {'Content-Type': 'multipart/form-data'} + headers: { 'Content-Type': 'multipart/form-data' } }); console.log(res.data); @@ -98,7 +98,7 @@ export const fetchCourseInfo = async (id: number | null) => { export const addThemes = async (id: number, title: string, sequence_number: number | null) => { console.log(sequence_number); - + const formData = new FormData(); formData.append('title', title); formData.append('sequence_number', String(sequence_number)); @@ -119,8 +119,8 @@ export const addThemes = async (id: number, title: string, sequence_number: numb }; export const fetchThemes = async (id: number | null) => { - console.log('Вызываемая тема ',id); - + console.log('Вызываемая тема ', id); + try { const res = await axiosInstance(`/v1/teacher/lessons?course_id=${id}`); @@ -134,7 +134,7 @@ export const fetchThemes = async (id: number | null) => { export const updateTheme = async (course_id: number | null, theme_id: number | null, title: string, sequence_number: number | null) => { console.log(course_id, title, sequence_number); - + const formData = new FormData(); formData.append('title', title); formData.append('sequence_number', String(sequence_number)); @@ -168,16 +168,9 @@ export const deleteTheme = async (id: number) => { // Lessons -export const addLesson = async ( - type: string, - token: string | null, - courseId: number | null, - lessonId: number | null, - value: lessonStateType | string, - videoType: number | null) => { - +export const addLesson = async (type: string, token: string | null, courseId: number | null, lessonId: number | null, value: lessonStateType | string, videoType: number | null) => { let formData = new FormData(); - console.log(value, value, type, courseId, lessonId, videoType, ); + console.log(value, value, type, courseId, lessonId, videoType); let headers: Record = token ? { Authorization: `Bearer ${token}` } : {}; let url = ''; let body: lessonStateType | string | FormData = value; @@ -186,30 +179,30 @@ export const addLesson = async ( url = `/v1/teacher/textcontent/store?course_id=${courseId}&lesson_id=${lessonId}&content=${value}`; headers['Content-Type'] = 'application/json'; body = value; - } else if(type === 'doc' && (typeof value === 'object') && value !== null){ + } else if (type === 'doc' && typeof value === 'object' && value !== null) { url = `/v1/teacher/document/store?lesson_id=${lessonId}`; formData.append('lesson_id', String(lessonId)); - if(value.file){ + if (value.file) { formData.append('document', value.file && value.file); } - formData.append('title', String(value.title)); - formData.append('description', String(value?.description)); + formData.append('title', String(value.title)); + formData.append('description', String(value?.description)); body = formData; - } else if(type === 'url' && typeof value === 'object' && value !== null){ + } else if (type === 'url' && typeof value === 'object' && value !== null) { url = `v1/teacher/usefullinks/store?lesson_id=${lessonId}&title=${value.title}&description=${value.description}&url=${value.url}`; formData.append('lesson_id', String(lessonId)); - formData.append('document', String(value?.url)); - formData.append('title', String(value.title)); - formData.append('description', String(value?.description)); + formData.append('document', String(value?.url)); + formData.append('title', String(value.title)); + formData.append('description', String(value?.description)); body = formData; - } else if(type === 'video' && typeof value === 'object' && value !== null){ + } else if (type === 'video' && typeof value === 'object' && value !== null) { headers['Content-Type'] = 'multipart/form-data'; url = `v1/teacher/video/store?lesson_id=${lessonId}&title=${value.title}&description=${value.description}&video_link=${value.video_link}&video_type_id=${videoType}`; formData.append('lesson_id', String(lessonId)); formData.append('video_link', String(value?.video_link)); - formData.append('title', String(value?.title)); + formData.append('title', String(value?.title)); formData.append('description', String(value?.description)); - if(value.cover){ + if (value.cover) { formData.append('cover', value?.cover && value?.cover); } body = formData; @@ -233,12 +226,12 @@ export const fetchLesson = async (type: string, courseId: number | null, lessonI if (type === 'text') { url = `/v1/teacher/textcontent?course_id=${courseId}&lesson_id=${lessonId}`; } else if (type === 'doc') { - url = `/v1/teacher/document?lesson_id=${lessonId}` + url = `/v1/teacher/document?lesson_id=${lessonId}`; } else if (type === 'url') { - url = `/v1/teacher/usefullinks?lesson_id=${lessonId}` + url = `/v1/teacher/usefullinks?lesson_id=${lessonId}`; } else if (type === 'video') { - url = `/v1/teacher/video?lesson_id=${lessonId}` - } + url = `/v1/teacher/video?lesson_id=${lessonId}`; + } try { const res = await axiosInstance.get(url); @@ -250,7 +243,7 @@ export const fetchLesson = async (type: string, courseId: number | null, lessonI } }; -export const fetchLessonShow = async (lessonId: number | null) => { +export const fetchLessonShow = async (lessonId: number | null) => { try { const res = await axiosInstance.get(`v1/teacher/lessons/show?lesson_id=${lessonId}`); console.log('Info lesson: ', res.data); @@ -261,23 +254,23 @@ export const fetchLessonShow = async (lessonId: number | null) => { } }; -export const deleteLesson = async (type:string, courseId: number | null, lesson_id: number | null, content_id: number | null) => { +export const deleteLesson = async (type: string, courseId: number | null, lesson_id: number | null, content_id: number | null) => { let url = ''; console.log('content id: ', content_id); - + if (type === 'text') { url = `/v1/teacher/textcontent/delete?course_id=${courseId}&lesson_id=${lesson_id}&content_id=${content_id}`; - } else if(type === 'doc'){ + } else if (type === 'doc') { url = `/v1/teacher/document/delete?lesson_id=${lesson_id}&document_id=${content_id}`; - } else if(type === 'url'){ + } else if (type === 'url') { url = `v1/teacher/usefullinks/delete?lesson_id=${lesson_id}&link_id=${content_id}`; - } else if(type === 'video'){ + } else if (type === 'video') { url = `/v1/teacher/video/delete?video_id=${content_id}`; - } - + } + try { const res = await axiosInstance.delete(url); - + const data = await res.data; return data; } catch (err) { @@ -287,7 +280,7 @@ export const deleteLesson = async (type:string, courseId: number | null, lesson_ }; export const updateLesson = async (type: string, token: string | null, course_id: number | null, lesson_id: number | null, contentId: number | null, value: any) => { - console.log(type ,contentId, value); + console.log(type, contentId, value); let headers: Record = token ? { Authorization: `Bearer ${token}` } : {}; let formData = new FormData(); let url = ''; @@ -296,31 +289,31 @@ export const updateLesson = async (type: string, token: string | null, course_id if (type === 'text') { url = `/v1/teacher/textcontent/update?course_id=${course_id}&lesson_id=${lesson_id}&content_id=${contentId}&content=${value}`; headers['Content-Type'] = 'application/json'; - } else if(type === 'doc'){ + } else if (type === 'doc') { url = `/v1/teacher/document/update?lesson_id=${lesson_id}&document_id=${contentId}&document=${value.file}`; formData.append('lesson_id', String(lesson_id)); formData.append('document', value.file); formData.append('document_id', String(contentId)); - formData.append('title', String(value.title)); - formData.append('description', String(value.description)); + formData.append('title', String(value.title)); + formData.append('description', String(value.description)); body = formData; - } else if(type === 'url'){ + } else if (type === 'url') { url = `/v1/teacher/usefullinks/update?lesson_id=${lesson_id}&title=${value.title}&description=${value.description}&url=${value.url}&link_id=${contentId}`; formData.append('lesson_id', String(lesson_id)); formData.append('url', value.url); formData.append('link_id', String(contentId)); - formData.append('title', String(value.title)); - formData.append('description', String(value.description)); + formData.append('title', String(value.title)); + formData.append('description', String(value.description)); body = formData; - } else if(type === 'video'){ + } else if (type === 'video') { url = `/v1/teacher/video/update?lesson_id=${lesson_id}&title=${value.title}&description=${value.description}&video_link=${value.video_link}&video_type_id=${value.video_type_id}&video_id=${contentId}`; - + formData.append('lesson_id', String(lesson_id)); formData.append('video_link', value.video_link); formData.append('video_type_id', String(value.video_type_id)); formData.append('video_id', String(contentId)); - formData.append('title', String(value.title)); - formData.append('description', String(value.description)); + formData.append('title', String(value.title)); + formData.append('description', String(value.description)); body = formData; } @@ -349,16 +342,20 @@ export const fetchVideoType = async () => { } }; -export const publishCourse = async (id: number) => { - const formData = new FormData(); - formData.append('course_id', String(id)); +export const publishCourse = async (id_kafedra: number, id_teacher: number, course_id: number, status: boolean) => { + const data = { + id_kafedra: id_kafedra, + status: [ + { + id_teacher: id_teacher, + course_id: course_id, + status: status + } + ] + }; try { - const res = await axiosInstance.post(`/v1/teacher/courses/published`, formData, { - headers: { - 'Content-Type': 'multipart/form-data' - } - }); + const res = await axiosInstance.post(`/v1/teacher/controls/department/course/status`, data); return res.data; } catch (err) { diff --git a/services/faculty.tsx b/services/faculty.tsx new file mode 100644 index 00000000..378823df --- /dev/null +++ b/services/faculty.tsx @@ -0,0 +1,41 @@ +import axiosInstance from '@/utils/axiosInstance'; + +export const fetchFaculty = async () => { + try { + const res = await axiosInstance.get(`/open/faculty`, { + baseURL: process.env.NEXT_PUBLIC_FACULTY_API + }); + const data = await res.data; + + return data; + } catch (err) { + console.log('Ошибка загрузки:', err); + return err; + } +}; + +export const fetchKafedra = async (id_faculty: number | null) => { + try { + const res = await axiosInstance.get(`/open/kafedra?id_faculty=${id_faculty}`, { + baseURL: process.env.NEXT_PUBLIC_FACULTY_API + }); + const data = await res.data; + + return data; + } catch (err) { + console.log('Ошибка загрузки:', err); + return err; + } +}; + +export const fetchDepartament = async (id_kafedra: number | null) => { + try { + const res = await axiosInstance.get(`/v1/teacher/controls/department?id_kafedra=${id_kafedra}`); + const data = await res.data; + + return data; + } catch (err) { + console.log('Ошибка загрузки:', err); + return err; + } +}; \ No newline at end of file diff --git a/styles/layout/forms.css b/styles/layout/forms.css index 4d93cefb..5afda0c7 100644 --- a/styles/layout/forms.css +++ b/styles/layout/forms.css @@ -85,12 +85,18 @@ /* width: 18px; height: 18px; */ border: 2px solid var(--mainColor); - /* border-radius: 50%; */ + border-radius: 2px; + padding: 2px; display: inline-block; position: relative; transition: all 0.3s ease; } +.radio-course-mark:hover { + background-color: var(--mainColor); + color: white; +} + /* Внутренняя точка при выборе */ .radio-course-mark::after { content: ""; diff --git a/types/layout.d.ts b/types/layout.d.ts index 33c989b6..6d05e676 100644 --- a/types/layout.d.ts +++ b/types/layout.d.ts @@ -76,8 +76,11 @@ export interface LayoutContextProps { crumbUrls: {type: string; crumbUrl: string }; contextAddCrumb: (id)=> void - mainCourseId: number | null, - setMainCourseId + mainCourseId: number | null; + setMainCourseId; + + departament: {last_name:string, name:string, father_name:string, info: string}, + setDepartament, // message: { state: boolean; value: MessageType }; // setMessage: React.Dispatch>; } diff --git a/utils/axiosInstance.tsx b/utils/axiosInstance.tsx index 0441957b..ea7be095 100644 --- a/utils/axiosInstance.tsx +++ b/utils/axiosInstance.tsx @@ -22,27 +22,27 @@ axiosInstance.interceptors.response.use( const status = error.response?.status; if (status === 401) { console.warn('Неавторизован. Удаляю токен...'); - // if (typeof window !== 'undefined') { - // window.location.href = '/auth/login'; - // localStorage.removeItem('userVisit'); - // } - // document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; + if (typeof window !== 'undefined') { + window.location.href = '/auth/login'; + localStorage.removeItem('userVisit'); + } + document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; } if (status === 403) { console.warn('Не имеет доступ. Перенаправляю...'); - // if (typeof window !== 'undefined') { - // window.location.href = '/'; - // } + if (typeof window !== 'undefined') { + window.location.href = '/'; + } } if (status === 404) { console.warn('404 - Перенаправляю...'); - // if (typeof window !== 'undefined') { - // window.location.href = '/pages/notfound'; - // localStorage.removeItem('userVisit'); - // } - // document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; + if (typeof window !== 'undefined') { + window.location.href = '/pages/notfound'; + localStorage.removeItem('userVisit'); + } + document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; } return Promise.reject(error); From 701e3a3ef5ffb097140c3014ae91770941fd9a4c Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Tue, 16 Sep 2025 09:28:03 +0600 Subject: [PATCH 168/183] =?UTF-8?q?=D0=9A=D0=B0=D0=B6=D0=B4=D1=8B=D0=B9=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=B7=D1=8B=D0=B2=D0=B0=D0=B5=D1=82=20=D0=B7=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=83=D0=B7=D0=BA=D1=83=20=D1=81=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../course/[course_Id]/[lesson_id]/page.tsx | 158 +++++++++++------- app/(main)/course/page.tsx | 35 +++- .../faculty/[id_kafedra]/[course_id]/page.tsx | 19 +++ app/(main)/faculty/[id_kafedra]/page.tsx | 8 +- app/(main)/faculty/page.tsx | 6 +- app/components/HomeClient.tsx | 18 +- app/components/SessionManager.tsx | 9 +- layout/AppMenu.tsx | 9 +- layout/AppTopbar.tsx | 20 ++- layout/context/layoutcontext.tsx | 4 +- services/courses.tsx | 6 +- services/query-tests.http | 32 ++-- types/layout.d.ts | 2 +- utils/axiosInstance.tsx | 12 +- 14 files changed, 211 insertions(+), 127 deletions(-) create mode 100644 app/(main)/faculty/[id_kafedra]/[course_id]/page.tsx diff --git a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx index 219259fa..4b262c59 100644 --- a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx +++ b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx @@ -6,32 +6,26 @@ import LessonPractica from '@/app/components/lessons/LessonPractica'; import LessonTest from '@/app/components/lessons/LessonTest'; import LessonVideo from '@/app/components/lessons/LessonVideo'; import { NotFound } from '@/app/components/NotFound'; -import PDFViewer from '@/app/components/PDFBook'; -import FormModal from '@/app/components/popUp/FormModal'; import GroupSkeleton from '@/app/components/skeleton/GroupSkeleton'; -import useBreadCrumbs from '@/hooks/useBreadCrumbs'; import useErrorMessage from '@/hooks/useErrorMessage'; import { useMediaQuery } from '@/hooks/useMediaQuery'; import { LayoutContext } from '@/layout/context/layoutcontext'; -import { lessonSchema } from '@/schemas/lessonSchema'; import { deleteLesson, fetchLessonShow } from '@/services/courses'; import { addLesson, deleteStep, fetchElement, fetchSteps, fetchTypes } from '@/services/steps'; import { lessonStateType } from '@/types/lessonStateType'; import { mainStepsType } from '@/types/mainStepType'; import { getConfirmOptions } from '@/utils/getConfirmOptions'; -import { yupResolver } from '@hookform/resolvers/yup'; import { useParams, usePathname, useRouter } from 'next/navigation'; -import { BreadCrumb } from 'primereact/breadcrumb'; import { Button } from 'primereact/button'; import { confirmDialog } from 'primereact/confirmdialog'; import { Dialog } from 'primereact/dialog'; import { InputText } from 'primereact/inputtext'; import React, { useContext, useEffect, useRef, useState } from 'react'; -import { useForm } from 'react-hook-form'; export default function LessonStep() { const param = useParams(); const course_id = param.course_Id; + const scrollRef = useRef(null); const [lessonInfoState, setLessonInfoState] = useState<{ title: string; documents_count: string; usefullinks_count: string; videos_count: string } | null>(null); const media = useMediaQuery('(max-width: 640px)'); @@ -176,31 +170,6 @@ export default function LessonStep() { } }; - const lessonInfo = ( -
    -
    -

    {lessonInfoState?.title}

    -
    -
    - ); - - const step = (icon: string, step: number, idx: number) => { - return ( -
    { - setSelectId(step); - handleFetchElement(step); - }} - > - {idx + 1} -
    - -
    -
    - ); - }; - useEffect(() => { if (Array.isArray(steps) && steps.length > 0) { const firstStep = steps[0]?.id; @@ -220,46 +189,70 @@ export default function LessonStep() { useEffect(() => { setTestovy(true); - contextFetchThemes(Number(course_id)); + contextFetchThemes(Number(course_id), null); }, [course_id]); + // useEffect(() => { + // console.log('Тема ', contextThemes, lesson_id); + // if (testovy || updateQuery || deleteQuery) { + // setTestovy(false); + // setUpdateeQuery(false); + // if (contextThemes?.lessons?.data?.length > 0) { + // setThemeNull(false); + // if (param.lesson_id == 'null' || deleteQuery) { + // console.log(contextThemes.lessons.data[0].id); + + // handleShow(contextThemes.lessons.data[0].id); + // console.log('variant 4', contextThemes.lessons.data[0].id); + // handleFetchSteps(contextThemes.lessons.data[0].id); + // setLesson_id(contextThemes.lessons.data[0].id); + // setDeleteQuery(false); + // } else { + // console.log('variant 5'); + // handleShow(Number(param.lesson_id)); + // setLesson_id((param.lesson_id && Number(param.lesson_id)) || null); + // handleFetchSteps(Number(param.lesson_id)); + // } + // } else { + // setThemeNull(true); + // console.log('variant 3'); + // } + // } + // }, [contextThemes]); + useEffect(() => { - console.log('Тема ', contextThemes, lesson_id); - if (testovy || updateQuery || deleteQuery) { - setTestovy(false); - setUpdateeQuery(false); - if (contextThemes?.lessons?.data?.length > 0) { - setThemeNull(false); - if (param.lesson_id == 'null' || deleteQuery) { - console.log(contextThemes.lessons.data[0].id); - - handleShow(contextThemes.lessons.data[0].id); - console.log('variant 4', contextThemes.lessons.data[0].id); - handleFetchSteps(contextThemes.lessons.data[0].id); - setLesson_id(contextThemes.lessons.data[0].id); - setDeleteQuery(false); - } else { - console.log('variant 5'); - handleShow(Number(param.lesson_id)); - setLesson_id((param.lesson_id && Number(param.lesson_id)) || null); - handleFetchSteps(Number(param.lesson_id)); - } + if (!contextThemes?.lessons?.data) return; + + const lessons = contextThemes.lessons.data; + + if (lessons.length > 0) { + setThemeNull(false); + + let chosenId: number | null = null; + + if (param.lesson_id === 'null' || deleteQuery) { + chosenId = lessons[0].id; + setDeleteQuery(false); } else { - setThemeNull(true); - console.log('variant 3'); + chosenId = param.lesson_id ? Number(param.lesson_id) : lessons[0].id; } + + setLesson_id(chosenId); + } else { + setThemeNull(true); } - }, [contextThemes]); - if (themeNull) { - return ( -
    - -
    - ); - } + setTestovy(false); + setUpdateeQuery(false); + }, [contextThemes, deleteQuery, param.lesson_id]); - const scrollRef = useRef(null); + useEffect(() => { + if (!lesson_id) return; + + handleShow(lesson_id); + handleFetchSteps(lesson_id); + changeUrl(lesson_id); + }, [lesson_id]); useEffect(() => { const el = scrollRef.current; @@ -276,6 +269,39 @@ export default function LessonStep() { return () => el.removeEventListener('wheel', onWheel); }, []); + const lessonInfo = ( +
    +
    +

    {lessonInfoState?.title}

    +
    +
    + ); + + const step = (icon: string, step: number, idx: number) => { + return ( +
    { + setSelectId(step); + handleFetchElement(step); + }} + > + {idx + 1} +
    + +
    +
    + ); + }; + + if (themeNull) { + return ( +
    + +
    + ); + } + return (
    {/* modal sectoin */} @@ -333,7 +359,9 @@ export default function LessonStep() { ) : (
    {skeleton ? ( -
    +
    + +
    ) : ( steps.map((item, idx) => { return ( diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index fce56061..5dbef64f 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -36,7 +36,7 @@ import { SelectButton, SelectButtonChangeEvent } from 'primereact/selectbutton'; import { mainStreamsType } from '@/types/mainStreamsType'; export default function Course() { - const { setMessage, course, setCourses, contextFetchCourse, setMainCourseId } = useContext(LayoutContext); + const { setMessage, setGlobalLoading, course, setCourses, contextFetchCourse, setMainCourseId } = useContext(LayoutContext); const [coursesValue, setValueCourses] = useState([]); const [hasCourses, setHasCourses] = useState(false); const [courseValue, setCourseValue] = useState({ title: '', description: '', video_url: '', image: '' }); @@ -251,6 +251,10 @@ export default function Course() { useEffect(() => { contextFetchCourse(1); + setGlobalLoading(true); + setTimeout(() => { + setGlobalLoading(false); + }, 900); }, []); useEffect(() => { @@ -320,7 +324,16 @@ export default function Course() { {/* Заголовок */}
    - setMainCourseId(shablonData.id)}> + { + setMainCourseId(shablonData.id); + setGlobalLoading(true); + setTimeout(() => { + setGlobalLoading(false); + }, 900); + }} + > {shablonData.title} {/* Используем subject_name из вашего шаблона */}
    @@ -345,7 +358,9 @@ export default function Course() { }} checked={forStreamId?.id === shablonData.id} /> - setActiveIndex(1)}>Связать к потоку + setActiveIndex(1)}> + Связать к потоку + @@ -620,7 +635,17 @@ export default function Course() { header="Название" // style={{ width: '80%' }} body={(rowData) => ( - setMainCourseId(rowData.id)} key={rowData.id}> + { + setGlobalLoading(true); + setTimeout(() => { + setGlobalLoading(false); + }, 1200); + setMainCourseId(rowData.id); + }} + key={rowData.id} + > {rowData.title} )} @@ -629,7 +654,7 @@ export default function Course() { header="Публикация" style={{ margin: '0 3px', textAlign: 'center' }} body={(rowData) => - rowData.is_published ? : + rowData.is_published ? : } > { + contextFetchThemes(Number(course_id), id_kafedra ? Number(id_kafedra) : null); + },[]); + + return
    page
    ; +} diff --git a/app/(main)/faculty/[id_kafedra]/page.tsx b/app/(main)/faculty/[id_kafedra]/page.tsx index 24a9c5d2..dc34b0ea 100644 --- a/app/(main)/faculty/[id_kafedra]/page.tsx +++ b/app/(main)/faculty/[id_kafedra]/page.tsx @@ -38,7 +38,7 @@ export default function Kafedra() { const [contentShow, setContentShow] = useState(false); const [progressSpinner, setProgressSpinner] = useState(false); - const { setMessage } = useContext(LayoutContext); + const { setMessage, setGlobalLoading } = useContext(LayoutContext); const showError = useErrorMessage(); const handleFetchKafedra = async () => { @@ -109,6 +109,10 @@ export default function Kafedra() { }; useEffect(() => { + setGlobalLoading(true); + setTimeout(() => { + setGlobalLoading(false); + }, 900); handleFetchKafedra(); }, []); @@ -136,7 +140,7 @@ export default function Kafedra() { rowIndex + 1} header="#" style={{ width: '20px' }}> -

    {rowData.title}

    }>
    + {rowData.title}}> ( diff --git a/app/(main)/faculty/page.tsx b/app/(main)/faculty/page.tsx index 64486ff3..b44bab4f 100644 --- a/app/(main)/faculty/page.tsx +++ b/app/(main)/faculty/page.tsx @@ -18,7 +18,7 @@ export default function Faculty() { } const showError = useErrorMessage(); - const { setMessage } = useContext(LayoutContext); + const { setMessage, setGlobalLoading } = useContext(LayoutContext); const [selected, setSelected] = useState(null); const [faculty, setFaculty] = useState([{ name_ru: '', id: null }]); @@ -68,6 +68,10 @@ export default function Faculty() { useEffect(() => { handleFetchFaculty(); + setGlobalLoading(true); + setTimeout(() => { + setGlobalLoading(false); + }, 900); }, []); useEffect(() => { diff --git a/app/components/HomeClient.tsx b/app/components/HomeClient.tsx index 09bfb4a2..33c1d70f 100644 --- a/app/components/HomeClient.tsx +++ b/app/components/HomeClient.tsx @@ -10,15 +10,19 @@ import FancyLinkBtn from './buttons/FancyLinkBtn'; import { LayoutContext } from '@/layout/context/layoutcontext'; export default function HomeClient() { - const { user } = useContext(LayoutContext); + const { user, setGlobalLoading } = useContext(LayoutContext); useEffect(() => { + setGlobalLoading(true); + setTimeout(() => { + setGlobalLoading(false); + }, 800); AOS.init(); }, []); return (
    - {/* {"Уроки"} */} + {/* {"Уроки"} */}
    @@ -39,11 +43,11 @@ export default function HomeClient() {
  • ачык онлайн курстар
  • жогорку билим берүү программалары
  • - {user && - - - - } + {user && ( + + + + )}
    diff --git a/app/components/SessionManager.tsx b/app/components/SessionManager.tsx index 0d8bf0fe..bb89b461 100644 --- a/app/components/SessionManager.tsx +++ b/app/components/SessionManager.tsx @@ -66,7 +66,7 @@ const SessionManager = () => { useEffect(() => { if (!pathname.startsWith('/teaching/lesson/') && !pathname.startsWith('/course/')) { - setGlobalLoading(true); + // setGlobalLoading(true); } const token = getToken('access_token'); @@ -76,12 +76,11 @@ const SessionManager = () => { logout({ setUser, setGlobalLoading }); window.location.href = '/auth/login'; - setGlobalLoading(false); return; } - setTimeout(() => { - setGlobalLoading(false); - }, 900); + // setTimeout(() => { + // setGlobalLoading(false); + // }, 900); }, [pathname]); return null; diff --git a/layout/AppMenu.tsx b/layout/AppMenu.tsx index f5f4ef92..a5cb77b2 100644 --- a/layout/AppMenu.tsx +++ b/layout/AppMenu.tsx @@ -43,7 +43,10 @@ const AppMenu = () => { const pathname = location; const { studentThemeCourse } = useParams(); const params = useParams(); + console.log(params); + const course_Id = params.course_Id; + const id_kafedra = params?.id_kafedra ? params.id_kafedra : null; const [courseList, setCourseList] = useState([]); const [selectId, setSelectId] = useState(null); @@ -147,7 +150,7 @@ const AppMenu = () => { console.log(data); if (data?.success) { - contextFetchThemes(Number(course_Id)); + contextFetchThemes(Number(course_Id), id_kafedra ? Number(id_kafedra) : null); clearValues(); setMessage({ state: true, @@ -168,7 +171,7 @@ const AppMenu = () => { const handleDeleteTheme = async (id: number) => { const data = await deleteTheme(id); if (data.success) { - contextFetchThemes(Number(course_Id)); + contextFetchThemes(Number(course_Id), id_kafedra ? Number(id_kafedra) : null); setDeleteQuery(true); setMessage({ state: true, @@ -190,7 +193,7 @@ const AppMenu = () => { const data = await updateTheme(Number(course_Id), selectId, editingLesson?.title ? editingLesson?.title : '', editingLesson?.sequence_number ? editingLesson?.sequence_number : null); if (data?.success) { setUpdateeQuery(true); - contextFetchThemes(Number(course_Id)); + contextFetchThemes(Number(course_Id), id_kafedra ? Number(id_kafedra) : null); clearValues(); setMessage({ state: true, diff --git a/layout/AppTopbar.tsx b/layout/AppTopbar.tsx index c5e997e4..108705c2 100644 --- a/layout/AppTopbar.tsx +++ b/layout/AppTopbar.tsx @@ -115,14 +115,18 @@ const AppTopbar = forwardRef((props, ref) => { (в разработке) - {pathName !== '/' && departament.name.length > 0 ? ( - - ) : pathName !== '/course' ? ( - + {pathName !== '/' ? ( + departament.name.length > 0 ? ( + + ) : pathName !== '/course' ? ( + + ) : ( + '' + ) ) : ( '' )} diff --git a/layout/context/layoutcontext.tsx b/layout/context/layoutcontext.tsx index 81e3593f..8f18b18d 100644 --- a/layout/context/layoutcontext.tsx +++ b/layout/context/layoutcontext.tsx @@ -116,8 +116,8 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { const [deleteQuery, setDeleteQuery] = useState(false); const [updateQuery, setUpdateeQuery] = useState(false); const [contextThemes, setContextThemes] = useState([]); - const contextFetchThemes = async (id: number | null) => { - const data = await fetchThemes(Number(id) || null); + const contextFetchThemes = async (id: number | null, id_kafedra: number | null) => { + const data = await fetchThemes(Number(id) || null, id_kafedra); if(data){ console.log(data); setContextThemes(data); diff --git a/services/courses.tsx b/services/courses.tsx index 07ff583c..77dcfe47 100644 --- a/services/courses.tsx +++ b/services/courses.tsx @@ -118,11 +118,11 @@ export const addThemes = async (id: number, title: string, sequence_number: numb } }; -export const fetchThemes = async (id: number | null) => { - console.log('Вызываемая тема ', id); +export const fetchThemes = async (id: number | null, id_kafedra: number | null) => { + console.log('Вызываемая тема ', id, id_kafedra); try { - const res = await axiosInstance(`/v1/teacher/lessons?course_id=${id}`); + const res = await axiosInstance.get(`/v1/teacher/lessons?course_id=${id}&id_kafedra=${id_kafedra ? id_kafedra : ''}`); const data = await res.data; return data; diff --git a/services/query-tests.http b/services/query-tests.http index afde90c2..003d1a60 100644 --- a/services/query-tests.http +++ b/services/query-tests.http @@ -1,5 +1,4 @@ -@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5teWVkdS5vc2hzdS5rZy9wdWJsaWMvYXBpL21vb2MvbG9naW4iLCJpYXQiOjE3NTY5MDE3MTksImV4cCI6MTc1NjkzMDU3OSwibmJmIjoxNzU2OTAxNzE5LCJqdGkiOiJDMlRGa3hFT3RITERWWmVsIiwic3ViIjoiNDY0NTYiLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3In0.DPG-hM-6ZB1Bed7J0XmYBCT216l45PMnKUzx6Zlv4MQ - +@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5teWVkdS5vc2hzdS5rZy9wdWJsaWMvYXBpL21vb2MvbG9naW4iLCJpYXQiOjE3NTc5ODE1MzQsImV4cCI6MTc1ODAxMDM5NCwibmJmIjoxNzU3OTgxNTM0LCJqdGkiOiJRY1FTNGdSSTZFRzRCc1NBIiwic3ViIjoiMTE4MjkyIiwicHJ2IjoiMjNiZDVjODk0OWY2MDBhZGIzOWU3MDFjNDAwODcyZGI3YTU5NzZmNyJ9.mhD-cf2IbMcImhwRIFkGkXZrlmRpM-f4GnpBnhNu8J8 # ### # http://api.mooc.oshsu.kg/public/api/v1/open/video/types @@ -9,27 +8,22 @@ # ### # # STREAMS GET -# GET http://api.mooc.oshsu.kg/public/api/v1/teacher/stream?course_id=77 -# Authorization: Bearer {{token}} -# Content-Type: application/json +GET https://api.mooc.oshsu.kg/public/api/v1/teacher/lessons?course_id=32 +Authorization: Bearer {{token}} +Content-Type: application/json # ### # test -POST https://api.mooc.oshsu.kg/public/api/v1/login?email=ajaparkulov@oshsu.kg&password=010270Ja -Content-Type: application/json +# POST https://api.mooc.oshsu.kg/public/api/v1/login?email=ajaparkulov@oshsu.kg&password=010270Ja +# Content-Type: application/json -{ - "email": "ajaparkulov@oshsu.kg", - "password": "010270Ja" -} +# { +# "email": "ajaparkulov@oshsu.kg", +# "password": "010270Ja" +# } # test -POST https://api.mooc.oshsu.kg/public/api/v1/teacher/lessons/step?lesson_id=77&type_id=3 -Authorization: Bearer {{token}} -Content-Type: application/json - -{ - "lesson_id": 77, - "type_id":3 -} \ No newline at end of file +# GET https://api.mooc.oshsu.kg/public/api/v1/teacher/lessons?course_id=32 +# Authorization: Bearer {{token}} +# Content-Type: application/json diff --git a/types/layout.d.ts b/types/layout.d.ts index 6d05e676..51d6b8f7 100644 --- a/types/layout.d.ts +++ b/types/layout.d.ts @@ -61,7 +61,7 @@ export interface LayoutContextProps { data: myMainCourseType[] }; setCourses; - contextFetchThemes: (id: number)=> void; + contextFetchThemes: (id: number, id_kafedra:number | null)=> void; contextThemes; setContextThemes; deleteQuery: boolean; diff --git a/utils/axiosInstance.tsx b/utils/axiosInstance.tsx index ea7be095..f5267ed5 100644 --- a/utils/axiosInstance.tsx +++ b/utils/axiosInstance.tsx @@ -38,15 +38,15 @@ axiosInstance.interceptors.response.use( if (status === 404) { console.warn('404 - Перенаправляю...'); - if (typeof window !== 'undefined') { - window.location.href = '/pages/notfound'; - localStorage.removeItem('userVisit'); - } - document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; + // if (typeof window !== 'undefined') { + // window.location.href = '/pages/notfound'; + // localStorage.removeItem('userVisit'); + // } + // document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; } return Promise.reject(error); } ); -export default axiosInstance; +export default axiosInstance; \ No newline at end of file From 26807891bdf67f3eb2b09ed982cbd1ab38c82243 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Tue, 16 Sep 2025 17:32:12 +0600 Subject: [PATCH 169/183] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=BA=D0=B0=20=D1=83=D1=80=D0=BE=D0=BA=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../faculty/[id_kafedra]/[course_id]/page.tsx | 213 +++++++++++++++++- app/(main)/faculty/page.tsx | 57 +++-- app/(main)/videoLesson/[video_url]/page.tsx | 10 + app/components/SessionManager.tsx | 4 +- app/components/lessons/LessonInfoCard.tsx | 211 +++++++++++++++++ app/globals.css | 1 + layout/AppMenu.tsx | 5 +- services/steps.tsx | 14 +- types/mainStepType.tsx | 22 +- types/themeType.tsx | 12 + utils/axiosInstance.tsx | 16 +- 11 files changed, 520 insertions(+), 45 deletions(-) create mode 100644 app/(main)/videoLesson/[video_url]/page.tsx create mode 100644 app/components/lessons/LessonInfoCard.tsx create mode 100644 types/themeType.tsx diff --git a/app/(main)/faculty/[id_kafedra]/[course_id]/page.tsx b/app/(main)/faculty/[id_kafedra]/[course_id]/page.tsx index c2b3d94e..c1930f29 100644 --- a/app/(main)/faculty/[id_kafedra]/[course_id]/page.tsx +++ b/app/(main)/faculty/[id_kafedra]/[course_id]/page.tsx @@ -1,19 +1,214 @@ 'use client'; -import { LayoutContext } from "@/layout/context/layoutcontext"; -import { useParams } from "next/navigation"; -import { useContext, useEffect } from "react"; +import LessonInfoCard from '@/app/components/lessons/LessonInfoCard'; +import { NotFound } from '@/app/components/NotFound'; +import useErrorMessage from '@/hooks/useErrorMessage'; +import { LayoutContext } from '@/layout/context/layoutcontext'; +import { fetchDepartamentSteps, fetchSteps } from '@/services/steps'; +import { mainStepsType } from '@/types/mainStepType'; +import { useParams } from 'next/navigation'; +import { Accordion, AccordionTab } from 'primereact/accordion'; +import { Dialog } from 'primereact/dialog'; +import { useContext, useEffect, useState } from 'react'; export default function LessonCheck() { - const { contextFetchThemes, contextThemes } = useContext(LayoutContext); + const { setMessage, contextFetchThemes, contextThemes } = useContext(LayoutContext); + const showError = useErrorMessage(); - const {id_kafedra, course_id} = useParams(); + const { id_kafedra, course_id } = useParams(); // const p = useParams(); // console.log(p); - useEffect(()=>{ + const [themes, setThemes] = useState([]); + const [themeShow, setThemeShow] = useState(false); + const [skeleton, setSkeleton] = useState(false); + const [hasSteps, setHasSteps] = useState(false); + const [steps, setSteps] = useState([]); + const [activeIndex, setActiveIndex] = useState(0); + const [videoCall, setVideoCall] = useState(false); + const [video_link, setVideoLink] = useState('https://www.youtube.com/watch?v=TdrL3QxjyVw&list=RDTdrL3QxjyVw&start_radio=1'); + + const handleFetchSteps = async (lesson_id: number) => { + setSkeleton(true); + const data = await fetchDepartamentSteps(Number(lesson_id), Number(id_kafedra)); + console.log('steps', data); + + if (data.success) { + setSkeleton(false); + if (data.steps.length < 1) { + setHasSteps(true); + } else { + setHasSteps(false); + setSteps(data.steps); + } + } else { + setSkeleton(false); + setHasSteps(false); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинчерээк кайталаныз' } + }); + if (data?.response?.status) { + showError(data.response.status); + } + } + }; + + const handleVideoCall = (value: string | null) => { + console.log(value); + + if (!value) { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка при обработке видео', detail: '' } + }); + } + + const url = new URL(typeof value === 'string' ? value : ''); + let videoId = null; + console.log(url); + + if (url.hostname === 'youtu.be') { + // короткая ссылка, видео ID — в пути + videoId = url.pathname.slice(1); // убираем первый слеш + } else if (url.hostname === 'www.youtube.com' || url.hostname === 'youtube.com') { + // стандартная ссылка, видео ID в параметре v + videoId = url.searchParams.get('v'); + } + console.log(videoId); + + if (!videoId) { + setMessage({ + state: true, + value: { severity: 'error', summary: 'Ошибка при обработке видео', detail: '' } + }); + return null; // не удалось получить ID + } + // return `https://www.youtube.com/embed/${videoId}`; + + console.log('value', videoId); + setVideoLink(`https://www.youtube.com/embed/${videoId}`); + setVideoCall(true); + }; + + // ПРОСИМ ТЕМЫ + useEffect(() => { contextFetchThemes(Number(course_id), id_kafedra ? Number(id_kafedra) : null); - },[]); - - return
    page
    ; + }, []); + + // САМИ ТЕМЫ, присваиваем в локальные темы + useEffect(() => { + console.log('Темы', contextThemes); + if (contextThemes.lessons?.data && contextThemes.lessons.data.length > 0) { + setThemes(contextThemes?.lessons.data || []); + setThemeShow(false); + } else { + setThemeShow(true); + } + }, [contextThemes]); + + // просто посмотреть пока + useEffect(() => { + console.log('Лоакльные темы ', themes); + + if (themes.length > 0 && [activeIndex as number]) { + console.log(activeIndex); + + if (activeIndex) { + const lessonId = themes[activeIndex as number]?.id; + console.log(lessonId); + + handleFetchSteps(lessonId); + } else { + const lessonId = themes[0]?.id; + console.log(lessonId); + + handleFetchSteps(lessonId); + } + } + }, [themes, activeIndex]); + + useEffect(() => { + console.log('Шаги', steps); + }, [steps]); + + return ( +
    + { + if (!videoCall) return; + setVideoCall(false); + }} + > +
    + +
    +
    + {themeShow ? ( + + ) : ( + setActiveIndex(e.index)}> + {themes.map((item) => { + console.log(item); + + return ( + +
    + {steps.length > 0 ? steps.map((i) => { + if(i.content){ + return ( + <> + { + + } + + ); + } + }): 'Данных нет' } +
    +
    + ); + })} +
    + // <> + // {/* + + // + + // + + // + // */} + // + )} +
    + ); } diff --git a/app/(main)/faculty/page.tsx b/app/(main)/faculty/page.tsx index b44bab4f..8fc99b3f 100644 --- a/app/(main)/faculty/page.tsx +++ b/app/(main)/faculty/page.tsx @@ -1,6 +1,7 @@ 'use client'; import { NotFound } from '@/app/components/NotFound'; +import GroupSkeleton from '@/app/components/skeleton/GroupSkeleton'; import useErrorMessage from '@/hooks/useErrorMessage'; import { LayoutContext } from '@/layout/context/layoutcontext'; import { fetchFaculty, fetchKafedra } from '@/services/faculty'; @@ -25,14 +26,17 @@ export default function Faculty() { const [kafedra, setKafedra] = useState([{ name_ru: '', id: null }]); const [selectShow, setSelectShow] = useState(false); const [facultyShow, setFacultyShow] = useState(false); + const [skeleton, setSkeleton] = useState(false); const handleFetchFaculty = async () => { + setSkeleton(true); const data = await fetchFaculty(); if (data && Array.isArray(data)) { const newFaculty = data.map((item) => { return { name_ru: item.name_ru, id: item.id }; }); setFaculty(newFaculty); + setSkeleton(false); if (newFaculty.length > 0) { setSelected(newFaculty[0]); @@ -41,6 +45,7 @@ export default function Faculty() { setSelectShow(true); } } else { + setSkeleton(false); setSelectShow(true); setMessage({ state: true, @@ -85,35 +90,43 @@ export default function Faculty() { return (
    ); } - + return (
    {/* modal sectoin */} diff --git a/app/(main)/faculty/[id_kafedra]/[course_id]/page.tsx b/app/(main)/faculty/[id_kafedra]/[course_id]/page.tsx index c1930f29..f18406bd 100644 --- a/app/(main)/faculty/[id_kafedra]/[course_id]/page.tsx +++ b/app/(main)/faculty/[id_kafedra]/[course_id]/page.tsx @@ -4,6 +4,7 @@ import LessonInfoCard from '@/app/components/lessons/LessonInfoCard'; import { NotFound } from '@/app/components/NotFound'; import useErrorMessage from '@/hooks/useErrorMessage'; import { LayoutContext } from '@/layout/context/layoutcontext'; +import { fetchCourseInfo } from '@/services/courses'; import { fetchDepartamentSteps, fetchSteps } from '@/services/steps'; import { mainStepsType } from '@/types/mainStepType'; import { useParams } from 'next/navigation'; @@ -26,13 +27,21 @@ export default function LessonCheck() { const [steps, setSteps] = useState([]); const [activeIndex, setActiveIndex] = useState(0); const [videoCall, setVideoCall] = useState(false); - const [video_link, setVideoLink] = useState('https://www.youtube.com/watch?v=TdrL3QxjyVw&list=RDTdrL3QxjyVw&start_radio=1'); + const [video_link, setVideoLink] = useState(''); + + const handleCourseInfo = async () => { + if (course_id) { + const data = await fetchCourseInfo(Number(course_id)); + console.log('info', data); + + if (data) { + } + } + }; const handleFetchSteps = async (lesson_id: number) => { setSkeleton(true); const data = await fetchDepartamentSteps(Number(lesson_id), Number(id_kafedra)); - console.log('steps', data); - if (data.success) { setSkeleton(false); if (data.steps.length < 1) { @@ -91,9 +100,10 @@ export default function LessonCheck() { setVideoCall(true); }; - // ПРОСИМ ТЕМЫ + // ПРОСИМ КУРС ДЛЯ НАЗВАНИЯ И ТЕМЫ useEffect(() => { contextFetchThemes(Number(course_id), id_kafedra ? Number(id_kafedra) : null); + // handleCourseInfo() }, []); // САМИ ТЕМЫ, присваиваем в локальные темы @@ -112,17 +122,10 @@ export default function LessonCheck() { console.log('Лоакльные темы ', themes); if (themes.length > 0 && [activeIndex as number]) { - console.log(activeIndex); - - if (activeIndex) { - const lessonId = themes[activeIndex as number]?.id; - console.log(lessonId); - - handleFetchSteps(lessonId); - } else { - const lessonId = themes[0]?.id; - console.log(lessonId); + console.log('active index ', activeIndex); + const lessonId = themes[activeIndex as number]?.id; + if (lessonId) { handleFetchSteps(lessonId); } } @@ -160,54 +163,45 @@ export default function LessonCheck() { ) : ( setActiveIndex(e.index)}> {themes.map((item) => { - console.log(item); + const content = steps.filter((j) => { + return j.content != null; + }); return ( - +
    - {steps.length > 0 ? steps.map((i) => { - if(i.content){ - return ( - <> - { - - } - - ); - } - }): 'Данных нет' } + {hasSteps ? ( +

    Данных нет

    + ) : content.length > 0 ? ( + content.map((i,idx) => { + if (i.content) { + return ( +
    + { + + } +
    + ); + } + }) + ) : ( +

    Данных нет

    + )}
    ); })}
    - // <> - // {/* - - // - - // - - // - // */} - // )}
    ); diff --git a/app/(main)/faculty/page.tsx b/app/(main)/faculty/page.tsx index 8fc99b3f..2d66b6c0 100644 --- a/app/(main)/faculty/page.tsx +++ b/app/(main)/faculty/page.tsx @@ -63,8 +63,12 @@ export default function Faculty() { console.log(data); if (data && Array.isArray(data)) { - setKafedra(data); - setFacultyShow(false); + if(data.length > 0){ + setKafedra(data); + setFacultyShow(false); + } else { + setFacultyShow(true); + } } else { setFacultyShow(true); } @@ -106,7 +110,7 @@ export default function Faculty() { ) : ( !selectShow && (
    -

    Кафедры

    +

    Кафедры

    {facultyShow ? ( ) : ( diff --git a/app/components/lessons/LessonInfoCard.tsx b/app/components/lessons/LessonInfoCard.tsx index 0a8211b2..fd6b0990 100644 --- a/app/components/lessons/LessonInfoCard.tsx +++ b/app/components/lessons/LessonInfoCard.tsx @@ -36,14 +36,20 @@ export default function LessonInfoCard({ const [practicaCall, setPracticaCall] = useState(false); const docCard = ( -
    + ); const linkCard = ( -
    +
    @@ -67,13 +73,13 @@ export default function LessonInfoCard({ {title} -

    {description}

    +

    {description !== 'null' && description }

    ); const videoCard = ( -
    +
    @@ -82,13 +88,13 @@ export default function LessonInfoCard({ {title} -

    {description}

    +

    {description !== 'null' && description }

    ); const testCard = ( -
    +
    @@ -130,7 +136,7 @@ export default function LessonInfoCard({ ); const practicaCard = ( -
    +
    @@ -140,35 +146,29 @@ export default function LessonInfoCard({
    ); - const docuemntPath = documentUrl && documentUrl?.document_path; - const practicaInfo = (
    -
    - {title} -

    {description}

    -
    -
    +
    {documentUrl ? ( - documentUrl.document && documentUrl.document?.length > 0 ? ( - - Документ: - {docuemntPath} + documentUrl.document_path && documentUrl.document_path?.length > 0 ? ( + + {title} ) : (
    - Документ: - {docuemntPath} + {title}
    ) ) : (
    - Документ: - {docuemntPath} + {title}
    )} +

    {description !== 'null' && description }

    +
    +
    - Ссылка: + {link && Ссылка: } {link} @@ -188,7 +188,7 @@ export default function LessonInfoCard({ setTestCall(false); }} > -
    {testInfo}
    +
    {testInfo}
    -
    {practicaInfo}
    +
    {practicaInfo}
    -
    {type === "document" && docCard}
    +
    {type === 'document' && docCard}
    {type === 'link' && linkCard}
    {type === 'video' && videoCard}
    {type === 'test' && testCard}
    diff --git a/app/globals.css b/app/globals.css index 16ef0340..367a019e 100644 --- a/app/globals.css +++ b/app/globals.css @@ -596,7 +596,7 @@ h1, h2, h3, h4, h5, h6 { .main-bg { background-color: #fff; border-radius: 12px; - padding: 4px 12px; + padding: 12px 12px; box-shadow: 0px 3px 5px rgba(0, 0, 0, 0.02), 0px 0px 2px rgba(0, 0, 0, 0.05), 0px 1px 4px rgba(0, 0, 0, 0.08); } diff --git a/layout/AppMenu.tsx b/layout/AppMenu.tsx index c2f070a2..a765052f 100644 --- a/layout/AppMenu.tsx +++ b/layout/AppMenu.tsx @@ -147,11 +147,7 @@ const AppMenu = () => { // add theme const handleAddTheme = async () => { - console.log(course_Id); - const data = await addThemes(Number(course_Id), themeValue?.title ? themeValue?.title : '', themeValue.sequence_number); - console.log(data); - if (data?.success) { contextFetchThemes(Number(course_Id), id_kafedra ? Number(id_kafedra) : null); clearValues(); @@ -174,6 +170,7 @@ const AppMenu = () => { const handleDeleteTheme = async (id: number) => { const data = await deleteTheme(id); if (data.success) { + console.warn('treu', data) contextFetchThemes(Number(course_Id), id_kafedra ? Number(id_kafedra) : null); setDeleteQuery(true); setMessage({ diff --git a/layout/context/layoutcontext.tsx b/layout/context/layoutcontext.tsx index 8f18b18d..9c01f338 100644 --- a/layout/context/layoutcontext.tsx +++ b/layout/context/layoutcontext.tsx @@ -68,23 +68,6 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { return window.innerWidth > 991; }; - useEffect(() => { - if (pathname === '/course') { - setLayoutState((prev) => ({ - ...prev, - staticMenuDesktopInactive: true, - staticMenuMobileActive: false, - overlayMenuActive: false, - profileSidebarVisible: false - })); - } else { - setLayoutState((prev) => ({ - ...prev, - staticMenuDesktopInactive: false - })); - } - }, [pathname]); - // breadCrumb urls const isTopicsChildPage = /^\/teaching\/[^/]+\/[^/]+$/.test(pathname); const [crumbUrls, setCrumbUrls] = useState<{ type: string; crumbUrl: string }>({ type: '', crumbUrl: '' }); @@ -93,12 +76,6 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { setCrumbUrls((prev) => ({ ...prev, [urlName]: url.crumbUrl })); }; - useEffect(() => { - if (isTopicsChildPage && crumbUrls) { - localStorage.setItem('currentBreadCrumb', JSON.stringify(crumbUrls)); - } - }, [crumbUrls]); - // fetch course const [mainCourseId, setMainCourseId] = useState(null); const [course, setCourses] = useState<{ current_page: number; total: number; per_page: number; data: myMainCourseType[] }>({ current_page: 1, total: 0, per_page: 10, data: [] }); @@ -118,7 +95,7 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { const [contextThemes, setContextThemes] = useState([]); const contextFetchThemes = async (id: number | null, id_kafedra: number | null) => { const data = await fetchThemes(Number(id) || null, id_kafedra); - if(data){ + if (data) { console.log(data); setContextThemes(data); } @@ -133,8 +110,31 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { }; // departament - const [departament, setDepartament] = useState<{last_name:string, name:string, father_name:string, info:string}>({last_name:'', name:'', father_name:'', info: ''}); - + const [departament, setDepartament] = useState<{ last_name: string; name: string; father_name: string; info: string }>({ last_name: '', name: '', father_name: '', info: '' }); + + useEffect(() => { + if (pathname === '/course') { + setLayoutState((prev) => ({ + ...prev, + staticMenuDesktopInactive: true, + staticMenuMobileActive: false, + overlayMenuActive: false, + profileSidebarVisible: false + })); + } else { + setLayoutState((prev) => ({ + ...prev, + staticMenuDesktopInactive: false + })); + } + }, [pathname]); + + useEffect(() => { + if (isTopicsChildPage && crumbUrls) { + localStorage.setItem('currentBreadCrumb', JSON.stringify(crumbUrls)); + } + }, [crumbUrls]); + const value: LayoutContextProps = { layoutConfig, setLayoutConfig, @@ -171,7 +171,7 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { setMainCourseId, departament, - setDepartament, + setDepartament }; return ( diff --git a/services/courses.tsx b/services/courses.tsx index 77dcfe47..6e994234 100644 --- a/services/courses.tsx +++ b/services/courses.tsx @@ -125,6 +125,8 @@ export const fetchThemes = async (id: number | null, id_kafedra: number | null) const res = await axiosInstance.get(`/v1/teacher/lessons?course_id=${id}&id_kafedra=${id_kafedra ? id_kafedra : ''}`); const data = await res.data; + console.log('COURSE', data); + return data; } catch (err) { console.log('Ошибка при получении темы', err); diff --git a/styles/layout/layout.scss b/styles/layout/layout.scss index 7348af10..f1a1e6ab 100644 --- a/styles/layout/layout.scss +++ b/styles/layout/layout.scss @@ -11,4 +11,5 @@ @import "./_utils"; @import "./_typography"; @import "./_button"; -@import "./forms.css"; \ No newline at end of file +@import "./forms.css"; +@import "./primereact.css"; \ No newline at end of file diff --git a/styles/layout/primereact.css b/styles/layout/primereact.css new file mode 100644 index 00000000..99845fe4 --- /dev/null +++ b/styles/layout/primereact.css @@ -0,0 +1,5 @@ +.p-accordion .p-accordion-header .p-accordion-header-link { + width: 100% !important; + padding: 7px !important; + font-size: 15px !important; +} \ No newline at end of file diff --git a/utils/axiosInstance.tsx b/utils/axiosInstance.tsx index feb80829..99b85f74 100644 --- a/utils/axiosInstance.tsx +++ b/utils/axiosInstance.tsx @@ -31,9 +31,9 @@ axiosInstance.interceptors.response.use( if (status === 403) { console.warn('Не имеет доступ. Перенаправляю...'); - // if (typeof window !== 'undefined') { - // window.location.href = '/'; - // } + if (typeof window !== 'undefined') { + // window.location.href = '/'; + } } if (status === 404) { From 164f35bb5a77838c2f58e91a10fabd2763107589 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Wed, 17 Sep 2025 15:07:27 +0600 Subject: [PATCH 171/183] =?UTF-8?q?=D0=9E=D1=82=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=87=D1=82=D0=BE=20=D0=B2=20?= =?UTF-8?q?=D1=82=D0=B5=D0=BC=D0=B5=20=D0=BD=D0=B5=D1=82=D1=83=20=D1=83?= =?UTF-8?q?=D1=80=D0=BE=D0=BA=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../course/[course_Id]/[lesson_id]/page.tsx | 46 +++++++++++++------ layout/context/layoutcontext.tsx | 1 + 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx index 806eb4a8..53df3ad7 100644 --- a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx +++ b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx @@ -29,7 +29,7 @@ export default function LessonStep() { const [lessonInfoState, setLessonInfoState] = useState<{ title: string; documents_count: string; usefullinks_count: string; videos_count: string } | null>(null); const media = useMediaQuery('(max-width: 640px)'); - const { setMessage, contextFetchThemes, contextThemes, setDeleteQuery, deleteQuery, updateQuery, setUpdateeQuery } = useContext(LayoutContext); + const { setMessage, contextFetchThemes, contextThemes, setContextThemes, setDeleteQuery, deleteQuery, updateQuery, setUpdateeQuery } = useContext(LayoutContext); const showError = useErrorMessage(); const [formVisible, setFormVisible] = useState(false); @@ -264,23 +264,40 @@ export default function LessonStep() { // }, [lesson_id]); useEffect(() => { - if (!contextThemes?.lessons?.data) return; + console.warn(contextThemes, contextThemes.length); + + if (contextThemes?.length < 1 || !contextThemes?.lessons?.data) { + setThemeNull(true); + return; + } else { + setThemeNull(false); + } const lessons = contextThemes.lessons.data; - if (lessons.length === 0) return; - - let chosenId: number | null = null; - - if (param.lesson_id && param.lesson_id !== 'null') { - const urlId = Number(param.lesson_id); - const exists = lessons.some((l) => l.id === urlId); - chosenId = exists ? urlId : lessons[0].id; + console.warn('SMOTRI ',lessons) + if (lessons.length < 1) { + setThemeNull(true); } else { - chosenId = lessons[0].id; + setThemeNull(false); } - - if (lesson_id !== chosenId) { - setLesson_id(chosenId); + console.log('contexttheme ',lessons); + + let chosenId: number | null = null; + if(lessons.length > 0) { + setThemeNull(false); + if (param.lesson_id && param.lesson_id !== 'null') { + const urlId = Number(param.lesson_id); + const exists = lessons.some((l) => l.id === urlId); + chosenId = exists ? urlId : lessons[0].id; + } else { + chosenId = lessons[0].id; + } + + if (lesson_id !== chosenId) { + setLesson_id(chosenId); + } + } else { + setThemeNull(true); } }, [contextThemes, deleteQuery, param.lesson_id]); @@ -309,6 +326,7 @@ export default function LessonStep() { return () => { el.removeEventListener('wheel', onWheel); setLesson_id(null); + setContextThemes([]); }; }, []); diff --git a/layout/context/layoutcontext.tsx b/layout/context/layoutcontext.tsx index 9c01f338..c12a848e 100644 --- a/layout/context/layoutcontext.tsx +++ b/layout/context/layoutcontext.tsx @@ -113,6 +113,7 @@ export const LayoutProvider = ({ children }: ChildContainerProps) => { const [departament, setDepartament] = useState<{ last_name: string; name: string; father_name: string; info: string }>({ last_name: '', name: '', father_name: '', info: '' }); useEffect(() => { + setContextThemes([]); if (pathname === '/course') { setLayoutState((prev) => ({ ...prev, From 34a0a690d924e9bae7f8dec8a3dc2b76d5cc6b02 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Wed, 17 Sep 2025 15:27:07 +0600 Subject: [PATCH 172/183] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BD=D0=B0=D0=B7=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=BA=D1=83=D1=80=D1=81=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D1=83?= =?UTF-8?q?=D1=80=D0=BE=D0=BA=D0=BE=D0=B2=20=D0=B7=D0=B0=D0=B2=20=D0=BA?= =?UTF-8?q?=D0=B0=D1=84=D0=B5=D0=B4=D1=80=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../faculty/[id_kafedra]/[course_id]/page.tsx | 94 ++++++++++--------- services/faculty.tsx | 12 +++ services/query-tests.http | 14 +-- 3 files changed, 69 insertions(+), 51 deletions(-) diff --git a/app/(main)/faculty/[id_kafedra]/[course_id]/page.tsx b/app/(main)/faculty/[id_kafedra]/[course_id]/page.tsx index f18406bd..3c7b5a3f 100644 --- a/app/(main)/faculty/[id_kafedra]/[course_id]/page.tsx +++ b/app/(main)/faculty/[id_kafedra]/[course_id]/page.tsx @@ -5,6 +5,7 @@ import { NotFound } from '@/app/components/NotFound'; import useErrorMessage from '@/hooks/useErrorMessage'; import { LayoutContext } from '@/layout/context/layoutcontext'; import { fetchCourseInfo } from '@/services/courses'; +import { depCourseInfo } from '@/services/faculty'; import { fetchDepartamentSteps, fetchSteps } from '@/services/steps'; import { mainStepsType } from '@/types/mainStepType'; import { useParams } from 'next/navigation'; @@ -28,13 +29,15 @@ export default function LessonCheck() { const [activeIndex, setActiveIndex] = useState(0); const [videoCall, setVideoCall] = useState(false); const [video_link, setVideoLink] = useState(''); + const [courseInfo, setCourseInfo] = useState({title: ''}); const handleCourseInfo = async () => { if (course_id) { - const data = await fetchCourseInfo(Number(course_id)); + const data = await depCourseInfo(Number(course_id), Number(id_kafedra)); console.log('info', data); - if (data) { + if (data.success) { + setCourseInfo({title: data.course.title}) } } }; @@ -103,7 +106,7 @@ export default function LessonCheck() { // ПРОСИМ КУРС ДЛЯ НАЗВАНИЯ И ТЕМЫ useEffect(() => { contextFetchThemes(Number(course_id), id_kafedra ? Number(id_kafedra) : null); - // handleCourseInfo() + handleCourseInfo(); }, []); // САМИ ТЕМЫ, присваиваем в локальные темы @@ -161,47 +164,50 @@ export default function LessonCheck() { {themeShow ? ( ) : ( - setActiveIndex(e.index)}> - {themes.map((item) => { - const content = steps.filter((j) => { - return j.content != null; - }); - - return ( - -
    - {hasSteps ? ( -

    Данных нет

    - ) : content.length > 0 ? ( - content.map((i,idx) => { - if (i.content) { - return ( -
    - { - - } -
    - ); - } - }) - ) : ( -

    Данных нет

    - )} -
    -
    - ); - })} -
    +
    +

    Название курса: {courseInfo.title}

    + setActiveIndex(e.index)}> + {themes.map((item) => { + const content = steps.filter((j) => { + return j.content != null; + }); + + return ( + +
    + {hasSteps ? ( +

    Данных нет

    + ) : content.length > 0 ? ( + content.map((i, idx) => { + if (i.content) { + return ( +
    + { + + } +
    + ); + } + }) + ) : ( +

    Данных нет

    + )} +
    +
    + ); + })} +
    +
    )}
    ); diff --git a/services/faculty.tsx b/services/faculty.tsx index 378823df..16ab4741 100644 --- a/services/faculty.tsx +++ b/services/faculty.tsx @@ -33,6 +33,18 @@ export const fetchDepartament = async (id_kafedra: number | null) => { const res = await axiosInstance.get(`/v1/teacher/controls/department?id_kafedra=${id_kafedra}`); const data = await res.data; + return data; + } catch (err) { + console.log('Ошибка загрузки:', err); + return err; + } +}; + +export const depCourseInfo = async (course_id:number | null, id_kafedra: number | null) => { + try { + const res = await axiosInstance.get(`/v1/teacher/controls/department/course?course_id=${course_id}&id_kafedra=${id_kafedra}`); + const data = await res.data; + return data; } catch (err) { console.log('Ошибка загрузки:', err); diff --git a/services/query-tests.http b/services/query-tests.http index 003d1a60..b0e1de88 100644 --- a/services/query-tests.http +++ b/services/query-tests.http @@ -1,4 +1,4 @@ -@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5teWVkdS5vc2hzdS5rZy9wdWJsaWMvYXBpL21vb2MvbG9naW4iLCJpYXQiOjE3NTc5ODE1MzQsImV4cCI6MTc1ODAxMDM5NCwibmJmIjoxNzU3OTgxNTM0LCJqdGkiOiJRY1FTNGdSSTZFRzRCc1NBIiwic3ViIjoiMTE4MjkyIiwicHJ2IjoiMjNiZDVjODk0OWY2MDBhZGIzOWU3MDFjNDAwODcyZGI3YTU5NzZmNyJ9.mhD-cf2IbMcImhwRIFkGkXZrlmRpM-f4GnpBnhNu8J8 +@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5teWVkdS5vc2hzdS5rZy9wdWJsaWMvYXBpL21vb2MvbG9naW4iLCJpYXQiOjE3NTgwOTk4NTEsImV4cCI6MTc1ODEyODcxMSwibmJmIjoxNzU4MDk5ODUxLCJqdGkiOiJ1RGxQMjdYMHFLa2NVbXVDIiwic3ViIjoiMTE4MjkyIiwicHJ2IjoiMjNiZDVjODk0OWY2MDBhZGIzOWU3MDFjNDAwODcyZGI3YTU5NzZmNyJ9.MMZZJ2d06qK5Emrl-hUvMOSQqzvGLmMIMaTAoXc2Jmk # ### # http://api.mooc.oshsu.kg/public/api/v1/open/video/types @@ -8,9 +8,9 @@ # ### # # STREAMS GET -GET https://api.mooc.oshsu.kg/public/api/v1/teacher/lessons?course_id=32 -Authorization: Bearer {{token}} -Content-Type: application/json +# GET https://api.mooc.oshsu.kg/public/api/v1/teacher/lessons?course_id=32 +# Authorization: Bearer {{token}} +# Content-Type: application/json # ### # test @@ -24,6 +24,6 @@ Content-Type: application/json # test -# GET https://api.mooc.oshsu.kg/public/api/v1/teacher/lessons?course_id=32 -# Authorization: Bearer {{token}} -# Content-Type: application/json +GET https://api.mooc.oshsu.kg/public/api/v1/teacher/controls/department/course?course_id=34&id_kafedra=139 +Authorization: Bearer {{token}} +Content-Type: application/json From c292d93fdd40c2157402e87e7ae076c51d3021e3 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Wed, 17 Sep 2025 16:11:02 +0600 Subject: [PATCH 173/183] =?UTF-8?q?=D1=83=D0=B1=D1=80=D0=B0=D0=BB=20=D0=BE?= =?UTF-8?q?=D1=88=D0=B8=D0=B1=D0=BA=D0=B8=20=D1=81=20=D0=BA=D0=B5=D0=B9=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../course/[course_Id]/[lesson_id]/page.tsx | 4 +-- .../faculty/[id_kafedra]/[course_id]/page.tsx | 4 +-- .../[connect_id]/[stream_id]/page.tsx | 2 +- layout/AppMenu.tsx | 35 ++++++++++++------- types/mainStepType.tsx | 21 +---------- 5 files changed, 27 insertions(+), 39 deletions(-) diff --git a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx index 53df3ad7..48c20742 100644 --- a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx +++ b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx @@ -274,20 +274,18 @@ export default function LessonStep() { } const lessons = contextThemes.lessons.data; - console.warn('SMOTRI ',lessons) if (lessons.length < 1) { setThemeNull(true); } else { setThemeNull(false); } - console.log('contexttheme ',lessons); let chosenId: number | null = null; if(lessons.length > 0) { setThemeNull(false); if (param.lesson_id && param.lesson_id !== 'null') { const urlId = Number(param.lesson_id); - const exists = lessons.some((l) => l.id === urlId); + const exists = lessons.some((l:{id: number}) => l.id === urlId); chosenId = exists ? urlId : lessons[0].id; } else { chosenId = lessons[0].id; diff --git a/app/(main)/faculty/[id_kafedra]/[course_id]/page.tsx b/app/(main)/faculty/[id_kafedra]/[course_id]/page.tsx index 3c7b5a3f..a6fb965b 100644 --- a/app/(main)/faculty/[id_kafedra]/[course_id]/page.tsx +++ b/app/(main)/faculty/[id_kafedra]/[course_id]/page.tsx @@ -173,7 +173,7 @@ export default function LessonCheck() { }); return ( - +
    {hasSteps ? (

    Данных нет

    @@ -181,7 +181,7 @@ export default function LessonCheck() { content.map((i, idx) => { if (i.content) { return ( -
    +
    { ( -
    +
    {rowData.last_name} {rowData.name} {rowData.father_name} diff --git a/layout/AppMenu.tsx b/layout/AppMenu.tsx index a765052f..1eae498f 100644 --- a/layout/AppMenu.tsx +++ b/layout/AppMenu.tsx @@ -44,7 +44,7 @@ const AppMenu = () => { const { studentThemeCourse } = useParams(); const params = useParams(); console.log(params); - + const course_Id = params.course_Id; const id_kafedra = params?.id_kafedra ? params.id_kafedra : null; @@ -55,7 +55,7 @@ const AppMenu = () => { const [editingLesson, setEditingLesson] = useState<{ title: string; sequence_number: number | null } | null>(null); const [themeValue, setThemeValue] = useState<{ title: string; sequence_number: number | null }>({ title: '', sequence_number: null }); - const [themesStudentList, setThemesStudentList] = useState<{ label: string; id: number; to: string; items?: AppMenuItem[] }[]>([]); + const [themesStudentList, setThemesStudentList] = useState<{ key?: string; label: string; id: number; to: string; items?: AppMenuItem[] }[]>([]); const showError = useErrorMessage(); const { setMessage } = useContext(LayoutContext); @@ -63,6 +63,12 @@ const AppMenu = () => { const byStatus: AppMenuItem[] = user?.is_working ? pathname.startsWith('/course/') ? [ + { + // key: 'prev', + label: '', + icon: 'pi pi-fw pi-arrow-left', + to: '/course' + }, { label: 'Темалар', icon: 'pi pi-fw pi-calendar-clock', @@ -80,6 +86,15 @@ const AppMenu = () => { const forDepartament = !pathname.startsWith('/course/') && departament.info.length > 0 ? [ + { + // key: 'prev', + label: '', + icon: 'pi pi-fw pi-arrow-left', + to: '#', + command: () => { + router.back(); + } + }, { label: 'Главная страница', icon: 'pi pi-home', @@ -100,19 +115,13 @@ const AppMenu = () => { const model: AppMenuItem[] = [ { - label: '', - icon: 'pi pi-fw pi-arrow-left', - to: '#', - command: ()=> { - router.back(); - } - }, - { - label: '', + // key: 'Департамент', + label: ' ', items: forDepartament }, { - label: '', + // key: 'Основной', + label: ' ', items: byStatus } ]; @@ -170,7 +179,7 @@ const AppMenu = () => { const handleDeleteTheme = async (id: number) => { const data = await deleteTheme(id); if (data.success) { - console.warn('treu', data) + console.warn('treu', data); contextFetchThemes(Number(course_Id), id_kafedra ? Number(id_kafedra) : null); setDeleteQuery(true); setMessage({ diff --git a/types/mainStepType.tsx b/types/mainStepType.tsx index ef8c143a..27258ab3 100644 --- a/types/mainStepType.tsx +++ b/types/mainStepType.tsx @@ -7,24 +7,5 @@ export interface mainStepsType { step: number; type: { active: true; created_at: string; id: 1; logo: string; description?: string | null; modelName: string; name: string; title: string; updated_at: string }; updated_at: string; + content?: {document: string, document_path: string, description: string | null, title: string, link: string, url: string, content: string, answers: [{text: string, is_correct: boolean, id: number | null}], score: number} } - -content: course_id: null; -created_at: '2025-09-12T06:24:54.000000Z'; -description: null; -document: '1_76_1757658294.pdf'; -id: 49; -lesson_id: 76; -status: true; -title: 'лекция'; -updated_at: '2025-09-12T06:24:54.000000Z'; -user_id: 1; - -type: active: true; -created_at: '2025-09-02T09:31:58.000000Z'; -id: 2; -logo: 'pi pi-folder'; -modelName: 'Document'; -name: 'document'; -title: 'Документ'; -updated_at: '2025-09-02T09:32:01.000000Z'; From c4b8c797c5de21f1f59787e58cb1e727615d1ed3 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Thu, 18 Sep 2025 11:55:16 +0600 Subject: [PATCH 174/183] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B2=D0=BE?= =?UTF-8?q?=D0=B4=20=D0=BD=D0=B0=20=D1=80=D1=83=D1=81=D1=81=D0=BA=D0=B8?= =?UTF-8?q?=D0=B9,=20=D0=B2=D0=B5=D1=80=D1=81=D1=82=D0=BA=D0=B0=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B2=20=D0=BA=D0=B0=D1=84=D0=B5=D0=B4=D1=80=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(full-page)/auth/login/page.tsx | 10 +- .../course/[course_Id]/[lesson_id]/page.tsx | 160 +++++++++--------- app/(main)/course/page.tsx | 36 ++-- app/components/CounterBanner.tsx | 8 +- app/components/HomeClient.tsx | 66 +------- app/components/VideoPlay.tsx | 2 +- app/components/cards/LessonCard.tsx | 31 +--- app/components/lessons/LessonDocument.tsx | 33 ++-- app/components/lessons/LessonInfoCard.tsx | 62 ++++--- app/components/lessons/LessonLink.tsx | 42 ++--- app/components/lessons/LessonPractica.tsx | 46 +++-- app/components/lessons/LessonTest.tsx | 58 ++----- app/components/lessons/LessonVideo.tsx | 69 +++----- app/components/tables/StreamList.tsx | 13 +- layout/AppFooter.tsx | 43 +---- layout/AppMenu.tsx | 35 ++-- layout/AppTopbar.tsx | 39 +---- utils/getConfirmOptions.tsx | 8 +- 18 files changed, 275 insertions(+), 486 deletions(-) diff --git a/app/(full-page)/auth/login/page.tsx b/app/(full-page)/auth/login/page.tsx index c71d1c70..3413ddc3 100644 --- a/app/(full-page)/auth/login/page.tsx +++ b/app/(full-page)/auth/login/page.tsx @@ -78,7 +78,6 @@ const LoginPage = () => { } } } else { - console.log('Ошибка при авторизации, повторите позже'); setMessage({ state: true, value: { severity: 'error', summary: 'Ошибка', detail: 'Введите корректные данные' } @@ -94,11 +93,6 @@ const LoginPage = () => { }); // messege - Ошибка при авторизации }; - useEffect(()=> { - console.log(departament); - - },[departament]); - return (
    {/*
    */} @@ -110,7 +104,7 @@ const LoginPage = () => {
    -

    Кирүү

    +

    Вход

    {/*
    - +
    diff --git a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx index 48c20742..dbd5235a 100644 --- a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx +++ b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx @@ -12,7 +12,6 @@ import { useMediaQuery } from '@/hooks/useMediaQuery'; import { LayoutContext } from '@/layout/context/layoutcontext'; import { deleteLesson, fetchLessonShow } from '@/services/courses'; import { addLesson, deleteStep, fetchElement, fetchSteps, fetchTypes } from '@/services/steps'; -import { lessonStateType } from '@/types/lessonStateType'; import { mainStepsType } from '@/types/mainStepType'; import { getConfirmOptions } from '@/utils/getConfirmOptions'; import { useParams, usePathname, useRouter } from 'next/navigation'; @@ -26,6 +25,8 @@ export default function LessonStep() { const param = useParams(); const course_id = param.course_Id; const scrollRef = useRef(null); + const router = useRouter(); + const pathname = usePathname(); const [lessonInfoState, setLessonInfoState] = useState<{ title: string; documents_count: string; usefullinks_count: string; videos_count: string } | null>(null); const media = useMediaQuery('(max-width: 640px)'); @@ -43,23 +44,25 @@ export default function LessonStep() { const [lesson_id, setLesson_id] = useState(null); const [sequence_number, setSequence_number] = useState(null); const [skeleton, setSkeleton] = useState(false); + const [wasCreated, setWasCreated] = useState(false); const [testovy, setTestovy] = useState(false); - const router = useRouter(); - const pathname = usePathname(); const changeUrl = (lessonId: number | null) => { router.replace(`/course/${course_id}/${lessonId ? lessonId : null}`); }; const handleShow = async (LessonId: number | null) => { + setSkeleton(true); const data = await fetchLessonShow(LessonId); if (data?.lesson) { + setSkeleton(false); setLessonInfoState({ title: data.lesson.title, videos_count: data.lesson.videos_count, usefullinks_count: data.lesson.usefullinks_count, documents_count: data.lesson.documents_count }); } else { + setSkeleton(false); setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинчерээк кайталаныз' } + value: { severity: 'error', summary: 'Ошибка!', detail: 'Повторите позже' } }); if (data?.response?.status) { showError(data.response.status); @@ -79,7 +82,7 @@ export default function LessonStep() { setSkeleton(false); setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинчерээк кайталаныз' } + value: { severity: 'error', summary: 'Ошибка!', detail: 'Повторите позже' } }); if (data?.response?.status) { showError(data.response.status); @@ -90,8 +93,6 @@ export default function LessonStep() { const handleFetchSteps = async (lesson_id: number | null) => { setSkeleton(true); const data = await fetchSteps(Number(lesson_id)); - console.log('steps', data); - if (data.success) { setSkeleton(false); if (data.steps.length < 1) { @@ -105,7 +106,7 @@ export default function LessonStep() { setHasSteps(false); setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинчерээк кайталаныз' } + value: { severity: 'error', summary: 'Ошибка!', detail: 'Повторите позже' } }); if (data?.response?.status) { showError(data.response.status); @@ -116,18 +117,17 @@ export default function LessonStep() { const handleAddLesson = async (lessonId: number, typeId: number) => { setFormVisible(false); const data = await addLesson({ lesson_id: lessonId, type_id: typeId }, sequence_number); - console.log(data); - if (data.success) { + setWasCreated(true); handleFetchSteps(lessonId); setMessage({ state: true, - value: { severity: 'success', summary: 'Ийгиликтүү кошулду!', detail: '' } + value: { severity: 'success', summary: 'Успешно добавлен!', detail: '' } }); } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинчерээк кайталаныз' } + value: { severity: 'error', summary: 'Ошибка!', detail: 'Повторите позже' } }); if (data?.response?.status) { showError(data.response.status); @@ -137,13 +137,16 @@ export default function LessonStep() { const handleFetchElement = async (stepId: number) => { if (lesson_id) { + setSkeleton(true); const data = await fetchElement(Number(lesson_id), stepId); if (data.success) { + setSkeleton(false); setElement({ content: data.content, step: data.step }); } else { + setSkeleton(false); setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинчерээк кайталаныз' } + value: { severity: 'error', summary: 'Ошибка!', detail: 'Повторите позже' } }); if (data?.response?.status) { showError(data.response.status); @@ -158,12 +161,12 @@ export default function LessonStep() { handleFetchSteps(Number(lesson_id)); setMessage({ state: true, - value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } + value: { severity: 'success', summary: 'Успешно удалено!', detail: '' } }); } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Өчүрүүдө ката кетти' } + value: { severity: 'error', summary: 'Ошибка при удалении!', detail: '' } }); if (data.response.status) { showError(data.response.status); @@ -171,12 +174,20 @@ export default function LessonStep() { } }; + // useEffect(() => { + // if (Array.isArray(steps) && steps.length > 0) { + // const firstStep = steps[0]?.id; + // // const firstStep = steps[steps.length - 1]?.id; + // setSelectId(firstStep); + // handleFetchElement(firstStep); + // } + // }, [steps]); useEffect(() => { if (Array.isArray(steps) && steps.length > 0) { - const firstStep = steps[0]?.id; - // const firstStep = steps[steps.length - 1]?.id; - setSelectId(firstStep); - handleFetchElement(firstStep); + const stepId = wasCreated ? steps[steps.length - 1].id : steps[0].id; + setSelectId(stepId); + handleFetchElement(stepId); + setWasCreated(false); } }, [steps]); @@ -190,39 +201,9 @@ export default function LessonStep() { useEffect(() => { setTestovy(true); - console.log('course-id', course_id); - contextFetchThemes(Number(course_id), null); }, [course_id]); - // useEffect(() => { - // console.log('Тема ', contextThemes, lesson_id); - // if (testovy || updateQuery || deleteQuery) { - // setTestovy(false); - // setUpdateeQuery(false); - // if (contextThemes?.lessons?.data?.length > 0) { - // setThemeNull(false); - // if (param.lesson_id == 'null' || deleteQuery) { - // console.log(contextThemes.lessons.data[0].id); - - // handleShow(contextThemes.lessons.data[0].id); - // console.log('variant 4', contextThemes.lessons.data[0].id); - // handleFetchSteps(contextThemes.lessons.data[0].id); - // setLesson_id(contextThemes.lessons.data[0].id); - // setDeleteQuery(false); - // } else { - // console.log('variant 5'); - // handleShow(Number(param.lesson_id)); - // setLesson_id((param.lesson_id && Number(param.lesson_id)) || null); - // handleFetchSteps(Number(param.lesson_id)); - // } - // } else { - // setThemeNull(true); - // console.log('variant 3'); - // } - // } - // }, [contextThemes]); - // useEffect(() => { // if (!contextThemes?.lessons?.data) return; @@ -264,8 +245,6 @@ export default function LessonStep() { // }, [lesson_id]); useEffect(() => { - console.warn(contextThemes, contextThemes.length); - if (contextThemes?.length < 1 || !contextThemes?.lessons?.data) { setThemeNull(true); return; @@ -279,18 +258,18 @@ export default function LessonStep() { } else { setThemeNull(false); } - + let chosenId: number | null = null; - if(lessons.length > 0) { + if (lessons.length > 0) { setThemeNull(false); if (param.lesson_id && param.lesson_id !== 'null') { const urlId = Number(param.lesson_id); - const exists = lessons.some((l:{id: number}) => l.id === urlId); + const exists = lessons.some((l: { id: number }) => l.id === urlId); chosenId = exists ? urlId : lessons[0].id; } else { chosenId = lessons[0].id; } - + if (lesson_id !== chosenId) { setLesson_id(chosenId); } @@ -298,7 +277,7 @@ export default function LessonStep() { setThemeNull(true); } }, [contextThemes, deleteQuery, param.lesson_id]); - + useEffect(() => { if (lesson_id && param.lesson_id !== String(lesson_id)) { changeUrl(lesson_id); @@ -329,10 +308,14 @@ export default function LessonStep() { }, []); const lessonInfo = ( -
    -
    -

    {lessonInfoState?.title}

    -
    +
    + {skeleton ? ( + + ) : ( +
    +

    {lessonInfoState?.title}

    +
    + )}
    ); @@ -356,7 +339,7 @@ export default function LessonStep() { if (themeNull) { return (
    - +
    ); } @@ -365,7 +348,7 @@ export default function LessonStep() {
    {/* modal sectoin */} { @@ -432,33 +415,50 @@ export default function LessonStep() { )}
    )} +
    - + {skeleton ? ( + + ) : ( + + )}
    {hasSteps && (
    - +
    )}
    - {!hasSteps && {element?.step.type.title}} - {!hasSteps && ( -
    {element?.step.type.name === 'document' && } diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index 5dbef64f..1be60b03 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -27,12 +27,8 @@ import { TabViewChange } from '@/types/tabViewChange'; import { useMediaQuery } from '@/hooks/useMediaQuery'; import useShortText from '@/hooks/useShortText'; import { ProgressSpinner } from 'primereact/progressspinner'; -import { RadioButton } from 'primereact/radiobutton'; import { DataView } from 'primereact/dataview'; -import { displayType } from '@/types/displayType'; import { FileWithPreview } from '@/types/fileuploadPreview'; -import { InputSwitch, InputSwitchChangeEvent } from 'primereact/inputswitch'; -import { SelectButton, SelectButtonChangeEvent } from 'primereact/selectbutton'; import { mainStreamsType } from '@/types/mainStreamsType'; export default function Course() { @@ -113,7 +109,7 @@ export default function Course() { setHasCourses(true); setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинчерээк кайталаныз' } + value: { severity: 'error', summary: 'Ошибка!', detail: 'Повторите позже' } }); if (data?.response?.status) { showError(data.response.status); @@ -128,12 +124,12 @@ export default function Course() { contextFetchCourse(1); setMessage({ state: true, - value: { severity: 'success', summary: 'Ийгиликтүү кошулду!', detail: '' } + value: { severity: 'success', summary: 'Успешно добавлен!', detail: '' } }); } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Кошуу учурунда катаа кетти' } + value: { severity: 'error', summary: 'Ошибка при добавлении!', detail: '' } }); if (data?.response?.status) { showError(data.response.status); @@ -149,12 +145,12 @@ export default function Course() { contextFetchCourse(1); setMessage({ state: true, - value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } + value: { severity: 'success', summary: 'Успешно удалено!', detail: '' } }); // messege - Успех! } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Өчүрүүдө ката кетти' } + value: { severity: 'error', summary: 'Ошибка при удалении!', detail: '' } }); // messege - Ошибка при добавлении if (data?.response?.status) { showError(data.response.status); @@ -181,12 +177,12 @@ export default function Course() { setSelectedCourse(null); setMessage({ state: true, - value: { severity: 'success', summary: 'Ийгиликтүү өзгөртүлдү!', detail: '' } + value: { severity: 'success', summary: 'Успешно изменено!', detail: '' } }); // messege - Успех! } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Өзгөртүүдө ката кетти' } + value: { severity: 'error', summary: 'Ошибка при изменении!', detail: '' } }); // messege - Ошибка при изменении курса if (data?.response?.status) { showError(data.response.status); @@ -387,11 +383,11 @@ export default function Course() {
    {/*
    */}
    - +
    { @@ -410,12 +406,12 @@ export default function Course() {
    - +
    { @@ -437,7 +433,7 @@ export default function Course() { {/*
    */}
    - +
    {/* )} */}
    - +
    - + ) : ( <> @@ -618,7 +614,7 @@ export default function Course() { {/* table section */} {hasCourses ? ( - + ) : ( <> {skeleton ? ( diff --git a/app/components/CounterBanner.tsx b/app/components/CounterBanner.tsx index 70317ba5..07f92438 100644 --- a/app/components/CounterBanner.tsx +++ b/app/components/CounterBanner.tsx @@ -15,7 +15,7 @@ export default function CounterBanner() {
    +
    - Курстар & видеосабактар + Курсы & видео уроки
    @@ -26,7 +26,7 @@ export default function CounterBanner() {
    +
    - Катталган студенттер + Зарегистрированные студенты
    @@ -37,7 +37,7 @@ export default function CounterBanner() {
    +
    - Окутуучулар + Преподаватели
    @@ -48,7 +48,7 @@ export default function CounterBanner() {
    %
    - Канааттануу деңгээли + Уровень удовлетворённости
    diff --git a/app/components/HomeClient.tsx b/app/components/HomeClient.tsx index 33c1d70f..56185f2d 100644 --- a/app/components/HomeClient.tsx +++ b/app/components/HomeClient.tsx @@ -30,22 +30,22 @@ export default function HomeClient() {
    Фото - ЫҢГАЙЛУУ ОКУУ ҮЧҮН ОНЛАЙН МЕЙКИНДИК + УДОБНОЕ ОНЛАЙН-ПРОСТРАНСТВО ДЛЯ ОБУЧЕНИЯ

    - Аралыктан окутуу порталына кош келиңиз! + Добро пожаловать на портал дистанционного обучения!

    {' '} - Университеттин онлайн билим берүү жаатындагы долбоорлорун бириктирүүдөбүз: + Мы объединяем проекты университета в сфере онлайн-образования:
      -
    • ачык онлайн курстар
    • -
    • жогорку билим берүү программалары
    • +
    • открытые онлайн-курсы
    • +
    • программы высшего образования
    {user && ( - + )}
    @@ -55,18 +55,6 @@ export default function HomeClient() {
    - {/*
    - Shape -
    */} - Пользователь
    @@ -76,48 +64,6 @@ export default function HomeClient() {
    Shape
    - - {/*
    -
    - 13000 -

    Студент

    -
    -
    */} - - {/*
    -
    - Куттуктайбыз! -

    Сиздин кабыл алуу ийгиликтүү аяктады

    -
    -
    */} - - {/*
    -
    - User experience className -

    Today at 12.00 PM

    -
    - - Join now - -
    */}
    diff --git a/app/components/VideoPlay.tsx b/app/components/VideoPlay.tsx index fb87d29f..fb64719b 100644 --- a/app/components/VideoPlay.tsx +++ b/app/components/VideoPlay.tsx @@ -16,7 +16,7 @@ export default function VideoPlay() { return (
    { diff --git a/app/components/cards/LessonCard.tsx b/app/components/cards/LessonCard.tsx index e65c7111..3926e375 100644 --- a/app/components/cards/LessonCard.tsx +++ b/app/components/cards/LessonCard.tsx @@ -44,10 +44,6 @@ export default function LessonCard({ const shortDescription = useShortText(cardValue.desctiption ? cardValue.desctiption : '', 90); const shortUrl = useShortText(cardValue?.url ? cardValue?.url : '', 100); const [progressSpinner, setProgressSpinner] = useState(false); - - // useEffect(()=> { - // console.log(urlForDownload); - // },[urlForDownload]); const media = useMediaQuery('(max-width: 640px)'); const toggleSpinner = () => { @@ -71,21 +67,6 @@ export default function LessonCard({ } }; - const cardHeader = - type.typeValue === 'video' && status === 'working' ? ( -
    -
    -
    - - {lessonDate} -
    -
    - ) : ( -
    - -
    - ); - const videoPreviw = type.typeValue === 'video' && (
    @@ -103,7 +84,7 @@ export default function LessonCard({
    ); - const btnLabel = type.typeValue === 'doc' || type.typeValue === 'practica' ? 'Ачуу' : type.typeValue === 'link' ? 'Өтүү' : ''; + const btnLabel = type.typeValue === 'doc' || type.typeValue === 'practica' ? 'Открыть' : type.typeValue === 'link' ? 'Перейти' : ''; return (
    @@ -137,7 +118,7 @@ export default function LessonCard({ {type.typeValue === 'practica' && cardValue.url ? (
    - Шилтеме: + Ссылка: {cardValue?.url}
    @@ -193,7 +174,7 @@ export default function LessonCard({ {type.typeValue === 'doc' && (
    -
    @@ -212,7 +193,7 @@ export default function LessonCard({ )} {type.typeValue === 'link' && (
    -
    )} @@ -222,7 +203,7 @@ export default function LessonCard({
    ); @@ -73,7 +73,7 @@ export default function LessonInfoCard({ {title} -

    {description !== 'null' && description }

    +

    {description !== 'null' && description}

    ); @@ -88,7 +88,7 @@ export default function LessonInfoCard({ {title} -

    {description !== 'null' && description }

    +

    {description !== 'null' && description}

    ); @@ -107,22 +107,18 @@ export default function LessonInfoCard({ ); const testInfo = ( -
    +
    {test?.answers && ( -
    -
    - {test?.content} -
    - {!media && '/'} Балл: - {`${test?.score}`} -
    +
    +
    + {test?.content}
    -
    +
    {test?.answers.map((item) => { return (
    @@ -130,6 +126,10 @@ export default function LessonInfoCard({ ); })}
    +
    + Балл за тест: + {`${test?.score}`} +
    )}
    @@ -147,12 +147,12 @@ export default function LessonInfoCard({ ); const practicaInfo = ( -
    -
    +
    +
    {documentUrl ? ( documentUrl.document_path && documentUrl.document_path?.length > 0 ? ( - {title} + {title} ) : (
    @@ -164,14 +164,24 @@ export default function LessonInfoCard({ {title}
    )} -

    {description !== 'null' && description }

    +

    {description !== 'null' && description}

    -
    -
    - {link && Ссылка: } - - {link} - +
    +
    + Документ: + {documentUrl && documentUrl.document_path && documentUrl.document_path?.length > 0 ? + + : + } +
    + +
    + Ссылка: + {link ? + {link} + + : ? + }
    @@ -181,18 +191,18 @@ export default function LessonInfoCard({
    { if (!testCall) return; setTestCall(false); }} > -
    {testInfo}
    +
    {testInfo}
    { if (!practicaCall) return; diff --git a/app/components/lessons/LessonLink.tsx b/app/components/lessons/LessonLink.tsx index e6f671df..ed55f375 100644 --- a/app/components/lessons/LessonLink.tsx +++ b/app/components/lessons/LessonLink.tsx @@ -103,7 +103,7 @@ export default function LessonLink({ element, content, fetchPropElement, clearPr } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинчерээк кайталаныз' } + value: { severity: 'error', summary: 'Ошибка!', detail: 'Повторите позже' } }); if (data?.response?.status) { showError(data.response.status); @@ -118,12 +118,12 @@ export default function LessonLink({ element, content, fetchPropElement, clearPr fetchPropElement(element.id); setMessage({ state: true, - value: { severity: 'success', summary: 'Ийгиликтүү Кошулдуу!', detail: '' } + value: { severity: 'success', summary: 'Успешно добавлен!', detail: '' } }); } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Кошуу учурунда катаа кетти' } + value: { severity: 'error', summary: 'Ошибка при добавлении!', detail: '' } }); if (data.response.status) { showError(data.response.status); @@ -138,12 +138,12 @@ export default function LessonLink({ element, content, fetchPropElement, clearPr fetchPropElement(element.id); setMessage({ state: true, - value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } + value: { severity: 'success', summary: 'Успешно удалено!', detail: '' } }); } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Өчүрүүдө ката кетти' } + value: { severity: 'error', summary: 'Ошибка при удалении!', detail: '' } }); if (data.response.status) { showError(data.response.status); @@ -159,14 +159,14 @@ export default function LessonLink({ element, content, fetchPropElement, clearPr clearValues(); setMessage({ state: true, - value: { severity: 'success', summary: 'Ийгиликтүү өзгөртүлдү!', detail: '' } + value: { severity: 'success', summary: 'Успешно изменено!', detail: '' } }); } else { setLinkValue({ title: '', description: '', url: '' }); setEditingLesson({ title: '', description: '', url: '' }); setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Өзгөртүүдө ката кетти' } + value: { severity: 'error', summary: 'Ошибка при изменении!', detail: '' } }); if (data?.response?.status) { showError(data.response.status); @@ -206,7 +206,7 @@ export default function LessonLink({ element, content, fetchPropElement, clearPr { @@ -219,7 +219,7 @@ export default function LessonLink({ element, content, fetchPropElement, clearPr { setLinkValue((prev) => ({ ...prev, title: e.target.value })); @@ -227,18 +227,18 @@ export default function LessonLink({ element, content, fetchPropElement, clearPr }} /> {errors.title?.message} - {additional.link && setLinkValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" />} + {additional.link && setLinkValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" />}
    {/*
    diff --git a/app/components/lessons/LessonPractica.tsx b/app/components/lessons/LessonPractica.tsx index 6798444d..739e7870 100644 --- a/app/components/lessons/LessonPractica.tsx +++ b/app/components/lessons/LessonPractica.tsx @@ -98,7 +98,7 @@ export default function LessonPractica({ element, content, fetchPropElement, cle const documentView = ( <> -
    +
    setPDFVisible(false)}>
    @@ -154,7 +154,7 @@ export default function LessonPractica({ element, content, fetchPropElement, cle } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинчерээк кайталаныз' } + value: { severity: 'error', summary: 'Ошибка!', detail: 'Повторите позже' } }); if (data?.response?.status) { showError(data.response.status); @@ -169,12 +169,12 @@ export default function LessonPractica({ element, content, fetchPropElement, cle fetchPropElement(element.id); setMessage({ state: true, - value: { severity: 'success', summary: 'Ийгиликтүү Кошулдуу!', detail: '' } + value: { severity: 'success', summary: 'Успешно добавлен!', detail: '' } }); } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Кошуу учурунда катаа кетти' } + value: { severity: 'error', summary: 'Ошибка при добавлении!', detail: '' } }); if (data.response.status) { showError(data.response.status); @@ -189,12 +189,12 @@ export default function LessonPractica({ element, content, fetchPropElement, cle fetchPropElement(element.id); setMessage({ state: true, - value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } + value: { severity: 'success', summary: 'Успешно удалено!', detail: '' } }); } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Өчүрүүдө ката кетти' } + value: { severity: 'error', summary: 'Ошибка при удалении!', detail: '' } }); if (data.response.status) { showError(data.response.status); @@ -210,14 +210,14 @@ export default function LessonPractica({ element, content, fetchPropElement, cle clearValues(); setMessage({ state: true, - value: { severity: 'success', summary: 'Ийгиликтүү өзгөртүлдү!', detail: '' } + value: { severity: 'success', summary: 'Успешно изменено!', detail: '' } }); } else { setDocValue({ title: '', description: '', document: null, url: '' }); setEditingLesson({ title: '', description: '', document: null, url: '' }); setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Өзгөртүүдө ката кетти' } + value: { severity: 'error', summary: 'Ошибка при изменении!', detail: '' } }); if (data?.response?.status) { showError(data.response.status); @@ -234,7 +234,7 @@ export default function LessonPractica({ element, content, fetchPropElement, cle
    {docShow ? ( - + ) : ( document && (
    - { + { setDocValue((prev) => ({ ...prev, title: e.target.value })); setValue('title', e.target.value, { shouldValidate: true }); }} /> @@ -303,7 +303,7 @@ export default function LessonPractica({ element, content, fetchPropElement, cle { @@ -314,22 +314,22 @@ export default function LessonPractica({ element, content, fetchPropElement, cle {errors.usefulLinkNotReq?.message}
    )} - { + { setDocValue((prev) => ({ ...prev, description: e.target.value })) setValue('title', e.target.value, { shouldValidate: true }); }} className="w-full" /> {errors.title?.message}
    - {/*
    diff --git a/app/components/lessons/LessonVideo.tsx b/app/components/lessons/LessonVideo.tsx index 7272b317..8ce4190e 100644 --- a/app/components/lessons/LessonVideo.tsx +++ b/app/components/lessons/LessonVideo.tsx @@ -23,13 +23,6 @@ import { Dialog } from 'primereact/dialog'; import { FileWithPreview } from '@/types/fileuploadPreview'; export default function LessonVideo({ element, content, fetchPropElement, clearProp }: { element: mainStepsType; content: any; fetchPropElement: (id: number) => void; clearProp: boolean }) { - interface docValueType { - title: string; - description: string; - file: File | null; - document?: string; - } - interface contentType { course_id: number | null; created_at: string; @@ -52,13 +45,6 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP id: number; } - interface videoInsideType { - id: number; - is_link: boolean; - short_title: string; - title: string; - } - interface videoValueType { title: string; description: string | null; // вместо ? → строго string | null @@ -69,17 +55,11 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP cover: File | null; } - const { course_id } = useParams(); - - const router = useRouter(); - const media = useMediaQuery('(max-width: 640px)'); const fileUploadRef = useRef(null); // videos const [video, setVideo] = useState(); const [selectedCity, setSelectedCity] = useState({ name: '', status: true, id: 1 }); - const [videoSelect, setVideoSelect] = useState([]); - const [videoTypes, setVideoTypes] = useState([]); const [videoCall, setVideoCall] = useState(false); const [videoLink, setVideoLink] = useState(''); const [videoValue, setVideoValue] = useState<{ title: string; description: string; file: null; url: string; video_link: string; cover: File | null; link: null }>({ @@ -104,7 +84,6 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP const [selectType, setSelectType] = useState(''); const [selectId, setSelectId] = useState(null); const [contentShow, setContentShow] = useState(false); - const [videoVisible, setVideoVisible] = useState(false); const clearFile = () => { fileUploadRef.current?.clear(); @@ -137,7 +116,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP if (!value) { setMessage({ state: true, - value: { severity: 'error', summary: 'Видеону иштетүүдө ката', detail: '' } + value: { severity: 'error', summary: 'Ошибка при обработке видео', detail: '' } }); } @@ -155,7 +134,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP if (!videoId) { setMessage({ state: true, - value: { severity: 'error', summary: 'Видеону иштетүүдө ката', detail: '' } + value: { severity: 'error', summary: 'Ошибка при обработке видео', detail: '' } }); return null; // не удалось получить ID } @@ -205,7 +184,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинчерээк кайталаныз' } + value: { severity: 'error', summary: 'Ошибка!', detail: 'Повторите позже' } }); if (data?.response?.status) { showError(data.response.status); @@ -223,12 +202,12 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP clearValues(); setMessage({ state: true, - value: { severity: 'success', summary: 'Ийгиликтүү Кошулдуу!', detail: '' } + value: { severity: 'success', summary: 'Успешно добавлен!', detail: '' } }); } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Кошуу учурунда катаа кетти' } + value: { severity: 'error', summary: 'Ошибка при добавлении!', detail: '' } }); if (data.response.status) { showError(data.response.status); @@ -245,12 +224,12 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP fetchPropElement(element.id); setMessage({ state: true, - value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } + value: { severity: 'success', summary: 'Успешно удалено!', detail: '' } }); } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Өчүрүүдө ката кетти' } + value: { severity: 'error', summary: 'Ошибка при удалении!', detail: '' } }); if (data.response.status) { showError(data.response.status); @@ -268,13 +247,13 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP clearValues(); setMessage({ state: true, - value: { severity: 'success', summary: 'Ийгиликтүү өзгөртүлдү!', detail: '' } + value: { severity: 'success', summary: 'Успешно изменено!', detail: '' } }); } else { setEditingLesson(null); setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Өзгөртүүдө ката кетти' } + value: { severity: 'error', summary: 'Ошибка при изменении!', detail: '' } }); if (data?.response?.status) { showError(data.response.status); @@ -293,7 +272,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP { @@ -309,7 +288,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP { setVideoValue((prev) => ({ ...prev, title: e.target.value })); @@ -319,7 +298,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP {errors.title?.message} {additional.video && (
    - setVideoValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" /> + setVideoValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" />
    @@ -333,14 +312,14 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP )}
    - {/*
    @@ -371,7 +350,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP
    */} {videoShow ? ( - + ) : ( <> {!videoCall ? ( @@ -383,7 +362,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP cardValue={{ title: video.title, id: video.id, desctiption: video?.description || '', type: 'video', photo: video?.cover_url }} cardBg={'#ddc4f51a'} type={{ typeValue: 'video', icon: 'pi pi-video' }} - typeColor={'va r(--mainColor)'} + typeColor={'var(--mainColor)'} lessonDate={new Date(video.created_at).toISOString().slice(0, 10)} urlForPDF={() => ''} urlForDownload="" @@ -416,7 +395,6 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP }; useEffect(() => { - console.log('content', content); if (content) { setContentShow(true); setVideo(content); @@ -426,17 +404,12 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP }, [content]); useEffect(() => { - console.log('video', element); setVideoValue({ title: '', description: '', file: null, url: '', video_link: '', cover: null, link: null }); }, [element]); - useEffect(() => { - console.log('edititing', editingLesson); - }, [editingLesson]); - return (
    - handleUpdateVideo()} clearValues={clearValues} visible={visible} setVisible={setVisisble} start={false}> + handleUpdateVideo()} clearValues={clearValues} visible={visible} setVisible={setVisisble} start={false}>
    { @@ -445,7 +418,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP
    { @@ -479,7 +452,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP {/* )} */} { @@ -488,7 +461,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP }} /> {errors.title?.message} - setEditingLesson((prev) => prev && { ...prev, description: e.target.value })} className="w-full" /> + setEditingLesson((prev) => prev && { ...prev, description: e.target.value })} className="w-full" /> }
    diff --git a/app/components/tables/StreamList.tsx b/app/components/tables/StreamList.tsx index 9cd5f0c3..c4bc74ce 100644 --- a/app/components/tables/StreamList.tsx +++ b/app/components/tables/StreamList.tsx @@ -81,8 +81,6 @@ export default function StreamList({ setPendingChanges([]); if (data) { - console.log(data); - profilactor(data); setHasStreams(false); setStreams(data); @@ -90,7 +88,7 @@ export default function StreamList({ setHasStreams(true); setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Байланыш менен көйгөй' } + value: { severity: 'error', summary: 'Ошибка!', detail: 'Проблема с соединением' } }); if (data?.response?.status) { showError(data.response.status); @@ -106,12 +104,12 @@ export default function StreamList({ handleFetchStreams(); setMessage({ state: true, - value: { severity: 'success', summary: 'Ийгиликтүү кошулду!', detail: '' } + value: { severity: 'success', summary: 'Успешно добавлен!', detail: '' } }); } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Кошуу учурунда катаа кетти' } + value: { severity: 'error', summary: 'Ошибка при добавлении', detail: '' } }); if (data?.response?.status) { showError(data.response.status); @@ -157,7 +155,6 @@ export default function StreamList({ useEffect(() => { setDisplayStreams(pendingChanges); - console.log(pendingChanges); }, [pendingChanges]); useEffect(() => { @@ -305,7 +302,7 @@ export default function StreamList({ > {streams && streams.length > 0 ? ( - rowIndex + 1} header="Номер"> + rowIndex + 1} header="#"> {/* */}

    {rowData.subject_name.name_ru}

    }>
    @@ -380,7 +377,7 @@ export default function StreamList({ {hasStreams ? ( <> - + ) : (
    diff --git a/layout/AppFooter.tsx b/layout/AppFooter.tsx index 8561ab3b..e1da9dcc 100644 --- a/layout/AppFooter.tsx +++ b/layout/AppFooter.tsx @@ -7,7 +7,7 @@ import axiosInstance from '@/utils/axiosInstance'; const AppFooter = () => { const { layoutConfig } = useContext(LayoutContext); - const [univer, setUniver] = useState<{ address_kg: string; contact_kg: string, info_ru: string, info_en: string }>({ address_kg: '', contact_kg: '', info_ru: '', info_en:'' }); + const [univer, setUniver] = useState<{ address_ru: string; contact_ru: string, info_ru: string, info_en: string }>({ address_ru: '', contact_ru: '', info_ru: '', info_en:'' }); // dark mode // Logo @@ -24,25 +24,18 @@ const AppFooter = () => { useEffect(() => { const handleInfo = async ()=> { const data = await fetchInfo(); - console.log(data); - if(data){ setUniver({ - address_kg: data[0]?.address_kg, - contact_kg: data[0]?.contact_kg, + address_ru: data[0]?.address_ru, + contact_ru: data[0]?.contact_ru, info_ru: data[0]?.info_ru, info_en: data[0]?.info_en, - // contact_kg: data[0]?.contact_kg }); } } handleInfo(); }, []); - useEffect(() => { - console.log(univer); - }, [univer]); - return (
    @@ -50,36 +43,10 @@ const AppFooter = () => {
    Логотип
    - {univer.address_kg} - {univer.contact_kg} -
    -
    - {/*
    -
    - -

    - Курстар -

    -
    -
    - lorem ipsum - lorem ipsum - lorem ipsum + {univer.address_ru} + {univer.contact_ru}
    -
    -
    - -

    - Байланыш -

    -
    -
    - lorem ipsum - lorem ipsum - lorem ipsum -
    -
    */}

    ©{univer.info_ru} | {univer.info_en} IT Academy diff --git a/layout/AppMenu.tsx b/layout/AppMenu.tsx index 1eae498f..b498daf3 100644 --- a/layout/AppMenu.tsx +++ b/layout/AppMenu.tsx @@ -17,7 +17,6 @@ import { yupResolver } from '@hookform/resolvers/yup'; import { lessonSchema } from '@/schemas/lessonSchema'; import useErrorMessage from '@/hooks/useErrorMessage'; import { Button } from 'primereact/button'; -import Link from 'next/link'; const AppMenu = () => { const { user, setDeleteQuery, setUpdateeQuery, contextFetchThemes, contextThemes, contextFetchStudentThemes, contextStudentThemes, departament } = useContext(LayoutContext); @@ -43,8 +42,6 @@ const AppMenu = () => { const pathname = location; const { studentThemeCourse } = useParams(); const params = useParams(); - console.log(params); - const course_Id = params.course_Id; const id_kafedra = params?.id_kafedra ? params.id_kafedra : null; @@ -70,7 +67,7 @@ const AppMenu = () => { to: '/course' }, { - label: 'Темалар', + label: 'Темы', icon: 'pi pi-fw pi-calendar-clock', items: courseList?.length > 0 ? courseList : [] } @@ -78,8 +75,8 @@ const AppMenu = () => { : [] : user?.is_student ? [ - { label: 'Окуу планы', icon: 'pi pi-fw pi-calendar-clock', to: '/teaching' }, - pathname.startsWith('/teaching/lesson/') ? { label: 'Темалар', icon: 'pi pi-fw pi-book', items: themesStudentList?.length > 0 ? themesStudentList : [] } : { label: '' } + { label: 'План обучения', icon: 'pi pi-fw pi-calendar-clock', to: '/teaching' }, + pathname.startsWith('/teaching/lesson/') ? { label: 'Темы', icon: 'pi pi-fw pi-book', items: themesStudentList?.length > 0 ? themesStudentList : [] } : { label: '' } ] : []; @@ -134,13 +131,12 @@ const AppMenu = () => { const editing = async (id: number) => { const data = await fetchLessonShow(id); - console.log(data); if (data.lesson) { setEditingLesson({ title: data.lesson.title, sequence_number: data.lesson.sequence_number }); } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Кийинчерээк кайталаныз' } + value: { severity: 'error', summary: 'Ошибка!', detail: 'Повторите позже' } }); if (data?.response?.status) { showError(data.response.status); @@ -162,13 +158,13 @@ const AppMenu = () => { clearValues(); setMessage({ state: true, - value: { severity: 'success', summary: 'Ийгиликтүү кошулду!', detail: '' } + value: { severity: 'success', summary: 'Успешно добавлен!', detail: '' } }); } else { setEditingLesson(null); setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Кошуу учурунда катаа кетти' } + value: { severity: 'error', summary: 'Ошибка при добавлении!', detail: '' } }); if (data?.response?.status) { showError(data.response.status); @@ -179,17 +175,16 @@ const AppMenu = () => { const handleDeleteTheme = async (id: number) => { const data = await deleteTheme(id); if (data.success) { - console.warn('treu', data); contextFetchThemes(Number(course_Id), id_kafedra ? Number(id_kafedra) : null); setDeleteQuery(true); setMessage({ state: true, - value: { severity: 'success', summary: 'Ийгиликтүү өчүрүлдү!', detail: '' } + value: { severity: 'success', summary: 'Успешно удалено!', detail: '' } }); } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Өчүрүүдө ката кетти' } + value: { severity: 'error', summary: 'Ошибка при удалении!', detail: '' } }); if (data?.response?.status) { showError(data.response.status); @@ -206,13 +201,13 @@ const AppMenu = () => { clearValues(); setMessage({ state: true, - value: { severity: 'success', summary: 'Ийгиликтүү өзгөртүлдү!', detail: '' } + value: { severity: 'success', summary: 'Успешно удалено!', detail: '' } }); } else { setEditingLesson(null); setMessage({ state: true, - value: { severity: 'error', summary: 'Катаа!', detail: 'Өзгөртүүдө ката кетти' } + value: { severity: 'error', summary: 'Ошибка при изменении!', detail: '' } }); if (data?.response?.status) { showError(data.response.status); @@ -274,7 +269,7 @@ const AppMenu = () => { return ( { handleUpdate(); }} @@ -298,7 +293,7 @@ const AppMenu = () => {

    { @@ -313,7 +308,7 @@ const AppMenu = () => { { handleAddTheme(); }} @@ -335,7 +330,7 @@ const AppMenu = () => { />
    - Аталышы + Название { {pathname.startsWith('/course/') && (
    - +
    )} diff --git a/layout/AppTopbar.tsx b/layout/AppTopbar.tsx index 108705c2..a13fa854 100644 --- a/layout/AppTopbar.tsx +++ b/layout/AppTopbar.tsx @@ -48,7 +48,7 @@ const AppTopbar = forwardRef((props, ref) => { ) }, { - label: 'Чыгуу', + label: 'Выход', icon: 'pi pi-sign-out', className: 'text-[12px]', items: [], @@ -60,7 +60,7 @@ const AppTopbar = forwardRef((props, ref) => { ] } : { - label: 'Кирүү', + label: 'Вход', icon: 'pi pi-sign-in', items: [], // url: '/auth/login' @@ -70,7 +70,7 @@ const AppTopbar = forwardRef((props, ref) => { } }, { - label: 'ОшМУнун сайты', + label: 'Сайт ОшГУ', icon: '', items: [], url: 'https://oshsu.kg' @@ -92,7 +92,7 @@ const AppTopbar = forwardRef((props, ref) => { ) }, { - label: 'Чыгуу', + label: 'Выход', icon: 'pi pi-sign-out', items: [], command: () => { @@ -102,10 +102,6 @@ const AppTopbar = forwardRef((props, ref) => { } ]; - useEffect(() => { - console.log('dep', departament); - }, [departament]); - return (
    @@ -136,31 +132,8 @@ const AppTopbar = forwardRef((props, ref) => { {media ? ( <> ) : ( - //
    - {/* - */} - - ОшМУнун сайты - - {/* - Байланыш - */} + Сайт ОшГУ
    )} @@ -171,7 +144,7 @@ const AppTopbar = forwardRef((props, ref) => { ) : (
    - +
    )} */} diff --git a/utils/getConfirmOptions.tsx b/utils/getConfirmOptions.tsx index 1e5ea5d3..d29380d6 100644 --- a/utils/getConfirmOptions.tsx +++ b/utils/getConfirmOptions.tsx @@ -12,13 +12,13 @@ type ConfirmDialogOptions = { }; export const getConfirmOptions = (id: number, onDelete: (id: number)=> void ): ConfirmDialogOptions => ({ - message: 'Сиз чын эле өчүрүүнү каалайсызбы?', - header: 'Өчүрүү', + message: 'Вы действительно хотите удалить?', + header: 'Удаление', icon: 'pi pi-info-circle', defaultFocus: 'reject', acceptClassName: 'p-button-danger', - acceptLabel: 'Өчүрүү', - rejectLabel: 'Артка', + acceptLabel: 'Удалить', + rejectLabel: 'Назад', rejectClassName: 'p-button-secondary reject-button', accept: () => onDelete(id) }); From e0bee72368dc63078cb7c665ed84995f2e0375e0 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Thu, 18 Sep 2025 14:58:43 +0600 Subject: [PATCH 175/183] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=87=D0=B5=D0=BA=D0=B1=D0=BE=D0=BA?= =?UTF-8?q?=D1=81=D0=B0=20=D0=BD=D0=B0=20=D1=80=D0=B0=D1=81=D1=81=D0=BC?= =?UTF-8?q?=D0=BE=D1=82=D1=80=D0=B5=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(full-page)/auth/login/page.tsx | 2 +- app/(main)/course/page.tsx | 83 +++++++++++++++++++++++++--- app/components/tables/StreamList.tsx | 2 +- app/globals.css | 8 ++- services/courses.tsx | 18 +++++- services/query-tests.http | 12 +++- services/streams.tsx | 18 +----- 7 files changed, 110 insertions(+), 33 deletions(-) diff --git a/app/(full-page)/auth/login/page.tsx b/app/(full-page)/auth/login/page.tsx index 3413ddc3..5727b1af 100644 --- a/app/(full-page)/auth/login/page.tsx +++ b/app/(full-page)/auth/login/page.tsx @@ -122,7 +122,7 @@ const LoginPage = () => { {errors.password && {errors.password.message}}
    - +
    diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index 1be60b03..12dfa88c 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -1,7 +1,7 @@ 'use client'; import FormModal from '@/app/components/popUp/FormModal'; -import { addCourse, deleteCourse, fetchCourseInfo, fetchCourses, publishCourse, updateCourse } from '@/services/courses'; +import { addCourse, deleteCourse, fetchCourseInfo, fetchCourses, publishCourse, updateCourse, veryfyCourse } from '@/services/courses'; import { Button } from 'primereact/button'; import { FileUpload, FileUploadSelectEvent } from 'primereact/fileupload'; import { InputText } from 'primereact/inputtext'; @@ -30,6 +30,7 @@ import { ProgressSpinner } from 'primereact/progressspinner'; import { DataView } from 'primereact/dataview'; import { FileWithPreview } from '@/types/fileuploadPreview'; import { mainStreamsType } from '@/types/mainStreamsType'; +import { InputSwitch } from 'primereact/inputswitch'; export default function Course() { const { setMessage, setGlobalLoading, course, setCourses, contextFetchCourse, setMainCourseId } = useContext(LayoutContext); @@ -50,6 +51,7 @@ export default function Course() { }); const [activeIndex, setActiveIndex] = useState(0); const [imageState, setImageState] = useState(null); + const [pendingChanges, setPendingChanges] = useState<{ status: boolean }[]>([]); const [editingLesson, setEditingLesson] = useState({ title: '', description: '', @@ -72,6 +74,33 @@ export default function Course() { }, 1000); }; + const handleEdit = async (e: { checked: boolean }, item: { status: boolean; id: number }) => { + setSkeleton(true); + + const { id } = item; + const status = e.checked; + + const forSentStreams = { + course_id: id, + status: status ? 1 : 0 + }; + console.log(forSentStreams); + const data = await veryfyCourse(forSentStreams); + if (data.success) { + contextFetchCourse(1); + setMessage({ + state: true, + value: { severity: 'success', summary: 'Успешно добавлен!', detail: '' } + }); + } else { + setSkeleton(false); + setMessage({ + state: true, + value: { severity: 'error', summary: 'Не удалось добавить курс', detail: '' } + }); + } + }; + const fileUploadRef = useRef(null); const clearFile = () => { fileUploadRef.current?.clear(); @@ -307,10 +336,6 @@ export default function Course() { } }, [editMode]); - useEffect(() => { - console.log('forstream ', forStreamCount); - }, [forStreamCount]); - const itemTemplate = (shablonData: any) => { return (
    @@ -342,6 +367,27 @@ export default function Course() {
    {imageBodyTemplate(shablonData)}
    +
    +
    + Публикация: + {shablonData.is_published ? : } +
    +
    + На рассмотрение: + +
    +
    + <> @@ -593,7 +639,7 @@ export default function Course() { ) : (
    -
    +
    {/* info section */} {skeleton ? ( @@ -646,6 +692,25 @@ export default function Course() { )} > + ( + <> + + + )} + > ( <> @@ -669,7 +734,7 @@ export default function Course() { }} checked={forStreamId?.id === rowData.id} /> - Связать + Связать )} diff --git a/app/components/tables/StreamList.tsx b/app/components/tables/StreamList.tsx index c4bc74ce..fdf020b7 100644 --- a/app/components/tables/StreamList.tsx +++ b/app/components/tables/StreamList.tsx @@ -348,7 +348,7 @@ export default function StreamList({ )} {callIndex === 1 && ( -
    +
    {skeleton ? ( ) : ( diff --git a/app/globals.css b/app/globals.css index 367a019e..1c59471a 100644 --- a/app/globals.css +++ b/app/globals.css @@ -809,4 +809,10 @@ h1, h2, h3, h4, h5, h6 { /* маленькая адаптация */ @media (max-width: 380px){ :root { --size-w: 50px; --size-h: 20px } -} \ No newline at end of file +} + +@media (max-width: 640px){ + .main-bg{ + padding: 6px 6px; + } +} diff --git a/services/courses.tsx b/services/courses.tsx index 6e994234..24eef237 100644 --- a/services/courses.tsx +++ b/services/courses.tsx @@ -361,7 +361,23 @@ export const publishCourse = async (id_kafedra: number, id_teacher: number, cour return res.data; } catch (err) { - console.log('Ошибка при добавлении курса', err); + console.log('Ошибка', err); + return err; + } +}; + +export const veryfyCourse = async (value: {course_id: number, status: number}) => { + const formData = new FormData(); + formData.append('course_id', String(value.course_id)) + formData.append('status', String(value.status)) + + try { + const res = await axiosInstance.post(`/v1/teacher/courses/send/verify`, formData); + + return res.data; + } catch (err) { + console.log('Ошибка', err); return err; } }; + diff --git a/services/query-tests.http b/services/query-tests.http index b0e1de88..4bfc96fc 100644 --- a/services/query-tests.http +++ b/services/query-tests.http @@ -1,4 +1,4 @@ -@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5teWVkdS5vc2hzdS5rZy9wdWJsaWMvYXBpL21vb2MvbG9naW4iLCJpYXQiOjE3NTgwOTk4NTEsImV4cCI6MTc1ODEyODcxMSwibmJmIjoxNzU4MDk5ODUxLCJqdGkiOiJ1RGxQMjdYMHFLa2NVbXVDIiwic3ViIjoiMTE4MjkyIiwicHJ2IjoiMjNiZDVjODk0OWY2MDBhZGIzOWU3MDFjNDAwODcyZGI3YTU5NzZmNyJ9.MMZZJ2d06qK5Emrl-hUvMOSQqzvGLmMIMaTAoXc2Jmk +@token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5teWVkdS5vc2hzdS5rZy9wdWJsaWMvYXBpL21vb2MvbG9naW4iLCJpYXQiOjE3NTgxODA4NTIsImV4cCI6MTc1ODIwOTcxMiwibmJmIjoxNzU4MTgwODUyLCJqdGkiOiJoQk85b0xFMGZqcTdUTVlxIiwic3ViIjoiMTE4MjkyIiwicHJ2IjoiMjNiZDVjODk0OWY2MDBhZGIzOWU3MDFjNDAwODcyZGI3YTU5NzZmNyJ9.T9LuBqZLazYsdQhKkx3O3oO67U-4WE7gtlghMheWNO8 # ### # http://api.mooc.oshsu.kg/public/api/v1/open/video/types @@ -14,8 +14,14 @@ # ### # test -# POST https://api.mooc.oshsu.kg/public/api/v1/login?email=ajaparkulov@oshsu.kg&password=010270Ja -# Content-Type: application/json +POST https://api.mooc.oshsu.kg/public/api/v1/teacher/courses/send/verify?course_id=41&status=0 +Authorization: Bearer {{token}} +Content-Type: application/json + +{ + "course_id" : 41, + "status" : 0 +} # { # "email": "ajaparkulov@oshsu.kg", diff --git a/services/streams.tsx b/services/streams.tsx index 6d6de776..fbf87d44 100644 --- a/services/streams.tsx +++ b/services/streams.tsx @@ -1,12 +1,8 @@ import { streamsType } from '@/types/streamType'; import axiosInstance from '@/utils/axiosInstance'; -let url = ''; - // streams export const fetchStreams = async (id: number | null) => { - console.log(id); - try { const res = await axiosInstance.get(`v1/teacher/stream?course_id=${id}`); const data = await res.data; @@ -19,16 +15,6 @@ export const fetchStreams = async (id: number | null) => { // export const connectStreams = async (value: {stream: streamsType[]}) => { export const connectStreams = async (value: {course_id: number | null, stream: streamsType[]}) => { - console.log(value); - - // const formData = new FormData(); - // formData.append('title', value.title); - // formData.append('description', value.description); - // if (value.image instanceof File) { - // formData.append('image', value.image); - // } - // formData.append('video_url', value.video_url); - try { const res = await axiosInstance.post(`/v1/teacher/stream/store`, value); @@ -41,9 +27,7 @@ export const connectStreams = async (value: {course_id: number | null, stream: s // students stream -export const fetchStreamStudents = async (connect_id: number | null, stream_id: number | null) => { - console.log(connect_id, stream_id); - +export const fetchStreamStudents = async (connect_id: number | null, stream_id: number | null) => { try { const res = await axiosInstance.get(`v1/teacher/stream/students?connect_id=${connect_id}&stream_id=${stream_id}`); const data = await res.data; From 2352fd04adba1175a49f9877ed5f09a899783933 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Thu, 18 Sep 2025 15:20:35 +0600 Subject: [PATCH 176/183] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BF=D0=B0=D0=B4=D0=B4=D0=B8=D0=BD=D0=B3=D0=B8=20?= =?UTF-8?q?=D0=B2=20responzive.scss=20-=20content.scss?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/tables/StreamList.tsx | 2 +- styles/layout/_content.scss | 2 +- styles/layout/_responsive.scss | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/components/tables/StreamList.tsx b/app/components/tables/StreamList.tsx index fdf020b7..d193d11c 100644 --- a/app/components/tables/StreamList.tsx +++ b/app/components/tables/StreamList.tsx @@ -348,7 +348,7 @@ export default function StreamList({ )} {callIndex === 1 && ( -
    +
    {skeleton ? ( ) : ( diff --git a/styles/layout/_content.scss b/styles/layout/_content.scss index 2c40e1da..e1d57587 100644 --- a/styles/layout/_content.scss +++ b/styles/layout/_content.scss @@ -3,7 +3,7 @@ flex-direction: column; min-height: 100vh; justify-content: space-between; - padding: 7rem 2rem 2rem 4rem; + padding: 7rem 1rem 1rem 2rem !important; transition: margin-left $transitionDuration; } diff --git a/styles/layout/_responsive.scss b/styles/layout/_responsive.scss index 3498da0a..c51594c2 100644 --- a/styles/layout/_responsive.scss +++ b/styles/layout/_responsive.scss @@ -11,7 +11,7 @@ &.layout-overlay { .layout-main-container { margin-left: 0; - padding-left: 2rem; + padding-left: 1rem !important; } .layout-sidebar { @@ -43,7 +43,7 @@ .layout-main-container { margin-left: 0; - padding-left: 2rem; + padding-left: 1rem !important; } } } @@ -62,7 +62,7 @@ .layout-wrapper { .layout-main-container { margin-left: 0; - padding-left: 2rem; + padding-left: 1rem !important; } .layout-sidebar { From 980961eb345289dee078c3d1c3f6fe014af5afb2 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Thu, 18 Sep 2025 15:49:11 +0600 Subject: [PATCH 177/183] =?UTF-8?q?=D0=92=D0=B5=D1=80=D1=81=D1=82=D0=BA?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/course/page.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index 12dfa88c..13733f55 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -693,7 +693,7 @@ export default function Course() { )} > ( <> @@ -711,13 +711,6 @@ export default function Course() { )} > - - rowData.is_published ? : - } - > )} > + + rowData.is_published ? : + } + > ( From 7c5670e29cad1099644eaace73d9a36749943bbb Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Thu, 18 Sep 2025 16:22:09 +0600 Subject: [PATCH 178/183] =?UTF-8?q?=D0=9E=D0=B1=D1=80=D0=B0=D1=82=D0=BD?= =?UTF-8?q?=D0=BE=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8=D0=BB=20=D0=B3?= =?UTF-8?q?=D0=BB=D0=B0=D0=B2=D0=BD=D1=8B=D0=B5=20=D0=BF=D0=B0=D0=B4=D0=B4?= =?UTF-8?q?=D0=B8=D0=BD=D0=B3=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/lessons/LessonPractica.tsx | 157 +++++++++++++--------- app/globals.css | 2 +- hooks/useErrorMessage.tsx | 12 +- services/steps.tsx | 7 +- styles/layout/_content.scss | 2 +- styles/layout/_responsive.scss | 6 +- 6 files changed, 104 insertions(+), 82 deletions(-) diff --git a/app/components/lessons/LessonPractica.tsx b/app/components/lessons/LessonPractica.tsx index 739e7870..3fd936b4 100644 --- a/app/components/lessons/LessonPractica.tsx +++ b/app/components/lessons/LessonPractica.tsx @@ -30,6 +30,7 @@ export default function LessonPractica({ element, content, fetchPropElement, cle description: string; document: File | null; url: string; + score: number | null; } interface contentType { @@ -47,26 +48,19 @@ export default function LessonPractica({ element, content, fetchPropElement, cle url: string; } - const { course_id } = useParams(); - const router = useRouter(); const media = useMediaQuery('(max-width: 640px)'); const fileUploadRef = useRef(null); const showError = useErrorMessage(); const { setMessage } = useContext(LayoutContext); - const [editingLesson, setEditingLesson] = useState({ title: '', description: '', document: null, url: '' }); + const [editingLesson, setEditingLesson] = useState({ title: '', description: '', document: null, url: '',score: 0 }); const [visible, setVisisble] = useState(false); const [imageState, setImageState] = useState(null); const [contentShow, setContentShow] = useState(false); // doc const [document, setDocuments] = useState(); - const [docValue, setDocValue] = useState({ - title: '', - description: '', - document: null, - url: '' - }); + const [docValue, setDocValue] = useState({ title: '', description: '', document: null, url: '',score: 0 }); const [docShow, setDocShow] = useState(false); const [urlPDF, setUrlPDF] = useState(''); const [PDFVisible, setPDFVisible] = useState(false); @@ -109,8 +103,8 @@ export default function LessonPractica({ element, content, fetchPropElement, cle const clearValues = () => { clearFile(); - setDocValue({ title: '', description: '', document: null, url: '' }); - setEditingLesson({ title: '', description: '', document: null, url: '' }); + setDocValue({ title: '', description: '', document: null, url: '', score: 0 }); + setEditingLesson({ title: '', description: '', document: null, url: '', score: 0 }); setSelectId(null); setSelectType(''); }; @@ -149,8 +143,10 @@ export default function LessonPractica({ element, content, fetchPropElement, cle const editing = async () => { const data = await fetchElement(element.lesson_id, element.id); + console.log(data); + if (data.success) { - setEditingLesson({ title: data.content.title, document: null, description: data.content.description, url: '' }); + setEditingLesson({ title: data.content.title, document: null, description: data.content.description, url: '', score: data.step.score}); } else { setMessage({ state: true, @@ -174,7 +170,7 @@ export default function LessonPractica({ element, content, fetchPropElement, cle } else { setMessage({ state: true, - value: { severity: 'error', summary: 'Ошибка при добавлении!', detail: '' } + value: { severity: 'error', summary: 'Ошибка при добавлении!', detail: '' } }); if (data.response.status) { showError(data.response.status); @@ -213,8 +209,8 @@ export default function LessonPractica({ element, content, fetchPropElement, cle value: { severity: 'success', summary: 'Успешно изменено!', detail: '' } }); } else { - setDocValue({ title: '', description: '', document: null, url: '' }); - setEditingLesson({ title: '', description: '', document: null, url: '' }); + setDocValue({ title: '', description: '', document: null, url: '', score:0 }); + setEditingLesson({ title: '', description: '', document: null, url: '', score:0 }); setMessage({ state: true, value: { severity: 'error', summary: 'Ошибка при изменении!', detail: '' } @@ -256,47 +252,34 @@ export default function LessonPractica({ element, content, fetchPropElement, cle ) : (
    - { - setDocValue((prev) => ({ ...prev, title: e.target.value })); - setValue('title', e.target.value, { shouldValidate: true }); - }} /> + { + setDocValue((prev) => ({ ...prev, title: e.target.value })); + setValue('title', e.target.value, { shouldValidate: true }); + }} + />
    {errors.title?.message} {additional.doc && ( - //
    - // {}} - // accept="application/pdf" - // onSelect={(e) => - // setDocValue((prev) => ({ - // ...prev, - // document: e.files[0] - // })) - // } - // /> - //
    { - console.log(e.target.files); - const file = e.target.files?.[0]; - if(file){ - setDocValue((prev) => ({ - ...prev, - document: file - })) - } - }} - /> + type="file" + accept="application/pdf" + className="border rounded p-1" + onChange={(e) => { + console.log(e.target.files); + const file = e.target.files?.[0]; + if (file) { + setDocValue((prev) => ({ + ...prev, + document: file + })); + } + }} + /> )} {additional.doc && (
    @@ -314,12 +297,29 @@ export default function LessonPractica({ element, content, fetchPropElement, cle {errors.usefulLinkNotReq?.message}
    )} - { - setDocValue((prev) => ({ ...prev, description: e.target.value })) - setValue('title', e.target.value, { shouldValidate: true }); - }} className="w-full" /> + { + setDocValue((prev) => ({ ...prev, description: e.target.value })); + setValue('title', e.target.value, { shouldValidate: true }); + }} + className="w-full" + /> {errors.title?.message} +
    + Максимальный балл + { + setDocValue((prev) => prev && { ...prev, score: Number(e.target.value) }); + }} + /> +
    +
    {/*
    - {element?.step.type.name === 'document' && } - {element?.step.type.name === 'video' && } - {element?.step.type.name === 'test' && } - {element?.step.type.name === 'practical' && } - {element?.step.type.name === 'link' && } + {element?.step.type.name === 'document' && } + {element?.step.type.name === 'video' && } + {element?.step.type.name === 'test' && } + {element?.step.type.name === 'practical' && } + {element?.step.type.name === 'link' && } + {skeleton ? ( +
    + +
    + ) : ( + !hasSteps && ( +
    +
    + ) + )} + + )}
    ); } diff --git a/app/(main)/course/page.tsx b/app/(main)/course/page.tsx index 352cfe7d..7b91c512 100644 --- a/app/(main)/course/page.tsx +++ b/app/(main)/course/page.tsx @@ -95,10 +95,10 @@ export default function Course() { } else { console.log(data); setSkeleton(false); - if(data.response.data.cause){ + if (data.response.data.cause) { setMessage({ state: true, - value: { severity: 'error', summary: 'Ошибка!', detail: data.response.data.cause} + value: { severity: 'error', summary: 'Ошибка!', detail: data.response.data.cause } }); } else { setMessage({ @@ -408,9 +408,7 @@ export default function Course() { }} checked={forStreamId?.id === shablonData.id} /> - setActiveIndex(1)}> - Связать - + Связан ({shablonData.connects_count}) @@ -427,6 +425,10 @@ export default function Course() { ); }; + useEffect(()=> { + console.log(forStreamCount); + },[forStreamCount]); + const imagestateStyle = imageState || editingLesson.image ? 'flex gap-1 items-center justify-between flex-col sm:flex-row' : ''; const imageTitle = useShortText(typeof editingLesson.image === 'string' ? editingLesson.image : '', 20); @@ -701,7 +703,7 @@ export default function Course() { )} >
    На рассмотр.
    } + header={() =>
    На рассмотр.
    } style={{ margin: '0 3px', textAlign: 'center' }} body={(rowData) => ( <> @@ -719,6 +721,13 @@ export default function Course() { )} >
    + + rowData.is_published ? : + } + > - Связать + Связан ({rowData.connects_count}) )} > - - rowData.is_published ? : - } - > ( diff --git a/app/components/cards/LessonCard.tsx b/app/components/cards/LessonCard.tsx index 3926e375..649ab732 100644 --- a/app/components/cards/LessonCard.tsx +++ b/app/components/cards/LessonCard.tsx @@ -1,5 +1,3 @@ -import Redacting from '../popUp/Redacting'; -import { getRedactor } from '@/utils/getRedactor'; import { getConfirmOptions } from '@/utils/getConfirmOptions'; import { Button } from 'primereact/button'; import { faPlay } from '@fortawesome/free-solid-svg-icons'; @@ -99,18 +97,18 @@ export default function LessonCard({ style={{ backgroundColor: cardBg }} >
    -
    +
    {/*
    {!cardValue.photo && }
    */} -
    - {shortTitle} +
    {cardValue.score ? ( -
    - {!media && '/'} Балл: +
    + Балл: {`${cardValue.score}`}
    ) : ( '' )} + {shortTitle}
    {type.typeValue !== 'practica' &&
    {shortDoc}
    } @@ -130,7 +128,7 @@ export default function LessonCard({ ) )} {answers && ( -
    +
    {answers.map((item) => { return (
    diff --git a/app/components/lessons/LessonDocument.tsx b/app/components/lessons/LessonDocument.tsx index 7593ea76..640e12d9 100644 --- a/app/components/lessons/LessonDocument.tsx +++ b/app/components/lessons/LessonDocument.tsx @@ -207,7 +207,6 @@ export default function LessonDocument({ element, content, fetchPropElement, cle const handleUpdateDoc = async () => { const token = getToken('access_token'); const data = await updateDocument(token, document?.lesson_id ? Number(document?.lesson_id) : null, Number(selectId), element.type.id, element.type.id, editingLesson); - console.log(data); if (data?.success) { fetchPropElement(element.id); @@ -281,7 +280,7 @@ export default function LessonDocument({ element, content, fetchPropElement, cle { console.log(e.target.files); const file = e.target.files?.[0]; @@ -294,27 +293,30 @@ export default function LessonDocument({ element, content, fetchPropElement, cle }} /> - { - setDocValue((prev) => ({ ...prev, title: e.target.value })); - setValue('title', e.target.value, { shouldValidate: true }); - }} - /> - {errors.title?.message} +
    + { + setDocValue((prev) => ({ ...prev, title: e.target.value })); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} +
    {additional.doc && setDocValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" />}
    {/*
    */} - { - console.log(e.target.files); - const file = e.target.files?.[0]; - if (file) { - setEditingLesson( - (prev) => - prev && { - ...prev, - file: file - } - ); - } - }} - /> - {/* {String(editingLesson?.file[0].objectURL)} */} - { - setEditingLesson((prev) => prev && { ...prev, title: e.target.value }); - setValue('title', e.target.value, { shouldValidate: true }); - }} - /> - {errors.title?.message} - setEditingLesson((prev) => prev && { ...prev, description: e.target.value })} - className="w-full" - /> - - ) : selectType === 'video' ? ( - <> - ) : ( - '' - )} + { + console.log(e.target.files); + const file = e.target.files?.[0]; + if (file) { + setEditingLesson( + (prev) => + prev && { + ...prev, + file: file + } + ); + } + }} + /> + {/* {String(editingLesson?.file[0].objectURL)} */} +
    + { + setEditingLesson((prev) => prev && { ...prev, title: e.target.value }); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} +
    + setEditingLesson((prev) => prev && { ...prev, description: e.target.value })} + className="w-full" + />
    diff --git a/app/components/lessons/LessonLink.tsx b/app/components/lessons/LessonLink.tsx index ed55f375..b7c9f954 100644 --- a/app/components/lessons/LessonLink.tsx +++ b/app/components/lessons/LessonLink.tsx @@ -97,7 +97,7 @@ export default function LessonLink({ element, content, fetchPropElement, clearPr const editing = async () => { const data = await fetchElement(element.lesson_id, element.id); console.log(data); - + if (data.success) { setEditingLesson({ title: data.content.title, description: data.content.description, url: data.content.url }); } else { @@ -113,7 +113,7 @@ export default function LessonLink({ element, content, fetchPropElement, clearPr const handleAddLink = async () => { toggleSpinner(); - const data = await addLink(linkValue, element.lesson_id, element.type_id, element.id); + const data = await addLink(linkValue, element.lesson_id, element.type_id, element.id); if (data.success) { fetchPropElement(element.id); setMessage({ @@ -216,32 +216,31 @@ export default function LessonLink({ element, content, fetchPropElement, clearPr /> {errors.usefulLink?.message}
    - { - setLinkValue((prev) => ({ ...prev, title: e.target.value })); - setValue('title', e.target.value, { shouldValidate: true }); - }} - /> - {errors.title?.message} +
    + { + setLinkValue((prev) => ({ ...prev, title: e.target.value })); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} +
    {additional.link && setLinkValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" />}
    {/*
    @@ -266,50 +265,43 @@ export default function LessonLink({ element, content, fetchPropElement, clearPr return (
    - handleUpdateLink()} - clearValues={clearValues} - visible={visible} - setVisible={setVisisble} - start={false} - > + handleUpdateLink()} clearValues={clearValues} visible={visible} setVisible={setVisisble} start={false}>
    - { - setEditingLesson((prev) => ({ ...prev, url: e.target.value })); - setValue('usefulLink', e.target.value, { shouldValidate: true }); - }} - /> - {errors.usefulLink?.message} -
    { - setEditingLesson((prev) => ({ ...prev, title: e.target.value })); - setValue('title', e.target.value, { shouldValidate: true }); + setEditingLesson((prev) => ({ ...prev, url: e.target.value })); + setValue('usefulLink', e.target.value, { shouldValidate: true }); }} /> - {errors.title?.message} - {additional.link && setEditingLesson((prev) => ({ ...prev, description: e.target.value }))} className="w-full" />} + {errors.usefulLink?.message} +
    + { + setEditingLesson((prev) => ({ ...prev, title: e.target.value })); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} + {additional.link && setEditingLesson((prev) => ({ ...prev, description: e.target.value }))} className="w-full" />} -
    - {/*
    {!clearProp && linkSection()} diff --git a/app/components/lessons/LessonPractica.tsx b/app/components/lessons/LessonPractica.tsx index 3fd936b4..bb36c18f 100644 --- a/app/components/lessons/LessonPractica.tsx +++ b/app/components/lessons/LessonPractica.tsx @@ -46,6 +46,7 @@ export default function LessonPractica({ element, content, fetchPropElement, cle user_id: number; document_path: string; url: string; + score?: number | null; } const router = useRouter(); @@ -54,13 +55,13 @@ export default function LessonPractica({ element, content, fetchPropElement, cle const showError = useErrorMessage(); const { setMessage } = useContext(LayoutContext); - const [editingLesson, setEditingLesson] = useState({ title: '', description: '', document: null, url: '',score: 0 }); + const [editingLesson, setEditingLesson] = useState({ title: '', description: '', document: null, url: '', score: 0 }); const [visible, setVisisble] = useState(false); const [imageState, setImageState] = useState(null); const [contentShow, setContentShow] = useState(false); // doc const [document, setDocuments] = useState(); - const [docValue, setDocValue] = useState({ title: '', description: '', document: null, url: '',score: 0 }); + const [docValue, setDocValue] = useState({ title: '', description: '', document: null, url: '', score: 0 }); const [docShow, setDocShow] = useState(false); const [urlPDF, setUrlPDF] = useState(''); const [PDFVisible, setPDFVisible] = useState(false); @@ -144,9 +145,9 @@ export default function LessonPractica({ element, content, fetchPropElement, cle const editing = async () => { const data = await fetchElement(element.lesson_id, element.id); console.log(data); - + if (data.success) { - setEditingLesson({ title: data.content.title, document: null, description: data.content.description, url: '', score: data.step.score}); + setEditingLesson({ title: data.content.title, document: null, description: data.content.description, url: '', score: data.step.score }); } else { setMessage({ state: true, @@ -209,8 +210,8 @@ export default function LessonPractica({ element, content, fetchPropElement, cle value: { severity: 'success', summary: 'Успешно изменено!', detail: '' } }); } else { - setDocValue({ title: '', description: '', document: null, url: '', score:0 }); - setEditingLesson({ title: '', description: '', document: null, url: '', score:0 }); + setDocValue({ title: '', description: '', document: null, url: '', score: 0 }); + setEditingLesson({ title: '', description: '', document: null, url: '', score: 0 }); setMessage({ state: true, value: { severity: 'error', summary: 'Ошибка при изменении!', detail: '' } @@ -237,7 +238,7 @@ export default function LessonPractica({ element, content, fetchPropElement, cle status="working" onSelected={(id: number, type: string) => selectedForEditing(id, type)} onDelete={(id: number) => handleDeleteDoc(id)} - cardValue={{ title: document?.title, id: document.id, desctiption: document?.description || '', type: 'practica', url: document.url, document: document.document }} + cardValue={{ title: document?.title, id: document.id, desctiption: document?.description || '', type: 'practica', url: document.url, document: document.document, score: element?.score || 0 }} cardBg={'#ddc4f51a'} type={{ typeValue: 'practica', icon: 'pi pi-list' }} typeColor={'var(--mainColor)'} @@ -252,18 +253,50 @@ export default function LessonPractica({ element, content, fetchPropElement, cle ) : (
    - +
    + { + setDocValue((prev) => ({ ...prev, title: e.target.value })); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> +
    + {errors.title?.message} +
    +
    + Балл + { + setDocValue((prev) => prev && { ...prev, score: Number(e.target.value) }); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} +
    +
    +
    + { - setDocValue((prev) => ({ ...prev, title: e.target.value })); + setDocValue((prev) => ({ ...prev, description: e.target.value })); setValue('title', e.target.value, { shouldValidate: true }); }} + className="w-full" /> + {errors.title?.message}
    - {errors.title?.message} {additional.doc && ( {errors.usefulLinkNotReq?.message}
    )} - { - setDocValue((prev) => ({ ...prev, description: e.target.value })); - setValue('title', e.target.value, { shouldValidate: true }); - }} - className="w-full" - /> - {errors.title?.message} - -
    - Максимальный балл - { - setDocValue((prev) => prev && { ...prev, score: Number(e.target.value) }); - }} - /> -
    {/*
    - {typeof editingLesson?.document === 'string' && String(editingLesson?.document)} + }} + />
    )} {additional.doc && ( @@ -425,33 +456,10 @@ export default function LessonPractica({ element, content, fetchPropElement, cle />
    )} - { - setEditingLesson((prev) => ({ ...prev, description: e.target.value })); - setValue('title', e.target.value, { shouldValidate: true }); - }} - className="w-full" - /> - {errors.title?.message} - -
    - Максимальный балл - { - setEditingLesson((prev) => prev && { ...prev, score: Number(e.target.value) }); - }} - /> -
    - +
    - setAdditional((prev) => ({ ...prev, doc: !prev.doc }))}> + setAdditional((prev) => ({ ...prev, doc: !prev.doc }))}> Дополнительно {additional.doc ? '-' : '+'}
    diff --git a/app/components/lessons/LessonTest.tsx b/app/components/lessons/LessonTest.tsx index 12ef920b..a0efaa25 100644 --- a/app/components/lessons/LessonTest.tsx +++ b/app/components/lessons/LessonTest.tsx @@ -211,7 +211,7 @@ export default function LessonTest({ element, content, fetchPropElement, clearPr ) : (
    -
    +
    {errors.title?.message}
    - Балл + Балл { setTestValue((prev) => ({ ...prev, score: Number(e.target.value) })); @@ -330,7 +330,7 @@ export default function LessonTest({ element, content, fetchPropElement, clearPr {errors.title?.message}
    - Балл + Балл { return (
    - { - setAnswer((prev) => prev.map((ans, i) => (i === index ? { ...ans, is_correct: true } : { ...ans, is_correct: false }))); - }} - /> + void; clearProp: boolean }) { @@ -285,17 +282,20 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP
    - { - setVideoValue((prev) => ({ ...prev, title: e.target.value })); - setValue('title', e.target.value, { shouldValidate: true }); - }} - /> - {errors.title?.message} +
    + { + setVideoValue((prev) => ({ ...prev, title: e.target.value })); + setValue('title', e.target.value, { shouldValidate: true }); + }} + /> + {errors.title?.message} +
    {additional.video && (
    setVideoValue((prev) => ({ ...prev, description: e.target.value }))} className="w-full" /> @@ -314,12 +314,12 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP
    {/*
    @@ -371,7 +371,7 @@ export default function LessonVideo({ element, content, fetchPropElement, clearP ) ) : (
    -
    +
    setVideoCall(false)}>
    - {selectShow ? ( + {skeleton ? ( + + ) : selectShow ? (

    Факультеты временно не доступны

    ) : ( -
    +
    setSelected(e.value)} options={faculty} optionLabel="name_ru" className="w-[90%] overflow-x-auto" panelClassName="w-[50%] overflow-x-scroll" />
    )}
    {/* data table */} -
    -

    Кафедры

    - {facultyShow ? ( - - ) : ( - - rowIndex + 1} header="#" style={{ width: '20px' }}> - ( - - {rowData.name_ru} - - )} - > - - )} -
    + {skeleton ? ( + + ) : ( + !selectShow && ( +
    +

    Кафедры

    + {facultyShow ? ( + + ) : ( + + rowIndex + 1} header="#" style={{ width: '20px' }}> + ( + + {rowData.name_ru} + + )} + > + + )} +
    + ) + )}
    ); } diff --git a/app/(main)/videoLesson/[video_url]/page.tsx b/app/(main)/videoLesson/[video_url]/page.tsx new file mode 100644 index 00000000..ff74b539 --- /dev/null +++ b/app/(main)/videoLesson/[video_url]/page.tsx @@ -0,0 +1,10 @@ +'use client'; + +import { useParams } from "next/navigation"; + +export default function VideoLesson() { + const {video_url} = useParams(); + console.log(video_url); + + return
    page
    ; +} diff --git a/app/components/SessionManager.tsx b/app/components/SessionManager.tsx index bb89b461..37e7f1a9 100644 --- a/app/components/SessionManager.tsx +++ b/app/components/SessionManager.tsx @@ -74,8 +74,8 @@ const SessionManager = () => { if (!token && pathname !== '/' && pathname !== '/auth/login') { console.log('Перенеправляю в login'); - logout({ setUser, setGlobalLoading }); - window.location.href = '/auth/login'; + // logout({ setUser, setGlobalLoading }); + // window.location.href = '/auth/login'; return; } // setTimeout(() => { diff --git a/app/components/lessons/LessonInfoCard.tsx b/app/components/lessons/LessonInfoCard.tsx new file mode 100644 index 00000000..0a8211b2 --- /dev/null +++ b/app/components/lessons/LessonInfoCard.tsx @@ -0,0 +1,211 @@ +'use client'; + +import { useMediaQuery } from '@/hooks/useMediaQuery'; +import Link from 'next/link'; +import { useParams } from 'next/navigation'; +import { Dialog } from 'primereact/dialog'; +import { useState } from 'react'; + +export default function LessonInfoCard({ + type, + icon, + title, + description, + documentUrl, + link, + video_link, + videoStart, + test +}: { + type: string; + icon: string; + title?: string; + description?: string; + documentUrl?: { document: string | null; document_path: string }; + link?: string; + video_link?: string; + videoStart?: (id: string) => void; + test?: { content: string; answers: { id: number | null; text: string; is_correct: boolean }[]; score: number | null }; +}) { + const { id_kafedra } = useParams(); + // console.log(id_kafedra); + + const media = useMediaQuery('(max-width: 640px)'); + + const [testCall, setTestCall] = useState(false); + const [practicaCall, setPracticaCall] = useState(false); + + const docCard = ( +
    +
    + +
    +
    + {documentUrl?.document_path ? ( + documentUrl?.document_path?.length > 0 ? ( + 0 ? String(documentUrl?.document_path) : '#') : '#'} className="max-w-[800px] text-[16px] text-wrap break-all hover:underline" download target="_blank" rel="noopener noreferrer"> + {title} + + ) : ( + {title} + ) + ) : ( + {title} + )} +

    {description}

    +
    +
    + ); + + const linkCard = ( +
    +
    + +
    +
    + + {title} + + +

    {description}

    +
    +
    + ); + + const videoCard = ( +
    +
    + +
    +
    + videoStart && videoStart(video_link || '')} className="cursor-pointer max-w-[800px] text-[16px] text-wrap break-all hover:underline"> + {title} + + +

    {description}

    +
    +
    + ); + + const testCard = ( +
    +
    + +
    +
    + setTestCall(true)} className="cursor-pointer max-w-[800px] text-[16px] text-wrap break-all hover:underline"> + Тест + +
    +
    + ); + + const testInfo = ( +
    + {test?.answers && ( +
    +
    + {test?.content} +
    + {!media && '/'} Балл: + {`${test?.score}`} +
    +
    +
    + {test?.answers.map((item) => { + return ( +
    + +
    + ); + })} +
    +
    + )} +
    + ); + + const practicaCard = ( +
    +
    + +
    + setPracticaCall(true)} className="cursor-pointer max-w-[800px] text-[16px] text-wrap break-all hover:underline"> + Практическое задание + +
    + ); + + const docuemntPath = documentUrl && documentUrl?.document_path; + + const practicaInfo = ( +
    + ); + + return ( +
    + { + if (!testCall) return; + setTestCall(false); + }} + > +
    {testInfo}
    +
    + { + if (!practicaCall) return; + setPracticaCall(false); + }} + > +
    {practicaInfo}
    +
    +
    {type === "document" && docCard}
    +
    {type === 'link' && linkCard}
    +
    {type === 'video' && videoCard}
    +
    {type === 'test' && testCard}
    +
    {type === 'practical' && practicaCard}
    +
    + ); +} diff --git a/app/globals.css b/app/globals.css index 6117ed4f..16ef0340 100644 --- a/app/globals.css +++ b/app/globals.css @@ -12,6 +12,7 @@ --redBgColor: #EC272F1A; --greenColor: rgb(46, 192, 46); --greenBgColor: rgb(199, 231, 199); + --yellowColor: rgb(210, 239, 130); --titleColor: #21225F; --bodyColor: #555555; --whiteColor: #ffffff; diff --git a/layout/AppMenu.tsx b/layout/AppMenu.tsx index a5cb77b2..c2f070a2 100644 --- a/layout/AppMenu.tsx +++ b/layout/AppMenu.tsx @@ -102,7 +102,10 @@ const AppMenu = () => { { label: '', icon: 'pi pi-fw pi-arrow-left', - to: '/course' + to: '#', + command: ()=> { + router.back(); + } }, { label: '', diff --git a/services/steps.tsx b/services/steps.tsx index d65b7f2c..0cb25835 100644 --- a/services/steps.tsx +++ b/services/steps.tsx @@ -1,5 +1,3 @@ -import { CourseCreateType } from '@/types/courseCreateType'; -import { lessonStateType } from '@/types/lessonStateType'; import axiosInstance from '@/utils/axiosInstance'; let url = ''; @@ -424,4 +422,16 @@ export const updateLink = async (value: { url: string | null; title: string; des console.log('Ошибка при обновлении урока', err); return err; } +}; + +export const fetchDepartamentSteps = async (lesson_id: number, id_kafedra: number) => { + try { + const res = await axiosInstance.get(`/v1/teacher/controls/department/course/views?lesson_id=${lesson_id}&id_kafedra=${id_kafedra}`); + const data = await res.data; + + return data; + } catch (err) { + console.log('Ошибка загрузки:', err); + return err; + } }; \ No newline at end of file diff --git a/types/mainStepType.tsx b/types/mainStepType.tsx index bdf9a7d1..ef8c143a 100644 --- a/types/mainStepType.tsx +++ b/types/mainStepType.tsx @@ -5,6 +5,26 @@ export interface mainStepsType { user_id: number; lesson_id: number; step: number; - type: { active: true; created_at: string; id: 1; logo: string; modelName: string; name: string; title: string; updated_at: string }; + type: { active: true; created_at: string; id: 1; logo: string; description?: string | null; modelName: string; name: string; title: string; updated_at: string }; updated_at: string; } + +content: course_id: null; +created_at: '2025-09-12T06:24:54.000000Z'; +description: null; +document: '1_76_1757658294.pdf'; +id: 49; +lesson_id: 76; +status: true; +title: 'лекция'; +updated_at: '2025-09-12T06:24:54.000000Z'; +user_id: 1; + +type: active: true; +created_at: '2025-09-02T09:31:58.000000Z'; +id: 2; +logo: 'pi pi-folder'; +modelName: 'Document'; +name: 'document'; +title: 'Документ'; +updated_at: '2025-09-02T09:32:01.000000Z'; diff --git a/types/themeType.tsx b/types/themeType.tsx new file mode 100644 index 00000000..bb86ac5a --- /dev/null +++ b/types/themeType.tsx @@ -0,0 +1,12 @@ +interface themeType { + course_id: number; + created_at: string; + id: number; + is_deleted: boolean; + is_published: boolean; + published_at: boolean | null; + sequence_number: number; + title: string; + updated_at: string; + user_id: number; +} diff --git a/utils/axiosInstance.tsx b/utils/axiosInstance.tsx index f5267ed5..feb80829 100644 --- a/utils/axiosInstance.tsx +++ b/utils/axiosInstance.tsx @@ -22,18 +22,18 @@ axiosInstance.interceptors.response.use( const status = error.response?.status; if (status === 401) { console.warn('Неавторизован. Удаляю токен...'); - if (typeof window !== 'undefined') { - window.location.href = '/auth/login'; - localStorage.removeItem('userVisit'); - } - document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; + // if (typeof window !== 'undefined') { + // window.location.href = '/auth/login'; + // localStorage.removeItem('userVisit'); + // } + // document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; } if (status === 403) { console.warn('Не имеет доступ. Перенаправляю...'); - if (typeof window !== 'undefined') { - window.location.href = '/'; - } + // if (typeof window !== 'undefined') { + // window.location.href = '/'; + // } } if (status === 404) { From 6dc030a892a5d1ab324134144a3816d186c3fc15 Mon Sep 17 00:00:00 2001 From: BekzhanKutmanov Date: Wed, 17 Sep 2025 14:16:50 +0600 Subject: [PATCH 170/183] =?UTF-8?q?=D0=92=D0=B5=D1=80=D1=81=D1=82=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B7=D0=B0=D0=B2=20=D0=BA=D0=B0=D1=84=D0=B5=D0=B4?= =?UTF-8?q?=D1=80=D1=8B,=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA=20=D1=83=20=D1=83?= =?UTF-8?q?=D1=80=D0=BE=D0=BA=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../course/[course_Id]/[lesson_id]/page.tsx | 97 ++++++++++++----- .../faculty/[id_kafedra]/[course_id]/page.tsx | 102 +++++++++--------- app/(main)/faculty/page.tsx | 10 +- app/components/lessons/LessonInfoCard.tsx | 56 +++++----- app/globals.css | 2 +- layout/AppMenu.tsx | 5 +- layout/context/layoutcontext.tsx | 54 +++++----- services/courses.tsx | 2 + styles/layout/layout.scss | 3 +- styles/layout/primereact.css | 5 + utils/axiosInstance.tsx | 6 +- 11 files changed, 194 insertions(+), 148 deletions(-) create mode 100644 styles/layout/primereact.css diff --git a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx index 4b262c59..806eb4a8 100644 --- a/app/(main)/course/[course_Id]/[lesson_id]/page.tsx +++ b/app/(main)/course/[course_Id]/[lesson_id]/page.tsx @@ -39,18 +39,19 @@ export default function LessonStep() { const [selectedId, setSelectId] = useState(null); const [hasSteps, setHasSteps] = useState(false); const [themeNull, setThemeNull] = useState(false); - const [lesson_id, setLesson_id] = useState((param.lesson_id && Number(param.lesson_id)) || null); + // const [lesson_id, setLesson_id] = useState(null); + const [lesson_id, setLesson_id] = useState(null); const [sequence_number, setSequence_number] = useState(null); const [skeleton, setSkeleton] = useState(false); const [testovy, setTestovy] = useState(false); const router = useRouter(); const pathname = usePathname(); - const changeUrl = (lessonId: number) => { + const changeUrl = (lessonId: number | null) => { router.replace(`/course/${course_id}/${lessonId ? lessonId : null}`); }; - const handleShow = async (LessonId: number) => { + const handleShow = async (LessonId: number | null) => { const data = await fetchLessonShow(LessonId); if (data?.lesson) { @@ -86,7 +87,7 @@ export default function LessonStep() { } }; - const handleFetchSteps = async (lesson_id: number) => { + const handleFetchSteps = async (lesson_id: number | null) => { setSkeleton(true); const data = await fetchSteps(Number(lesson_id)); console.log('steps', data); @@ -189,6 +190,8 @@ export default function LessonStep() { useEffect(() => { setTestovy(true); + console.log('course-id', course_id); + contextFetchThemes(Number(course_id), null); }, [course_id]); @@ -220,38 +223,75 @@ export default function LessonStep() { // } // }, [contextThemes]); + // useEffect(() => { + // if (!contextThemes?.lessons?.data) return; + + // const lessons = contextThemes.lessons.data; + + // if (lessons.length > 0) { + // setThemeNull(false); + // console.warn(lessons, deleteQuery); + // let chosenId: number | null = null; + + // if (param.lesson_id === 'null' || deleteQuery) { + // console.log('var 1'); + // // alert(1); + + // chosenId = lessons[0].id; + // setDeleteQuery(false); + // } else { + // console.log('var 2'); + // // alert(2); + // chosenId = param.lesson_id ? Number(param.lesson_id) : lessons[0].id; + // } + // // alert(chosenId); + // setLesson_id(chosenId); + // } else { + // setThemeNull(true); + // } + + // setTestovy(false); + // setUpdateeQuery(false); + // }, [contextThemes, deleteQuery, param.lesson_id]); + + // useEffect(() => { + // console.warn('LESSONID ', lesson_id); + // if (!lesson_id) return; + + // handleShow(lesson_id); + // handleFetchSteps(lesson_id); + // changeUrl(lesson_id); + // }, [lesson_id]); + useEffect(() => { if (!contextThemes?.lessons?.data) return; const lessons = contextThemes.lessons.data; + if (lessons.length === 0) return; - if (lessons.length > 0) { - setThemeNull(false); - - let chosenId: number | null = null; - - if (param.lesson_id === 'null' || deleteQuery) { - chosenId = lessons[0].id; - setDeleteQuery(false); - } else { - chosenId = param.lesson_id ? Number(param.lesson_id) : lessons[0].id; - } + let chosenId: number | null = null; - setLesson_id(chosenId); + if (param.lesson_id && param.lesson_id !== 'null') { + const urlId = Number(param.lesson_id); + const exists = lessons.some((l) => l.id === urlId); + chosenId = exists ? urlId : lessons[0].id; } else { - setThemeNull(true); + chosenId = lessons[0].id; } - setTestovy(false); - setUpdateeQuery(false); + if (lesson_id !== chosenId) { + setLesson_id(chosenId); + } }, [contextThemes, deleteQuery, param.lesson_id]); - + useEffect(() => { - if (!lesson_id) return; - - handleShow(lesson_id); - handleFetchSteps(lesson_id); - changeUrl(lesson_id); + if (lesson_id && param.lesson_id !== String(lesson_id)) { + changeUrl(lesson_id); + } + if (lesson_id) { + handleFetchSteps(lesson_id); + handleShow(lesson_id); + } }, [lesson_id]); useEffect(() => { @@ -266,7 +306,10 @@ export default function LessonStep() { }; el.addEventListener('wheel', onWheel, { passive: false }); - return () => el.removeEventListener('wheel', onWheel); + return () => { + el.removeEventListener('wheel', onWheel); + setLesson_id(null); + }; }, []); const lessonInfo = ( @@ -301,7 +344,7 @@ export default function LessonStep() {