dx4 v2
This commit is contained in:
+174
-96
@@ -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"]} -
|
||||
</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"]} -</span>
|
||||
<span className="text-right">{d["t-end"]}</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user