// Social Media Content Calendar
const CCIcons = window.Icons;
const { toDateKey: ccKey, parseDateKey: ccParse, relativeDate: ccRel } = window.PMStore;
const PLATFORMS = [
{ id: 'instagram', label: 'Instagram', short: 'IG', color: '#E1306C', icon: 'Instagram' },
{ id: 'facebook', label: 'Facebook', short: 'FB', color: '#1877F2', icon: 'Facebook' },
{ id: 'linkedin', label: 'LinkedIn', short: 'LI', color: '#0A66C2', icon: 'Linkedin' },
{ id: 'tiktok', label: 'TikTok', short: 'TT', color: '#010101', icon: 'TikTok' },
{ id: 'twitter', label: 'X / Twitter', short: 'TW', color: '#1DA1F2', icon: 'Twitter' },
{ id: 'youtube', label: 'YouTube', short: 'YT', color: '#FF0000', icon: 'Youtube' },
];
const CONTENT_TYPES = [
{ id: 'post', label: 'Gönderi', emoji: '📷' },
{ id: 'story', label: 'Hikaye', emoji: '⚡' },
{ id: 'reel', label: 'Reel', emoji: '🎬' },
{ id: 'video', label: 'Video', emoji: '▶️' },
{ id: 'carousel', label: 'Carousel', emoji: '🔄' },
{ id: 'article', label: 'Makale', emoji: '📝' },
];
const CONTENT_STATUSES = [
{ id: 'brief', label: 'Brief', color: '#94a3b8' },
{ id: 'production', label: 'Üretimde', color: '#3b82f6' },
{ id: 'review', label: 'İncelemede', color: '#a855f7' },
{ id: 'client_review', label: 'Müşteri Onayı', color: '#f59e0b' },
{ id: 'approved', label: 'Onaylandı', color: '#22c55e' },
{ id: 'scheduled', label: 'Zamanlandı', color: '#6366f1' },
{ id: 'published', label: 'Yayınlandı', color: '#ff601f' },
];
const DAYS_TR = ['Pzt', 'Sal', 'Çar', 'Per', 'Cum', 'Cmt', 'Paz'];
const MONTHS_TR = ['Ocak','Şubat','Mart','Nisan','Mayıs','Haziran','Temmuz','Ağustos','Eylül','Ekim','Kasım','Aralık'];
function d0(date) { const d = new Date(date); d.setHours(0,0,0,0); return d; }
function ContentCalendar({ state, onSelectTask, onAddTask, onUpdateTask }) {
const today = d0(new Date());
const [year, setYear] = React.useState(today.getFullYear());
const [month, setMonth] = React.useState(today.getMonth());
const [pfFilter, setPF] = React.useState('all');
const [projFilter, setPJ] = React.useState('all');
const [viewMode, setVM] = React.useState('month');
const [createDate, setCD] = React.useState(null);
const prevMonth = () => month === 0 ? (setMonth(11), setYear(y => y-1)) : setMonth(m => m-1);
const nextMonth = () => month === 11? (setMonth(0), setYear(y => y+1)) : setMonth(m => m+1);
const goToday = () => { setYear(today.getFullYear()); setMonth(today.getMonth()); };
const projById = Object.fromEntries((state.projects||[]).map(p => [p.id, p]));
const contentTasks = (state.tasks||[]).filter(t =>
t.platform && !t.archived &&
(pfFilter === 'all' || t.platform === pfFilter) &&
(projFilter === 'all' || t.projectId === projFilter)
);
// Monthly grid
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
const startDow = (firstDay.getDay() + 6) % 7;
const cells = [];
for (let i = 0; i < startDow; i++) cells.push(null);
for (let d = 1; d <= lastDay.getDate(); d++) cells.push(new Date(year, month, d));
const tasksByDate = {};
contentTasks.forEach(t => {
if (t.due) { (tasksByDate[t.due] = tasksByDate[t.due] || []).push(t); }
});
// Stats bar
const monthStart = ccKey(new Date(year, month, 1));
const monthEnd = ccKey(new Date(year, month + 1, 0));
const monthTasks = contentTasks.filter(t => t.due && t.due >= monthStart && t.due <= monthEnd);
return (
{/* ── Toolbar ─────────────────────────────────────────── */}
{MONTHS_TR[month]} {year}
{PLATFORMS.map(p => (
))}
{[{id:'month',label:'Aylık'},{id:'list',label:'Liste'},{id:'pipeline',label:'Pipeline'}].map(v => (
))}
{/* ── Ay özeti ────────────────────────────────────────── */}
{monthTasks.length > 0 && (
{monthTasks.length} içerik
{PLATFORMS.filter(p => monthTasks.some(t => t.platform===p.id)).map(p => {
const cnt = monthTasks.filter(t => t.platform===p.id).length;
return (
{p.short} {cnt}
);
})}
{CONTENT_STATUSES.map(s => {
const cnt = monthTasks.filter(t => (t.contentStatus||'brief')===s.id).length;
if (!cnt) return null;
return (
{s.label} {cnt}
);
})}
)}
{/* ── Görünümler ──────────────────────────────────────── */}
{viewMode === 'month' && (
{cells.map((date, i) => {
if (!date) return
;
const dk = ccKey(date);
const dayTasks = tasksByDate[dk] || [];
const isToday = date.getTime() === today.getTime();
const isWeekend = date.getDay() === 0 || date.getDay() === 6;
return (
{date.getDate()}
{dayTasks.slice(0,3).map(t => (
onSelectTask(t.id)}/>
))}
{dayTasks.length > 3 && (
+{dayTasks.length-3} daha
)}
);
})}
)}
{viewMode === 'list' && (
setCD(ccKey(today))}
onUpdateTask={onUpdateTask}
/>
)}
{viewMode === 'pipeline' && (
setCD(ccKey(today))}
/>
)}
{createDate && (
{ onAddTask(data); setCD(null); }}
onClose={() => setCD(null)}
/>
)}
);
}
/* ── İçerik kartı (aylık grid için) ───────────────────────── */
function ContentCard({ task, project, onClick }) {
const pf = PLATFORMS.find(p => p.id === task.platform);
const ct = CONTENT_TYPES.find(c => c.id === task.contentType);
const cs = CONTENT_STATUSES.find(s => s.id === (task.contentStatus || 'brief'));
return (
{pf?.short}
{ct && {ct.emoji}}
{task.title}
{project &&
{project.name}
}
);
}
/* ── Liste görünümü ────────────────────────────────────────── */
function ContentListView({ tasks, projById, onSelectTask, onNew, onUpdateTask }) {
const sorted = [...tasks].sort((a,b) => (a.due||'9999').localeCompare(b.due||'9999'));
// Group by week
const groups = [];
sorted.forEach(t => {
const d = t.due ? ccParse(t.due) : null;
const weekLabel = d ? getWeekLabel(d) : 'Tarihi Belirsiz';
const last = groups[groups.length-1];
if (last && last.label === weekLabel) last.tasks.push(t);
else groups.push({ label: weekLabel, tasks: [t] });
});
return (
{groups.length === 0 && (
İçerik yok
Filtre değiştirin veya yeni içerik ekleyin.
)}
{groups.map(g => (
{g.label} {g.tasks.length}
{g.tasks.map(t => (
onSelectTask(t.id)}
onStatusChange={(s) => onUpdateTask(t.id, { contentStatus: s })}
/>
))}
))}
);
}
function ContentListRow({ task, project, onClick, onStatusChange }) {
const pf = PLATFORMS.find(p => p.id === task.platform);
const ct = CONTENT_TYPES.find(c => c.id === task.contentType);
const cs = CONTENT_STATUSES.find(s => s.id === (task.contentStatus||'brief'));
return (
{pf?.short}
{task.title}
{project && <>{project.name}>}
{ct && · {ct.emoji} {ct.label}}
{task.due && · {ccRel(task.due)}}
{task.description &&
{task.description.slice(0,80)}{task.description.length>80?'…':''}
}
e.stopPropagation()}>
);
}
/* ── Pipeline (Kanban benzeri onay akışı) ─────────────────── */
function ContentPipeline({ tasks, projById, onSelectTask, onUpdateTask, onNew }) {
const [drag, setDrag] = React.useState(null);
const drop = (statusId) => {
if (drag) onUpdateTask(drag, { contentStatus: statusId });
setDrag(null);
};
return (
{CONTENT_STATUSES.map(status => {
const colTasks = tasks.filter(t => (t.contentStatus||'brief') === status.id);
return (
e.preventDefault()}
onDrop={() => drop(status.id)}>
{status.label}
{colTasks.length}
{colTasks.length === 0 && (
Boş
)}
{colTasks.map(t => {
const pf = PLATFORMS.find(p => p.id === t.platform);
const ct = CONTENT_TYPES.find(c => c.id === t.contentType);
return (
setDrag(t.id)} onDragEnd={() => setDrag(null)}
onClick={() => onSelectTask(t.id)}>
{pf?.short}
{ct && {ct.emoji}}
{t.due && {ccRel(t.due)}}
{t.title}
{projById[t.projectId] && (
{projById[t.projectId].name}
)}
{t.description && (
{t.description.slice(0,60)}{t.description.length>60?'…':''}
)}
);
})}
);
})}
);
}
/* ── İçerik oluştur modal ──────────────────────────────────── */
function ContentCreateModal({ date, state, onSave, onClose }) {
const [platform, setPF] = React.useState('instagram');
const [contentType, setCT] = React.useState('post');
const [title, setTT] = React.useState('');
const [caption, setCA] = React.useState('');
const [projectId, setPJ] = React.useState(state.activeProjectId || state.projects[0]?.id || '');
const [postDate, setPD] = React.useState(date || new Date().toISOString().slice(0,10));
const [contentStatus, setCS] = React.useState('brief');
const selPf = PLATFORMS.find(p => p.id === platform);
const save = () => {
if (!title.trim()) return;
onSave({
title: title.trim(),
description: caption.trim(),
due: postDate || null,
projectId,
platform,
contentType,
contentStatus,
status: 'todo',
priority: 'medium',
});
};
return (
e.stopPropagation()}>
{selPf?.short}
Yeni İçerik Planı
{/* Platform */}
Platform
{PLATFORMS.map(p => {
const Icon = CCIcons[p.icon];
return (
);
})}
{/* İçerik tipi */}
İçerik Tipi
{CONTENT_TYPES.map(ct => (
))}
);
}
/* ── Yardımcılar ───────────────────────────────────────────── */
function getWeekLabel(date) {
const d = new Date(date); d.setHours(0,0,0,0);
const day = (d.getDay() + 6) % 7;
const mon = new Date(d); mon.setDate(d.getDate() - day);
const sun = new Date(mon); sun.setDate(mon.getDate() + 6);
const fmt = (dt) => `${dt.getDate()} ${MONTHS_TR[dt.getMonth()]}`;
return `${fmt(mon)} – ${fmt(sun)}`;
}
window.ContentCalendar = ContentCalendar;
window.PLATFORMS = PLATFORMS;
window.CONTENT_TYPES = CONTENT_TYPES;
window.CONTENT_STATUSES = CONTENT_STATUSES;