From 187318094858d06a5f262fafdfeeed6cf5a29de9 Mon Sep 17 00:00:00 2001 From: seung6lee Date: Wed, 4 Mar 2026 21:39:44 +0900 Subject: [PATCH] class 3-5 initial --- .gitignore | 175 +++++++++++++++++++ .prettierrc | 4 + README.md | 15 ++ bun.lockb | Bin 0 -> 43645 bytes client_secret.json | 12 ++ jsconfig.json | 22 +++ package.json | 19 +++ public/images/calendar.png | Bin 0 -> 305 bytes public/index.html | 35 ++++ public/script.js | 83 +++++++++ public/style.css | 270 +++++++++++++++++++++++++++++ public/style.css.map | 1 + public/style.scss | 341 +++++++++++++++++++++++++++++++++++++ server.js | 59 +++++++ token.json | 8 + 15 files changed, 1044 insertions(+) create mode 100644 .gitignore create mode 100644 .prettierrc create mode 100644 README.md create mode 100755 bun.lockb create mode 100644 client_secret.json create mode 100644 jsconfig.json create mode 100644 package.json create mode 100644 public/images/calendar.png create mode 100644 public/index.html create mode 100644 public/script.js create mode 100644 public/style.css create mode 100644 public/style.css.map create mode 100644 public/style.scss create mode 100644 server.js create mode 100644 token.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b1ee42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..5a938ce --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 4, + "useTabs": false +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..97e0789 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# twofive + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run server.js +``` + +This project was created using `bun init` in bun v1.0.26. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..80f29992ece350877f61c9c1f3a20929af0c7c36 GIT binary patch literal 43645 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 b8*pBd0zpRtdVf6F1;!2cQ;Glo{`>y`&3vkp literal 0 HcmV?d00001 diff --git a/client_secret.json b/client_secret.json new file mode 100644 index 0000000..6041ae4 --- /dev/null +++ b/client_secret.json @@ -0,0 +1,12 @@ +{ + "web": { + "client_id": "769538006975-q773s454k2l1r4q35vat4psbjfgg59pk.apps.googleusercontent.com", + "project_id": "g-cal-task-management25", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_secret": "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", + "redirect_uris": ["http://localhost:4000/callback"], + "javascript_origins": ["http://localhost"] + } +} diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..95c20ce --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + /* Linting */ + "skipLibCheck": true, + "strict": true, + "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1834383 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "twofive", + "module": "server.js", + "type": "module", + "devDependencies": { + "@types/bun": "latest", + "sass": "^1.85.1" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "express": "^4.21.2", + "open": "^10.1.0" + }, + "scripts": { + "sass-watch": "sass --watch ./public/style.scss ./public/style.css" + } +} \ No newline at end of file diff --git a/public/images/calendar.png b/public/images/calendar.png new file mode 100644 index 0000000000000000000000000000000000000000..9fe0700a5af6e379a3ab8d17d287db346b5caf37 GIT binary patch literal 305 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|&H|6fVg?3oVGw3ym^DWND9BhG zY9GR_^H(FF7ngQu&X%Q~loCIDuWbbJ5+ literal 0 HcmV?d00001 diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..e26ecad --- /dev/null +++ b/public/index.html @@ -0,0 +1,35 @@ + + + + + + Class 3-5 + + + + + + + +
+
+ +
+ +
+ + diff --git a/public/script.js b/public/script.js new file mode 100644 index 0000000..4653dfc --- /dev/null +++ b/public/script.js @@ -0,0 +1,83 @@ +class meal_list extends HTMLElement { + constructor() { + super(); + + const now = new Date(); + const hours = now.getHours(); + + this.date = new Date() + .toISOString() + .slice(0, 10) + .replace(/-/g, ""); + + switch (true) { + case hours < 8: + this.selectedType = 1; + break; + case hours < 14: + this.selectedType = 2; + break; + default: + this.selectedType = 3; + } + } + + connectedCallback() { + this.render(); + } + + async get_meal(date, type) { + try { + console.log(`/api/meal?date=${date}&type=${type}`) + let response = await fetch(`/api/meal?date=${date}&type=${type}`); + + if (!response.ok) throw new Error("Failed to fetch meal data"); + + let data = await response.json(); + + return data; + } catch (error) { + console.error("❌ API Error:", error); + return ["식단 정보를 불러올 수 없습니다."]; + } + } + + async render() { + const header = document.createElement("header"); + + const select = document.createElement("select"); + const types = ["아침", "점심", "저녁"]; + types.forEach((type, index) => { + const option = document.createElement("option"); + option.textContent = types[index]; + option.value = index + 1; + if (index + 1 === this.selectedType) option.selected = true; + select.appendChild(option); + }); + select.addEventListener("change", async () => { + this.selectedType = Number(select.value); + await this.render(); + }); + + const res = await this.get_meal(this.date, this.selectedType); + + const ul = document.createElement("ul"); + res.meal_list.forEach((meal) => { + const li = document.createElement("li"); + li.textContent = meal; + ul.appendChild(li); + }); + + const kcal = document.createElement("p"); + kcal.textContent = res.kcal; + + header.appendChild(select); + header.appendChild(kcal); + + this.innerHTML = ""; + this.append(header); + this.appendChild(ul); + } +} + +customElements.define("meal-list", meal_list); \ No newline at end of file diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000..aa459c0 --- /dev/null +++ b/public/style.css @@ -0,0 +1,270 @@ +@font-face { + font-family: "GMarketSans"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2001@1.1/GmarketSansMedium.woff") format("woff"); + font-weight: 500; + font-display: swap; +} +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + width: 100%; + height: 100vh; + font-family: "GMarketSans", serif; + display: flex; + flex-direction: column; + gap: 10px; + background-color: #F0F0F0; + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.06) 1px, transparent 1px), linear-gradient(to bottom, rgba(0, 0, 0, 0.06) 1px, transparent 1px); + background-size: 28px 28px; + color: #0A0A0A; +} + +.box { + border-radius: 0; + position: relative; +} + +.title { + height: 32px; + width: 100%; + border-bottom: 1px solid #C8C8C8; + margin-bottom: 12px; + font-size: 12px; + font-weight: 500; + letter-spacing: 0.12em; + text-transform: uppercase; + color: #888888; + display: flex; + align-items: center; +} +.title::before { + content: ""; + display: inline-block; + width: 3px; + height: 14px; + background-color: #FF4B00; + margin-right: 8px; + flex-shrink: 0; +} + +#header { + width: 100%; + height: 60px; + background-color: #FFFFFF; + border-bottom: 1px solid #C8C8C8; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 0 24px; + position: relative; +} +#header::after { + content: ""; + position: absolute; + bottom: -1px; + left: 0; + width: 100%; + height: 1px; + background: repeating-linear-gradient(to right, #FF4B00 0px, #FF4B00 4px, transparent 4px, transparent 12px); +} +#header h1 { + font-size: 16px; + font-weight: 500; + letter-spacing: 0.08em; + text-transform: uppercase; + color: #0A0A0A; +} +#header h1 span { + color: #FF4B00; +} +#header ul { + display: flex; + flex-direction: row; +} +#header ul li { + display: inline-block; + padding: 0 1.5rem; + font-size: 13px; + font-weight: 500; + letter-spacing: 0.06em; +} +#header ul li a { + text-decoration: none; + color: #4A4A4A; + text-transform: uppercase; + transition: color 0.15s ease; + position: relative; + padding-bottom: 4px; +} +#header ul li a::after { + content: ""; + position: absolute; + bottom: 0; + left: 0; + width: 0; + height: 1px; + background-color: #FF4B00; + transition: width 0.2s ease; +} +#header ul li a:hover { + color: #0A0A0A; +} +#header ul li a:hover::after { + width: 100%; +} +#header ul li a.active { + color: #FF4B00; +} +#header ul li a.active::after { + width: 100%; +} + +#main { + width: 100%; + height: calc(100% - 60px - 10px); + padding: 10px; + display: flex; + flex-direction: row; + gap: 25px; +} + +#side { + width: 400px; + height: 100%; + display: flex; + flex-direction: column; + gap: 25px; +} + +#info { + width: 100%; + height: 100%; + background-color: #FFFFFF; + border: 1px solid #C8C8C8; + padding: 10px; + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.03) 1px, transparent 1px), linear-gradient(to bottom, rgba(0, 0, 0, 0.03) 1px, transparent 1px); + background-size: 20px 20px; +} +#info meal-list { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + gap: 10px; + overflow-y: hidden; +} +#info meal-list header { + width: 100%; + height: 40px; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +} +#info meal-list header select { + border: 1px solid #C8C8C8; + border-radius: 0; + padding: 4px 8px; + font-size: 12px; + font-family: "GMarketSans", serif; + letter-spacing: 0.04em; + background-color: #FFFFFF; + color: #0A0A0A; + cursor: pointer; + transition: border-color 0.15s, box-shadow 0.15s; +} +#info meal-list header select:hover { + border-color: #0A0A0A; + box-shadow: none; +} +#info meal-list header select:focus { + outline: none; + border-color: #FF4B00; +} +#info meal-list ul { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + overflow-y: auto; + overflow-x: hidden; + cursor: ns-resize; +} +#info meal-list ul::-webkit-scrollbar { + display: none; +} +#info meal-list ul li { + width: 100%; + height: 56px; + border-radius: 0; + border: 1px solid #C8C8C8; + border-left: 3px solid transparent; + display: flex; + flex-direction: row; + align-items: center; + padding-left: 10px; + flex-shrink: 0; + background-color: #FFFFFF; + font-size: 13px; + color: #4A4A4A; + transition: border-left-color 0.15s, background-color 0.15s, color 0.15s; +} +#info meal-list ul li:hover { + border-left-color: #FF4B00; + background-color: rgba(255, 75, 0, 0.08); + color: #0A0A0A; +} +#info meal-list ul li.active { + border-left-color: #FF4B00; + background-color: rgba(255, 75, 0, 0.08); + color: #0A0A0A; + font-weight: 500; +} + +#calendar { + border: 1px solid #C8C8C8; + border-top: 3px solid #FF4B00; + width: calc(100% - 400px - 25px); + height: 100%; + background-color: #FFFFFF; + position: relative; +} +#calendar iframe { + border-radius: 0; + width: 100%; + height: 100%; + border: none; +} + +@media (max-width: 800px) { + h1 { + font-size: 16px; + } + #main { + width: 100%; + flex-direction: column; + } + #side { + width: 100%; + height: auto; + } + #task { + height: auto; + } + #info { + height: auto; + } + #calendar { + width: 100%; + height: 400px; + flex-shrink: 0; + } +} + +/*# sourceMappingURL=style.css.map */ diff --git a/public/style.css.map b/public/style.css.map new file mode 100644 index 0000000..cd9a8e4 --- /dev/null +++ b/public/style.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["style.scss"],"names":[],"mappings":"AAAA;EACI;EACA;EACA;EACA;;AA+BJ;EACI;EACA;EACA;;;AAIJ;EACI;EACA;EACA;EACA;EACA;EACA,KA3BY;EA4BZ,kBAjCK;EAYL,kBACI;EAEJ;EAoBA,OAxCK;;;AA4CT;EACI;EACA;;;AAIJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OAxDK;EAyDL;EACA;;AAGA;EACI;EACA;EACA;EACA;EACA,kBAxEO;EAyEP;EACA;;;AAKR;EACI;EACA,QAjEY;EAkEZ,kBAzEK;EA0EL;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAOJ;EACI;EACA;EACA;EACA;EACA,OA5GC;;AA+GD;EACI,OAnHG;;AAuHX;EACI;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA,OA/HP;EAgIO;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA,kBAjJL;EAkJK;;AAGJ;EACI,OAnJX;;AAqJW;EACI;;AAIR;EACI,OA9JL;;AAgKK;EACI;;;AASxB;EACI;EACA;EACA,SAhKY;EAiKZ;EACA;EACA,KAlKY;;;AAsKhB;EACI,OAtKY;EAuKZ;EACA;EACA;EACA,KA3KY;;;AA+KhB;EACI;EACA;EACA,kBAvLK;EAwLL;EACA,SArLY;EAOZ,kBACI;EAEJ;;AAgLA;EACI;EACA;EACA;EACA;EACA,KA/LQ;EAgMR;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA,kBArNP;EAsNO,OA5NP;EA6NO;EACA;;AAEA;EACI,cAjOX;EAkOW;;AAGJ;EACI;EACA,cA1OL;;AA+OP;EACI;EACA;EACA;EACA;EACA,KAvOI;EAwOJ;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,cAzPA;EA0PA;EACA,kBA/PP;EAgQO;EACA,OArQP;EAsQO;;AAEA;EACI,mBA9QL;EA+QK,kBA9QL;EA+QK,OA7QX;;AAgRO;EACI,mBApRL;EAqRK,kBApRL;EAqRK,OAnRX;EAoRW;;;AAQpB;EACI;EACA;EACA;EACA;EACA,kBA3RK;EA4RL;;AAEA;EACI;EACA;EACA;EACA;;;AAKR;EACI;IACI;;EAGJ;IACI;IACA;;EAGJ;IACI;IACA;;EAGJ;IACI;;EAGJ;IACI;;EAGJ;IACI;IACA;IACA","file":"style.css"} \ No newline at end of file diff --git a/public/style.scss b/public/style.scss new file mode 100644 index 0000000..b6adcf0 --- /dev/null +++ b/public/style.scss @@ -0,0 +1,341 @@ +@font-face { + font-family: 'GMarketSans'; + src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2001@1.1/GmarketSansMedium.woff') format('woff'); + font-weight: 500; + font-display: swap; +} + +// ─── Design Tokens ──────────────────────────────────────────────────────────── +$accent: #FF4B00; +$accent-light: rgba(255, 75, 0, 0.08); + +$black: #0A0A0A; +$gray-9: #1A1A1A; +$gray-7: #4A4A4A; +$gray-5: #888888; +$gray-3: #C8C8C8; +$gray-1: #F0F0F0; +$white: #FFFFFF; + +$grid-line: rgba(0, 0, 0, 0.08); + +$small_margin: 10px; +$big_margin: 25px; +$nav_width: 400px; +$header_height: 60px; + +// ─── Grid Background Mixin ──────────────────────────────────────────────────── +@mixin grid-bg($size: 28px, $color: $grid-line) { + background-image: + linear-gradient(to right, $color 1px, transparent 1px), + linear-gradient(to bottom, $color 1px, transparent 1px); + background-size: $size $size; +} + +// ─── Reset ──────────────────────────────────────────────────────────────────── +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +// ─── Body ───────────────────────────────────────────────────────────────────── +body { + width: 100%; + height: 100vh; + font-family: "GMarketSans", serif; + display: flex; + flex-direction: column; + gap: $small_margin; + background-color: $gray-1; + @include grid-bg(28px, rgba(0,0,0,0.06)); + color: $black; +} + +// ─── Box ────────────────────────────────────────────────────────────────────── +.box { + border-radius: 0; + position: relative; +} + +// ─── Title ──────────────────────────────────────────────────────────────────── +.title { + height: 32px; + width: 100%; + border-bottom: 1px solid $gray-3; + margin-bottom: 12px; + font-size: 12px; + font-weight: 500; + letter-spacing: 0.12em; + text-transform: uppercase; + color: $gray-5; + display: flex; + align-items: center; + + // 포인트 컬러 왼쪽 태그 + &::before { + content: ''; + display: inline-block; + width: 3px; + height: 14px; + background-color: $accent; + margin-right: 8px; + flex-shrink: 0; + } +} + +// ─── Header ─────────────────────────────────────────────────────────────────── +#header { + width: 100%; + height: $header_height; + background-color: $white; + border-bottom: 1px solid $gray-3; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 0 24px; + position: relative; + + // 헤더 하단 그리드 강조선 + &::after { + content: ''; + position: absolute; + bottom: -1px; + left: 0; + width: 100%; + height: 1px; + background: repeating-linear-gradient( + to right, + $accent 0px, $accent 4px, + transparent 4px, transparent 12px + ); + } + + h1 { + font-size: 16px; + font-weight: 500; + letter-spacing: 0.08em; + text-transform: uppercase; + color: $black; + + // 포인트 컬러 강조 + span { + color: $accent; + } + } + + ul { + display: flex; + flex-direction: row; + + li { + display: inline-block; + padding: 0 1.5rem; + font-size: 13px; + font-weight: 500; + letter-spacing: 0.06em; + + a { + text-decoration: none; + color: $gray-7; + text-transform: uppercase; + transition: color 0.15s ease; + position: relative; + padding-bottom: 4px; + + &::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 0; + height: 1px; + background-color: $accent; + transition: width 0.2s ease; + } + + &:hover { + color: $black; + + &::after { + width: 100%; + } + } + + &.active { + color: $accent; + + &::after { + width: 100%; + } + } + } + } + } +} + +// ─── Main ───────────────────────────────────────────────────────────────────── +#main { + width: 100%; + height: calc(100% - #{$header_height} - #{$small_margin}); + padding: $small_margin; + display: flex; + flex-direction: row; + gap: $big_margin; +} + +// ─── Side ───────────────────────────────────────────────────────────────────── +#side { + width: $nav_width; + height: 100%; + display: flex; + flex-direction: column; + gap: $big_margin; +} + +// ─── Info ───────────────────────────────────────────────────────────────────── +#info { + width: 100%; + height: 100%; + background-color: $white; + border: 1px solid $gray-3; + padding: $small_margin; + + // 내부 그리드 배경 + @include grid-bg(20px, rgba(0,0,0,0.03)); + + meal-list { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + gap: $small_margin; + overflow-y: hidden; + + header { + width: 100%; + height: 40px; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + select { + border: 1px solid $gray-3; + border-radius: 0; + padding: 4px 8px; + font-size: 12px; + font-family: "GMarketSans", serif; + letter-spacing: 0.04em; + background-color: $white; + color: $black; + cursor: pointer; + transition: border-color 0.15s, box-shadow 0.15s; + + &:hover { + border-color: $black; + box-shadow: none; + } + + &:focus { + outline: none; + border-color: $accent; + } + } + } + + ul { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + gap: $small_margin; + overflow-y: auto; + overflow-x: hidden; + cursor: ns-resize; + + &::-webkit-scrollbar { + display: none; + } + + li { + width: 100%; + height: 56px; + border-radius: 0; + border: 1px solid $gray-3; + border-left: 3px solid transparent; // 기본 상태 + display: flex; + flex-direction: row; + align-items: center; + padding-left: $small_margin; + flex-shrink: 0; + background-color: $white; + font-size: 13px; + color: $gray-7; + transition: border-left-color 0.15s, background-color 0.15s, color 0.15s; + + &:hover { + border-left-color: $accent; + background-color: $accent-light; + color: $black; + } + + &.active { + border-left-color: $accent; + background-color: $accent-light; + color: $black; + font-weight: 500; + } + } + } + } +} + +// ─── Calendar ───────────────────────────────────────────────────────────────── +#calendar { + border: 1px solid $gray-3; + border-top: 3px solid $accent; // 포인트 컬러 상단 강조선 + width: calc(100% - #{$nav_width} - #{$big_margin}); + height: 100%; + background-color: $white; + position: relative; + + iframe { + border-radius: 0; + width: 100%; + height: 100%; + border: none; + } +} + +// ─── Responsive ─────────────────────────────────────────────────────────────── +@media (max-width: 800px) { + h1 { + font-size: 16px; + } + + #main { + width: 100%; + flex-direction: column; + } + + #side { + width: 100%; + height: auto; + } + + #task { + height: auto; + } + + #info { + height: auto; + } + + #calendar { + width: 100%; + height: 400px; + flex-shrink: 0; + } +} \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000..8ce0f87 --- /dev/null +++ b/server.js @@ -0,0 +1,59 @@ +import express from "express"; +import path from "path"; +import open from "open"; +import fs from "fs"; + +const app = express(); + +app.use("/public", express.static(path.join(__dirname, "public"))); +// app.use(express.urlencoded({ extends: true })); +app.use(express.json()); + +app.listen(4000, () => { + console.log("✅Pass: Server running at http://127.0.0.1:4000"); +}); + +app.get("/", (req, res) => { + res.sendFile(__dirname + "/public/index.html"); +}); + +app.get("/api/meal", async (req, res) => { + const item = req.query; + const date = item.date; + const type = item.type; + + const base_url = new URL("https://open.neis.go.kr/hub/mealServiceDietInfo"); + const params = new URLSearchParams({ + key: "6b442e68fabb4bee943947613b31eeee", + Type: "json", + ATPT_OFCDC_SC_CODE: "D10", + SD_SCHUL_CODE: "7240450", + MLSV_YMD: date, + MMEAL_SC_CODE: type, + }); + const url = `${base_url}?${params.toString()}`; + + try { + let data = await fetch(url); + + if (!data.ok) { + res.send("No Information"); + } + + data = await data.json(); + + if (!data.mealServiceDietInfo[1].row[0]) { + res.send("No Information"); + } + + let meal_list = data.mealServiceDietInfo[1].row[0].DDISH_NM; + meal_list = meal_list.replace(/\([^)]+\)/g, "").split("
"); + + res.json({ + meal_list: meal_list, + kcal: data.mealServiceDietInfo[1].row[0].CAL_INFO, + }); + } catch (error) { + res.send("No Information"); + } +}); \ No newline at end of file diff --git a/token.json b/token.json new file mode 100644 index 0000000..1290e02 --- /dev/null +++ b/token.json @@ -0,0 +1,8 @@ +{ + "access_token": "ya29.a0AeXRPp6dQRljlR_5KWE8DgcWu4lkKXDAgErjVzNGkgIZwiCKXqk5MtqKfB2Wp_DG58SzfM2UmPJJpeIbflU-D0_xxCnD0u0En_OccmeRsSEE56KY5q_NtQtnpwUekEvtvEjhIiGfU0M5NdgljLvnAN7JkPvv-Kjd2liy59j17QaCgYKAWkSARASFQHGX2MiA14X1b8pOFBNNRtkZ3xKug0177", + "expires_in": 3599, + "scope": "https://www.googleapis.com/auth/calendar", + "token_type": "Bearer", + "refresh_token_expires_in": 571598, + "refresh_token": "1//0erCBDblHqeswCgYIARAAGA4SNwF-L9IrKNzH7uKvucIavo21bciIQe04jXA3w-aHCLvrUmUNoaIVNn1wx4fapAmpifxS-kjcPyk" +} \ No newline at end of file