WallpaperMachine
The wallpapers you love. A new home on your Mac.
MWServiceTeam Let's Eat
Translation pending
LifestyleJavaScript · CSSGitHub @sunyun-seoko2OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
🍽️ 밥먹자! 오늘 뭐 먹지? — 팀의 점심 메뉴 고민은 룰렛으로, 그 밖의 결정은 투표로 끝내는 웹앱 밥먹자! 는 빌드 과정 없이 HTML · CSS · Vanilla JS 만으로 동작하는 정적 웹앱입니다. 가게를 등록하고 네이버 지도로 위치를 확인한 뒤, 평일 11:00에 자동으로 도는 룰렛 으로 오늘 점심을 정합니다. 음료수 내기처럼 식사와 상관없는 결정은 자유 투표 로 정할 수 있습니다. Azure Table Storage 를 저장소로 쓰면 여러 사람이 같은 화면을 거의 실시간으로 공유하므로, 팀 전체가 하나의 룰렛·투표를 함께 볼 수 있습니다. 🍱 화면 구성 | 탭 | 설명 | |---|---| | 🥗 점심 | 메인 화면: 오늘의 인원 배치 · 랜덤 룰렛 · 지도 · 자유 투표 | | ⚙️ 설정 | 대상자 / 가게 / 입맛 보호 / 기록 관리 | 모바일에서는 인원 배치 → 룰렛 → 지도 → 투표 순서로 세로로 쌓입니다. 금요일 점심: 별도 탭은 없지만 가게 목록은 따로 관리됩니다. 금요일에는 점심 탭의 룰렛과 지도가 금요일 점심 가게로 바뀝니다.Read the full README ↗
오늘 뭐 먹지? — 팀의 점심 메뉴 고민은 룰렛으로, 그 밖의 결정은 투표로 끝내는 웹앱
밥먹자! 는 빌드 과정 없이 HTML · CSS · Vanilla JS 만으로 동작하는 정적 웹앱입니다. 가게를 등록하고 네이버 지도로 위치를 확인한 뒤, 평일 11:00에 자동으로 도는 룰렛 으로 오늘 점심을 정합니다. 음료수 내기처럼 식사와 상관없는 결정은 자유 투표 로 정할 수 있습니다.
Azure Table Storage 를 저장소로 쓰면 여러 사람이 같은 화면을 거의 실시간으로 공유하므로, 팀 전체가 하나의 룰렛·투표를 함께 볼 수 있습니다.
| 탭 | 설명 |
|---|---|
| 🥗 점심 | 메인 화면: 오늘의 인원 배치 · 랜덤 룰렛 · 지도 · 자유 투표 |
| ⚙️ 설정 | 대상자 / 가게 / 입맛 보호 / 기록 관리 |
메인 화면 배치 (데스크톱 기준)
| 1열 | 2열 | 3열 |
|---|---|---|
| 👥 오늘의 인원 배치 | 🎯 랜덤 룰렛 | 🗺️ 지도 |
| 🗳️ 자유 투표 (1~2열) |
모바일에서는 인원 배치 → 룰렛 → 지도 → 투표 순서로 세로로 쌓입니다.
금요일 점심: 별도 탭은 없지만 가게 목록은 따로 관리됩니다. 금요일에는 점심 탭의 룰렛과 지도가 금요일 점심 가게로 바뀝니다.
[!NOTE] 🍻 저녁 탭은 현재 숨김 상태 입니다. 탭과 함께 설정 메뉴의 저녁 선택지(중복 허용 · 못 가는 가게 선택 · 기록 관리 필터)와 오후의 "오늘 저녁 및 회식" 제목도 숨겨집니다. 기능 코드와 데이터는 그대로 있으며,
js/config.js의showDinnerTab을true로 바꾸면 모두 다시 나타납니다.
37.5718135, 127.0010162 (네이버 Geocoder 로 종로33길 15 를 변환한 값) 이며, 서버(Azure Functions)도 같은 상수를 씁니다.버튼 없이 완전 자동 으로 동작합니다.
[!NOTE] 서버 실행이 실패한 경우의 브라우저 폴백(11:03~)은 누군가 페이지를 열어 두고 있어야 동작합니다. 서버와 브라우저, 여러 브라우저가 동시에 실행해도 같은 날짜로 계산한 후보·당첨이 같아서 결과는 하나로 맞춰집니다. 브라우저가 백그라운드 탭의 타이머를 늦추면 최대 1분쯤 늦게 시작할 수 있습니다.
식사와 별개로 쓰는 일반 투표입니다. 음료수 내기, 회식 날짜 정하기 등 무엇이든 물어볼 수 있습니다. 모든 탭에서 같은 투표 하나가 보입니다.
정적 파일이므로 아무 정적 서버로 띄우면 됩니다.
# Python
python -m http.server 8000
# 또는 Node
npx http-server -p 8000
[!WARNING]
file://로 직접 열면 네이버 지도 API 도메인 인증이 실패합니다. 반드시localhost로 띄우세요.
[!TIP] Azure 설정 없이 혼자 써보려면
js/config.js의storage를'local'로 바꾸세요. 데이터가 브라우저 localStorage 에만 저장됩니다.
모든 설정은 js/config.js 의 window.AppConfig 에 있습니다.
| 키 | 기본값 | 설명 |
|---|---|---|
storage | 'azure' | 'azure' = 공유 저장소, 'local' = 브라우저 단독 |
azure.account | — | Storage Account 이름 |
azure.sas | — | SAS 토큰 (앞의 ? 제외) |
azure.tableStores · tableVotes · tablePeople · tableRandomHistory | stores · votes · people · randomhistory | 테이블 이름 |
pollIntervalMs | 3000 | 평시 폴링 간격 (ms) |
pollIntervalVoteMs | 3000 | 투표 진행 중 폴링 간격 (ms) |
pollIntervalRouletteMs | 1000 | 룰렛 회전 중 폴링 간격 (ms) |
rouletteSpinLeadMs | 2000 | 다른 브라우저가 회전을 받을 수 있도록 시작 전 대기 (ms) |
rouletteSpinDurationMs | 8000 | 룰렛 회전 시간 (ms) |
serverAutoRoulette | true | true = 11:00 룰렛을 Azure Functions 가 실행하고, 브라우저 폴백은 11:03부터 동작 (false = 예전처럼 11:00부터 브라우저가 실행) |
showDinnerTab | false | 저녁 탭과 설정 메뉴의 저녁 선택지 표시 여부 (끄면 화면에서만 숨김) |
index.html 하단의 스크립트 태그에서 ncpKeyId 를 본인 키로 바꿉니다.
<script src="https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=<YOUR_KEY_ID>&submodules=geocoder"></script>
ncpKeyId, 구 계정은 ncpClientId 를 사용합니다. 인증 오류가 나면 서로 바꿔보세요.http://localhost:8000https://<USERNAME>.github.ioAuthentication Failed 가 발생합니다.git init
git add .
git commit -m "Initial commit"
git branch -M main
git remote add origin https://github.com/<USERNAME>/<REPO>.git
git push -u origin main
Deploy from a branch · Branch: main / (root) → Savehttps://<USERNAME>.github.io/<REPO>/ 접속[!NOTE]
index.html의 스크립트에는?v=11같은 캐시 버스팅 쿼리가 붙어 있습니다. JS 를 수정해 배포할 때 숫자를 올려야 사용자 브라우저에 바로 반영됩니다.
[!IMPORTANT] 코드를 수정하면 모든 변경 사항을 커밋하고 GitHub(
main)에 push 합니다. GitHub Pages 는main을 그대로 배포하므로, push 하지 않은 변경은 팀원 화면에 반영되지 않습니다.
index.html 의 ?v= 숫자를 올립니다.git add -A
git commit -m "변경 내용 요약"
git push origin main
storage 값에 따라 어댑터가 선택됩니다.
| 모드 | 어댑터 | 공유 | 용도 |
|---|---|---|---|
'azure' | js/storage-azure.js | ✅ 여러 사용자 | 팀 운영 |
'local' | js/storage.js | ❌ 해당 브라우저만 | 개발 · 개인 사용 |
| 테이블 | 내용 |
|---|---|
stores | 가게 목록 |
votes | 자유 투표(PartitionKey = general) · 이전 투표 기록 · 식사별 룰렛 세션 · 이전 룰렛 기록(PartitionKey = roulette_history, 최대 15개) |
people | 대상자 · 그룹 배치 · 고정 · 입맛 보호 정보 |
randomhistory | 이번 주 당첨 기록 (랜덤 후보 제외용) |
https://<USERNAME>.github.io, http://localhost:8000GET, POST, PUT, DELETE, OPTIONS, MERGE* · Max age: 3600js/config.js 의 azure.account, azure.sas 에 입력Azure 모드에서는 서버 푸시 없이 폴링 으로 화면을 맞춥니다.
평일 11:00(서울)에 functions/ 의 lunchRoulette 함수가 룰렛을 실행합니다.
maybeRunAutoRoulette 와 같은 로직 (시드 난수, 후보 5곳, 이번 주 당첨 제외, 입맛 보호, 인원 고정)으로 후보와 당첨을 정해 votes 테이블에 세션을 저장하고, 당첨 기록 · 이전 룰렛 기록도 남깁니다.2026년 10월 15일 목요일 점심 룰렛 결과, 강조색 Accent) · 🎯 가게 이름 🎯(초록 Good) 과 그 아래 가운데 정렬된 네이버 지도 열기 버튼 (네이버 지도 URL 이 없는 가게는 버튼 없음) 입니다. 도보 시간 · 주소 · 메모 · 밥먹자 링크는 넣지 않고, 후보 목록도 넣지 않습니다. 카드 폭은 Teams 기본값을 따르며 픽셀 단위로 고정할 수 없습니다.lets-eat-function-app (리소스 그룹 lets-eat-rg) 입니다.0 0 2 * * 1-5 (= 서울 11:00, 월~금). Linux Flex 는 WEBSITE_TIME_ZONE 을 지원하지 않아 UTC 로 표기합니다.| 이름 | 설명 |
|---|---|
STORAGE_ACCOUNT | Storage Account 이름 (js/config.js 의 azure.account 와 같은 값) |
STORAGE_SAS | SAS 토큰 (js/config.js 의 azure.sas 와 같은 값, 앞의 ? 는 있어도 없어도 됨) |
TEAMS_WEBHOOK_URL | Teams 워크플로 웹후크 URL (없으면 Teams 알림만 건너뜀) |
TEAMS_BANNER_URL | (선택) 카드 상단 배너 이미지 주소. 카드 폭에 맞춰 표시되므로 가로로 긴 이미지를 권장합니다. 기본값은 GitHub Pages 의 assets/teams-banner.jpg. 외부에서 열리는 공개 https 주소여야 하고, PNG/JPG 를 권장합니다 (Teams 는 WebP 를 안정적으로 표시하지 못함) |
ROULETTE_SPIN_LEAD_MS | 저장 후 회전 시작까지 대기 (기본 5000) |
ROULETTE_SPIN_DURATION_MS | 회전 시간 (기본 8000) |
테이블 이름을 바꿨다면
TABLE_STORES·TABLE_VOTES·TABLE_PEOPLE·TABLE_RANDOM_HISTORY로 지정할 수 있습니다. 로컬 테스트용 설정은functions/local.settings.example.json을local.settings.json으로 복사해 채웁니다. 타이머 트리거 때문에AzureWebJobsStorage가 필요하므로 로컬 실행 시에는 Azurite 에뮬레이터(UseDevelopmentStorage=true)를 먼저 켜 두세요.
TEAMS_WEBHOOK_URL 앱 설정에 붙여넣기[!NOTE] 웹후크가
202를 돌려줘도 "요청을 접수했다"는 뜻일 뿐입니다. 실제 게시 실패는 Teams / Power Automate 의 해당 워크플로 실행 기록(run history) 에서 확인하세요.
저장소 루트의 PowerShell 에서 실행합니다. Windows 10+ 기본 tar.exe(bsdtar)는 zip 을 만들고 경로를 슬래시(/)로 기록합니다.
tar.exe -a -c -f functions.zip -C functions host.json package.json src
[!WARNING]
- Git Bash 의
tar는 GNU tar 라서-a를 줘도 zip 이 아닌 tar 파일이 만들어지고, 배포가End of Central Directory record could not be found로 실패합니다. Git Bash 에서는/c/Windows/System32/tar.exe로 전체 경로를 쓰세요.- PowerShell 5.1 의
Compress-Archive는 zip 안의 경로를 역슬래시(\)로 기록해 Linux(Flex Consumption)에서 배포가 깨집니다. 쓰지 마세요.
az functionapp deployment source config-zip -g lets-eat-rg -n lets-eat-function-app --src functions.zip --build-remote true
앱 설정 등록:
az functionapp config appsettings set -g lets-eat-rg -n lets-eat-function-app --settings \
STORAGE_ACCOUNT=<STORAGE_ACCOUNT> STORAGE_SAS="<SAS>" \
TEAMS_WEBHOOK_URL="<WEBHOOK_URL>" \
ROULETTE_SPIN_LEAD_MS=5000 ROULETTE_SPIN_DURATION_MS=8000
Azure Portal → Function App → 함수 → lunchRoulette → 테스트/실행 으로 수동 실행할 수 있습니다.
[!WARNING] 평일에 오늘 룰렛이 아직 실행되지 않았다면 실제로 룰렛이 실행 되어 세션 · 기록이 저장되고 Teams 에도 게시됩니다. 이미 실행됐거나 주말이면 로그만 남기고 끝납니다. 로그는 Application Insights 또는 함수의 모니터 탭에서 확인합니다.
모든 시간은 서울(Asia/Seoul) 기준입니다.
| 규칙 | 시간 |
|---|---|
| 금요일 점심 가게로 전환 | 금요일 하루 동안 (룰렛 · 지도) |
| 자동 탭 선택 — 저녁 | 평일 13:30 ~ 19:00 (저녁 탭을 켰을 때만) |
| 점심 룰렛 자동 실행 | 서버(Azure Functions) 평일 11:00 (월~목 점심, 금 금요일 점심) · 서버 실패 시 브라우저 폴백 11:03 ~ 13:00 |
| 룰렛 자동 초기화 | 매일 09:00 |
| 종료된 투표 자동 보관 | 종료 다음날 09:30 |
| 인원 고정 적용 | 월~목 10:50 ~ 13:30 |
| 인원 고정 해제 | 13:30 이후, 금요일 (다음 평일 10:50 재적용) |
| 당첨 기록 대상 | 월~금 결과만 |
| 당첨 기록 초기화 | 매주 토요일 10:00 |
.
├── index.html # 화면 구조 + 스크립트 로드
├── styles.css # 전체 스타일 (라이트/다크 테마)
├── assets/ # 헤더 · 탭 장식 이미지, teams-banner.jpg (Teams 카드 배너)
├── functions/ # Azure Functions (서버 자동 룰렛 + Teams 알림)
│ ├── host.json
│ ├── package.json
│ ├── local.settings.example.json
│ └── src/
│ ├── functions/lunchRoulette.js # 평일 11:00 타이머 트리거
│ ├── roulette.js # 후보 선정 · 시드 난수 · 도보 계산 (app.js 포팅)
│ ├── tables.js # Azure Table REST 클라이언트
│ └── teams.js # Teams Adaptive Card 전송
└── js/
├── config.js # 앱 설정 (저장소, 프록시, 폴링 간격)
├── storage.js # localStorage 어댑터
├── storage-azure.js # Azure Table Storage 어댑터
├── stores.js # 가게 CRUD
├── maps.js # 네이버 지도 · Geocoder · 마커
├── week-history.js # 주간 당첨 기록 규칙
├── roulette.js # 캔버스 룰렛
├── voting.js # 자유 투표 (참여 인원, 시간 제한, 1인 1표)
└── app.js # UI 이벤트 · 상태 · 전체 흐름
[!CAUTION]
js/config.js의 SAS 토큰은 브라우저에 그대로 노출 됩니다. 페이지 주소를 아는 사람은 누구나 테이블을 읽고, 쓰고, 지울 수 있습니다.
TEAMS_WEBHOOK_URL)에만 두고, 클라이언트 코드나 저장소에는 절대 넣지 마세요. URL 을 아는 사람은 누구나 채널에 글을 올릴 수 있습니다. (functions/local.settings.json 은 .gitignore 로 제외됩니다.)MIT — 자유롭게 사용 · 수정 · 배포할 수 있습니다.