upd
This commit is contained in:
@@ -43,7 +43,7 @@ function Input({
|
||||
value={value}
|
||||
onChange={(e) => handleChange && handleChange(e.target.value)}
|
||||
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}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import Timeline from "./Timeline";
|
||||
import {
|
||||
format,
|
||||
getDate,
|
||||
getHours,
|
||||
getMonth,
|
||||
parseISO,
|
||||
setDate,
|
||||
setHours,
|
||||
setMonth,
|
||||
@@ -119,6 +121,15 @@ function Schedule({ selectedDay, slots, events }: Props) {
|
||||
// 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(() => {
|
||||
if (!builds) return;
|
||||
|
||||
@@ -169,13 +180,21 @@ function Schedule({ selectedDay, slots, events }: Props) {
|
||||
|
||||
<div className="flex mt-10">
|
||||
<div className="">
|
||||
{Array.from({ length: 24 }).map((_, index) => (
|
||||
{Array.from({ length: countWorkingHours() }).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-center h-[180px] w-[84px] border-r border-b border-[#DAE0E5]"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
))}
|
||||
@@ -194,6 +213,8 @@ function Schedule({ selectedDay, slots, events }: Props) {
|
||||
onChangeDraftMode={handleChangeDraftMode}
|
||||
onChangeStartAt={handleChangeStartAt}
|
||||
onChangeDuration={handleChangeDuration}
|
||||
startTime={company?.startTime}
|
||||
endTime={company?.endTime}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,15 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { useState, MouseEvent, useEffect, useRef } from "react";
|
||||
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 IScheduledSession from "../types/IScheduledSession";
|
||||
import useStore from "../stores/useStore";
|
||||
@@ -14,6 +22,8 @@ interface Props {
|
||||
timelineEvents: IScheduledSession[];
|
||||
slot: number;
|
||||
draftMode: boolean;
|
||||
startTime?: string; // 10:00
|
||||
endTime?: string;
|
||||
onChangeDraftMode: (draftMode: boolean) => void;
|
||||
onChangeStartAt: (startAt: Date) => void;
|
||||
onChangeDuration: (duration: number) => void;
|
||||
@@ -27,6 +37,8 @@ function Timeline({
|
||||
timelineEvents,
|
||||
slot,
|
||||
draftMode,
|
||||
startTime,
|
||||
endTime,
|
||||
onChangeDraftMode,
|
||||
onChangeStartAt,
|
||||
onChangeDuration,
|
||||
@@ -57,9 +69,14 @@ function Timeline({
|
||||
setDuration(30);
|
||||
}
|
||||
|
||||
setStartAt(
|
||||
addMinutes(startOfDay(new Date()), roundedY / minutePx).toISOString()
|
||||
);
|
||||
if (startTime) {
|
||||
const newStartOfDay = parseISO(startTime);
|
||||
setStartAt(addMinutes(newStartOfDay, roundedY / minutePx).toISOString());
|
||||
} else {
|
||||
setStartAt(
|
||||
addMinutes(startOfDay(new Date()), roundedY / minutePx).toISOString()
|
||||
);
|
||||
}
|
||||
|
||||
onChangeSlot(slot);
|
||||
}
|
||||
@@ -70,10 +87,6 @@ function Timeline({
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
const y = e.clientY - rect.top;
|
||||
|
||||
// if (y < startPosY + minutePx * 30) return;
|
||||
|
||||
// if (y < startPosY + minutePx * 30) return;
|
||||
|
||||
if (y < startPosY + minutePx * 30) {
|
||||
setDuration(30);
|
||||
} else {
|
||||
@@ -117,6 +130,15 @@ function Timeline({
|
||||
// );
|
||||
// }, [timelineEvents]);
|
||||
|
||||
function countWorkingHours() {
|
||||
if (!startTime || !endTime) return 24;
|
||||
|
||||
const startHour = getHours(parseISO(startTime));
|
||||
const endHour = getHours(parseISO(endTime));
|
||||
|
||||
return endHour - startHour;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!startAt) return;
|
||||
|
||||
@@ -150,7 +172,7 @@ function Timeline({
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={handleMouseUp}
|
||||
>
|
||||
{Array.from({ length: 24 }).map((_, index) => (
|
||||
{Array.from({ length: countWorkingHours() }).map((_, index) => (
|
||||
<TimelineSlot
|
||||
key={index}
|
||||
height={timelineSlotHeight}
|
||||
@@ -179,7 +201,12 @@ function Timeline({
|
||||
top: `${
|
||||
differenceInMinutes(
|
||||
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
|
||||
}px`,
|
||||
height: `${
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import Button from "../Button";
|
||||
import CloseIcon from "../icons/CloseIcon";
|
||||
import useModalStore from "../../stores/useModalStore";
|
||||
import DataField from "../DataField";
|
||||
import useAuthStore from "../../stores/useAuthStore";
|
||||
import MoreIcon from "../icons/MoreIcon";
|
||||
// import MoreIcon from "../icons/MoreIcon";
|
||||
import useStore from "../../stores/useStore";
|
||||
import ModalTabButton from "../ModalTabButton";
|
||||
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() {
|
||||
const [selectedTab, setSelectedTab] = useState<"company" | "employees">(
|
||||
@@ -16,6 +20,45 @@ function CompanyModal() {
|
||||
const { user } = useAuthStore();
|
||||
const { setModal } = useModalStore();
|
||||
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 (
|
||||
<div className="bg-white rounded-lg w-[624px] h-[408px] flex flex-col">
|
||||
@@ -44,9 +87,34 @@ function CompanyModal() {
|
||||
</div>
|
||||
</div>
|
||||
{selectedTab === "company" && (
|
||||
<div>
|
||||
<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>
|
||||
// <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">
|
||||
// {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 && (
|
||||
<img
|
||||
src={`/images/companies/${company.avatar}`}
|
||||
@@ -55,7 +123,9 @@ function CompanyModal() {
|
||||
/>
|
||||
)}
|
||||
</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="space-y-4">
|
||||
<DataField label="Сайт" value={company?.site} copyIcon />
|
||||
@@ -66,25 +136,35 @@ function CompanyModal() {
|
||||
<DataField label="Адрес" value={company?.address} copyIcon />
|
||||
</div>
|
||||
</div>
|
||||
{/* <div>
|
||||
<a href="#" className="text-[#49A1F5] text-xs">
|
||||
Сообщить о проблеме
|
||||
</a>
|
||||
</div> */}
|
||||
</div>
|
||||
<div className="border-b border-[#DAE0E5] space-y-4 pb-6">
|
||||
<p className="text-sm font-semibold">Рабочее время</p>
|
||||
<div className="flex items-end gap-4">
|
||||
<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 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>
|
||||
)}
|
||||
{selectedTab === "employees" && (
|
||||
@@ -125,7 +205,7 @@ function CompanyModal() {
|
||||
<div className="col-span-2">
|
||||
<p className="text-xs">{manager.phone}</p>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
{/* <div className="flex justify-center">
|
||||
{user?.role === "admin" && (
|
||||
<Button
|
||||
variant="tertiary"
|
||||
@@ -133,7 +213,7 @@ function CompanyModal() {
|
||||
onlyIcon
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,8 @@ interface ICompany {
|
||||
phone?: string;
|
||||
address?: string;
|
||||
avatar?: string;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
}
|
||||
|
||||
export default ICompany;
|
||||
|
||||
@@ -26,6 +26,12 @@ const companySchema = new Schema(
|
||||
address: {
|
||||
type: String,
|
||||
},
|
||||
startTime: {
|
||||
type: String,
|
||||
},
|
||||
endTime: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
|
||||
@@ -24,6 +24,23 @@ router.get("/:companyId", async (req, res) => {
|
||||
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) => {
|
||||
if (req.params.companyId != res.locals.user.companyId) {
|
||||
res.json({ error: "Access denied" });
|
||||
|
||||
Reference in New Issue
Block a user