-
+
+
+
+
+
+
${cells}
+
- ${data.members.map(m=>`${avatarHtml(m,14)} ${escapeHtml(m.name)}`).join('')}
+ ${CATEGORIES.map(c=>`${c.label}`).join('')}
`;
$('#cal-prev').onclick=()=>{ calRef=new Date(y,m-1,1); renderSchedule(); };
$('#cal-next').onclick=()=>{ calRef=new Date(y,m+1,1); renderSchedule(); };
- $('#cal-today').onclick=()=>{ calRef=new Date(); renderSchedule(); };
- $$('#main .cal-task').forEach(el=>el.onclick=()=>openTaskModal(el.dataset.id));
+ $('#cal-today').onclick=()=>{ calRef=new Date(); selDay=todayStr(); renderSchedule(); };
+ $('#cal-addev').onclick=()=>openEventModal(null, selDay);
+ $('#sc-mine').onclick=()=>{ schedScope='mine'; renderSchedule(); };
+ $('#sc-all').onclick=()=>{ schedScope='all'; renderSchedule(); };
+ $$('#main .cal-day[data-day]').forEach(el=>el.onclick=e=>{ if(e.target.closest('.cal-task')) return; selDay=el.dataset.day; renderSchedule(); });
+ $$('#main .cal-task').forEach(el=>el.onclick=e=>{ e.stopPropagation(); el.dataset.type==='event'?openEventModal(el.dataset.id):openTaskModal(el.dataset.id); });
+ renderDayPanel();
}
+function chipHtml(it){
+ if(it.type==='event'){
+ const c=catByKey(it.obj.category);
+ return `
${escapeHtml(it.obj.start)} ${escapeHtml(it.obj.title)}
`;
+ }
+ const t=it.obj;
+ return `
✓ ${escapeHtml(t.title)}
`;
+}
+
+function renderDayPanel(){
+ const panel = $('#day-panel'); if(!panel) return;
+ const items = dayItems(selDay, schedScope);
+ const d = parseDate(selDay);
+ const dname = d ? d.toLocaleDateString('ru-RU',{weekday:'long',day:'numeric',month:'long'}) : selDay;
+ const isToday = selDay===todayStr();
+ panel.innerHTML = `
+
+
${dname} ${isToday?'сегодня':''}
+
+
+ ${items.length? `
${items.map(it=>dayRowHtml(it)).join('')}
` : emptyHtml('📅','На этот день ничего не запланировано.')}`;
+ $('#dp-addev')?.addEventListener('click', ()=>openEventModal(null, selDay));
+ panel.querySelectorAll('.day-row').forEach(r=>{
+ r.onclick=()=>{ r.dataset.type==='event'?openEventModal(r.dataset.id):openTaskModal(r.dataset.id); };
+ });
+}
+
+function dayRowHtml(it){
+ if(it.type==='event'){
+ const e=it.obj, c=catByKey(e.category), owner=member(e.ownerId);
+ return `
+
${escapeHtml(e.start)}
${escapeHtml(e.end)}
+
+
+
${escapeHtml(e.title)}
+
${avatarHtml(owner,14)} ${escapeHtml(owner?owner.name:'—')} · ${c.label}
+
+
✎
+
`;
+ }
+ const t=it.obj, cat=catByKey(t.category), a=member(t.assigneeId);
+ return `
+
${escapeHtml(t.time||'—')}
+
+
+
${escapeHtml(t.title)}
+
${avatarHtml(a,14)} ${escapeHtml(a?a.name:'—')} · ${cat.label}${t.duration?' · ⏱ '+formatDuration(t.duration):''}
+
+
${STATUS[t.status].label}
+
✎
+
`;
+}
+
+/* ---------- модалка события ---------- */
+let editingEventId = null;
+function openEventModal(id, dateStr){
+ editingEventId = id;
+ const e = id ? data.events.find(x=>x.id===id) : null;
+ $('#ev-title').textContent = e ? 'Событие' : 'Новое событие';
+ $('#ev-name').value = e?.title||'';
+ $('#ev-date').value = e?.date || dateStr || selDay || todayStr();
+ $('#ev-start').value = e?.start||'';
+ $('#ev-end').value = e?.end||'';
+ $('#ev-owner').innerHTML = data.members.map(m=>`
`).join('');
+ $('#ev-cat').innerHTML = catOptions();
+ $('#ev-cat').value = e?.category||'other';
+ $('#ev-delete').classList.toggle('hidden', !e);
+ $('#event-modal').classList.remove('hidden');
+ setTimeout(()=>$('#ev-name').focus(),50);
+}
+$('#ev-close').onclick = ()=>$('#event-modal').classList.add('hidden');
+$('#event-modal').addEventListener('click', e=>{ if(e.target.id==='event-modal') $('#event-modal').classList.add('hidden'); });
+$('#ev-save').onclick = ()=>{
+ const title=$('#ev-name').value.trim();
+ const date=$('#ev-date').value;
+ const start=$('#ev-start').value, end=$('#ev-end').value;
+ if(!title){ toast('Введите название'); $('#ev-name').focus(); return; }
+ if(!date){ toast('Выберите дату'); return; }
+ if(!start||!end){ toast('Укажите время начала и конца'); return; }
+ if(start>=end){ toast('Конец должен быть позже начала'); return; }
+ const payload={ title, date, start, end, ownerId:$('#ev-owner').value, category:$('#ev-cat').value };
+ if(editingEventId){ Object.assign(data.events.find(x=>x.id===editingEventId), payload); toast('Сохранено'); }
+ else { data.events.push(Object.assign({id:uid()}, payload)); toast('Событие добавлено 📅'); }
+ selDay = date;
+ save(); $('#event-modal').classList.add('hidden'); render();
+};
+$('#ev-delete').onclick = ()=>{
+ if(!editingEventId) return;
+ if(!confirm('Удалить событие?')) return;
+ data.events = data.events.filter(x=>x.id!==editingEventId);
+ save(); $('#event-modal').classList.add('hidden'); render();
+ toast('Событие удалено');
+};
+
/* ---------- Семья ---------- */
function renderFamily(){
const cards = data.members.map(m=>{
@@ -401,6 +552,9 @@ function openTaskModal(id){
$('#f-date').value = t?.dueDate||'';
$('#f-time').value = t?.time||'';
$('#f-priority').value = t?.priority||'medium';
+ $('#f-duration').value = t?.duration||'';
+ $('#f-category').innerHTML = catOptions();
+ $('#f-category').value = t?.category||'other';
$('#f-from').textContent = member(me).name;
// список исполнителей
$('#f-assignee').innerHTML = data.members.map(m=>`
`).join('');
@@ -428,7 +582,9 @@ $('#modal-save').onclick = ()=>{
assigneeId,
dueDate: $('#f-date').value || null,
time: $('#f-time').value || null,
+ duration: $('#f-duration').value ? Number($('#f-duration').value) : null,
priority: $('#f-priority').value,
+ category: $('#f-category').value,
};
if(editingId){
const t = data.tasks.find(x=>x.id===editingId);
diff --git a/index.html b/index.html
index 94f1de4..4b41d28 100644
--- a/index.html
+++ b/index.html
@@ -120,6 +120,27 @@ window.addEventListener('error',function(e){