Update Progress

Operations Analytics Dashboard
Created by: Pongsatorn Srinorakut
Back to List
Add New Progress Update
Progress History
Date Title Description Progress
27/08/2025 09:46 Operations Analytics Dashboard แสดงสถานะได้แล้ว Operations Analytics Dashboard แสดงสถานะได้แล้ว แต่ยังมีบางข้อมูลยังไม่สามารถแสดงสถานะได้ตามที่ต้องการ จะทำการ Review Dashboard และปรับเปลี่ยนการแสดงข้อมูลอีกครั้ง
1. Dashboard ปัจจุบันสามารถเลือกได้แต่ Site งานเท่านั้น อยากได้เพิ่มเติมให้สามารถเลือกข้อมูลในแต่ละ Week ได้
2. กราฟ Overtime ไม่มี Target ในการเปรียบเทียบในแต่ละ Week อยากได้กราฟแสดงข้อมูล Target เพิ่มเติม จะได้เปรียบเทียบว่าได้ตาม Target ที่วางแผน OT ไว้หรือไม่
3. Site Comparison สรุปภาพรวม ตอนนี้ข้อมูลภายใน 1 site แต่มีหลายโปรเจคอยู่ ยังไม่ได้ถูกแยกออกจากกัน
4. ข้อมูลกราฟ Sale Site ไหนที่ไม่มี VMI ให้ลงข้อมูล Billing แทน จะได้เปรียบเทียบข้อมูล performance ส่วนอื่นได้ เช่น %OT
50%
20/08/2025 09:44 Dashboard ไม่แสดงสถานะกราฟ Dashboard ไม่แสดงสถานะกราฟ รบกวนพี่กานต์ช่วยดู Code ให้หน่อยคับ ว่าสามารถแก้ไขเพื่อให้แสดงสถานะ Dashboard ได้หรือไม่ครับ รายละเอียด Code ด้านล่าง
function doGet() {
return HtmlService.createTemplateFromFile('index')
.evaluate()
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename).getContent();
}

function getOperationsData() {
try {
const SHEET_ID = '1rlWOP4IwvrSPM8LP-lhD5R-kgRxHGrfdJl8fc3iv-0k';
const SHEET_NAME = 'ชีต1';

const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);
const data = sheet.getDataRange().getValues();

// Remove header row
const headers = data[0];
const rows = data.slice(1);

// Convert to objects
const formattedData = rows.map(row => ({
weeklyDate: row[0] ? new Date(row[0]) : null,
week: row[1] || '',
site: row[2] || '',
planAttendance: parseFloat(row[3]) || 0,
actualAttendance: parseFloat(row[4]) || 0,
planMan: parseFloat(row[5]) || 0,
actualMan: parseFloat(row[6]) || 0,
planOvertime: parseFloat(row[7]) || 0,
actualOvertime: parseFloat(row[8]) || 0,
accident: parseInt(row[9]) || 0,
planKPI: parseFloat(row[10]) || 0,
actualKPI: parseFloat(row[11]) || 0,
saleVMI: parseFloat(row[12]) || 0,
stockVMI: parseFloat(row[13]) || 0
}));

return formattedData.filter(item => item.weeklyDate && item.site);

} catch (error) {
console.error('Error fetching data:', error);
return [];
}
}

function getFilterOptions() {
try {
const data = getOperationsData();

const sites = [...new Set(data.map(item => item.site))].sort();
const years = [...new Set(data.map(item => item.weeklyDate ? item.weeklyDate.getFullYear() : null))].filter(Boolean).sort();
const months = [...new Set(data.map(item => {
if (!item.weeklyDate) return null;
return item.weeklyDate.getMonth() + 1;
}))].filter(Boolean).sort((a, b) => a - b);

return {
sites: sites,
years: years,
months: months
};

} catch (error) {
console.error('Error getting filter options:', error);
return { sites: [], years: [], months: [] };
}
}

function getFilteredData(filters = {}) {
try {
let data = getOperationsData();

// Apply filters
if (filters.site && filters.site !== 'all') {
data = data.filter(item => item.site === filters.site);
}

if (filters.year && filters.year !== 'all') {
data = data.filter(item => item.weeklyDate && item.weeklyDate.getFullYear() == filters.year);
}

if (filters.month && filters.month !== 'all') {
data = data.filter(item => item.weeklyDate && (item.weeklyDate.getMonth() + 1) == filters.month);
}

if (filters.viewType === 'monthly') {
return aggregateDataByMonth(data);
} else {
return aggregateDataByWeek(data);
}

} catch (error) {
console.error('Error filtering data:', error);
return [];
}
}

function aggregateDataByWeek(data) {
const grouped = {};

data.forEach(item => {
if (!item.weeklyDate) return;

const key = `${item.weeklyDate.getFullYear()}-W${item.week}`;

if (!grouped[key]) {
grouped[key] = {
period: `สัปดาห์ ${item.week}/${item.weeklyDate.getFullYear()}`,
weeklyDate: item.weeklyDate,
week: item.week,
year: item.weeklyDate.getFullYear(),
planAttendance: 0,
actualAttendance: 0,
planMan: 0,
actualMan: 0,
planOvertime: 0,
actualOvertime: 0,
accident: 0,
planKPI: 0,
actualKPI: 0,
saleVMI: 0,
stockVMI: 0,
count: 0
};
}

grouped[key].planAttendance += item.planAttendance;
grouped[key].actualAttendance += item.actualAttendance;
grouped[key].planMan += item.planMan;
grouped[key].actualMan += item.actualMan;
grouped[key].planOvertime += item.planOvertime;
grouped[key].actualOvertime += item.actualOvertime;
grouped[key].accident += item.accident;
grouped[key].planKPI += item.planKPI;
grouped[key].actualKPI += item.actualKPI;
grouped[key].saleVMI += item.saleVMI;
grouped[key].stockVMI += item.stockVMI;
grouped[key].count += 1;
});

// Calculate averages for percentage values
return Object.values(grouped).map(item => ({
...item,
planAttendance: item.count > 0 ? (item.planAttendance / item.count) : 0,
actualAttendance: item.count > 0 ? (item.actualAttendance / item.count) : 0,
planMan: item.count > 0 ? (item.planMan / item.count) : 0,
actualMan: item.count > 0 ? (item.actualMan / item.count) : 0,
planOvertime: item.count > 0 ? (item.planOvertime / item.count) : 0,
planKPI: item.count > 0 ? (item.planKPI / item.count) : 0,
actualKPI: item.count > 0 ? (item.actualKPI / item.count) : 0
})).sort((a, b) => a.weeklyDate - b.weeklyDate);
}

function aggregateDataByMonth(data) {
const grouped = {};

data.forEach(item => {
if (!item.weeklyDate) return;

const key = `${item.weeklyDate.getFullYear()}-${item.weeklyDate.getMonth() + 1}`;

if (!grouped[key]) {
grouped[key] = {
period: `${getThaiMonth(item.weeklyDate.getMonth())} ${item.weeklyDate.getFullYear()}`,
month: item.weeklyDate.getMonth() + 1,
year: item.weeklyDate.getFullYear(),
planAttendance: 0,
actualAttendance: 0,
planMan: 0,
actualMan: 0,
planOvertime: 0,
actualOvertime: 0,
accident: 0,
planKPI: 0,
actualKPI: 0,
saleVMI: 0,
stockVMI: 0,
count: 0
};
}

grouped[key].planAttendance += item.planAttendance;
grouped[key].actualAttendance += item.actualAttendance;
grouped[key].planMan += item.planMan;
grouped[key].actualMan += item.actualMan;
grouped[key].planOvertime += item.planOvertime;
grouped[key].actualOvertime += item.actualOvertime;
grouped[key].accident += item.accident;
grouped[key].planKPI += item.planKPI;
grouped[key].actualKPI += item.actualKPI;
grouped[key].saleVMI += item.saleVMI;
grouped[key].stockVMI += item.stockVMI;
grouped[key].count += 1;
});

// Calculate averages for percentage values
return Object.values(grouped).map(item => ({
...item,
planAttendance: item.count > 0 ? (item.planAttendance / item.count) : 0,
actualAttendance: item.count > 0 ? (item.actualAttendance / item.count) : 0,
planMan: item.count > 0 ? (item.planMan / item.count) : 0,
actualMan: item.count > 0 ? (item.actualMan / item.count) : 0,
planOvertime: item.count > 0 ? (item.planOvertime / item.count) : 0,
planKPI: item.count > 0 ? (item.planKPI / item.count) : 0,
actualKPI: item.count > 0 ? (item.actualKPI / item.count) : 0
})).sort((a, b) => {
if (a.year !== b.year) return a.year - b.year;
return a.month - b.month;
});
}

function getThaiMonth(monthIndex) {
const thaiMonths = [
'มกราคม', 'กุมภาพันธ์', 'มีนาคม', 'เมษายน', 'พฤษภาคม', 'มิถุนายน',
'กรกฎาคม', 'สิงหาคม', 'กันยายน', 'ตุลาคม', 'พฤศจิกายน', 'ธันวาคม'
];
return thaiMonths[monthIndex] || '';
}

function getSiteAnalysisData(filters = {}) {
try {
let data = getOperationsData();

// Apply year and month filters
if (filters.year && filters.year !== 'all') {
data = data.filter(item => item.weeklyDate && item.weeklyDate.getFullYear() == filters.year);
}

if (filters.month && filters.month !== 'all') {
data = data.filter(item => item.weeklyDate && (item.weeklyDate.getMonth() + 1) == filters.month);
}

const grouped = {};

data.forEach(item => {
if (!item.site) return;

if (!grouped[item.site]) {
grouped[item.site] = {
site: item.site,
planAttendance: 0,
actualAttendance: 0,
planMan: 0,
actualMan: 0,
planOvertime: 0,
actualOvertime: 0,
accident: 0,
planKPI: 0,
actualKPI: 0,
saleVMI: 0,
stockVMI: 0,
count: 0
};
}

grouped[item.site].planAttendance += item.planAttendance;
grouped[item.site].actualAttendance += item.actualAttendance;
grouped[item.site].planMan += item.planMan;
grouped[item.site].actualMan += item.actualMan;
grouped[item.site].planOvertime += item.planOvertime;
grouped[item.site].actualOvertime += item.actualOvertime;
grouped[item.site].accident += item.accident;
grouped[item.site].planKPI += item.planKPI;
grouped[item.site].actualKPI += item.actualKPI;
grouped[item.site].saleVMI += item.saleVMI;
grouped[item.site].stockVMI += item.stockVMI;
grouped[item.site].count += 1;
});

// Calculate averages for percentage values
return Object.values(grouped).map(item => ({
...item,
planAttendance: item.count > 0 ? (item.planAttendance / item.count) : 0,
actualAttendance: item.count > 0 ? (item.actualAttendance / item.count) : 0,
planMan: item.count > 0 ? (item.planMan / item.count) : 0,
actualMan: item.count > 0 ? (item.actualMan / item.count) : 0,
planOvertime: item.count > 0 ? (item.planOvertime / item.count) : 0,
planKPI: item.count > 0 ? (item.planKPI / item.count) : 0,
actualKPI: item.count > 0 ? (item.actualKPI / item.count) : 0
})).sort((a, b) => a.site.localeCompare(b.site));

} catch (error) {
console.error('Error getting site analysis data:', error);
return [];
}
}
20%