546 lines
20 KiB
PHP
546 lines
20 KiB
PHP
<?php
|
||
$pdo = new PDO("mysql:host=localhost;dbname=egbim;charset=utf8mb4","egbim","baron3840!!");
|
||
|
||
$rows = $pdo->query("
|
||
SELECT
|
||
s.schedule_date,
|
||
s.time_period,
|
||
s.content,
|
||
s.emp_no,
|
||
m.emp_name,
|
||
c.client_name
|
||
FROM sales_schedules s
|
||
LEFT JOIN sales_members m ON s.emp_no = m.emp_no
|
||
LEFT JOIN sales_clients c ON s.client_code = c.client_code
|
||
")->fetchAll(PDO::FETCH_ASSOC);
|
||
|
||
// 모든 emp_no는 소문자로 통일!
|
||
$tagMap = [
|
||
"j01201" => "권",
|
||
"223070" => "염",
|
||
"m21430" => "김",
|
||
"b21367" => "윤"
|
||
];
|
||
|
||
$eventsData = [];
|
||
|
||
foreach ($rows as $r) {
|
||
|
||
$date = $r["schedule_date"];
|
||
$empNo = strtolower($r["emp_no"]); // ★ emp_no를 소문자로 통일
|
||
|
||
// 날짜 키가 없으면 배열 초기화
|
||
if (!isset($eventsData[$date])) {
|
||
$eventsData[$date] = [];
|
||
}
|
||
|
||
// 여러 개가 정상적으로 PUSH됨
|
||
$eventsData[$date][] = [
|
||
"time" => $r["time_period"],
|
||
"title" => $r["content"],
|
||
"tags" => [ $tagMap[$empNo] ?? "" ]
|
||
];
|
||
}
|
||
|
||
echo "<script>var eventsData = " . json_encode($eventsData, JSON_UNESCAPED_UNICODE) . ";</script>";
|
||
?>
|
||
|
||
<div id="scheduleModal" class="modal calendar">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<div class="filter-options">
|
||
<div class="checkbox-group green">
|
||
<input type="checkbox" id="check1" value="green" checked />
|
||
<label for="check1">권혁진 수석</label>
|
||
</div>
|
||
<div class="checkbox-group purple">
|
||
<input type="checkbox" id="check2" value="purple" checked />
|
||
<label for="check2">염승호 수석</label>
|
||
</div>
|
||
<div class="checkbox-group teal">
|
||
<input type="checkbox" id="check3" value="teal" checked />
|
||
<label for="check3">김지영 선임</label>
|
||
</div>
|
||
<div class="checkbox-group black">
|
||
<input type="checkbox" id="check4" value="black" checked />
|
||
<label for="check4">윤준수 선임</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="date-navigation">
|
||
<button class="prev-btn" id="prevMonth">‹</button>
|
||
<div class="current-date" id="currentDate">2025.11</div>
|
||
<button class="next-btn" id="nextMonth">›</button>
|
||
</div>
|
||
|
||
<div class="flex">
|
||
<div class="toggle-area">
|
||
<input type="radio" id="option1" name="toggle" value="접기" checked />
|
||
<label for="option1">접기</label>
|
||
|
||
<input type="radio" id="option2" name="toggle" value="펼치기" />
|
||
<label for="option2">펼치기</label>
|
||
<div class="toggle-slider"></div>
|
||
</div>
|
||
<button class="modal-close" onclick="closeModal()">×</button>
|
||
</div>
|
||
</div>
|
||
<div class="modal-body" id="modalBody">
|
||
<div class="weekdays">
|
||
<div class="weekday">일</div>
|
||
<div class="weekday">월</div>
|
||
<div class="weekday">화</div>
|
||
<div class="weekday">수</div>
|
||
<div class="weekday">목</div>
|
||
<div class="weekday">금</div>
|
||
<div class="weekday">토</div>
|
||
</div>
|
||
<div class="calendar-days">
|
||
<div class="days collapsed" id="calendarDays"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// 모달 배경 클릭시 닫기
|
||
const modal = document.getElementById("scheduleModal");
|
||
modal.addEventListener("click", function (e) {
|
||
if (e.target === modal) {
|
||
closeModal();
|
||
}
|
||
});
|
||
</script>
|
||
<script>
|
||
function initializeCalendar() {
|
||
const tagMapping = {
|
||
"권": "green",
|
||
"염": "purple",
|
||
"김": "teal",
|
||
"윤": "black"
|
||
};
|
||
// 사람 고정 순서 (우선순위 낮을수록 뒤로)
|
||
const PERSON_ORDER = {
|
||
"권": 1, // 권혁진
|
||
"염": 2, // 염승호
|
||
"김": 3, // 김지영
|
||
"윤": 4 // 윤준수
|
||
};
|
||
|
||
function sortEventsByPerson(events) {
|
||
return events.slice().sort((a, b) => {
|
||
const aTag = a.tags[0] || "";
|
||
const bTag = b.tags[0] || "";
|
||
|
||
return (PERSON_ORDER[aTag] || 99) - (PERSON_ORDER[bTag] || 99);
|
||
});
|
||
}
|
||
|
||
/* ===============================
|
||
오전 / 오후 판별 (핵심)
|
||
→ title 문자열 기준
|
||
=============================== */
|
||
function isMorning(event) {
|
||
return event.time === "오전";
|
||
}
|
||
|
||
function isAfternoon(event) {
|
||
return event.time === "오후";
|
||
}
|
||
|
||
const today = new Date();
|
||
let currentYear = today.getFullYear(); // 2025 대신
|
||
let currentMonth = today.getMonth(); // 11 대신
|
||
let selectedFilters = ["green", "purple", "teal", "black"];
|
||
|
||
function renderCalendar() {
|
||
const calendarDays = document.getElementById("calendarDays");
|
||
const currentDate = document.getElementById("currentDate");
|
||
|
||
currentDate.textContent = `${currentYear}.${String(currentMonth + 1).padStart(2, "0")}`;
|
||
|
||
const firstDay = new Date(currentYear, currentMonth, 1);
|
||
const lastDay = new Date(currentYear, currentMonth + 1, 0);
|
||
const prevLastDay = new Date(currentYear, currentMonth, 0);
|
||
|
||
const firstDayOfWeek = firstDay.getDay();
|
||
const lastDate = lastDay.getDate();
|
||
const prevLastDate = prevLastDay.getDate();
|
||
|
||
// 오늘 날짜의 주차 계산
|
||
const today = new Date();
|
||
let todayWeekStart = -1;
|
||
let todayWeekEnd = -1;
|
||
|
||
// 현재 달력에 오늘이 포함되어 있는지 확인
|
||
if (
|
||
currentMonth === today.getMonth() &&
|
||
currentYear === today.getFullYear()
|
||
) {
|
||
const todayDate = today.getDate();
|
||
const currentDate = new Date(currentYear, currentMonth, todayDate);
|
||
const dayOfWeek = currentDate.getDay();
|
||
|
||
// 이번 주의 시작(일요일)과 끝(토요일) 날짜 계산
|
||
todayWeekStart = todayDate - dayOfWeek;
|
||
todayWeekEnd = todayDate + (6 - dayOfWeek);
|
||
}
|
||
|
||
let weeks = "";
|
||
let currentWeekDays = "";
|
||
let dayCount = 0;
|
||
let weekNumber = 0;
|
||
|
||
// Previous month days
|
||
const prevMonth = currentMonth === 0 ? 11 : currentMonth - 1;
|
||
const prevYear = currentMonth === 0 ? currentYear - 1 : currentYear;
|
||
|
||
for (let i = firstDayOfWeek - 1; i >= 0; i--) {
|
||
const dayNum = prevLastDate - i;
|
||
const dateKey = `${prevYear}-${String(prevMonth + 1).padStart(2, "0")}-${String(dayNum).padStart(2, "0")}`;
|
||
const dayEvents = eventsData[dateKey] || [];
|
||
|
||
const filteredEvents = sortEventsByPerson(
|
||
dayEvents.filter((event) =>
|
||
event.tags.some((tag) => selectedFilters.includes(tagMapping[tag]))
|
||
)
|
||
);
|
||
|
||
let morningHtml = "";
|
||
let afternoonHtml = "";
|
||
|
||
filteredEvents.forEach((event) => {
|
||
|
||
const tagsHtml = event.tags
|
||
.filter((tag) => selectedFilters.includes(tagMapping[tag]))
|
||
.map(
|
||
(tag) =>
|
||
`<span class="event-tag badge-${tagMapping[tag]}">${tag}</span>`
|
||
)
|
||
.join("");
|
||
|
||
const html = `
|
||
<div class="event-item">
|
||
<span class="event-title">${tagsHtml}${event.title}</span>
|
||
</div>
|
||
`;
|
||
|
||
if (isMorning(event)) {
|
||
morningHtml += html;
|
||
}
|
||
|
||
if (isAfternoon(event)) {
|
||
afternoonHtml += html;
|
||
}
|
||
});
|
||
|
||
currentWeekDays += `<div class="day other-month">
|
||
<div class="day-number">${dayNum}</div>
|
||
<dl>
|
||
<dt>오전</dt>
|
||
<dd class="day-content">${morningHtml}</dd>
|
||
</dl>
|
||
<dl>
|
||
<dt>오후</dt>
|
||
<dd class="day-content">${afternoonHtml}</dd>
|
||
</dl>
|
||
</div>`;
|
||
dayCount++;
|
||
}
|
||
|
||
// Current month days
|
||
for (let i = 1; i <= lastDate; i++) {
|
||
const isToday =
|
||
i === today.getDate() &&
|
||
currentMonth === today.getMonth() &&
|
||
currentYear === today.getFullYear();
|
||
|
||
const isCurrentWeek =
|
||
todayWeekStart >= 0 && i >= todayWeekStart && i <= todayWeekEnd;
|
||
|
||
const dateKey = `${currentYear}-${String(currentMonth + 1).padStart(2, "0")}-${String(i).padStart(2, "0")}`;
|
||
const dayEvents = eventsData[dateKey] || [];
|
||
|
||
const filteredEvents = sortEventsByPerson(
|
||
dayEvents.filter((event) =>
|
||
event.tags.some((tag) => selectedFilters.includes(tagMapping[tag]))
|
||
)
|
||
);
|
||
|
||
filteredEvents.forEach((event) => {
|
||
console.log(event.title);
|
||
});
|
||
let morningHtml = "";
|
||
let afternoonHtml = "";
|
||
|
||
filteredEvents.forEach((event) => {
|
||
|
||
const tagsHtml = event.tags
|
||
.filter((tag) => selectedFilters.includes(tagMapping[tag]))
|
||
.map(
|
||
(tag) =>
|
||
`<span class="event-tag badge-${tagMapping[tag]}">${tag}</span>`
|
||
)
|
||
.join("");
|
||
|
||
const html = `
|
||
<div class="event-item">
|
||
|
||
<span class="event-title">${tagsHtml}${event.title}</span>
|
||
</div>
|
||
`;
|
||
|
||
if (isMorning(event)) {
|
||
morningHtml += html;
|
||
}
|
||
|
||
if (isAfternoon(event)) {
|
||
afternoonHtml += html;
|
||
}
|
||
});
|
||
|
||
currentWeekDays += `<div class="day ${isToday ? "today" : ""} ${isCurrentWeek ? "current-week" : ""}">
|
||
<div class="day-number">${i}</div>
|
||
<dl>
|
||
<dt>오전</dt>
|
||
<dd class="day-content">${morningHtml}</dd>
|
||
</dl>
|
||
<dl>
|
||
<dt>오후</dt>
|
||
<dd class="day-content">${afternoonHtml}</dd>
|
||
</dl>
|
||
</div>`;
|
||
dayCount++;
|
||
|
||
// 7일마다 한 주 완성
|
||
if (dayCount === 7) {
|
||
const hasCurrentWeek =
|
||
currentWeekDays.includes('class="day today"') ||
|
||
currentWeekDays.includes("current-week");
|
||
weeks += `<div class="week ${hasCurrentWeek ? "current-week-row" : ""}">${currentWeekDays}</div>`;
|
||
currentWeekDays = "";
|
||
dayCount = 0;
|
||
weekNumber++;
|
||
}
|
||
}
|
||
|
||
// Next month days
|
||
const nextMonth = currentMonth === 11 ? 0 : currentMonth + 1;
|
||
const nextYear = currentMonth === 11 ? currentYear + 1 : currentYear;
|
||
const remainingDays = 42 - (firstDayOfWeek + lastDate);
|
||
|
||
for (let i = 1; i <= remainingDays; i++) {
|
||
const dateKey = `${nextYear}-${String(nextMonth + 1).padStart(2, "0")}-${String(i).padStart(2, "0")}`;
|
||
const dayEvents = eventsData[dateKey] || [];
|
||
|
||
const filteredEvents = sortEventsByPerson(
|
||
dayEvents.filter((event) =>
|
||
event.tags.some((tag) => selectedFilters.includes(tagMapping[tag]))
|
||
)
|
||
);
|
||
|
||
let morningHtml = "";
|
||
let afternoonHtml = "";
|
||
|
||
filteredEvents.forEach((event) => {
|
||
|
||
const tagsHtml = event.tags
|
||
.filter((tag) => selectedFilters.includes(tagMapping[tag]))
|
||
.map(
|
||
(tag) =>
|
||
`<span class="event-tag badge-${tagMapping[tag]}">${tag}</span>`
|
||
)
|
||
.join("");
|
||
|
||
const html = `
|
||
<div class="event-item">
|
||
|
||
<span class="event-title">${tagsHtml}${event.title}</span>
|
||
</div>
|
||
`;
|
||
|
||
if (isMorning(event)) morningHtml += html;
|
||
if (isAfternoon(event)) afternoonHtml += html;
|
||
});
|
||
|
||
currentWeekDays += `<div class="day other-month">
|
||
<div class="day-number">${i}</div>
|
||
<dl>
|
||
<dt>오전</dt>
|
||
<dd class="day-content">${morningHtml}</dd>
|
||
</dl>
|
||
<dl>
|
||
<dt>오후</dt>
|
||
<dd class="day-content">${afternoonHtml}</dd>
|
||
</dl>
|
||
</div>`;
|
||
dayCount++;
|
||
|
||
// 7일마다 한 주 완성
|
||
if (dayCount === 7) {
|
||
weeks += `<div class="week">${currentWeekDays}</div>`;
|
||
currentWeekDays = "";
|
||
dayCount = 0;
|
||
}
|
||
}
|
||
|
||
calendarDays.innerHTML = weeks;
|
||
|
||
document.querySelectorAll(".day:not(.other-month)").forEach((day) => {
|
||
day.addEventListener("click", function () {
|
||
document
|
||
.querySelectorAll(".day")
|
||
.forEach((d) => d.classList.remove("selected"));
|
||
this.classList.add("selected");
|
||
});
|
||
});
|
||
}
|
||
|
||
function checkOverflow() {
|
||
const calendarDaysEl = document.getElementById("calendarDays");
|
||
if (!calendarDaysEl.classList.contains("collapsed")) return;
|
||
|
||
document.querySelectorAll(".day-content").forEach((content) => {
|
||
// 스크롤 높이가 실제 높이보다 크면 overflow 발생
|
||
if (content.scrollHeight > content.clientHeight) {
|
||
content.classList.add("has-overflow");
|
||
} else {
|
||
content.classList.remove("has-overflow");
|
||
}
|
||
});
|
||
}
|
||
// document.getElementById("prevMonth").addEventListener("click", () => {
|
||
// currentMonth--;
|
||
// if (currentMonth < 0) {
|
||
// currentMonth = 11;
|
||
// currentYear--;
|
||
// }
|
||
// renderCalendar();
|
||
// checkOverflow();
|
||
// });
|
||
|
||
// document.getElementById("nextMonth").addEventListener("click", () => {
|
||
// currentMonth++;
|
||
// if (currentMonth > 11) {
|
||
// currentMonth = 0;
|
||
// currentYear++;
|
||
// }
|
||
// renderCalendar();
|
||
// checkOverflow();
|
||
// });
|
||
|
||
// 필터 체크박스
|
||
const filterCheckboxes = document.querySelectorAll(
|
||
'.filter-options input[type="checkbox"]',
|
||
);
|
||
filterCheckboxes.forEach((checkbox) => {
|
||
checkbox.addEventListener("change", () => {
|
||
selectedFilters = Array.from(filterCheckboxes)
|
||
.filter((cb) => cb.checked)
|
||
.map((cb) => cb.value);
|
||
renderCalendar();
|
||
checkOverflow();
|
||
});
|
||
});
|
||
|
||
// 접기/펼치기 토글
|
||
const calendarDaysEl = document.getElementById("calendarDays");
|
||
const toggleRadios = document.querySelectorAll('input[name="toggle"]');
|
||
|
||
toggleRadios.forEach((radio) => {
|
||
radio.addEventListener("change", (e) => {
|
||
if (e.target.value === "접기") {
|
||
calendarDaysEl.classList.add("collapsed");
|
||
} else {
|
||
calendarDaysEl.classList.remove("collapsed");
|
||
}
|
||
});
|
||
});
|
||
|
||
calendarDaysEl.classList.add("collapsed");
|
||
renderCalendar();
|
||
checkOverflow();
|
||
bindCalendarEvents();
|
||
|
||
//바인드 함수 컨트롤
|
||
function bindCalendarEvents() {
|
||
|
||
// 이전 월
|
||
const prevBtn = document.getElementById("prevMonth");
|
||
if (prevBtn) {
|
||
prevBtn.onclick = () => {
|
||
currentMonth--;
|
||
if (currentMonth < 0) {
|
||
currentMonth = 11;
|
||
currentYear--;
|
||
}
|
||
renderCalendar();
|
||
checkOverflow();
|
||
};
|
||
}
|
||
|
||
// 다음 월
|
||
const nextBtn = document.getElementById("nextMonth");
|
||
if (nextBtn) {
|
||
nextBtn.onclick = () => {
|
||
currentMonth++;
|
||
if (currentMonth > 11) {
|
||
currentMonth = 0;
|
||
currentYear++;
|
||
}
|
||
renderCalendar();
|
||
checkOverflow();
|
||
};
|
||
}
|
||
|
||
// 필터 체크박스
|
||
const filterCheckboxes = document.querySelectorAll('.filter-options input[type="checkbox"]');
|
||
filterCheckboxes.forEach((checkbox) => {
|
||
checkbox.onclick = () => {
|
||
selectedFilters = Array.from(filterCheckboxes)
|
||
.filter((cb) => cb.checked)
|
||
.map((cb) => cb.value);
|
||
|
||
renderCalendar();
|
||
checkOverflow();
|
||
};
|
||
});
|
||
|
||
// 접기&펼치기 토글
|
||
const radios = document.querySelectorAll('input[name="toggle"]');
|
||
|
||
radios.forEach((radio) => {
|
||
radio.addEventListener("change", (e) => {
|
||
const calendarDaysEl = document.getElementById("calendarDays");
|
||
if (e.target.value === "접기") {
|
||
calendarDaysEl.classList.add("collapsed");
|
||
} else {
|
||
calendarDaysEl.classList.remove("collapsed");
|
||
|
||
// 스크롤을 맨 아래로 이동
|
||
setTimeout(() => {
|
||
const scheduleList = document.querySelector(".calendar-days");
|
||
const scheduleItem = document.querySelector(".current-week-row");
|
||
if (scheduleList && scheduleItem) {
|
||
// 현재 주가 보이도록 + 살짝 아래로 여유 있게
|
||
scheduleList.scrollTo({
|
||
top:
|
||
scheduleItem.offsetTop -
|
||
scheduleList.offsetHeight / 2 +
|
||
scheduleItem.offsetHeight / 2,
|
||
// 또는 그냥 맨 아래로 내리고 싶으면:
|
||
// top: scheduleList.scrollHeight,
|
||
behavior: "smooth",
|
||
});
|
||
}
|
||
}, 100);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
// 모달이 로드되면 자동 실행
|
||
initializeCalendar();
|
||
</script>
|
||
</div>
|