This commit is contained in:
2026-01-07 22:27:52 +03:00
parent 481e532dce
commit ff1174671a
55 changed files with 11201 additions and 858 deletions
+174 -96
View File
@@ -1,20 +1,22 @@
import React, { useEffect, useRef, useState } from "react";
import { Fragment, memo, useEffect, useRef, useState } from "react";
import {
BiDotsHorizontalRounded,
BiExit,
BiSolidUser,
BiStar,
BiPencil,
BiLink,
BiWrench,
} from "react-icons/bi";
import Logo from "../assets/logo.svg";
import {
Switch,
LoadingSpinner,
DatePicker,
TextPreloader,
// CoolMouseBg,
} from "../trash/trash-components";
import {
AdminReq,
AdminUserFRequest,
classUserFRequest,
RaspGet,
@@ -24,7 +26,7 @@ import { AnimatePresence, LayoutGroup, motion } from "motion/react";
import { apiFormatDate, isTimeBeforeNow } from "../trash/dengi";
import { useAtom } from "jotai";
import { API_BASE_URL } from "../App";
import { copyToClipboard, ifstyle } from "../trash/helpers";
import { copyDXLink, ifstyle } from "../trash/helpers";
export function DX4App({
classIDAtom,
@@ -41,6 +43,7 @@ export function DX4App({
const [pickmeMode, setPickmeMode] = useAtom(pickmeModeAtom);
const [rasp, setRasp] = useState([false, {}, 0]);
const [selectedDate, setSelectedDate] = useState(new Date());
const [managing, setManaging] = useState(false);
useEffect(() => {
let cancelled = false;
@@ -49,14 +52,14 @@ export function DX4App({
if (!dengi_cash.wasCached(cacheName)) {
RaspGet(API_BASE_URL, classId, selectedDate).then(
(d) => {
if (cancelled) return;
if (d[0] == 1) {
try {
setRasp([
true,
d[1]["data"] && d[1]["data"][0] ? d[1]["data"] : -322,
d[1]["type"] ? d[1]["type"] : -1,
]);
if (!cancelled)
setRasp([
true,
d[1]["data"] && d[1]["data"][0] ? d[1]["data"] : -322,
d[1]["type"] ? d[1]["type"] : -1,
]);
//console.log(d, d[1] ? d[1] : false);
dengi_cash.cache(cacheName, [
d[1]["data"] && d[1]["data"][0] ? d[1]["data"] : -322,
@@ -64,18 +67,20 @@ export function DX4App({
]);
} catch (exception) {
//console.error(exception);
setRasp([true, exception.toString()]);
if (!cancelled) setRasp([true, exception.toString()]);
}
} else {
setRasp([true, "ошибка: " + d[1].toString()]);
if (!cancelled) setRasp([true, "ошибка: " + d[1].toString()]);
//console.log(d[1]);
}
},
(err) => {
if (cancelled) return;
setRasp([true, "ошибка: " + err]);
}
);
} else {
if (cancelled) return;
setRasp([true, ...dengi_cash.get(cacheName)]);
}
@@ -133,8 +138,8 @@ export function DX4App({
else promises.push(Promise.resolve([0]));
if (adminId !== "")
promises.push(
dengi_cash.withCacheAsync(`adminfr-${adminId}`, async () =>
AdminUserFRequest(API_BASE_URL, adminId)
dengi_cash.withCacheAsync(`admindata-${adminId}`, async () =>
AdminReq(API_BASE_URL, adminId)
)
);
else promises.push(Promise.resolve([0]));
@@ -143,6 +148,8 @@ export function DX4App({
const classF = classRes[0] === 1 ? classRes[1].userclassname : false;
const adminF = adminRes[0] === 1 ? adminRes[1].adminName : false;
setUserFrNames({ admin: adminF, class: classF });
if (adminRes[0] === 1)
setManaging([-1, 2].includes(adminRes[1].adminPrems));
});
return () => {
cancelled = true;
@@ -259,28 +266,65 @@ export function DX4App({
</motion.div>
</motion.div>
{adminId != "" && managing && (
<motion.div
className="flex w-full justify-end"
layout="position"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
<a
// onClick={() => {
// setsetRoute(setRoute, `/editor/${apiFormatDate(selectedDate)}`);
// }}
href={`#/classprofile`}
className={`flex items-center justify-center border p-1 gap-2 px-2 cursor-pointer rounded-xl transition-colors ${
pickmeMode
? "border-pink-400/30 text-pink-300/80 lg:hover:text-white lg:hover:border-pink-500"
: "border-zinc-700 text-zinc-400 lg:hover:text-white lg:hover:border-white"
} `}
>
<BiWrench /> управление
</a>
</motion.div>
)}
<motion.div
layout="position"
className="flex flex-col gap-5 w-full not-md:px-4"
>
{adminId != "" && (
<motion.div className="flex w-full justify-end">
<div className="flex gap-2 items-center justify-center">
<DatePicker
date={selectedDate}
setDate={setSelectedDate}
indicator={
typeof rasp[2] == "number" && [1, 2].includes(rasp[2])
}
pink={pickmeMode}
/>
{adminId != "" && (
<a
// onClick={() => {
// setsetRoute(setRoute, `/editor/${apiFormatDate(selectedDate)}`);
// }}
href={`#/editor/${apiFormatDate(selectedDate)}`}
className="flex items-center justify-center border p-1 gap-2 px-2 lg:hover:border-white cursor-pointer rounded-xl transition-colors border-zinc-700 text-zinc-400 lg:hover:text-white"
href={
userFrNames.admin
? `#/editor/${apiFormatDate(selectedDate)}`
: undefined
}
className={`flex items-center justify-center p-1.5 cursor-pointer border transition *:transition-colors rounded-full ${
pickmeMode
? "border-pink-400/30 lg:hover:text-white hover:lg:bg-pink-500"
: "border-zinc-700 text-white lg:hover:text-black lg:hover:bg-white"
} `}
>
<BiPencil /> открыть в редакторе
{userFrNames.admin ? (
<BiPencil size={28} />
) : (
<LoadingSpinner size={28} />
)}
</a>
</motion.div>
)}
<DatePicker
date={selectedDate}
setDate={setSelectedDate}
indicator={typeof rasp[2] == "number" && [1, 2].includes(rasp[2])}
/>
)}
</div>
<div className="relative">
<LayoutGroup>
<RaspView {...{ rasp, pickmeMode, selectedDate }} />
@@ -306,7 +350,7 @@ export function DX4App({
</>
);
}
const CoolMainBg = React.memo(({ pickmeMode }) => {
const CoolMainBg = memo(({ pickmeMode }) => {
return (
<div
className={`fixed top-0 left-0 w-full h-full -z-5 overflow-hidden bg-gradient-to-br to-black to-50% bg-fixed
@@ -315,7 +359,7 @@ const CoolMainBg = React.memo(({ pickmeMode }) => {
);
});
export const RaspView = React.memo(({ rasp, pickmeMode, selectedDate }) => {
export const RaspView = memo(({ rasp, pickmeMode, selectedDate }) => {
return (
<AnimatePresence mode="popLayout">
{rasp[0] && typeof rasp[1] == "object" && (
@@ -324,10 +368,14 @@ export const RaspView = React.memo(({ rasp, pickmeMode, selectedDate }) => {
//layout="position"
key={selectedDate}
className={
"w-full border-collapse border-spacing-x-0 border-spacing-y-[10px] bg-gradient-to-br backdrop-blur-xl mb-10 " +
"w-full border-collapse border-spacing-x-0 border-spacing-y-[10px] bg-gradient-to-br mb-10 " +
(pickmeMode ? "from-pink-400/30" : "from-zinc-800/40") +
" to-transparent rounded-2xl selectable"
}
onContextMenu={(ev) => {
ev.preventDefault();
itemRedo.action();
}}
variants={{
hid: { opacity: 0 },
show: { opacity: 1 },
@@ -372,61 +420,7 @@ export const RaspView = React.memo(({ rasp, pickmeMode, selectedDate }) => {
</thead>
<tbody className="*:*:text-center *:*:py-2 *:not-last:border-b *:not-last:border-zinc-800 text-white">
{rasp[1].map((d, i) => (
<tr
//layout="position"
key={`table-header-${i}`}
className="transition-colors *:transition-colors lg:hover:bg-zinc-300/15"
//rasp-key={"rasp_key_" + i + Array(d).join()}
>
<td
//layout="position"
//layoutId={`table-num-${i}`}
>
{d["num"]}
</td>
<td
//layout="position"
//layoutId={`table-subj-${i}`}
>
{d.subject
.toString()
.split("\n")
.map((line, i, array) => (
<React.Fragment key={i}>
{line}
{i < array.length - 1 && <br />}
</React.Fragment>
)) || d.subject}
</td>
<td
//layout="position"
//layoutId={`table-cab-${i}`}
>
{d.cabinet
.toString()
.split("\n")
.map((line, i, array) => (
<React.Fragment key={i}>
{line}
{i < array.length - 1 && <br />}
</React.Fragment>
)) || d.cabinet}
</td>
<td>
<div
//layout="position"
//layoutId={`table-time-${i}`}
className="flex items-center justify-center min-h-9"
>
<div className="flex gap-0.5 flex-col *:block w-[70%] *:font-light">
<span className="text-left">
{d["t-begin"]}&nbsp;&nbsp;-
</span>
<span className="text-right">{d["t-end"]}</span>
</div>
</div>
</td>
</tr>
<TableItem {...{ d }} key={`table-header-${i}`} />
))}
</tbody>
</motion.table>
@@ -457,7 +451,7 @@ export const RaspView = React.memo(({ rasp, pickmeMode, selectedDate }) => {
);
});
const UserSettings = React.memo(
const UserSettings = memo(
({
openUserSettings,
userFrNames,
@@ -476,8 +470,8 @@ const UserSettings = React.memo(
<motion.div
className="flex flex-col bg-grain gap-2 bg-zinc-700/20 p-2 rounded-2xl"
variants={{
hidden: { opacity: 0, y: 10, filter: "blur(2px)" },
show: { opacity: 1, y: 0, filter: "blur(0)" },
hidden: { opacity: 0, y: 10 },
show: { opacity: 1, y: 0 },
}}
initial="hidden"
animate="show"
@@ -507,21 +501,20 @@ const UserSettings = React.memo(
// ],
[
<BiSolidUser />,
`админ ${
userFrNames["admin"] ? userFrNames["admin"] : adminId
}`,
() => {},
<>
{`админ ${userFrNames["admin"] ? userFrNames["admin"] : ""}`}
{!userFrNames["admin"] && <TextPreloader width="30px" />}
</>,
() => {
setsetRoute(setRoute, "/adminprofile");
},
adminId != "",
],
[
<BiLink />,
classLinkCopied ? "скопированно" : "коп. ссылку",
classLinkCopied ? "скопировано" : "коп. ссылку",
() => {
copyToClipboard(
location.href.substring(0, location.href.search("#")) +
"#//" +
classId
).then((a) => {
copyDXLink(classId).then((a) => {
if (a) {
setClassLinkCopied(true);
setTimeout(() => {
@@ -556,3 +549,88 @@ const UserSettings = React.memo(
);
}
);
class GlobalListener {
constructor() {
this.listeners = [];
}
listen(fn, once = false) {
const wrapper = (...args) => {
try {
fn(...args);
} finally {
if (once) this.listeners = this.listeners.filter((l) => l !== wrapper);
}
};
this.listeners.push(wrapper);
return () => {
this.listeners = this.listeners.filter((l) => l !== wrapper);
};
}
action(...args) {
this.listeners.slice().forEach((fn) => {
try {
fn(...args);
} catch (err) {
console.error(err);
}
});
}
}
const itemRedo = new GlobalListener();
const TableItem = memo(({ d }) => {
const [sel, setSel] = useState(false);
useEffect(() => {
itemRedo.listen(() => {
setSel(false);
});
}, []);
return (
<tr
//layout="position"
className={
"transition-colors *:transition-colors " +
(sel
? "bg-zinc-300/15 lg:hover:bg-zinc-300/20"
: " lg:hover:bg-zinc-300/15")
}
onClick={() => {
setSel(!sel);
}}
>
<td>{d["num"]}</td>
<td>
{d.subject
.toString()
.split("\n")
.map((line, i, array) => (
<Fragment key={i}>
{line}
{i < array.length - 1 && <br />}
</Fragment>
)) || d.subject}
</td>
<td>
{d.cabinet
.toString()
.split("\n")
.map((line, i, array) => (
<Fragment key={i}>
{line}
{i < array.length - 1 && <br />}
</Fragment>
)) || d.cabinet}
</td>
<td>
<div className="flex items-center justify-center min-h-9">
<div className="flex gap-0.5 flex-col *:block w-[70%] *:font-light">
<span className="text-left">{d["t-begin"]}&nbsp;&nbsp;-</span>
<span className="text-right">{d["t-end"]}</span>
</div>
</div>
</td>
</tr>
);
});