Skip to content

Commit bca567e

Browse files
authored
Add files via upload
1 parent e26f5b3 commit bca567e

1 file changed

Lines changed: 48 additions & 17 deletions

File tree

index.html

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,18 @@
122122
.stat-sub{margin-top:5px;font-size:11px;color:var(--silver)}
123123
.stat-head-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}
124124
.stat-head-row .stat-name{margin-bottom:0}
125-
.stat-cycles{font-size:10px;color:var(--stone);background:var(--light-gray);border-radius:9999px;padding:2px 7px;font-weight:500;white-space:nowrap;flex-shrink:0}
126-
.cycle-row{display:flex;align-items:center;justify-content:space-between;gap:6px;margin-bottom:10px}
127-
.cycle-dots{display:flex;gap:4px;align-items:center}
128-
.cycle-dot{width:9px;height:9px;border-radius:9999px;background:var(--light-gray)}
125+
.stat-cycles{font-size:10px;color:var(--stone);background:rgba(0,0,0,.08);border-radius:9999px;padding:2px 7px;font-weight:500;white-space:nowrap;flex-shrink:0}
126+
.cycle-row{display:flex;align-items:flex-start;justify-content:space-between;gap:6px;margin-bottom:10px}
127+
.cycle-dots{display:flex;gap:7px;align-items:flex-start}
128+
.cycle-dot-wrap{display:flex;flex-direction:column;align-items:center;gap:3px}
129+
.cycle-dot{width:12px;height:12px;border-radius:9999px;background:rgba(0,0,0,.13);cursor:pointer;transition:background .18s,transform .15s;flex-shrink:0}
130+
.cycle-dot:hover{transform:scale(1.28)}
129131
.cycle-dot.filled{background:var(--black)}
130-
.cycle-badge{font-size:10px;color:var(--stone);white-space:nowrap}
132+
.cycle-date{font-size:9px;color:var(--stone);line-height:1;min-height:10px;opacity:.75;white-space:nowrap}
133+
.cycle-badge{font-size:10px;color:var(--stone);white-space:nowrap;padding-top:1px}
131134
.cycle-badge.fee-due{color:#c2410c;font-weight:700}
135+
.cycle-complete-btn{font-family:'Pretendard',sans-serif;font-size:10px;color:var(--white);background:#c2410c;border:none;border-radius:9999px;padding:3px 11px;cursor:pointer;font-weight:600;display:block;margin-bottom:8px;margin-left:auto}
136+
.cycle-complete-btn:hover{background:#9a3412}
132137

133138
.records-header{margin-bottom:16px}
134139
.records-title{font-size:20px;font-weight:700;letter-spacing:-.03em;color:var(--black)}
@@ -383,10 +388,31 @@
383388

384389
function switchTab(dk,sid){activeSessionTab[dk]=sid;renderDetail()}
385390

391+
const CARD_COLORS=['#eff6ff','#f0fdf4','#fdf4ff','#fff7ed','#fdf2f8','#f0fdfa','#fefce8','#fef2f2','#f5f3ff','#ecfeff','#fff1f2','#f0f9ff'];
392+
function strHash(s){let h=5381;for(let i=0;i<s.length;i++)h=((h<<5)+h)+s.charCodeAt(i);return h>>>0;}
393+
function loadCycleData(){try{return JSON.parse(localStorage.getItem('beatcheck_cycle')||'{}')}catch(e){return{}}}
394+
function saveCycleData(d){localStorage.setItem('beatcheck_cycle',JSON.stringify(d))}
395+
let cycleData=loadCycleData();
396+
function toggleCycleSession(sid,idx){
397+
if(!cycleData[sid])cycleData[sid]={completedCycles:0,sessions:[null,null,null,null]};
398+
const s=cycleData[sid];
399+
if(!s.sessions||s.sessions.length!==4)s.sessions=[null,null,null,null];
400+
if(s.sessions[idx])s.sessions[idx]=null;
401+
else{const t=new Date();s.sessions[idx]=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,'0')}-${String(t.getDate()).padStart(2,'0')}`;}
402+
saveCycleData(cycleData);renderStats();
403+
}
404+
function completeCycle(sid){
405+
if(!cycleData[sid])cycleData[sid]={completedCycles:0,sessions:[null,null,null,null]};
406+
cycleData[sid].completedCycles=(cycleData[sid].completedCycles||0)+1;
407+
cycleData[sid].sessions=[null,null,null,null];
408+
saveCycleData(cycleData);renderStats();
409+
}
386410
function renderStats(){
387411
const sec=document.getElementById('statsSection');
388-
const sm={};
389-
Object.values(SCHEDULE).forEach(d=>d.sessions.forEach(s=>s.students.forEach(st=>{if(!sm[st.id])sm[st.id]={name:st.name,sessions:0,present:0}})));
412+
const sm={};const orderedIds=[];
413+
Object.values(SCHEDULE).forEach(d=>d.sessions.forEach(s=>s.students.forEach(st=>{
414+
if(!sm[st.id]){sm[st.id]={name:st.name,sessions:0,present:0};orderedIds.push(st.id);}
415+
})));
390416
const todayD=new Date(today.getFullYear(),today.getMonth(),today.getDate());
391417
Object.entries(attendance).forEach(([dk,records])=>{
392418
const{y,m,d}=parseDK(dk);if(new Date(y,m,d)>todayD)return;
@@ -396,17 +422,22 @@
396422
else if(val===false)sm[sid].sessions++;
397423
});
398424
});
399-
const students=Object.entries(sm).filter(([,v])=>v.sessions>0).sort(([,a],[,b])=>(b.present/Math.max(b.sessions,1))-(a.present/Math.max(a.sessions,1)));
400-
if(!students.length){sec.innerHTML='<div class="records-empty"><div class="empty-icon">📊</div><p style="font-size:14px;color:var(--silver)">아직 출석 기록이 없습니다</p></div>';return}
401-
sec.innerHTML=`<div class="stats-grid">${students.map(([id,data])=>{
425+
if(!orderedIds.length){sec.innerHTML='<div class="records-empty"><div class="empty-icon">📊</div><p style="font-size:14px;color:var(--silver)">아직 출석 기록이 없습니다</p></div>';return}
426+
sec.innerHTML=`<div class="stats-grid">${orderedIds.map(id=>{
427+
const data=sm[id];
402428
const pct=data.sessions>0?Math.round(data.present/data.sessions*100):0;
403-
const isFeeDue=data.present>0&&data.present%4===0;
404-
const cyclePos=isFeeDue?4:data.present%4;
405-
const completedCycles=Math.floor(data.present/4);
406-
const dots=Array(4).fill(0).map((_,i)=>`<span class="cycle-dot${i<cyclePos?' filled':''}"></span>`).join('');
407-
const badgeText=isFeeDue?'레슨비 납부 시점!':`${4-cyclePos}회 후 레슨비`;
408-
const badgeCls=isFeeDue?'cycle-badge fee-due':'cycle-badge';
409-
return`<div class="stat-card"><div class="stat-head-row"><div class="stat-name">${data.name}</div><span class="stat-cycles">${completedCycles}사이클</span></div><div class="cycle-row"><div class="cycle-dots">${dots}</div><span class="${badgeCls}">${badgeText}</span></div><div class="stat-bar-row"><div class="stat-bar-bg"><div class="stat-bar-fill" style="width:${pct}%"></div></div><span class="stat-pct">${pct}%</span></div><div class="stat-sub">출석 ${data.present}/${data.sessions}회</div></div>`;
429+
const cd=cycleData[id]||{completedCycles:0,sessions:[null,null,null,null]};
430+
const sess=cd.sessions&&cd.sessions.length===4?cd.sessions:[null,null,null,null];
431+
const filledCount=sess.filter(x=>x!==null).length;
432+
const isFeeDue=filledCount===4;
433+
const bgColor=CARD_COLORS[strHash(id)%CARD_COLORS.length];
434+
const dots=sess.map((date,i)=>{
435+
const dl=date?`${parseInt(date.split('-')[1])}/${parseInt(date.split('-')[2])}`:''
436+
return`<div class="cycle-dot-wrap"><span class="cycle-dot${date?' filled':''}" onclick="toggleCycleSession('${id}',${i})"></span><span class="cycle-date">${dl}</span></div>`;
437+
}).join('');
438+
const rightBadge=isFeeDue?`<span class="cycle-badge fee-due">레슨비!</span>`:`<span class="cycle-badge">${4-filledCount}회 후 레슨비</span>`;
439+
const completeBtn=isFeeDue?`<button class="cycle-complete-btn" onclick="completeCycle('${id}')">사이클 완료</button>`:'';
440+
return`<div class="stat-card" style="background:${bgColor}"><div class="stat-head-row"><div class="stat-name">${data.name}</div><span class="stat-cycles">${cd.completedCycles||0}사이클</span></div><div class="cycle-row"><div class="cycle-dots">${dots}</div>${rightBadge}</div>${completeBtn}<div class="stat-bar-row"><div class="stat-bar-bg"><div class="stat-bar-fill" style="width:${pct}%"></div></div><span class="stat-pct">${pct}%</span></div><div class="stat-sub">출석 ${data.present}/${data.sessions}회</div></div>`;
410441
}).join('')}</div>`;
411442
}
412443

0 commit comments

Comments
 (0)