export file frontend
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import { supabase } from "$lib/supabaseClient";
|
||||
import { onMount } from "svelte";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import * as XLSX from 'xlsx';
|
||||
|
||||
type Timesheets = {
|
||||
id: number;
|
||||
@@ -447,44 +448,182 @@
|
||||
|
||||
async function exportTimesheets() {
|
||||
if (!selectedMonth || !selectedYear) {
|
||||
alert("Please select a month and year to export.");
|
||||
alert("Please select a month and year to export.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("https://flow.catalis.app/webhook-test/villabugis-timesheets", {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
month: selectedMonth,
|
||||
year: selectedYear
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Export failed: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const randomUuid = uuidv4().toString();
|
||||
|
||||
// Jika response adalah file (application/octet-stream atau Excel)
|
||||
const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = `timesheet-${selectedYear}-${String(selectedMonth).padStart(2, '0')}-${randomUuid}.xlsx`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error("Export error:", error);
|
||||
alert("Failed to export timesheet.");
|
||||
}
|
||||
}
|
||||
async function exportToExcelFromFrontend() {
|
||||
if (!selectedMonth || !selectedYear) {
|
||||
alert("Please select both month and year");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("https://flow.catalis.app/webhook-test/villabugis-timesheets", {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
month: selectedMonth,
|
||||
year: selectedYear
|
||||
})
|
||||
const startDate = new Date(selectedYear, selectedMonth - 1, 1);
|
||||
const endDate = new Date(selectedYear, selectedMonth, 0, 23, 59, 59);
|
||||
|
||||
// 1. Fetch timesheet data
|
||||
const { data: timesheetData, error: timesheetErr } = await supabase
|
||||
.from("vb_timesheet_data")
|
||||
.select(`
|
||||
id,
|
||||
work_description,
|
||||
total_work_hour,
|
||||
type_of_work,
|
||||
category_of_work,
|
||||
approval,
|
||||
datetime_in,
|
||||
datetime_out,
|
||||
remarks,
|
||||
approved_date,
|
||||
villa_name,
|
||||
entered_name,
|
||||
approved_name
|
||||
`)
|
||||
.gte("datetime_in", startDate.toISOString())
|
||||
.lte("datetime_in", endDate.toISOString());
|
||||
|
||||
if (timesheetErr) {
|
||||
console.error("Error fetching timesheet:", timesheetErr);
|
||||
alert("Failed to fetch timesheet data.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Fetch active villas
|
||||
const { data: villaData, error: villaErr } = await supabase
|
||||
.from("vb_villas")
|
||||
.select("villa_name")
|
||||
.eq("villa_status", "Active");
|
||||
|
||||
if (villaErr) {
|
||||
console.error("Error fetching villas:", villaErr);
|
||||
alert("Failed to fetch villas.");
|
||||
return;
|
||||
}
|
||||
|
||||
const categoryHeaders = [
|
||||
"Cleaning",
|
||||
"Gardening/Pool",
|
||||
"Maintenance",
|
||||
"Supervision",
|
||||
"Guest Service",
|
||||
"Administration",
|
||||
"Non Billable"
|
||||
];
|
||||
|
||||
const sheet2Name = "Data";
|
||||
const ws2 = XLSX.utils.json_to_sheet(timesheetData || []);
|
||||
|
||||
// 3. Build Sheet1 rows
|
||||
const sheet1Rows: any[][] = [];
|
||||
|
||||
// A1:B1 → Period label
|
||||
const periodText = `${startDate.toLocaleString("default", { month: "short" })}/${selectedYear}`;
|
||||
sheet1Rows.push(["Period", periodText]);
|
||||
|
||||
// A2:I2 → Headers
|
||||
const headers = ["Villa Name", ...categoryHeaders, "TOTAL"];
|
||||
sheet1Rows.push(headers);
|
||||
|
||||
// A3:A(n) → Villa rows
|
||||
villaData?.forEach((villa, idx) => {
|
||||
const excelRow = idx + 3; // Excel row number
|
||||
const row: any[] = [villa.villa_name];
|
||||
|
||||
// B–H formulas
|
||||
categoryHeaders.forEach((cat, catIdx) => {
|
||||
const colLetter = String.fromCharCode(66 + catIdx); // 'B' = 66
|
||||
const formula = `SUMIFS(${sheet2Name}!$C:$C, ${sheet2Name}!$K:$K, $A${excelRow}, ${sheet2Name}!$E:$E, ${colLetter}$2, ${sheet2Name}!$F:$F, TRUE)`;
|
||||
row.push({ f: formula });
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Export failed: ${response.statusText}`);
|
||||
// I column (TOTAL)
|
||||
row.push({ f: `SUM(B${excelRow}:H${excelRow})` });
|
||||
|
||||
sheet1Rows.push(row);
|
||||
});
|
||||
|
||||
// Horizontal summary beside the villa table
|
||||
const headerRowIdx = 1;
|
||||
const officeRowIdx = 2;
|
||||
const fbRowIdx = 3;
|
||||
const villasOnlyRowIdx = 4;
|
||||
|
||||
const ensureCols = (row: any[], length: number) => {
|
||||
while (row.length < length) row.push(null);
|
||||
return row;
|
||||
};
|
||||
|
||||
[headerRowIdx, officeRowIdx, fbRowIdx, villasOnlyRowIdx].forEach((idx) => {
|
||||
sheet1Rows[idx] = sheet1Rows[idx] || [];
|
||||
ensureCols(sheet1Rows[idx], 13); // at least to column M
|
||||
});
|
||||
|
||||
// Insert labels and formulas
|
||||
sheet1Rows[headerRowIdx][10] = "Total Work Hours"; // K2
|
||||
sheet1Rows[headerRowIdx][11] = { f: `SUM(${sheet2Name}!$C:$C)` }; // L2
|
||||
|
||||
sheet1Rows[officeRowIdx][10] = "Office"; // K3
|
||||
sheet1Rows[officeRowIdx][11] = { f: `SUMIFS(${sheet2Name}!$C:$C, ${sheet2Name}!$K:$K, K3)` }; // L3
|
||||
|
||||
sheet1Rows[fbRowIdx][10] = "FB"; // K4
|
||||
sheet1Rows[fbRowIdx][11] = { f: `SUMIFS(${sheet2Name}!$C:$C, ${sheet2Name}!$K:$K, K4)` }; // L4
|
||||
|
||||
sheet1Rows[villasOnlyRowIdx][10] = "Villas Only"; // K5
|
||||
sheet1Rows[villasOnlyRowIdx][11] = { f: `L2-L3` }; // L5
|
||||
|
||||
// Convert to worksheet
|
||||
const ws1 = XLSX.utils.aoa_to_sheet(sheet1Rows);
|
||||
|
||||
// Bold header row (A2:I2)
|
||||
for (let c = 0; c <= headers.length; c++) {
|
||||
const cellRef = XLSX.utils.encode_cell({ r: 1, c });
|
||||
if (ws1[cellRef]) {
|
||||
ws1[cellRef].s = { font: { bold: true } };
|
||||
}
|
||||
|
||||
const randomUuid = uuidv4().toString();
|
||||
|
||||
// Jika response adalah file (application/octet-stream atau Excel)
|
||||
const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = `timesheet-${selectedYear}-${String(selectedMonth).padStart(2, '0')}-${randomUuid}.xlsx`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error("Export error:", error);
|
||||
alert("Failed to export timesheet.");
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Build Workbook
|
||||
const wb = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(wb, ws1, "Summary");
|
||||
XLSX.utils.book_append_sheet(wb, ws2, sheet2Name);
|
||||
|
||||
// 5. Export
|
||||
const filename = `timesheet_${selectedYear}-${String(selectedMonth).padStart(2, "0")}.xlsx`;
|
||||
XLSX.writeFile(wb, filename);
|
||||
}
|
||||
// Function to delete a timesheet
|
||||
async function deleteTimesheet(id: string) {
|
||||
if (confirm("Are you sure you want to delete this Timesheet?")) {
|
||||
@@ -862,7 +1001,7 @@
|
||||
<!-- Export Button -->
|
||||
<button
|
||||
class="bg-green-600 text-white px-3 py-1 rounded hover:bg-green-700 text-sm"
|
||||
on:click={exportTimesheets}
|
||||
on:click={exportToExcelFromFrontend}
|
||||
>
|
||||
⬇️ Export
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user