diff --git a/client/.env b/client/.env index 4918fbd..0af9df1 100644 --- a/client/.env +++ b/client/.env @@ -1 +1 @@ -VITE_SERVER_URL=http://192.168.1.170:3000 \ No newline at end of file +VITE_SERVER_URL=http://192.168.1.170:3001 \ No newline at end of file diff --git a/client/package.json b/client/package.json index 17bf2f3..fdcb3f5 100644 --- a/client/package.json +++ b/client/package.json @@ -21,6 +21,7 @@ "zustand": "^4.4.1" }, "devDependencies": { + "@types/node": "^20.8.7", "@types/react": "^18.2.15", "@types/react-datepicker": "^4.19.0", "@types/react-dom": "^18.2.7", diff --git a/client/src/components/modals/CreateSchedule.tsx b/client/src/components/modals/CreateSchedule.tsx index 45e883c..6fc76c5 100644 --- a/client/src/components/modals/CreateSchedule.tsx +++ b/client/src/components/modals/CreateSchedule.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable no-irregular-whitespace */ import { ru } from "date-fns/locale"; @@ -13,24 +14,29 @@ import Input from "../Input"; import { addMonths, addWeeks, + areIntervalsOverlapping, differenceInDays, eachMinuteOfInterval, endOfDay, format, parse, + parseISO, startOfDay, + subDays, } from "date-fns"; import api from "../../utils/api"; interface CreateScheduleProps { companyId: string; buildId: string; + schedules?: any[]; handleCreate: () => void; } function CreateSchedule({ companyId, buildId, + schedules, handleCreate, }: CreateScheduleProps) { const setModal = useModalStore((state) => state.setModal); @@ -50,9 +56,12 @@ function CreateSchedule({ setStartDate(startOfDay(date)); setEndDate( endOfDay( - scheduleDuration !== 3 - ? addWeeks(date, scheduleDuration) - : addMonths(date, 1) + subDays( + scheduleDuration !== 3 + ? addWeeks(date, scheduleDuration) + : addMonths(date, 1), + 1 + ) ) ); }, [date, scheduleDuration]); @@ -102,6 +111,25 @@ function CreateSchedule({ } async function handleClickCreateSchedule() { + if (!startDate || !endDate) return; + + if ( + schedules?.some((schedule) => + areIntervalsOverlapping( + { start: startDate, end: endDate }, + { + start: parseISO(schedule.startDate), + end: parseISO(schedule.endDate), + } + ) + ) + ) { + alert( + "Данные даты пересекаются с другим расписанием! Выберите другие даты." + ); + return; + } + await createSchedule(); handleCreate(); setModal(null); diff --git a/client/src/pages/DashboardPage.tsx b/client/src/pages/DashboardPage.tsx index 82856dc..9cf96bb 100644 --- a/client/src/pages/DashboardPage.tsx +++ b/client/src/pages/DashboardPage.tsx @@ -512,6 +512,7 @@ function DashboardPage() { ) diff --git a/client/vite.config.ts b/client/vite.config.ts index 861b04b..1b7a244 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -1,7 +1,20 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react-swc' +import { defineConfig, loadEnv } from "vite"; +import react from "@vitejs/plugin-react-swc"; // https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react()], -}) +export default ({ mode }) => { + process.env = { ...process.env, ...loadEnv(mode, process.cwd()) }; + const config = { + plugins: [react()], + server: { + proxy: { + "/api": { + target: process.env.VITE_SERVER_URL, + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, ""), + }, + }, + }, + }; + return defineConfig(config); +}; diff --git a/client/yarn.lock b/client/yarn.lock index 9b6f8dc..f2c22b8 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -321,6 +321,13 @@ resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz" integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== +"@types/node@^20.8.7": + version "20.8.7" + resolved "https://registry.npmjs.org/@types/node/-/node-20.8.7.tgz" + integrity sha512-21TKHHh3eUHIi2MloeptJWALuCu5H7HQTdTrWIFReA8ad+aggoX+lRes3ex7/FtpC+sVUpFMQ+QTfYr74mruiQ== + dependencies: + undici-types "~5.25.1" + "@types/prop-types@*": version "15.7.5" resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz" @@ -1717,6 +1724,11 @@ typescript@^5.0.2: resolved "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz" integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== +undici-types@~5.25.1: + version "5.25.3" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz" + integrity sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA== + update-browserslist-db@^1.0.11: version "1.0.11" resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz" diff --git a/server/.env b/server/.env index b2b3968..4969e48 100644 --- a/server/.env +++ b/server/.env @@ -1,3 +1,3 @@ -PORT=3000 +PORT=3001 MONGO_URI=mongodb://root:p62Z!ZatgY25@194.26.138.94:27017/ JWT_SECRET=yDcdWJgvlj2bJAuovYfQHTvtc3U9xQPw \ No newline at end of file diff --git a/server/ecosystem.config.cjs b/server/ecosystem.config.cjs new file mode 100644 index 0000000..cfc51eb --- /dev/null +++ b/server/ecosystem.config.cjs @@ -0,0 +1,10 @@ +module.exports = { + apps: [ + { + name: "crm.stream.graff.tech-server", + exec_mode: "cluster", + script: "yarn", + args: "start", + }, + ], +}; diff --git a/server/src/index.ts b/server/src/index.ts index 6fc685b..002f2a9 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -10,6 +10,7 @@ import companiesRouter from "./routes/companies.js"; import scheduledSessionsRouter from "./routes/scheduledSessions.js"; import usersRouter from "./routes/users.js"; import buildsRouter from "./routes/builds.js"; +import actionsRouter from "./routes/actions.js"; const app = express(); const port = process.env.PORT || 3000; @@ -21,6 +22,7 @@ app.use(cors()); app.use("/login", loginRouter); app.use("/registration", registrationRouter); +app.use("/actions", actionsRouter); app.use("/app", authMiddleware, appRouter); app.use("/companies", authMiddleware, companiesRouter); app.use("/users", authMiddleware, usersRouter); diff --git a/server/src/models/Action.ts b/server/src/models/Action.ts new file mode 100644 index 0000000..a6689b0 --- /dev/null +++ b/server/src/models/Action.ts @@ -0,0 +1,23 @@ +import { model, Schema } from "mongoose"; + +const actionSchema = new Schema( + { + sessionId: { + type: Schema.Types.ObjectId, + required: true, + }, + pointName: { + type: String, + required: true, + }, + }, + { + timestamps: true, + toJSON: { virtuals: true }, + toObject: { virtuals: true }, + } +); + +const Action = model("Action", actionSchema); + +export default Action; diff --git a/server/src/routes/actions.ts b/server/src/routes/actions.ts new file mode 100644 index 0000000..9a5e527 --- /dev/null +++ b/server/src/routes/actions.ts @@ -0,0 +1,25 @@ +import { Router } from "express"; +import Action from "../models/Action.js"; + +const actionsRouter = Router(); + +actionsRouter.post("/", async (req, res) => { + if (!req.body.sessionId) { + res.json({ error: "'sessionId' parameter is required" }); + return; + } + + if (!req.body.pointName) { + res.json({ error: "'pointName' parameter is required" }); + return; + } + + const action = await Action.create({ + sessionId: req.body.sessionId, + pointName: req.body.pointName, + }); + + res.json(action); +}); + +export default actionsRouter;