This commit is contained in:
2024-12-20 20:14:02 +05:00
parent 07934c610c
commit f9dbbecea6
7 changed files with 191 additions and 38 deletions
+1 -1
View File
@@ -43,7 +43,7 @@ function Input({
value={value} value={value}
onChange={(e) => handleChange && handleChange(e.target.value)} onChange={(e) => handleChange && handleChange(e.target.value)}
onFocus={handleFocus} onFocus={handleFocus}
className={`px-3 py-2.5 outline-none rounded-lg ring-1 ring-[#DAE0E5] focus:ring-[#49A1F5] ring-inset transition-all text-sm ${className}`} className={`px-3 py-[9px] outline-none rounded-lg ring-1 ring-[#DAE0E5] focus:ring-[#49A1F5] ring-inset transition-all text-sm ${className}`}
/> />
); );
} }
+23 -2
View File
@@ -5,7 +5,9 @@ import Timeline from "./Timeline";
import { import {
format, format,
getDate, getDate,
getHours,
getMonth, getMonth,
parseISO,
setDate, setDate,
setHours, setHours,
setMonth, setMonth,
@@ -119,6 +121,15 @@ function Schedule({ selectedDay, slots, events }: Props) {
// setModal(null); // setModal(null);
} }
function countWorkingHours() {
if (!company?.startTime || !company?.endTime) return 24;
const startHour = getHours(parseISO(company.startTime));
const endHour = getHours(parseISO(company.endTime));
return endHour - startHour;
}
useEffect(() => { useEffect(() => {
if (!builds) return; if (!builds) return;
@@ -169,13 +180,21 @@ function Schedule({ selectedDay, slots, events }: Props) {
<div className="flex mt-10"> <div className="flex mt-10">
<div className=""> <div className="">
{Array.from({ length: 24 }).map((_, index) => ( {Array.from({ length: countWorkingHours() }).map((_, index) => (
<div <div
key={index} key={index}
className="flex items-center justify-center h-[180px] w-[84px] border-r border-b border-[#DAE0E5]" className="flex items-center justify-center h-[180px] w-[84px] border-r border-b border-[#DAE0E5]"
> >
<p className="font-semibold"> <p className="font-semibold">
{format(setHours(startOfDay(new Date()), index), "HH:mm")} {format(
setHours(
startOfDay(new Date()),
company?.startTime && company.endTime
? getHours(parseISO(company.startTime)) + index
: index
),
"HH:mm"
)}
</p> </p>
</div> </div>
))} ))}
@@ -194,6 +213,8 @@ function Schedule({ selectedDay, slots, events }: Props) {
onChangeDraftMode={handleChangeDraftMode} onChangeDraftMode={handleChangeDraftMode}
onChangeStartAt={handleChangeStartAt} onChangeStartAt={handleChangeStartAt}
onChangeDuration={handleChangeDuration} onChangeDuration={handleChangeDuration}
startTime={company?.startTime}
endTime={company?.endTime}
/> />
))} ))}
</div> </div>
+37 -10
View File
@@ -2,7 +2,15 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { useState, MouseEvent, useEffect, useRef } from "react"; import { useState, MouseEvent, useEffect, useRef } from "react";
import TimelineSlot from "./TimelineSlot"; import TimelineSlot from "./TimelineSlot";
import { addMinutes, differenceInMinutes, format, startOfDay } from "date-fns"; import {
addHours,
addMinutes,
differenceInMinutes,
format,
getHours,
parseISO,
startOfDay,
} from "date-fns";
import Button from "./Button"; import Button from "./Button";
import IScheduledSession from "../types/IScheduledSession"; import IScheduledSession from "../types/IScheduledSession";
import useStore from "../stores/useStore"; import useStore from "../stores/useStore";
@@ -14,6 +22,8 @@ interface Props {
timelineEvents: IScheduledSession[]; timelineEvents: IScheduledSession[];
slot: number; slot: number;
draftMode: boolean; draftMode: boolean;
startTime?: string; // 10:00
endTime?: string;
onChangeDraftMode: (draftMode: boolean) => void; onChangeDraftMode: (draftMode: boolean) => void;
onChangeStartAt: (startAt: Date) => void; onChangeStartAt: (startAt: Date) => void;
onChangeDuration: (duration: number) => void; onChangeDuration: (duration: number) => void;
@@ -27,6 +37,8 @@ function Timeline({
timelineEvents, timelineEvents,
slot, slot,
draftMode, draftMode,
startTime,
endTime,
onChangeDraftMode, onChangeDraftMode,
onChangeStartAt, onChangeStartAt,
onChangeDuration, onChangeDuration,
@@ -57,9 +69,14 @@ function Timeline({
setDuration(30); setDuration(30);
} }
setStartAt( if (startTime) {
addMinutes(startOfDay(new Date()), roundedY / minutePx).toISOString() const newStartOfDay = parseISO(startTime);
); setStartAt(addMinutes(newStartOfDay, roundedY / minutePx).toISOString());
} else {
setStartAt(
addMinutes(startOfDay(new Date()), roundedY / minutePx).toISOString()
);
}
onChangeSlot(slot); onChangeSlot(slot);
} }
@@ -70,10 +87,6 @@ function Timeline({
const rect = e.currentTarget.getBoundingClientRect(); const rect = e.currentTarget.getBoundingClientRect();
const y = e.clientY - rect.top; const y = e.clientY - rect.top;
// if (y < startPosY + minutePx * 30) return;
// if (y < startPosY + minutePx * 30) return;
if (y < startPosY + minutePx * 30) { if (y < startPosY + minutePx * 30) {
setDuration(30); setDuration(30);
} else { } else {
@@ -117,6 +130,15 @@ function Timeline({
// ); // );
// }, [timelineEvents]); // }, [timelineEvents]);
function countWorkingHours() {
if (!startTime || !endTime) return 24;
const startHour = getHours(parseISO(startTime));
const endHour = getHours(parseISO(endTime));
return endHour - startHour;
}
useEffect(() => { useEffect(() => {
if (!startAt) return; if (!startAt) return;
@@ -150,7 +172,7 @@ function Timeline({
onMouseMove={handleMouseMove} onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp} onMouseUp={handleMouseUp}
> >
{Array.from({ length: 24 }).map((_, index) => ( {Array.from({ length: countWorkingHours() }).map((_, index) => (
<TimelineSlot <TimelineSlot
key={index} key={index}
height={timelineSlotHeight} height={timelineSlotHeight}
@@ -179,7 +201,12 @@ function Timeline({
top: `${ top: `${
differenceInMinutes( differenceInMinutes(
new Date(event.startAt), new Date(event.startAt),
startOfDay(new Date(event.startAt)) startTime
? addHours(
startOfDay(new Date(event.startAt)),
getHours(parseISO(startTime))
)
: startOfDay(new Date(event.startAt))
) * minutePx ) * minutePx
}px`, }px`,
height: `${ height: `${
+105 -25
View File
@@ -1,13 +1,17 @@
import { useState } from "react"; import { useEffect, useState } from "react";
import Button from "../Button"; import Button from "../Button";
import CloseIcon from "../icons/CloseIcon"; import CloseIcon from "../icons/CloseIcon";
import useModalStore from "../../stores/useModalStore"; import useModalStore from "../../stores/useModalStore";
import DataField from "../DataField"; import DataField from "../DataField";
import useAuthStore from "../../stores/useAuthStore"; import useAuthStore from "../../stores/useAuthStore";
import MoreIcon from "../icons/MoreIcon"; // import MoreIcon from "../icons/MoreIcon";
import useStore from "../../stores/useStore"; import useStore from "../../stores/useStore";
import ModalTabButton from "../ModalTabButton"; import ModalTabButton from "../ModalTabButton";
import AddManagerModal from "./AddManagerModal"; import AddManagerModal from "./AddManagerModal";
import Input from "../Input";
import api from "../../utils/api";
import toast from "react-hot-toast";
import { format, parse, parseISO } from "date-fns";
function CompanyModal() { function CompanyModal() {
const [selectedTab, setSelectedTab] = useState<"company" | "employees">( const [selectedTab, setSelectedTab] = useState<"company" | "employees">(
@@ -16,6 +20,45 @@ function CompanyModal() {
const { user } = useAuthStore(); const { user } = useAuthStore();
const { setModal } = useModalStore(); const { setModal } = useModalStore();
const { company, managers } = useStore(); const { company, managers } = useStore();
const [startTime, setStartTime] = useState<string>(
company?.startTime ? format(parseISO(company.startTime), "HH:mm") : ""
);
const [endTime, setEndTime] = useState<string>(
company?.endTime ? format(parseISO(company.endTime), "HH:mm") : ""
);
async function handleClickSaveWorkingHours() {
if (!startTime || !endTime) return;
try {
await api
.put(`companies/${company?.id}`, {
json: {
startTime: parse(startTime, "HH:mm", new Date()),
endTime: parse(endTime, "HH:mm", new Date()),
},
})
.json();
toast.success("Изменения сохранены");
setModal(null);
setTimeout(() => {
window.location.reload();
}, 2000);
} catch (error) {
toast.error("Что-то пошло не так");
}
}
useEffect(() => {
if (startTime) {
setStartTime((prev) => `${prev.split(":")[0]}:00`);
}
if (endTime) {
setEndTime((prev) => `${prev.split(":")[0]}:00`);
}
}, [startTime, endTime]);
return ( return (
<div className="bg-white rounded-lg w-[624px] h-[408px] flex flex-col"> <div className="bg-white rounded-lg w-[624px] h-[408px] flex flex-col">
@@ -44,9 +87,34 @@ function CompanyModal() {
</div> </div>
</div> </div>
{selectedTab === "company" && ( {selectedTab === "company" && (
<div> // <div>
<div className="flex gap-6 p-6 border-b border-[#DAE0E5]"> // <div className="flex gap-6 p-6 border-b border-[#DAE0E5]">
<div className="min-w-[88px] min-h-[88px] w-[88px] h-[88px] bg-[#E6ECF2] rounded-full"> // <div className="min-w-[88px] min-h-[88px] w-[88px] h-[88px] bg-[#E6ECF2] rounded-full">
// {company?.avatar && (
// <img
// src={`/images/companies/${company.avatar}`}
// alt=""
// className="pointer-events-none"
// />
// )}
// </div>
// <div className="w-full space-y-6">
// <div className="grid grid-cols-2 gap-6">
// <div className="space-y-4">
// <DataField label="Сайт" value={company?.site} copyIcon />
// <DataField label="Телефон" value={company?.phone} copyIcon />
// </div>
// <div className="space-y-4">
// <DataField label="Email" value={company?.email} copyIcon />
// <DataField label="Адрес" value={company?.address} copyIcon />
// </div>
// </div>
// </div>
// </div>
// </div>
<div className="flex gap-10 p-6">
<div className="">
<div className="min-w-[88px] min-h-[88px] w-[88px] h-[88px] rounded-full bg-[#E6ECF2]">
{company?.avatar && ( {company?.avatar && (
<img <img
src={`/images/companies/${company.avatar}`} src={`/images/companies/${company.avatar}`}
@@ -55,7 +123,9 @@ function CompanyModal() {
/> />
)} )}
</div> </div>
<div className="w-full space-y-6"> </div>
<div className="w-full space-y-6">
<div className="border-b border-[#DAE0E5] pb-6">
<div className="grid grid-cols-2 gap-6"> <div className="grid grid-cols-2 gap-6">
<div className="space-y-4"> <div className="space-y-4">
<DataField label="Сайт" value={company?.site} copyIcon /> <DataField label="Сайт" value={company?.site} copyIcon />
@@ -66,25 +136,35 @@ function CompanyModal() {
<DataField label="Адрес" value={company?.address} copyIcon /> <DataField label="Адрес" value={company?.address} copyIcon />
</div> </div>
</div> </div>
{/* <div> </div>
<a href="#" className="text-[#49A1F5] text-xs"> <div className="border-b border-[#DAE0E5] space-y-4 pb-6">
Сообщить о проблеме <p className="text-sm font-semibold">Рабочее время</p>
</a> <div className="flex items-end gap-4">
</div> */} <div className="flex items-center gap-2">
<div className="space-y-1">
<p className="text-xs text-[#77828C]">Начало</p>
<Input
type="time"
value={startTime}
handleChange={(value) => setStartTime(value)}
/>
</div>
<p className="text-[#77828C] mt-4">-</p>
<div className="space-y-1">
<p className="text-xs text-[#77828C]">Конец</p>
<Input
type="time"
value={endTime}
handleChange={(value) => setEndTime(value)}
/>
</div>
</div>
<Button size="medium" onClick={handleClickSaveWorkingHours}>
Сохранить
</Button>
</div>
</div> </div>
</div> </div>
{/* <div className="p-6 space-y-4">
<div className="space-y-4">
<p className="text-xs font-semibold">Проекты</p>
{Array.from({ length: 3 }).map((_, index) => (
<div key={index} className="flex items-center gap-2">
<div className="w-8 h-8 bg-[#E6ECF2] rounded-full"></div>
<p className="text-xs font-semibold"></p>
</div>
))}
</div>
<div className=""></div>
</div> */}
</div> </div>
)} )}
{selectedTab === "employees" && ( {selectedTab === "employees" && (
@@ -125,7 +205,7 @@ function CompanyModal() {
<div className="col-span-2"> <div className="col-span-2">
<p className="text-xs">{manager.phone}</p> <p className="text-xs">{manager.phone}</p>
</div> </div>
<div className="flex justify-center"> {/* <div className="flex justify-center">
{user?.role === "admin" && ( {user?.role === "admin" && (
<Button <Button
variant="tertiary" variant="tertiary"
@@ -133,7 +213,7 @@ function CompanyModal() {
onlyIcon onlyIcon
/> />
)} )}
</div> </div> */}
</div> </div>
))} ))}
</div> </div>
+2
View File
@@ -7,6 +7,8 @@ interface ICompany {
phone?: string; phone?: string;
address?: string; address?: string;
avatar?: string; avatar?: string;
startTime?: string;
endTime?: string;
} }
export default ICompany; export default ICompany;
+6
View File
@@ -26,6 +26,12 @@ const companySchema = new Schema(
address: { address: {
type: String, type: String,
}, },
startTime: {
type: String,
},
endTime: {
type: String,
},
}, },
{ {
timestamps: true, timestamps: true,
+17
View File
@@ -24,6 +24,23 @@ router.get("/:companyId", async (req, res) => {
res.json(company); res.json(company);
}); });
router.put("/:companyId", async (req, res) => {
if (req.params.companyId != res.locals.user.companyId) {
res.json({ error: "Access denied" });
return;
}
const company = await Company.findByIdAndUpdate(
req.params.companyId,
req.body,
{
new: true,
}
);
res.json(company);
});
router.get("/:companyId/builds", async (req, res) => { router.get("/:companyId/builds", async (req, res) => {
if (req.params.companyId != res.locals.user.companyId) { if (req.params.companyId != res.locals.user.companyId) {
res.json({ error: "Access denied" }); res.json({ error: "Access denied" });