Showing
29 changed files
with
1038 additions
and
180 deletions
This diff is collapsed. Click to expand it.
... | @@ -9,10 +9,12 @@ | ... | @@ -9,10 +9,12 @@ |
9 | "axios": "^0.27.2", | 9 | "axios": "^0.27.2", |
10 | "body-parser": "^1.20.0", | 10 | "body-parser": "^1.20.0", |
11 | "cors": "^2.8.5", | 11 | "cors": "^2.8.5", |
12 | + "crypto-js": "^4.1.1", | ||
12 | "express": "^4.18.1", | 13 | "express": "^4.18.1", |
13 | "fs": "^0.0.1-security", | 14 | "fs": "^0.0.1-security", |
14 | "localforage": "^1.10.0", | 15 | "localforage": "^1.10.0", |
15 | "mysql": "^2.18.1", | 16 | "mysql": "^2.18.1", |
17 | + "mysql2": "^2.3.3", | ||
16 | "puppeteer": "^14.1.1", | 18 | "puppeteer": "^14.1.1", |
17 | "react": "^18.1.0", | 19 | "react": "^18.1.0", |
18 | "react-dom": "^18.1.0", | 20 | "react-dom": "^18.1.0", | ... | ... |
... | @@ -55,6 +55,7 @@ function process(sche, k, v) { | ... | @@ -55,6 +55,7 @@ function process(sche, k, v) { |
55 | case "URL": | 55 | case "URL": |
56 | k = "url"; | 56 | k = "url"; |
57 | sche.subjectID = v.substring(v.indexOf("se_") + 3, v.indexOf("&m")); | 57 | sche.subjectID = v.substring(v.indexOf("se_") + 3, v.indexOf("&m")); |
58 | + v = `https://khcanvas.khu.ac.kr/courses/${sche.subjectID}/assignments/${sche.uid}`; | ||
58 | break; | 59 | break; |
59 | default: | 60 | default: |
60 | errArr.push(k); | 61 | errArr.push(k); | ... | ... |
1 | const mysql = require("mysql"); | 1 | const mysql = require("mysql"); |
2 | +const mysql2 = require("mysql2/promise"); | ||
2 | const fs = require("fs"); | 3 | const fs = require("fs"); |
3 | const parseICal = require("./ICal"); | 4 | const parseICal = require("./ICal"); |
4 | 5 | ||
... | @@ -61,8 +62,19 @@ function jcalToSQL(jcal, userID) { | ... | @@ -61,8 +62,19 @@ function jcalToSQL(jcal, userID) { |
61 | 62 | ||
62 | // iCal의 소유주를 DB에 등록 후 userID get | 63 | // iCal의 소유주를 DB에 등록 후 userID get |
63 | // subjectID가 subjects, userID가 users에 있어야함 | 64 | // subjectID가 subjects, userID가 users에 있어야함 |
64 | -const fdata = fs.readFileSync("C:/Users/teddy/Downloads/data.ics", "utf8"); | ||
65 | -const jcal = parseICal(fdata); | ||
66 | -jcalToSQL(jcal, 1); | ||
67 | 65 | ||
68 | -module.exports = jcalToSQL; | 66 | +// const fdata = fs.readFileSync("C:/Users/teddy/Downloads/data.ics", "utf8"); |
67 | +// const jcal = parseICal(fdata); | ||
68 | +// jcalToSQL(jcal, 1); | ||
69 | + | ||
70 | +const [id, pw] = fs | ||
71 | + .readFileSync("server/libs/sql.pvdata", "utf8") | ||
72 | + .split("\r\n"); | ||
73 | +const connectOption = { | ||
74 | + host: "localhost", | ||
75 | + user: id, | ||
76 | + password: pw, | ||
77 | + database: "db", | ||
78 | +}; | ||
79 | + | ||
80 | +module.exports = { jcalToSQL, connectOption }; | ... | ... |
1 | const express = require("express"); | 1 | const express = require("express"); |
2 | -const mysql = require("mysql"); | 2 | +const mysql2 = require("mysql2/promise"); |
3 | -const fs = require("fs"); | 3 | +const { connectOption } = require("../libs/MySQL"); |
4 | -const router = express.Router(); | 4 | +const dbRouter = express.Router(); |
5 | 5 | ||
6 | -const [id, pw] = fs | 6 | +const schedules_dateRouter = require("./schedules_date"); |
7 | - .readFileSync("server/libs/sql.pvdata", "utf8") | 7 | +const schedules_repeatRouter = require("./Schedules_repeat"); |
8 | - .split("\r\n"); | 8 | +const schedules_timeRouter = require("./Schedules_time"); |
9 | +const subjectsRouter = require("./Subjects"); | ||
10 | +const userSubjectRouter = require("./user-subject"); | ||
11 | +const usersRouter = require("./Users"); | ||
9 | 12 | ||
10 | -const connection = mysql.createConnection({ | 13 | +async function route() { |
11 | - host: "localhost", | 14 | + const connection = await mysql2.createConnection(connectOption); |
12 | - user: id, | ||
13 | - password: pw, | ||
14 | - database: "mydb", | ||
15 | -}); | ||
16 | 15 | ||
17 | -router.get("/", (req, res) => { | 16 | + // (userID, uid) |
18 | - res.send("DB Root"); | 17 | + dbRouter.delete("/schedule", async (req, res) => { |
19 | -}); | 18 | + console.log("delete /db/schedule"); |
20 | - | 19 | + try { |
21 | -router.get("/mytable", (req, res) => { | 20 | + const queryString = ` |
22 | - connection.query("SELECT * from mytable", (error, rows) => { | 21 | + DELETE FROM \`${req.body.table}\` sc |
23 | - if (error) throw error; | 22 | + WHERE sc.userID = ${req.body.userID} |
24 | - console.log(rows); | 23 | + AND sc.uid = ${req.body.uid}`; |
25 | - res.send(rows); | 24 | + await connection.query(queryString); |
25 | + res.end(); | ||
26 | + } catch (e) { | ||
27 | + console.log(e); | ||
28 | + res.end(); | ||
29 | + } | ||
26 | }); | 30 | }); |
27 | -}); | ||
28 | 31 | ||
29 | -module.exports = router; | 32 | + dbRouter.use("/user-subject", userSubjectRouter); |
33 | + dbRouter.use("/users", usersRouter); | ||
34 | + dbRouter.use("/subjects", subjectsRouter); | ||
35 | + dbRouter.use("/schedules_date", schedules_dateRouter); | ||
36 | + dbRouter.use("/schedules_time", schedules_timeRouter); | ||
37 | + dbRouter.use("/schedules_repeat", schedules_repeatRouter); | ||
38 | +} | ||
39 | + | ||
40 | +route(); | ||
41 | + | ||
42 | +module.exports = dbRouter; | ... | ... |
server/routers/Schedules_date.js
0 → 100644
1 | +const express = require("express"); | ||
2 | +const mysql2 = require("mysql2/promise"); | ||
3 | +const { connectOption } = require("../libs/MySQL"); | ||
4 | +const schedules_dateRouter = express.Router(); | ||
5 | + | ||
6 | +async function route() { | ||
7 | + const connection = await mysql2.createConnection(connectOption); | ||
8 | + | ||
9 | + // (userID, date) => schedules | ||
10 | + schedules_dateRouter.get("/", async (req, res) => { | ||
11 | + // console.log("/db/schedules_date"); | ||
12 | + try { | ||
13 | + const queryString = ` | ||
14 | + SELECT sc.label, sc.type, sc.description, sc.url, sc.detail, | ||
15 | + sbj.name, us.nickname, us.color, sc.uid, "schedules_date" \`table\` | ||
16 | + FROM schedules_date sc | ||
17 | + INNER JOIN \`user-subject\` us | ||
18 | + ON sc.userID = us.userID | ||
19 | + AND sc.subjectID = us.subjectID | ||
20 | + AND us.status = 1 | ||
21 | + INNER JOIN subjects sbj | ||
22 | + ON sc.subjectID = sbj.ID | ||
23 | + WHERE sc.date = "${req.query.date}" | ||
24 | + AND sc.userID = ${req.query.userID} | ||
25 | + AND sc.status = 1`; | ||
26 | + const [results] = await connection.query(queryString); | ||
27 | + res.send(results); | ||
28 | + } catch (e) { | ||
29 | + console.log(e); | ||
30 | + res.end(); | ||
31 | + } | ||
32 | + }); | ||
33 | + | ||
34 | + // (userID, label, subjectID, type, description, url, date) | ||
35 | + schedules_dateRouter.post("/", async (req, res) => { | ||
36 | + console.log("post /db/schedules_date"); | ||
37 | + try { | ||
38 | + let queryString = ` | ||
39 | + INSERT INTO \`schedules_date\` | ||
40 | + (userID, label, subjectID, type, description, url, date, status) | ||
41 | + VALUES (${req.body.userID}, "${req.body.label}", ${req.body.subjectID}, "${req.body.type}", | ||
42 | + "${req.body.description}", "${req.body.url}", "${req.body.date}", 1);`; | ||
43 | + await connection.query(queryString); | ||
44 | + | ||
45 | + queryString = `UPDATE \`schedules_date\` | ||
46 | + SET uid = -ID | ||
47 | + WHERE ID = LAST_INSERT_ID();`; | ||
48 | + await connection.query(queryString); | ||
49 | + | ||
50 | + queryString = ` | ||
51 | + SELECT sc.label, sc.type, sc.description, sc.url, sc.detail, | ||
52 | + sbj.name, us.nickname, us.color, sc.uid, "schedules_date" \`table\` | ||
53 | + FROM schedules_date sc | ||
54 | + INNER JOIN \`user-subject\` us | ||
55 | + ON sc.userID = us.userID | ||
56 | + AND sc.subjectID = us.subjectID | ||
57 | + AND us.status = 1 | ||
58 | + INNER JOIN subjects sbj | ||
59 | + ON sc.subjectID = sbj.ID | ||
60 | + WHERE sc.ID = LAST_INSERT_ID()`; | ||
61 | + [results] = await connection.query(queryString); | ||
62 | + res.send(results[0]); | ||
63 | + } catch (e) { | ||
64 | + console.log(e); | ||
65 | + res.end(); | ||
66 | + } | ||
67 | + }); | ||
68 | +} | ||
69 | +route(); | ||
70 | + | ||
71 | +module.exports = schedules_dateRouter; |
server/routers/Schedules_repeat.js
0 → 100644
1 | +const express = require("express"); | ||
2 | +const mysql2 = require("mysql2/promise"); | ||
3 | +const { connectOption } = require("../libs/MySQL"); | ||
4 | +const schedules_repeatRouter = express.Router(); | ||
5 | + | ||
6 | +async function route() { | ||
7 | + const connection = await mysql2.createConnection(connectOption); | ||
8 | + | ||
9 | + // (userID, day) => schedules | ||
10 | + schedules_repeatRouter.get("/", async (req, res) => { | ||
11 | + // console.log("/db/schedules_repeat"); | ||
12 | + try { | ||
13 | + const queryString = ` | ||
14 | + SELECT sc.label, sc.type, sc.description, sc.uid, sc.url, sc.detail, sbj.name, | ||
15 | + us.nickname, us.color, sc.startTime, sc.endTime, "schedules_repeat" \`table\` | ||
16 | + FROM schedules_repeat sc | ||
17 | + INNER JOIN \`user-subject\` us | ||
18 | + ON sc.userID = us.userID | ||
19 | + AND sc.subjectID = us.subjectID | ||
20 | + AND us.status = 1 | ||
21 | + INNER JOIN subjects sbj | ||
22 | + ON sc.subjectID = sbj.ID | ||
23 | + WHERE sc.day = "${req.query.day}" | ||
24 | + AND sc.userID = ${req.query.userID} | ||
25 | + AND sc.status = 1 | ||
26 | + ORDER BY sc.startTime`; | ||
27 | + const [results] = await connection.query(queryString); | ||
28 | + res.send(results); | ||
29 | + } catch (e) { | ||
30 | + console.log(e); | ||
31 | + res.end(); | ||
32 | + } | ||
33 | + }); | ||
34 | + | ||
35 | + // (userID, label, subjectID, type, desciption, url, status, day, startTime, endTime) | ||
36 | + schedules_repeatRouter.post("/", async (req, res) => { | ||
37 | + console.log("/db/schedules_repeat"); | ||
38 | + try { | ||
39 | + const queryString = ` | ||
40 | + INSERT INTO schedules_repeat (userID, label, subjectID, type, status, day, startTime, endTime) | ||
41 | + VALUE (${req.body.userID}, "${req.body.label}", ${req.body.subjectID}, "${req.body.type}", | ||
42 | + 1, ${req.body.day}, "${req.body.startTime}", "${req.body.endTime}");`; | ||
43 | + | ||
44 | + queryString = ` | ||
45 | + UPDATE schedules_repeat | ||
46 | + SET uid = -ID | ||
47 | + WHERE ID = LAST_INSERT_ID()`; | ||
48 | + await connection.query(queryString); | ||
49 | + | ||
50 | + queryString = ` | ||
51 | + SELECT sc.label, sc.type, sc.description, sc.uid, sc.url, sc.detail, sbj.name, | ||
52 | + us.nickname, us.color, sc.startTime, sc.endTime, "schedules_repeat" \`table\` | ||
53 | + FROM schedules_repeat sc | ||
54 | + INNER JOIN \`user-subject\` us | ||
55 | + ON sc.userID = us.userID | ||
56 | + AND sc.subjectID = us.subjectID | ||
57 | + AND us.status = 1 | ||
58 | + INNER JOIN subjects sbj | ||
59 | + ON sc.subjectID = sbj.ID | ||
60 | + WHERE sc.ID = LAST_INSERT_ID() | ||
61 | + ORDER BY sc.startTime`; | ||
62 | + [results] = await connection.query(queryString); | ||
63 | + res.send(results[0]); | ||
64 | + } catch (e) { | ||
65 | + console.log(e); | ||
66 | + res.end(); | ||
67 | + } | ||
68 | + }); | ||
69 | +} | ||
70 | +route(); | ||
71 | + | ||
72 | +module.exports = schedules_repeatRouter; |
server/routers/Schedules_time.js
0 → 100644
1 | +const express = require("express"); | ||
2 | +const mysql2 = require("mysql2/promise"); | ||
3 | +const { connectOption } = require("../libs/MySQL"); | ||
4 | +const schedules_timeRouter = express.Router(); | ||
5 | + | ||
6 | +async function route() { | ||
7 | + const connection = await mysql2.createConnection(connectOption); | ||
8 | + | ||
9 | + // (userID, date) => schedules | ||
10 | + schedules_timeRouter.get("/", async (req, res) => { | ||
11 | + // console.log("/db/schedules_time"); | ||
12 | + try { | ||
13 | + const queryString = ` | ||
14 | + SELECT sc.label, sc.type, sc.description, sc.url, sc.detail, sbj.name, us.nickname, | ||
15 | + us.color, sc.uid, sc.startTime, sc.endTime, "schedules_time" \`table\` | ||
16 | + FROM schedules_time sc | ||
17 | + INNER JOIN \`user-subject\` us | ||
18 | + ON sc.userID = us.userID | ||
19 | + AND sc.subjectID = us.subjectID | ||
20 | + AND us.status = 1 | ||
21 | + INNER JOIN subjects sbj | ||
22 | + ON sc.subjectID = sbj.ID | ||
23 | + WHERE sc.date = "${req.query.date}" | ||
24 | + AND sc.userID = ${req.query.userID} | ||
25 | + AND sc.status = 1 | ||
26 | + ORDER BY sc.endTime`; | ||
27 | + const [results] = await connection.query(queryString); | ||
28 | + res.send(results); | ||
29 | + } catch (e) { | ||
30 | + console.log(e); | ||
31 | + res.end(); | ||
32 | + } | ||
33 | + }); | ||
34 | + | ||
35 | + // (userID, label, subjectID, type, description, url, date, startTime, endTime) | ||
36 | + schedules_timeRouter.post("/", async (req, res) => { | ||
37 | + console.log("post /db/schedules_time"); | ||
38 | + try { | ||
39 | + const startTime = req.body.startTime ? `"${req.body.startTime}"` : "null"; | ||
40 | + const endTime = req.body.endTime ? `"${req.body.endTime}"` : "null"; | ||
41 | + let queryString = ` | ||
42 | + INSERT INTO \`schedules_time\` | ||
43 | + (userID, label, subjectID, type, description, url, date, status, startTime, endTime) | ||
44 | + VALUES (${req.body.userID}, "${req.body.label}", ${req.body.subjectID}, "${req.body.type}", "${req.body.description}", | ||
45 | + "${req.body.url}", "${req.body.date}", 1, ${startTime}, ${endTime});`; | ||
46 | + await connection.query(queryString); | ||
47 | + | ||
48 | + queryString = ` | ||
49 | + UPDATE schedules_time | ||
50 | + SET uid = -ID | ||
51 | + WHERE ID = LAST_INSERT_ID()`; | ||
52 | + await connection.query(queryString); | ||
53 | + | ||
54 | + queryString = `SELECT sc.label, sc.type, sc.description, sc.url, sc.detail, sbj.name, us.nickname, | ||
55 | + us.color, sc.uid, sc.startTime, sc.endTime, "schedules_time" \`table\` | ||
56 | + FROM schedules_time sc | ||
57 | + INNER JOIN \`user-subject\` us | ||
58 | + ON sc.userID = us.userID | ||
59 | + AND sc.subjectID = us.subjectID | ||
60 | + AND us.status = 1 | ||
61 | + INNER JOIN subjects sbj | ||
62 | + ON sc.subjectID = sbj.ID | ||
63 | + WHERE sc.ID = LAST_INSERT_ID() | ||
64 | + ORDER BY sc.endTime`; | ||
65 | + [results] = await connection.query(queryString); | ||
66 | + res.send(results[0]); | ||
67 | + } catch (e) { | ||
68 | + console.log(e); | ||
69 | + res.end(); | ||
70 | + } | ||
71 | + }); | ||
72 | +} | ||
73 | +route(); | ||
74 | + | ||
75 | +module.exports = schedules_timeRouter; |
server/routers/Subjects.js
0 → 100644
1 | +const express = require("express"); | ||
2 | +const mysql2 = require("mysql2/promise"); | ||
3 | +const { connectOption } = require("../libs/MySQL"); | ||
4 | +const subjectsRouter = express.Router(); | ||
5 | + | ||
6 | +async function route() { | ||
7 | + const connection = await mysql2.createConnection(connectOption); | ||
8 | + | ||
9 | + // (ID) => null | name //unused | ||
10 | + subjectsRouter.get("/", async (req, res) => { | ||
11 | + console.log("/db/subjects"); | ||
12 | + try { | ||
13 | + const queryString = ` | ||
14 | + SELECT name FROM subjects sbj | ||
15 | + WHERE sbj.ID = ${req.query.ID}`; | ||
16 | + const [results] = await connection.query(queryString); | ||
17 | + res.send(results.length && results[0].name); | ||
18 | + } catch (e) { | ||
19 | + console.log(e); | ||
20 | + res.end(); | ||
21 | + } | ||
22 | + }); | ||
23 | +} | ||
24 | +route(); | ||
25 | + | ||
26 | +module.exports = subjectsRouter; |
server/routers/User-subject.js
0 → 100644
1 | +const express = require("express"); | ||
2 | +const mysql2 = require("mysql2/promise"); | ||
3 | +const { connectOption } = require("../libs/MySQL"); | ||
4 | +const userSubjectRouter = express.Router(); | ||
5 | + | ||
6 | +async function route() { | ||
7 | + const connection = await mysql2.createConnection(connectOption); | ||
8 | + | ||
9 | + // (userID) => [{userID, subjectID, nickname, status, color, name}, ...] | ||
10 | + userSubjectRouter.get("/", async (req, res) => { | ||
11 | + console.log("/db/user-subject"); | ||
12 | + try { | ||
13 | + const queryString = ` | ||
14 | + SELECT us.userID, us.subjectID, us.nickname, us.status, us.color, sub.name | ||
15 | + FROM \`user-subject\` us | ||
16 | + INNER JOIN subjects sub | ||
17 | + ON us.subjectID = sub.ID | ||
18 | + WHERE us.userID = ${req.query.userID}`; | ||
19 | + const [results] = await connection.query(queryString); | ||
20 | + res.send(results); | ||
21 | + } catch (e) { | ||
22 | + console.log(e); | ||
23 | + res.end(); | ||
24 | + } | ||
25 | + }); | ||
26 | + | ||
27 | + // (userID, subjectID, status) | ||
28 | + userSubjectRouter.put("/check", async (req, res) => { | ||
29 | + console.log("/db/user-subject/check"); | ||
30 | + try { | ||
31 | + const queryString = ` | ||
32 | + UPDATE \`user-subject\` | ||
33 | + SET status = ${req.body.status} | ||
34 | + WHERE userID = ${req.body.userID} | ||
35 | + AND subjectID = ${req.body.subjectID}`; | ||
36 | + await connection.query(queryString); | ||
37 | + res.end(); | ||
38 | + } catch (e) { | ||
39 | + console.log(e); | ||
40 | + res.end(); | ||
41 | + } | ||
42 | + }); | ||
43 | + | ||
44 | + // (userID, subjectID, nickname, color) | ||
45 | + userSubjectRouter.put("/modify", async (req, res) => { | ||
46 | + console.log("/db/user-subject/modify"); | ||
47 | + try { | ||
48 | + const queryString = ` | ||
49 | + UPDATE \`user-subject\` | ||
50 | + SET color = '${req.body.color}', | ||
51 | + nickname = '${req.body.nickname}' | ||
52 | + WHERE userID = ${req.body.userID} | ||
53 | + AND subjectID = ${req.body.subjectID}`; | ||
54 | + await connection.query(queryString); | ||
55 | + res.end(); | ||
56 | + } catch (e) { | ||
57 | + console.log(e); | ||
58 | + res.end(); | ||
59 | + } | ||
60 | + }); | ||
61 | +} | ||
62 | +route(); | ||
63 | + | ||
64 | +module.exports = userSubjectRouter; |
server/routers/Users.js
0 → 100644
1 | +const express = require("express"); | ||
2 | +const mysql2 = require("mysql2/promise"); | ||
3 | +const { connectOption } = require("../libs/MySQL"); | ||
4 | +const usersRouter = express.Router(); | ||
5 | + | ||
6 | +async function route() { | ||
7 | + const connection = await mysql2.createConnection(connectOption); | ||
8 | + | ||
9 | + // (loginID) => null | ID(str) | ||
10 | + usersRouter.get("/", async (req, res) => { | ||
11 | + console.log("/db/users"); | ||
12 | + try { | ||
13 | + const queryString = ` | ||
14 | + SELECT ID FROM users us | ||
15 | + WHERE us.loginID = '${req.query.loginID}'`; | ||
16 | + const [results] = await connection.query(queryString); | ||
17 | + res.send(results.length ? results[0].ID.toString() : null); | ||
18 | + } catch (e) { | ||
19 | + console.log(e); | ||
20 | + res.end(); | ||
21 | + } | ||
22 | + }); | ||
23 | + | ||
24 | + // (loginID, loginPW) => null | "correct" | ||
25 | + usersRouter.get("/check", async (req, res) => { | ||
26 | + console.log("/db/users/check"); | ||
27 | + try { | ||
28 | + const queryString = ` | ||
29 | + SELECT loginPW FROM users us | ||
30 | + WHERE us.loginID = '${req.query.loginID}'`; | ||
31 | + const [results] = await connection.query(queryString); | ||
32 | + res.send(results[0].loginPW === req.query.loginPW ? "correct" : null); | ||
33 | + } catch (e) { | ||
34 | + console.log(e); | ||
35 | + res.end(); | ||
36 | + } | ||
37 | + }); | ||
38 | +} | ||
39 | +route(); | ||
40 | + | ||
41 | +module.exports = usersRouter; |
1 | const express = require("express"); | 1 | const express = require("express"); |
2 | -const bodyParser = require("body-parser"); | ||
3 | const fs = require("fs"); | 2 | const fs = require("fs"); |
4 | const cors = require("cors"); | 3 | const cors = require("cors"); |
5 | -const { getPeed, getSubjects, login } = require("./libs/E_Campus.js"); | ||
6 | 4 | ||
7 | const loginRouter = require("./routers/Login"); | 5 | const loginRouter = require("./routers/Login"); |
8 | const dbRouter = require("./routers/DB"); | 6 | const dbRouter = require("./routers/DB"); |
... | @@ -15,12 +13,13 @@ app.use( | ... | @@ -15,12 +13,13 @@ app.use( |
15 | credentials: true, | 13 | credentials: true, |
16 | }) | 14 | }) |
17 | ); | 15 | ); |
18 | -app.use(bodyParser.urlencoded({ extended: false })); | 16 | +// app.use(bodyParser.urlencoded({ extended: false })); |
19 | -app.use(bodyParser.json()); | 17 | +// app.use(bodyParser.json()); |
18 | +app.use(express.json()); | ||
19 | +app.use(express.urlencoded({ extended: true })); | ||
20 | 20 | ||
21 | app.post("/", (req, res) => { | 21 | app.post("/", (req, res) => { |
22 | - res.send({ body: req.body }); | 22 | + console.log("/"); |
23 | - console.log("listened /"); | ||
24 | }); | 23 | }); |
25 | 24 | ||
26 | app.use("/login", loginRouter); | 25 | app.use("/login", loginRouter); | ... | ... |
1 | -import { useContext, useEffect, useState } from "react"; | 1 | +import { useContext, useEffect, useRef, useState } from "react"; |
2 | +import localforage from "localforage"; | ||
2 | 3 | ||
3 | import { toYMD, toYMDStr } from "../utils/Dates"; | 4 | import { toYMD, toYMDStr } from "../utils/Dates"; |
4 | -import { scheForage } from "../utils/LocalForage"; | ||
5 | 5 | ||
6 | import { CalendarStateContext } from "../pages/Calendar"; | 6 | import { CalendarStateContext } from "../pages/Calendar"; |
7 | import ScheduleItem from "./ScheduleItem"; | 7 | import ScheduleItem from "./ScheduleItem"; |
8 | +import axios from "axios"; | ||
8 | 9 | ||
9 | const GridItem = ({ targetDate }) => { | 10 | const GridItem = ({ targetDate }) => { |
10 | - const { state } = useContext(CalendarStateContext); | 11 | + const { state: calState, subsObj } = useContext(CalendarStateContext); |
11 | - const { month: calMonth } = toYMD(state.date); | 12 | + const { month: calMonth } = toYMD(calState.date); |
12 | const { month, date } = toYMD(targetDate); | 13 | const { month, date } = toYMD(targetDate); |
13 | const [schedules, setSchedules] = useState(); | 14 | const [schedules, setSchedules] = useState(); |
15 | + const [state, setState] = useState({ | ||
16 | + label: "", | ||
17 | + startTime: "", | ||
18 | + endTime: "", | ||
19 | + description: "", | ||
20 | + url: "", | ||
21 | + type: "assignment", | ||
22 | + subjectID: 0, | ||
23 | + }); | ||
14 | 24 | ||
15 | useEffect(() => { | 25 | useEffect(() => { |
16 | async function loadScheduleItems() { | 26 | async function loadScheduleItems() { |
17 | - setSchedules(await scheForage.getItem(toYMDStr(targetDate))); | 27 | + const params = { |
28 | + userID: await localforage.getItem("userID"), | ||
29 | + date: toYMDStr(targetDate, "-"), | ||
30 | + day: targetDate.getDay(), | ||
31 | + }; | ||
32 | + const { data: scdate } = await axios.get( | ||
33 | + "http://localhost:3001/db/schedules_date", | ||
34 | + { params } | ||
35 | + ); | ||
36 | + const { data: sctime } = await axios.get( | ||
37 | + "http://localhost:3001/db/schedules_time", | ||
38 | + { params } | ||
39 | + ); | ||
40 | + const { data: scrpeat } = await axios.get( | ||
41 | + "http://localhost:3001/db/schedules_repeat", | ||
42 | + { params } | ||
43 | + ); | ||
44 | + const subs = await localforage.getItem("subjects"); | ||
45 | + setState({ ...state, subjectID: subs[0].subjectID }); | ||
46 | + setSchedules(scrpeat.concat(scdate, sctime)); | ||
18 | } | 47 | } |
19 | loadScheduleItems(); | 48 | loadScheduleItems(); |
20 | }, [targetDate]); | 49 | }, [targetDate]); |
21 | 50 | ||
51 | + const handleChangeState = (e) => { | ||
52 | + setState({ | ||
53 | + ...state, | ||
54 | + [e.target.name]: e.target.value, | ||
55 | + }); | ||
56 | + }; | ||
57 | + | ||
58 | + const finishSchedule = async (table, uid) => { | ||
59 | + for (const i in schedules) | ||
60 | + if (schedules[i].uid === uid) { | ||
61 | + await axios.delete("http://localhost:3001/db/schedule", { | ||
62 | + data: { | ||
63 | + table, | ||
64 | + uid, | ||
65 | + userID: await localforage.getItem("userID"), | ||
66 | + }, | ||
67 | + }); | ||
68 | + schedules.splice(i, 1); | ||
69 | + setSchedules(schedules); | ||
70 | + } | ||
71 | + }; | ||
72 | + | ||
73 | + const popupRef = useRef(); | ||
74 | + const click = async (e) => { | ||
75 | + if (e.target.classList.contains("gi")) { | ||
76 | + popupRef.current.style.display = "flex"; | ||
77 | + } else if (e.target.className === "gipc") { | ||
78 | + popupRef.current.style.display = "none"; | ||
79 | + } else if (e.target.className === "gipa") { | ||
80 | + // (userID, label, subjectID, type, description, url, date) | ||
81 | + const table = | ||
82 | + state.startTime || state.endTime ? "schedules_time" : "schedules_date"; | ||
83 | + const { data: sche } = await axios.post( | ||
84 | + "http://localhost:3001/db/" + table, | ||
85 | + { | ||
86 | + userID: await localforage.getItem("userID"), | ||
87 | + ...state, | ||
88 | + date: toYMDStr(targetDate, "-"), | ||
89 | + } | ||
90 | + ); | ||
91 | + setSchedules(schedules.concat(sche)); | ||
92 | + popupRef.current.style.display = "none"; | ||
93 | + } | ||
94 | + }; | ||
95 | + | ||
22 | return ( | 96 | return ( |
23 | - <div className="GridItem" relative={month - calMonth || null}> | 97 | + <div |
24 | - <span className="date"> | 98 | + className="GridItem gi" |
99 | + relative={month - calMonth || null} | ||
100 | + onClick={click} | ||
101 | + > | ||
102 | + <span className="date gi"> | ||
25 | {calMonth !== month ? month + "/" + date : date} | 103 | {calMonth !== month ? month + "/" + date : date} |
26 | </span> | 104 | </span> |
27 | {schedules && | 105 | {schedules && |
28 | schedules.map((sche, index) => ( | 106 | schedules.map((sche, index) => ( |
29 | - <ScheduleItem key={index} schedule={sche} /> | 107 | + <ScheduleItem key={index} schedule={sche} finish={finishSchedule} /> |
108 | + ))} | ||
109 | + <div className="gi_popup" popup="true" ref={popupRef}> | ||
110 | + <span>일정 추가</span> | ||
111 | + <div className="gipd"> | ||
112 | + <input | ||
113 | + name="label" | ||
114 | + placeholder="이름" | ||
115 | + value={state.label} | ||
116 | + onChange={handleChangeState} | ||
117 | + /> | ||
118 | + <select | ||
119 | + name="subjectID" | ||
120 | + value={state.subjectID} | ||
121 | + onChange={handleChangeState} | ||
122 | + > | ||
123 | + {Object.values(subsObj).map((sub, index) => ( | ||
124 | + <option key={index} value={sub.subjectID}> | ||
125 | + {sub.name} | ||
126 | + </option> | ||
30 | ))} | 127 | ))} |
128 | + </select> | ||
129 | + <select name="type" value={state.type} onChange={handleChangeState}> | ||
130 | + <option value={"assignment"}>E-Campus</option> | ||
131 | + <option value={"zoom"}>Zoom</option> | ||
132 | + </select> | ||
133 | + <input | ||
134 | + name="startTime" | ||
135 | + placeholder="시작 (HH:MM)" | ||
136 | + value={state.startTime} | ||
137 | + onChange={handleChangeState} | ||
138 | + /> | ||
139 | + <input | ||
140 | + name="endTime" | ||
141 | + placeholder="종료 (HH:MM)" | ||
142 | + value={state.endTime} | ||
143 | + onChange={handleChangeState} | ||
144 | + /> | ||
145 | + <textarea | ||
146 | + name="description" | ||
147 | + placeholder="설명" | ||
148 | + value={state.description} | ||
149 | + onChange={handleChangeState} | ||
150 | + /> | ||
151 | + <textarea | ||
152 | + name="url" | ||
153 | + placeholder="링크" | ||
154 | + value={state.url} | ||
155 | + onChange={handleChangeState} | ||
156 | + /> | ||
157 | + </div> | ||
158 | + <div className="gip_btn"> | ||
159 | + <button className="gipc">취소</button> | ||
160 | + <button className="gipa">추가</button> | ||
161 | + </div> | ||
162 | + </div> | ||
31 | </div> | 163 | </div> |
32 | ); | 164 | ); |
33 | }; | 165 | }; | ... | ... |
1 | +import localforage from "localforage"; | ||
1 | import { useContext } from "react"; | 2 | import { useContext } from "react"; |
2 | import { useNavigate } from "react-router-dom"; | 3 | import { useNavigate } from "react-router-dom"; |
3 | 4 | ||
4 | import { CalendarStateContext } from "../pages/Calendar"; | 5 | import { CalendarStateContext } from "../pages/Calendar"; |
5 | import "../styles/Header.css"; | 6 | import "../styles/Header.css"; |
6 | import { moveDate, toYMD } from "../utils/Dates"; | 7 | import { moveDate, toYMD } from "../utils/Dates"; |
7 | -import { dataForage } from "../utils/LocalForage"; | ||
8 | 8 | ||
9 | const Header = () => { | 9 | const Header = () => { |
10 | const { state, setState } = useContext(CalendarStateContext); | 10 | const { state, setState } = useContext(CalendarStateContext); |
... | @@ -93,7 +93,7 @@ const Header = () => { | ... | @@ -93,7 +93,7 @@ const Header = () => { |
93 | <button | 93 | <button |
94 | className="hrb_r" | 94 | className="hrb_r" |
95 | onClick={async () => { | 95 | onClick={async () => { |
96 | - await dataForage.setItem("session", ""); | 96 | + await localforage.setItem("session", null); |
97 | navigate("/"); | 97 | navigate("/"); |
98 | }} | 98 | }} |
99 | > | 99 | > | ... | ... |
1 | -import { useContext } from "react"; | 1 | +import { useRef } from "react"; |
2 | - | ||
3 | -import { CalendarStateContext } from "../pages/Calendar"; | ||
4 | import zoomSymbol from "../assets/zoom.png"; | 2 | import zoomSymbol from "../assets/zoom.png"; |
5 | import ecampusSymbol from "../assets/e-Campus.png"; | 3 | import ecampusSymbol from "../assets/e-Campus.png"; |
6 | 4 | ||
7 | -const ScheduleItem = ({ schedule }) => { | 5 | +const ScheduleItem = ({ schedule, finish }) => { |
8 | - const { subCode, type, category, label, start, end } = schedule; | 6 | + const { |
9 | - const { subsObj } = useContext(CalendarStateContext); | 7 | + name: subjectName, |
10 | - const subject = subsObj[subCode]; | 8 | + nickname: subjectNickname, |
11 | - if (!subject) { | 9 | + uid: scheUID, |
12 | - console.log("can't find " + subCode); | 10 | + color: subjectColor, |
13 | - return; | 11 | + url, |
14 | - } | 12 | + type, |
15 | - if (!subject.selected) return; | 13 | + label, |
14 | + description = null, | ||
15 | + detail = null, | ||
16 | + startTime = null, //HHMMSS문자열 | ||
17 | + endTime = null, | ||
18 | + table, | ||
19 | + } = schedule; | ||
20 | + let sTime = startTime ? startTime.substring(0, 5) : ""; //HHMM | ||
21 | + let eTime = endTime ? endTime.substring(0, 5) : ""; | ||
16 | 22 | ||
17 | const selectSymbol = () => { | 23 | const selectSymbol = () => { |
18 | let symbol; | 24 | let symbol; |
... | @@ -20,7 +26,7 @@ const ScheduleItem = ({ schedule }) => { | ... | @@ -20,7 +26,7 @@ const ScheduleItem = ({ schedule }) => { |
20 | case "zoom": | 26 | case "zoom": |
21 | symbol = zoomSymbol; | 27 | symbol = zoomSymbol; |
22 | break; | 28 | break; |
23 | - case "ecampus": | 29 | + case "assignment": |
24 | symbol = ecampusSymbol; | 30 | symbol = ecampusSymbol; |
25 | break; | 31 | break; |
26 | default: | 32 | default: |
... | @@ -28,13 +34,53 @@ const ScheduleItem = ({ schedule }) => { | ... | @@ -28,13 +34,53 @@ const ScheduleItem = ({ schedule }) => { |
28 | return symbol; | 34 | return symbol; |
29 | }; | 35 | }; |
30 | 36 | ||
37 | + const popupRef = useRef(); | ||
38 | + const thisRef = useRef(); | ||
39 | + | ||
40 | + const click = (e) => { | ||
41 | + //Item | ||
42 | + if (e.target.classList.contains("ss")) | ||
43 | + popupRef.current.style.display = "grid"; | ||
44 | + else { | ||
45 | + if (e.target.className === "spc") popupRef.current.style.display = "none"; //popup close | ||
46 | + if (e.target.className === "spd") { | ||
47 | + finish(table, scheUID); | ||
48 | + thisRef.current.style.display = "none"; | ||
49 | + } | ||
50 | + } | ||
51 | + }; | ||
52 | + | ||
31 | return ( | 53 | return ( |
32 | - <div className="ScheduleItem" style={{ borderColor: subject.color }}> | 54 | + <div |
33 | - <img className="s_symbol" src={selectSymbol()} alt="404" /> | 55 | + className="ScheduleItem ss" |
34 | - {start && <span className="s_start">{start[0] + ":" + start[1]}</span>} | 56 | + style={{ borderColor: "#" + subjectColor }} |
35 | - {end && <span className="s_end">{end[0] + ":" + end[1]}</span>} | 57 | + onClick={click} |
36 | - <span className="s_category">{category}</span> | 58 | + ref={thisRef} |
37 | - <span className="s_slabel">{label}</span> | 59 | + > |
60 | + <img className="s_symbol ss" src={selectSymbol()} alt="404" /> | ||
61 | + {startTime && <span className="s_start ss">{sTime}</span>} | ||
62 | + {endTime && <span className="s_end ss">{eTime}</span>} | ||
63 | + <span className="s_slabel ss">{label}</span> | ||
64 | + | ||
65 | + <div className="s_popup" popup="true" ref={popupRef}> | ||
66 | + <div className="spl"> | ||
67 | + <span>{subjectName}</span> | ||
68 | + {url ? ( | ||
69 | + <a href={url} target="_blank"> | ||
70 | + {label} | ||
71 | + </a> | ||
72 | + ) : ( | ||
73 | + <span>{label}</span> | ||
74 | + )} | ||
75 | + {(startTime || endTime) && <span>{sTime + " ~ " + eTime}</span>} | ||
76 | + {description && <span>{description}</span>} | ||
77 | + {detail && <div dangerouslySetInnerHTML={{ __html: detail }}></div>} | ||
78 | + </div> | ||
79 | + <div className="sp_btn"> | ||
80 | + <button className="spd">완료</button> | ||
81 | + <button className="spc">닫기</button> | ||
82 | + </div> | ||
83 | + </div> | ||
38 | </div> | 84 | </div> |
39 | ); | 85 | ); |
40 | }; | 86 | }; | ... | ... |
... | @@ -11,12 +11,7 @@ const Side = () => { | ... | @@ -11,12 +11,7 @@ const Side = () => { |
11 | 11 | ||
12 | for (const code in subsObj) { | 12 | for (const code in subsObj) { |
13 | sideSubjects.push( | 13 | sideSubjects.push( |
14 | - <SideSubject | 14 | + <SideSubject key={code} subject={subsObj[code]} dispatch={dispatch} /> |
15 | - key={code} | ||
16 | - code={code} | ||
17 | - subject={subsObj[code]} | ||
18 | - dispatch={dispatch} | ||
19 | - /> | ||
20 | ); | 15 | ); |
21 | } | 16 | } |
22 | 17 | ... | ... |
1 | -const SideSubject = ({ code, subject, dispatch }) => { | 1 | +import { useRef, useState } from "react"; |
2 | + | ||
3 | +const SideSubject = ({ subject, dispatch }) => { | ||
2 | const defaultColor = "#EFEFEF"; | 4 | const defaultColor = "#EFEFEF"; |
5 | + const [state, setState] = useState({ | ||
6 | + nickname: subject.nickname || subject.name, | ||
7 | + color: subject.color, | ||
8 | + }); | ||
9 | + | ||
10 | + const handleChangeState = (e) => { | ||
11 | + setState({ | ||
12 | + ...state, | ||
13 | + [e.target.name]: e.target.value, | ||
14 | + }); | ||
15 | + }; | ||
3 | 16 | ||
4 | const check = (e) => { | 17 | const check = (e) => { |
5 | - dispatch({ type: "CHECKED", code }); | 18 | + dispatch({ type: "CHECKED", subjectID: subject.subjectID }); |
6 | - if (subject.selected) e.target.style["background-color"] = defaultColor; | 19 | + if (subject.status) e.target.style["background-color"] = defaultColor; |
7 | else e.target.style["background-color"] = subject.color; | 20 | else e.target.style["background-color"] = subject.color; |
8 | }; | 21 | }; |
9 | 22 | ||
23 | + const popupRef = useRef(); | ||
24 | + | ||
25 | + const labelClick = (e) => { | ||
26 | + popupRef.current.style.display = "flex"; | ||
27 | + }; | ||
28 | + | ||
29 | + const popupClose = (e) => { | ||
30 | + setState({ | ||
31 | + nickname: subject.nickname || subject.name, | ||
32 | + color: subject.color, | ||
33 | + }); | ||
34 | + popupRef.current.style.display = "none"; | ||
35 | + }; | ||
36 | + | ||
37 | + const popupApply = (e) => { | ||
38 | + if (state.nickname.length < 1) alert("이름을 입력해 주세요"); | ||
39 | + else if (state.color.length !== 6) alert("색상은 6자리 16진수 값 입니다"); | ||
40 | + else { | ||
41 | + let check = true; | ||
42 | + for (const c of state.color) if (isNaN(parseInt(c, 16))) check = false; | ||
43 | + if (!check) alert("색상은 16진수 값 입니다"); | ||
44 | + else { | ||
45 | + dispatch({ | ||
46 | + type: "MODIFY", | ||
47 | + subjectID: subject.subjectID, | ||
48 | + nickname: state.nickname, | ||
49 | + color: state.color, | ||
50 | + }); | ||
51 | + e.target.offsetParent.style.display = "none"; | ||
52 | + } | ||
53 | + } | ||
54 | + }; | ||
55 | + | ||
10 | return ( | 56 | return ( |
11 | <div className="SideSubject"> | 57 | <div className="SideSubject"> |
12 | <div | 58 | <div |
13 | className="ssc" | 59 | className="ssc" |
14 | onClick={check} | 60 | onClick={check} |
15 | style={{ | 61 | style={{ |
16 | - backgroundColor: subject.selected ? subject.color : defaultColor, | 62 | + backgroundColor: subject.status ? "#" + subject.color : defaultColor, |
17 | }} | 63 | }} |
18 | ></div> | 64 | ></div> |
19 | - | 65 | + <span className="ssl" onClick={labelClick}> |
20 | - <span>{subject.name}</span> | 66 | + {subject.nickname || subject.name} |
67 | + </span> | ||
68 | + <div className="ss_popup" popup="true" ref={popupRef}> | ||
69 | + <div className="sspd"> | ||
70 | + <div className="sspd_1"> | ||
71 | + <span>이름</span> | ||
72 | + <input | ||
73 | + name="nickname" | ||
74 | + value={state.nickname} | ||
75 | + onChange={handleChangeState} | ||
76 | + /> | ||
77 | + </div> | ||
78 | + <div className="sspd_2"> | ||
79 | + <span>색상</span> | ||
80 | + <input | ||
81 | + name="color" | ||
82 | + value={state.color} | ||
83 | + onChange={handleChangeState} | ||
84 | + /> | ||
85 | + </div> | ||
86 | + </div> | ||
87 | + <div className="ssp_btns"> | ||
88 | + <button onClick={popupApply}>적용 </button> | ||
89 | + <button onClick={popupClose}>취소</button> | ||
90 | + </div> | ||
91 | + </div> | ||
21 | </div> | 92 | </div> |
22 | ); | 93 | ); |
23 | }; | 94 | }; | ... | ... |
src/components/Week.js
0 → 100644
1 | +import React, { useContext } from "react"; | ||
2 | +import GridItem from "./GridItem.js"; | ||
3 | + | ||
4 | +import { CalendarStateContext } from "../pages/Calendar"; | ||
5 | +import { moveDate, toSunday } from "../utils/Dates"; | ||
6 | +import "../styles/Week.css"; | ||
7 | + | ||
8 | +const GridHead = () => { | ||
9 | + const days = ["일", "월", "화", "수", "목", "금", "토"]; | ||
10 | + const renderItems = () => { | ||
11 | + const items = []; | ||
12 | + for (let i = 0; i < 7; i++) { | ||
13 | + items.push( | ||
14 | + <div className="GridHeadItem" key={i}> | ||
15 | + {days[i]} | ||
16 | + </div> | ||
17 | + ); | ||
18 | + } | ||
19 | + return items; | ||
20 | + }; | ||
21 | + | ||
22 | + return <div className="GridHead">{renderItems()}</div>; | ||
23 | +}; | ||
24 | + | ||
25 | +const Row = () => { | ||
26 | + const { state } = useContext(CalendarStateContext); | ||
27 | + | ||
28 | + const renderItems = () => { | ||
29 | + const items = []; | ||
30 | + const ndate = new Date(state.date); | ||
31 | + toSunday(ndate); | ||
32 | + | ||
33 | + for (let i = 0; i < 7; i++) { | ||
34 | + items.push(<GridItem key={i} targetDate={new Date(ndate)} />); | ||
35 | + moveDate(ndate, "day", 1); | ||
36 | + } | ||
37 | + | ||
38 | + return items; | ||
39 | + }; | ||
40 | + | ||
41 | + return <div className="Row">{renderItems()}</div>; | ||
42 | +}; | ||
43 | + | ||
44 | +const Week = () => { | ||
45 | + return ( | ||
46 | + <div className="Week"> | ||
47 | + <GridHead /> | ||
48 | + <Row /> | ||
49 | + </div> | ||
50 | + ); | ||
51 | +}; | ||
52 | + | ||
53 | +export default Week; |
1 | import React, { useEffect, useReducer, useState } from "react"; | 1 | import React, { useEffect, useReducer, useState } from "react"; |
2 | import { useNavigate, Route, Routes } from "react-router-dom"; | 2 | import { useNavigate, Route, Routes } from "react-router-dom"; |
3 | 3 | ||
4 | -import { initTempSubjects } from "../utils/Test"; | ||
5 | -import { dataForage, subForage } from "../utils/LocalForage"; | ||
6 | - | ||
7 | import Month from "../components/Month"; | 4 | import Month from "../components/Month"; |
8 | import Header from "../components/Header"; | 5 | import Header from "../components/Header"; |
9 | import Side from "../components/Side"; | 6 | import Side from "../components/Side"; |
7 | +import localforage from "localforage"; | ||
8 | +import axios from "axios"; | ||
9 | +import Week from "../components/Week"; | ||
10 | 10 | ||
11 | export const CalendarStateContext = React.createContext(); | 11 | export const CalendarStateContext = React.createContext(); |
12 | 12 | ||
13 | const render = (subsObj, args) => { | 13 | const render = (subsObj, args) => { |
14 | + let sub; | ||
14 | switch (args.type) { | 15 | switch (args.type) { |
15 | case "CHECKED": | 16 | case "CHECKED": |
16 | - const sub = subsObj[args.code]; | 17 | + sub = subsObj[args.subjectID]; |
17 | - sub.selected = !sub.selected; | 18 | + sub.status = !sub.status; |
18 | - subForage.setItem(args.code, sub); | 19 | + axios.put("http://localhost:3001/db/user-subject/check", { |
19 | - return { ...subsObj, [args.code]: sub }; | 20 | + userID: sub.userID, |
21 | + subjectID: args.subjectID, | ||
22 | + status: +sub.status, | ||
23 | + }); | ||
24 | + return { ...subsObj, [args.subjectID]: sub }; | ||
25 | + case "MODIFY": | ||
26 | + sub = subsObj[args.subjectID]; | ||
27 | + sub.nickname = args.nickname; | ||
28 | + sub.color = args.color; | ||
29 | + axios.put("http://localhost:3001/db/user-subject/modify", { | ||
30 | + userID: sub.userID, | ||
31 | + subjectID: args.subjectID, | ||
32 | + nickname: sub.nickname, | ||
33 | + color: sub.color, | ||
34 | + }); | ||
35 | + return { ...subsObj, [args.subjectID]: sub }; | ||
20 | case "INIT": | 36 | case "INIT": |
21 | return args.subsObj; | 37 | return args.subsObj; |
22 | default: | 38 | default: |
... | @@ -36,12 +52,17 @@ const Calendar = () => { | ... | @@ -36,12 +52,17 @@ const Calendar = () => { |
36 | const navigate = useNavigate(); | 52 | const navigate = useNavigate(); |
37 | useEffect(() => { | 53 | useEffect(() => { |
38 | async function onMount() { | 54 | async function onMount() { |
39 | - if (!(await dataForage.getItem("session"))) return navigate("/login"); | 55 | + if (!(await localforage.getItem("session"))) return navigate("/login"); |
40 | - | 56 | + // get user's subjects |
41 | - if (!(await dataForage.getItem("Subjects"))) await initTempSubjects(); | 57 | + const userID = await localforage.getItem("userID"); |
58 | + const { data: subjects } = await axios.get( | ||
59 | + "http://localhost:3001/db/user-subject", | ||
60 | + { params: { userID } } | ||
61 | + ); | ||
62 | + await localforage.setItem("subjects", subjects); | ||
42 | let tsubsObj = {}; | 63 | let tsubsObj = {}; |
43 | - for (const code of await dataForage.getItem("Subjects")) { | 64 | + for (const sub of subjects) { |
44 | - tsubsObj[code] = await subForage.getItem(code); | 65 | + tsubsObj[sub.subjectID] = sub; |
45 | } | 66 | } |
46 | dispatch({ type: "INIT", subsObj: tsubsObj }); | 67 | dispatch({ type: "INIT", subsObj: tsubsObj }); |
47 | } | 68 | } |
... | @@ -58,7 +79,7 @@ const Calendar = () => { | ... | @@ -58,7 +79,7 @@ const Calendar = () => { |
58 | <Side /> | 79 | <Side /> |
59 | <Routes> | 80 | <Routes> |
60 | <Route path="/month/*" element={<Month />} /> | 81 | <Route path="/month/*" element={<Month />} /> |
61 | - <Route path="/week/*" element={<></>} /> | 82 | + <Route path="/week/*" element={<Week />} /> |
62 | <Route path="/day/*" element={<></>} /> | 83 | <Route path="/day/*" element={<></>} /> |
63 | </Routes> | 84 | </Routes> |
64 | </div> | 85 | </div> | ... | ... |
... | @@ -4,7 +4,6 @@ import { useNavigate } from "react-router-dom"; | ... | @@ -4,7 +4,6 @@ import { useNavigate } from "react-router-dom"; |
4 | //import { login } from "../libs/E_Campus"; | 4 | //import { login } from "../libs/E_Campus"; |
5 | 5 | ||
6 | import "../styles/Debug.css"; | 6 | import "../styles/Debug.css"; |
7 | -import { initTempSubjects } from "../utils/Test"; | ||
8 | 7 | ||
9 | const Debug = () => { | 8 | const Debug = () => { |
10 | console.log("visit Debug"); | 9 | console.log("visit Debug"); |
... | @@ -26,10 +25,6 @@ const Debug = () => { | ... | @@ -26,10 +25,6 @@ const Debug = () => { |
26 | const handleSubmit = async (e) => { | 25 | const handleSubmit = async (e) => { |
27 | let result; | 26 | let result; |
28 | switch (state.type) { | 27 | switch (state.type) { |
29 | - case "initSubjects": | ||
30 | - await initTempSubjects(); | ||
31 | - result = "inited"; | ||
32 | - break; | ||
33 | case "login": | 28 | case "login": |
34 | //result = login(state.input1, state.input2); | 29 | //result = login(state.input1, state.input2); |
35 | break; | 30 | break; |
... | @@ -66,7 +61,6 @@ const Debug = () => { | ... | @@ -66,7 +61,6 @@ const Debug = () => { |
66 | <div> | 61 | <div> |
67 | <span>type : </span> | 62 | <span>type : </span> |
68 | <select name="type" value={state.type} onChange={handleChangeState}> | 63 | <select name="type" value={state.type} onChange={handleChangeState}> |
69 | - <option value={"initSubjects"}>initSubjects</option> | ||
70 | <option value={"login"}>login</option> | 64 | <option value={"login"}>login</option> |
71 | <option value={"server"}>server</option> | 65 | <option value={"server"}>server</option> |
72 | </select> | 66 | </select> | ... | ... |
1 | +import localforage from "localforage"; | ||
1 | import { useEffect } from "react"; | 2 | import { useEffect } from "react"; |
2 | import { useNavigate } from "react-router-dom"; | 3 | import { useNavigate } from "react-router-dom"; |
3 | 4 | ||
4 | -import { dataForage } from "../utils/LocalForage"; | ||
5 | - | ||
6 | const Home = () => { | 5 | const Home = () => { |
7 | console.log("visit Home"); | 6 | console.log("visit Home"); |
8 | 7 | ||
... | @@ -10,7 +9,7 @@ const Home = () => { | ... | @@ -10,7 +9,7 @@ const Home = () => { |
10 | useEffect(() => { | 9 | useEffect(() => { |
11 | async function where() { | 10 | async function where() { |
12 | let destination; | 11 | let destination; |
13 | - if (await dataForage.getItem("session")) { | 12 | + if (await localforage.getItem("session")) { |
14 | destination = "/calendar/month"; | 13 | destination = "/calendar/month"; |
15 | } else { | 14 | } else { |
16 | destination = "/login"; | 15 | destination = "/login"; | ... | ... |
1 | import { useEffect, useState } from "react"; | 1 | import { useEffect, useState } from "react"; |
2 | import { useNavigate } from "react-router-dom"; | 2 | import { useNavigate } from "react-router-dom"; |
3 | +import localforage from "localforage"; | ||
3 | 4 | ||
4 | -import { dataForage } from "../utils/LocalForage"; | ||
5 | import "../styles/Login.css"; | 5 | import "../styles/Login.css"; |
6 | import axios from "axios"; | 6 | import axios from "axios"; |
7 | +import cryptoJs from "crypto-js"; | ||
7 | 8 | ||
8 | const Login = () => { | 9 | const Login = () => { |
9 | console.log("visit Login"); | 10 | console.log("visit Login"); |
10 | const [state, setState] = useState({ | 11 | const [state, setState] = useState({ |
11 | id: "", | 12 | id: "", |
12 | pw: "", | 13 | pw: "", |
14 | + btn: "Login", | ||
13 | }); | 15 | }); |
14 | 16 | ||
15 | const handleChangeState = (e) => { | 17 | const handleChangeState = (e) => { |
... | @@ -21,21 +23,57 @@ const Login = () => { | ... | @@ -21,21 +23,57 @@ const Login = () => { |
21 | 23 | ||
22 | const navigate = useNavigate(); | 24 | const navigate = useNavigate(); |
23 | const login = async () => { | 25 | const login = async () => { |
24 | - const res = await axios.post("http://localhost:3001/login/", { | 26 | + setState({ ...state, btn: "Login..." }); |
25 | - id: state.id, | 27 | + const { data: userDBID } = await axios.get( |
26 | - pw: state.pw, | 28 | + "http://localhost:3001/db/users", |
27 | - }); | 29 | + { |
30 | + params: { | ||
31 | + loginID: state.id, | ||
32 | + }, | ||
33 | + } | ||
34 | + ); | ||
28 | 35 | ||
29 | - if (res.data === "login failed") alert("ID/PW를 확인해주세요"); | 36 | + if (userDBID) { |
37 | + //pass crawling | ||
38 | + const hashpw = cryptoJs.SHA256(state.pw).toString(); | ||
39 | + const { data: isCorrectPW } = await axios.get( | ||
40 | + "http://localhost:3001/db/users/check", | ||
41 | + { | ||
42 | + params: { loginID: state.id, loginPW: hashpw }, | ||
43 | + } | ||
44 | + ); | ||
45 | + if (isCorrectPW) await localforage.setItem("userID", Number(userDBID)); | ||
30 | else { | 46 | else { |
31 | - await dataForage.setItem("session", true); | 47 | + setState({ ...state, btn: "Login" }); |
32 | - navigate("/"); | 48 | + alert("ID/PW를 확인해주세요"); |
49 | + return; | ||
33 | } | 50 | } |
51 | + } else { | ||
52 | + //crawling | ||
53 | + const { data: loginResult } = await axios.post( | ||
54 | + "http://localhost:3001/login/", | ||
55 | + { | ||
56 | + id: state.id, | ||
57 | + pw: state.pw, | ||
58 | + } | ||
59 | + ); | ||
60 | + if (loginResult === "login failed") { | ||
61 | + setState({ ...state, btn: "Login" }); | ||
62 | + alert("ID/PW를 확인해주세요"); | ||
63 | + return; | ||
64 | + } | ||
65 | + // + else (성공시) localforage에 userID추가 | ||
66 | + } | ||
67 | + // + localforage에 id pw 추가 | ||
68 | + await localforage.setItem("id", state.id); | ||
69 | + await localforage.setItem("pw", state.pw); | ||
70 | + await localforage.setItem("session", true); | ||
71 | + navigate("/"); | ||
34 | }; | 72 | }; |
35 | 73 | ||
36 | useEffect(() => { | 74 | useEffect(() => { |
37 | async function ifAlreadyLogined() { | 75 | async function ifAlreadyLogined() { |
38 | - if (await dataForage.getItem("session")) navigate("/"); | 76 | + if (await localforage.getItem("session")) navigate("/"); |
39 | } | 77 | } |
40 | ifAlreadyLogined(); | 78 | ifAlreadyLogined(); |
41 | }, [navigate]); | 79 | }, [navigate]); |
... | @@ -61,7 +99,7 @@ const Login = () => { | ... | @@ -61,7 +99,7 @@ const Login = () => { |
61 | type="password" | 99 | type="password" |
62 | /> | 100 | /> |
63 | </div> | 101 | </div> |
64 | - <button onClick={login}>Login</button> | 102 | + <button onClick={login}>{state.btn}</button> |
65 | </div> | 103 | </div> |
66 | ); | 104 | ); |
67 | }; | 105 | }; | ... | ... |
1 | +import localforage from "localforage"; | ||
1 | import { Navigate, useNavigate } from "react-router-dom"; | 2 | import { Navigate, useNavigate } from "react-router-dom"; |
2 | 3 | ||
3 | import "../styles/Settings.css"; | 4 | import "../styles/Settings.css"; |
4 | -import { dataForage } from "../utils/LocalForage"; | ||
5 | 5 | ||
6 | const Settings = () => { | 6 | const Settings = () => { |
7 | console.log("visit Settings"); | 7 | console.log("visit Settings"); |
8 | 8 | ||
9 | - const session = dataForage.getItem("session"); | 9 | + const session = localforage.getItem("session"); |
10 | 10 | ||
11 | const navigate = useNavigate(); | 11 | const navigate = useNavigate(); |
12 | 12 | ... | ... |
... | @@ -47,12 +47,61 @@ button:disabled { | ... | @@ -47,12 +47,61 @@ button:disabled { |
47 | display: flex; | 47 | display: flex; |
48 | } | 48 | } |
49 | 49 | ||
50 | +.GridItem { | ||
51 | + position: relative; | ||
52 | +} | ||
53 | + | ||
54 | +.gi_popup { | ||
55 | + flex-direction: column; | ||
56 | + padding: 5px; | ||
57 | + top: 30px; | ||
58 | +} | ||
59 | + | ||
60 | +.gi_popup > span { | ||
61 | + font-size: large; | ||
62 | +} | ||
63 | + | ||
64 | +.gip_btn { | ||
65 | + display: flex; | ||
66 | + flex-direction: row-reverse; | ||
67 | +} | ||
68 | + | ||
69 | +.gip_btn > button { | ||
70 | + padding: 5px 9px 5px 9px; | ||
71 | + margin-left: 2px; | ||
72 | +} | ||
73 | + | ||
74 | +.gipd { | ||
75 | + margin: 5px 0 5px 0; | ||
76 | + display: grid; | ||
77 | + grid-template-columns: repeat(2, minmax(100px, auto)); | ||
78 | + grid-template-rows: repeat(6, minmax(30px, auto)); | ||
79 | + row-gap: 5px; | ||
80 | + column-gap: 5px; | ||
81 | +} | ||
82 | + | ||
83 | +.gipd > * { | ||
84 | + grid-column: 1 / span 2; | ||
85 | +} | ||
86 | +.gipd > :nth-child(4) { | ||
87 | + grid-column: 1; | ||
88 | +} | ||
89 | +.gipd > :nth-child(5) { | ||
90 | + grid-column: 2; | ||
91 | +} | ||
92 | +.gipd > :nth-child(6), | ||
93 | +.gipd > :nth-child(7) { | ||
94 | + resize: none; | ||
95 | + height: 60px; | ||
96 | +} | ||
97 | + | ||
50 | .ScheduleItem { | 98 | .ScheduleItem { |
51 | - height: 23px; | ||
52 | display: flex; | 99 | display: flex; |
53 | margin: 1px 0 1px 0; | 100 | margin: 1px 0 1px 0; |
54 | border: solid 3px bisque; | 101 | border: solid 3px bisque; |
55 | border-radius: 3px; | 102 | border-radius: 3px; |
103 | + cursor: pointer; | ||
104 | + position: relative; | ||
56 | } | 105 | } |
57 | 106 | ||
58 | .ScheduleItem > span { | 107 | .ScheduleItem > span { |
... | @@ -63,7 +112,7 @@ button:disabled { | ... | @@ -63,7 +112,7 @@ button:disabled { |
63 | } | 112 | } |
64 | 113 | ||
65 | .s_start { | 114 | .s_start { |
66 | - color: deepskyblue; | 115 | + color: blue; |
67 | /* text-shadow: -1px 0 white, 0 1px white, 1px 0 white, 0 -1px white; */ | 116 | /* text-shadow: -1px 0 white, 0 1px white, 1px 0 white, 0 -1px white; */ |
68 | } | 117 | } |
69 | 118 | ||
... | @@ -78,3 +127,61 @@ button:disabled { | ... | @@ -78,3 +127,61 @@ button:disabled { |
78 | border-radius: 3px; | 127 | border-radius: 3px; |
79 | padding: 0; | 128 | padding: 0; |
80 | } | 129 | } |
130 | + | ||
131 | +[popup] { | ||
132 | + position: absolute; | ||
133 | + z-index: 1000; | ||
134 | + background: rgb(250, 250, 250); | ||
135 | + box-shadow: 0px 0px 5px gray; | ||
136 | + border: solid thin grey; | ||
137 | + border-radius: 5px; | ||
138 | + display: none; | ||
139 | + cursor: auto; | ||
140 | +} | ||
141 | + | ||
142 | +.s_popup { | ||
143 | + top: calc(100% + 5px); | ||
144 | + left: 20px; | ||
145 | + padding: 10px; | ||
146 | + width: 300px; | ||
147 | +} | ||
148 | + | ||
149 | +.spl { | ||
150 | + display: flex; | ||
151 | + flex-direction: column; | ||
152 | + align-items: flex-start; | ||
153 | +} | ||
154 | + | ||
155 | +.spl > * { | ||
156 | + margin-bottom: 3px; | ||
157 | +} | ||
158 | + | ||
159 | +.spl > :nth-child(1) { | ||
160 | + margin-bottom: 5px; | ||
161 | + font-size: large; | ||
162 | +} | ||
163 | + | ||
164 | +.spl > :nth-child(2) { | ||
165 | + font-size: medium; | ||
166 | +} | ||
167 | + | ||
168 | +.spl > div { | ||
169 | + margin-top: 5px; | ||
170 | + font-size: small; | ||
171 | + background-color: white; | ||
172 | + border: solid 1px rgb(235, 235, 235); | ||
173 | + width: calc(100% - 2px); | ||
174 | +} | ||
175 | + | ||
176 | +.sp_btn { | ||
177 | + display: flex; | ||
178 | + margin-left: calc(100% - 100px); | ||
179 | +} | ||
180 | + | ||
181 | +.sp_btn > button { | ||
182 | + margin-left: 2px; | ||
183 | + margin-top: 2px; | ||
184 | + width: 50px; | ||
185 | + height: 35px; | ||
186 | + cursor: pointer; | ||
187 | +} | ... | ... |
... | @@ -12,7 +12,9 @@ | ... | @@ -12,7 +12,9 @@ |
12 | 12 | ||
13 | .GridItem { | 13 | .GridItem { |
14 | border: solid thin lightgray; | 14 | border: solid thin lightgray; |
15 | - height: 150px; | 15 | + display: flex; |
16 | + flex-direction: column; | ||
17 | + /* height: 150px; */ | ||
16 | padding: 5px; | 18 | padding: 5px; |
17 | } | 19 | } |
18 | 20 | ||
... | @@ -34,7 +36,7 @@ | ... | @@ -34,7 +36,7 @@ |
34 | .Grid { | 36 | .Grid { |
35 | display: grid; | 37 | display: grid; |
36 | grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr; | 38 | grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr; |
37 | - overflow-y: auto; | 39 | + overflow-y: scroll; |
38 | height: calc(100vh - 115px); | 40 | height: calc(100vh - 115px); |
39 | border: solid thin gray; | 41 | border: solid thin gray; |
40 | } | 42 | } | ... | ... |
1 | aside { | 1 | aside { |
2 | width: 220px; | 2 | width: 220px; |
3 | + flex-shrink: 0; | ||
3 | margin-right: 8px; | 4 | margin-right: 8px; |
4 | padding-top: 50px; | 5 | padding-top: 50px; |
5 | } | 6 | } |
6 | 7 | ||
7 | .SideSubject { | 8 | .SideSubject { |
8 | - height: 25px; | ||
9 | padding: 5px 0px 5px 0px; | 9 | padding: 5px 0px 5px 0px; |
10 | display: flex; | 10 | display: flex; |
11 | + position: relative; | ||
11 | } | 12 | } |
12 | 13 | ||
13 | .ssc { | 14 | .ssc { |
... | @@ -20,5 +21,48 @@ aside { | ... | @@ -20,5 +21,48 @@ aside { |
20 | } | 21 | } |
21 | 22 | ||
22 | .SideSubject > span { | 23 | .SideSubject > span { |
24 | + cursor: pointer; | ||
23 | line-height: 25px; | 25 | line-height: 25px; |
24 | } | 26 | } |
27 | + | ||
28 | +.ss_popup { | ||
29 | + flex-direction: column; | ||
30 | + align-items: flex-end; | ||
31 | + top: 30px; | ||
32 | + left: 30px; | ||
33 | + /* height: 150px; | ||
34 | + width: 250px; */ | ||
35 | + /* transform: translate(-50%, -50%); */ | ||
36 | + padding: 5px; | ||
37 | +} | ||
38 | + | ||
39 | +.sspd { | ||
40 | + display: flex; | ||
41 | + flex-direction: column; | ||
42 | +} | ||
43 | + | ||
44 | +.sspd > div { | ||
45 | + display: flex; | ||
46 | + margin-bottom: 3px; | ||
47 | +} | ||
48 | + | ||
49 | +.sspd > div > span { | ||
50 | + text-align: center; | ||
51 | + line-height: 30px; | ||
52 | + margin-right: 5px; | ||
53 | + width: 35px; | ||
54 | +} | ||
55 | + | ||
56 | +.sspd > div > input { | ||
57 | + flex-grow: 1; | ||
58 | + height: 24px; | ||
59 | +} | ||
60 | + | ||
61 | +.ssp_btns { | ||
62 | + margin-top: 2px; | ||
63 | +} | ||
64 | + | ||
65 | +.ssp_btns > button { | ||
66 | + padding: 5px 9px 5px 9px; | ||
67 | + margin-left: 2px; | ||
68 | +} | ... | ... |
src/styles/Week.css
0 → 100644
1 | +.Week { | ||
2 | + display: flex; | ||
3 | + flex-direction: column; | ||
4 | + flex-grow: 1; | ||
5 | +} | ||
6 | + | ||
7 | +.GridItem, | ||
8 | +.GridHeadItem { | ||
9 | + flex-basis: 100px; | ||
10 | + flex-grow: 1; | ||
11 | +} | ||
12 | + | ||
13 | +.GridItem { | ||
14 | + border: solid thin lightgray; | ||
15 | + display: flex; | ||
16 | + flex-direction: column; | ||
17 | + /* height: 150px; */ | ||
18 | + padding: 5px; | ||
19 | +} | ||
20 | + | ||
21 | +.GridHeadItem { | ||
22 | + height: 20px; | ||
23 | + text-align: center; | ||
24 | + line-height: 20px; | ||
25 | + padding: 10px 5px 10px 5px; | ||
26 | +} | ||
27 | + | ||
28 | +.GridHead { | ||
29 | + display: flex; | ||
30 | +} | ||
31 | + | ||
32 | +.GridHead { | ||
33 | + margin-right: 16.8px; | ||
34 | +} | ||
35 | + | ||
36 | +.Row { | ||
37 | + display: flex; | ||
38 | + overflow-y: scroll; | ||
39 | + height: calc(100vh - 115px); | ||
40 | + border: solid thin gray; | ||
41 | +} | ||
42 | + | ||
43 | +.GridItem[relative] > span { | ||
44 | + color: gray; | ||
45 | +} |
... | @@ -6,12 +6,12 @@ function toYMD(dateObj) { | ... | @@ -6,12 +6,12 @@ function toYMD(dateObj) { |
6 | return { year, month, date }; | 6 | return { year, month, date }; |
7 | } | 7 | } |
8 | 8 | ||
9 | -function toYMDStr(dateObj) { | 9 | +function toYMDStr(dateObj, joint) { |
10 | return [ | 10 | return [ |
11 | dateObj.getFullYear(), | 11 | dateObj.getFullYear(), |
12 | dateObj.getMonth() + 1, | 12 | dateObj.getMonth() + 1, |
13 | dateObj.getDate(), | 13 | dateObj.getDate(), |
14 | - ].join("/"); | 14 | + ].join(joint); |
15 | } | 15 | } |
16 | 16 | ||
17 | function toSunday(dateObj) { | 17 | function toSunday(dateObj) { | ... | ... |
src/utils/Test.js
deleted
100644 → 0
1 | -import { subForage, scheForage, dataForage } from "./LocalForage"; | ||
2 | -import { toYMDStr } from "./Dates"; | ||
3 | - | ||
4 | -async function initTempSubjects() { | ||
5 | - const tempsch = [ | ||
6 | - { | ||
7 | - subCode: "1", | ||
8 | - type: "zoom", | ||
9 | - category: "과목A", | ||
10 | - label: "", | ||
11 | - start: [9, 30], | ||
12 | - }, | ||
13 | - { | ||
14 | - subCode: "2", | ||
15 | - type: "ecampus", | ||
16 | - category: "과목B", | ||
17 | - label: "과제", | ||
18 | - end: [23, 59], | ||
19 | - }, | ||
20 | - ]; | ||
21 | - | ||
22 | - await scheForage.setItem(toYMDStr(new Date("2022-5-20")), tempsch); | ||
23 | - await scheForage.setItem(toYMDStr(new Date("2022-5-27")), tempsch); | ||
24 | - await scheForage.setItem(toYMDStr(new Date("2022-6-3")), tempsch); | ||
25 | - | ||
26 | - let tcolors = [ | ||
27 | - "red", | ||
28 | - "green", | ||
29 | - "blue", | ||
30 | - "orange", | ||
31 | - "gold", | ||
32 | - "aqua", | ||
33 | - "chartreuse", | ||
34 | - ]; | ||
35 | - const subCodeLst = ["1", "2"]; | ||
36 | - const subObj = { | ||
37 | - 1: { | ||
38 | - name: "과목A", | ||
39 | - color: tcolors[0], | ||
40 | - selected: true, | ||
41 | - }, | ||
42 | - 2: { | ||
43 | - name: "과목B", | ||
44 | - color: tcolors[1], | ||
45 | - selected: true, | ||
46 | - }, | ||
47 | - }; | ||
48 | - | ||
49 | - await subForage.setItem("1", subObj["1"]); | ||
50 | - await subForage.setItem("2", subObj["2"]); | ||
51 | - | ||
52 | - for (let i = 2; i < 7; i++) { | ||
53 | - let code = (i + 1).toString(); | ||
54 | - let tsub = { | ||
55 | - name: "과목" + code, | ||
56 | - color: tcolors[i], | ||
57 | - selected: true, | ||
58 | - }; | ||
59 | - subCodeLst.push(code); | ||
60 | - await subForage.setItem(code, tsub); | ||
61 | - } | ||
62 | - dataForage.setItem("Subjects", subCodeLst); | ||
63 | -} | ||
64 | - | ||
65 | -export { initTempSubjects }; |
-
Please register or login to post a comment