TKK_E32232276/lib/dashboard/d_guru/function/absen_excel/download.dart

430 lines
16 KiB
Dart

import 'dart:io';
import 'package:flutter/material.dart' hide Border, BorderStyle;
import 'package:excel/excel.dart';
import 'package:apk/dashboard/d_guru/function/absen_excel/storage_helper.dart';
import 'package:apk/dashboard/d_guru/database/holiday_service.dart';
class AbsensiExcelDownloader {
static Future<void> downloadExcel({
required BuildContext context,
required String namaSekolah,
required String monthName,
required String yearMonth,
required String namaKelas,
required String namaWaliKelas,
required String nipWaliKelas,
required String wilayah,
required List<Map<String, dynamic>> muridList,
required int daysInMonth,
required Map<String, Map<dynamic, dynamic>> attendanceMap,
required int Function(String, String) countStatus,
}) async {
try {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const Center(child: CircularProgressIndicator()),
);
int year = DateTime.now().year;
int month = DateTime.now().month;
final parts = yearMonth.split('-');
if (parts.length == 2) {
year = int.parse(parts[0]);
month = int.parse(parts[1]);
}
final excel = Excel.createExcel();
final sheet = excel['Rekap Absensi'];
excel.setDefaultSheet('Rekap Absensi');
// Kop
if (namaSekolah.isNotEmpty) {
sheet.appendRow([TextCellValue(namaSekolah.toUpperCase())]);
}
sheet.appendRow([TextCellValue('ABSENSI SISWA')]);
sheet.appendRow([TextCellValue('BULAN : ${monthName.toUpperCase()}')]);
sheet.appendRow([TextCellValue('')]);
sheet.appendRow([TextCellValue('Kelas'), TextCellValue(':'), TextCellValue(namaKelas)]);
sheet.appendRow([TextCellValue('Jumlah Murid'), TextCellValue(':'), TextCellValue('${muridList.length} Siswa')]);
sheet.appendRow([TextCellValue('')]);
// Header Table
List<CellValue> headerRow1 = [
TextCellValue('NO'),
TextCellValue('NIS'),
TextCellValue('NAMA SISWA'),
TextCellValue('TANGGAL'),
];
for (int i = 2; i <= daysInMonth; i++) {
headerRow1.add(TextCellValue(''));
}
headerRow1.add(TextCellValue('KET'));
for (int i = 1; i < 4; i++) {
headerRow1.add(TextCellValue(''));
}
sheet.appendRow(headerRow1);
List<CellValue> headerRow2 = [
TextCellValue(''),
TextCellValue(''),
TextCellValue(''),
];
for (int i = 1; i <= daysInMonth; i++) {
headerRow2.add(IntCellValue(i));
}
headerRow2.addAll([
TextCellValue('H'),
TextCellValue('I'),
TextCellValue('S'),
TextCellValue('A'),
]);
sheet.appendRow(headerRow2);
// Merge cells for header
sheet.merge(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 7), CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 8));
sheet.merge(CellIndex.indexByColumnRow(columnIndex: 1, rowIndex: 7), CellIndex.indexByColumnRow(columnIndex: 1, rowIndex: 8));
sheet.merge(CellIndex.indexByColumnRow(columnIndex: 2, rowIndex: 7), CellIndex.indexByColumnRow(columnIndex: 2, rowIndex: 8));
sheet.merge(CellIndex.indexByColumnRow(columnIndex: 3, rowIndex: 7), CellIndex.indexByColumnRow(columnIndex: 3 + daysInMonth - 1, rowIndex: 7));
sheet.merge(CellIndex.indexByColumnRow(columnIndex: 3 + daysInMonth, rowIndex: 7), CellIndex.indexByColumnRow(columnIndex: 3 + daysInMonth + 3, rowIndex: 7));
// Data Rows
for (int index = 0; index < muridList.length; index++) {
final m = muridList[index];
final nisStr = m['nis']?.toString();
List<CellValue> row = [
IntCellValue(index + 1),
nisStr != null ? TextCellValue(nisStr) : TextCellValue('-'),
TextCellValue(m['nama']?.toString() ?? '-'),
];
for (int d = 1; d <= daysInMonth; d++) {
String status = '-';
if (nisStr != null && attendanceMap.containsKey(nisStr)) {
final studentMap = attendanceMap[nisStr]!;
status = studentMap[d]?.toString() ?? studentMap[d.toString()]?.toString() ?? '-';
}
row.add(TextCellValue(status));
}
String hVal = nisStr != null ? countStatus(nisStr, 'H').toString() : '-';
String iVal = nisStr != null ? countStatus(nisStr, 'I').toString() : '-';
String sVal = nisStr != null ? countStatus(nisStr, 'S').toString() : '-';
String aVal = nisStr != null ? countStatus(nisStr, 'A').toString() : '-';
row.addAll([
TextCellValue(hVal),
TextCellValue(iVal),
TextCellValue(sVal),
TextCellValue(aVal),
]);
sheet.appendRow(row);
}
// Signature
int totalColumns = 3 + daysInMonth + 4;
int signColumnIndex = 3 + daysInMonth - 2;
final now = DateTime.now();
final tanggal =
'${now.day.toString().padLeft(2, '0')}-${now.month.toString().padLeft(2, '0')}-${now.year}';
final lokasiTanggal =
wilayah.isNotEmpty ? '$wilayah, $tanggal' : tanggal;
final nipText = 'NIP. $nipWaliKelas';
List<CellValue> emptyRow =
List.generate(totalColumns, (index) => TextCellValue(''));
// Baris 1: wilayah + tanggal
List<CellValue> signRow0 =
List.generate(totalColumns, (index) => TextCellValue(''));
signRow0[signColumnIndex] = TextCellValue(lokasiTanggal);
// Baris 2: jabatan
List<CellValue> signRow1 =
List.generate(totalColumns, (index) => TextCellValue(''));
signRow1[signColumnIndex] = TextCellValue('Wali Kelas,');
// Baris nama (bold + underline) — muncul setelah ruang tanda tangan
List<CellValue> signRow2 =
List.generate(totalColumns, (index) => TextCellValue(''));
signRow2[signColumnIndex] = TextCellValue(namaWaliKelas);
// Baris NIP
List<CellValue> signRow3 =
List.generate(totalColumns, (index) => TextCellValue(''));
signRow3[signColumnIndex] = TextCellValue(nipText);
sheet.appendRow(emptyRow); // pemisah dari tabel
sheet.appendRow(signRow0); // wilayah, tanggal
sheet.appendRow(signRow1); // Wali Kelas,
sheet.appendRow(emptyRow); // ruang tanda tangan
sheet.appendRow(emptyRow);
sheet.appendRow(emptyRow);
sheet.appendRow(signRow2); // nama (di atas garis — bold)
sheet.appendRow(signRow3); // NIP
// =========================
// STYLE & BORDERS
// =========================
final sideBorder = Border(
borderStyle: BorderStyle.Thin,
borderColorHex: ExcelColor.black,
);
final kopStyle = CellStyle(
bold: true,
horizontalAlign: HorizontalAlign.Center,
verticalAlign: VerticalAlign.Center,
);
final headerStyle = CellStyle(
bold: true,
horizontalAlign: HorizontalAlign.Center,
verticalAlign: VerticalAlign.Center,
leftBorder: sideBorder,
rightBorder: sideBorder,
topBorder: sideBorder,
bottomBorder: sideBorder,
textWrapping: TextWrapping.WrapText,
);
final dataCenterStyle = CellStyle(
horizontalAlign: HorizontalAlign.Center,
verticalAlign: VerticalAlign.Center,
leftBorder: sideBorder,
rightBorder: sideBorder,
topBorder: sideBorder,
bottomBorder: sideBorder,
textWrapping: TextWrapping.WrapText,
);
final sundayStyle = CellStyle(
fontColorHex: ExcelColor.red,
backgroundColorHex: ExcelColor.red100,
horizontalAlign: HorizontalAlign.Center,
verticalAlign: VerticalAlign.Center,
leftBorder: sideBorder,
rightBorder: sideBorder,
topBorder: sideBorder,
bottomBorder: sideBorder,
textWrapping: TextWrapping.WrapText,
);
final dataLeftStyle = CellStyle(
horizontalAlign: HorizontalAlign.Left,
verticalAlign: VerticalAlign.Center,
leftBorder: sideBorder,
rightBorder: sideBorder,
topBorder: sideBorder,
bottomBorder: sideBorder,
textWrapping: TextWrapping.WrapText,
);
// Merge Kop (Baris 1, 2, 3)
sheet.merge(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 0), CellIndex.indexByColumnRow(columnIndex: totalColumns - 1, rowIndex: 0));
sheet.merge(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 1), CellIndex.indexByColumnRow(columnIndex: totalColumns - 1, rowIndex: 1));
sheet.merge(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: 2), CellIndex.indexByColumnRow(columnIndex: totalColumns - 1, rowIndex: 2));
// Apply Kop Style
for (int i = 0; i < 3; i++) {
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: i)).cellStyle = kopStyle;
}
// Apply Header Table Style (baris 8 dan 9 -> row index 7 dan 8)
for (int r = 7; r <= 8; r++) {
for (int c = 0; c < totalColumns; c++) {
sheet.cell(CellIndex.indexByColumnRow(columnIndex: c, rowIndex: r)).cellStyle = headerStyle;
}
}
// Override Sunday & Holiday Headers (Row index 8)
for (int i = 1; i <= daysInMonth; i++) {
final date = DateTime(year, month, i);
if (date.weekday == DateTime.sunday || HolidayService.isHoliday(date)) {
int columnIndex = 3 + i - 1;
sheet.cell(CellIndex.indexByColumnRow(columnIndex: columnIndex, rowIndex: 8)).cellStyle = CellStyle(
bold: true,
fontColorHex: ExcelColor.red,
backgroundColorHex: ExcelColor.red100,
horizontalAlign: HorizontalAlign.Center,
verticalAlign: VerticalAlign.Center,
leftBorder: sideBorder,
rightBorder: sideBorder,
topBorder: sideBorder,
bottomBorder: sideBorder,
);
}
}
// Apply Data Rows Style (mulai dari row index 9)
int startDataRow = 9;
for (int i = 0; i < muridList.length; i++) {
int r = startDataRow + i;
// NO
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 0, rowIndex: r)).cellStyle = dataCenterStyle;
// NIS
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 1, rowIndex: r)).cellStyle = dataCenterStyle;
// NAMA SISWA
sheet.cell(CellIndex.indexByColumnRow(columnIndex: 2, rowIndex: r)).cellStyle = dataLeftStyle;
// Attendance & Rekap (H, I, S, A)
for (int c = 3; c < totalColumns; c++) {
bool isSundayOrHoliday = false;
if (c < 3 + daysInMonth) {
int d = c - 2;
final date = DateTime(year, month, d);
isSundayOrHoliday = date.weekday == DateTime.sunday || HolidayService.isHoliday(date);
}
if (isSundayOrHoliday) {
sheet.cell(CellIndex.indexByColumnRow(columnIndex: c, rowIndex: r)).cellStyle = sundayStyle;
} else {
sheet.cell(CellIndex.indexByColumnRow(columnIndex: c, rowIndex: r)).cellStyle = dataCenterStyle;
}
}
}
// Apply Signature Style
final signNormalStyle = CellStyle(
horizontalAlign: HorizontalAlign.Left,
verticalAlign: VerticalAlign.Center,
fontSize: 11,
);
final signNameStyle = CellStyle(
bold: true,
underline: Underline.Single,
horizontalAlign: HorizontalAlign.Left,
verticalAlign: VerticalAlign.Center,
fontSize: 11,
);
int signStartRow = startDataRow + muridList.length;
// +0: empty separator
// +1: wilayah, tanggal
// +2: Wali Kelas,
// +3,+4,+5: empty (signature space)
// +6: nama (bold+underline)
// +7: NIP
sheet.cell(CellIndex.indexByColumnRow(columnIndex: signColumnIndex, rowIndex: signStartRow + 1)).cellStyle = signNormalStyle;
sheet.cell(CellIndex.indexByColumnRow(columnIndex: signColumnIndex, rowIndex: signStartRow + 2)).cellStyle = signNormalStyle;
sheet.cell(CellIndex.indexByColumnRow(columnIndex: signColumnIndex, rowIndex: signStartRow + 6)).cellStyle = signNameStyle;
sheet.cell(CellIndex.indexByColumnRow(columnIndex: signColumnIndex, rowIndex: signStartRow + 7)).cellStyle = signNormalStyle;
// =========================
// UKURAN KOLOM
// =========================
sheet.setColumnWidth(0, 6); // NO
sheet.setColumnWidth(1, 15); // NIS
sheet.setColumnWidth(2, 30); // NAMA SISWA
// Tanggal (Harian)
for (int d = 1; d <= daysInMonth; d++) {
int columnIndex = 3 + d - 1;
bool hasTime = false;
for (var m in muridList) {
final nisStr = m['nis']?.toString();
if (nisStr != null && attendanceMap.containsKey(nisStr)) {
final status = attendanceMap[nisStr]![d] ?? '-';
if (status.length > 1) {
hasTime = true;
break;
}
}
}
sheet.setColumnWidth(columnIndex, hasTime ? 15 : 5);
}
// Rekap columns (H, I, S, A)
for (int i = 3 + daysInMonth; i < totalColumns; i++) {
sheet.setColumnWidth(i, 5);
}
// =========================
// SAVE FILE
// =========================
final downloadDir = await StorageHelper.getDownloadDirectoryPath();
final baseName = 'Absensi_${namaKelas.replaceAll('/', '_')}_${monthName.replaceAll(' ', '_')}';
var finalPath = StorageHelper.getUniqueFilePath(downloadDir, baseName, 'xlsx');
var finalFileName = finalPath.split('/').last;
final fileBytes = excel.save();
if (fileBytes != null) {
try {
final file = File(finalPath);
file.createSync(recursive: true);
file.writeAsBytesSync(fileBytes);
} catch (e) {
final nowTime = DateTime.now();
final timestamp = '${nowTime.hour.toString().padLeft(2, '0')}${nowTime.minute.toString().padLeft(2, '0')}${nowTime.second.toString().padLeft(2, '0')}';
final fallbackFileName = '${baseName}_$timestamp.xlsx';
finalPath = '$downloadDir/$fallbackFileName';
finalFileName = fallbackFileName;
final file = File(finalPath);
file.createSync(recursive: true);
file.writeAsBytesSync(fileBytes);
}
}
if (context.mounted) {
Navigator.pop(context); // Tutup loading dialog
showDialog(
context: context,
builder: (ctx) => AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
title: const Row(
children: [
Icon(Icons.check_circle, color: Colors.green, size: 28),
SizedBox(width: 10),
Expanded(
child: Text(
'Excel Berhasil Didownload',
style: TextStyle(fontFamily: 'Inter', fontWeight: FontWeight.bold, fontSize: 16),
),
),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('File: $finalFileName', style: const TextStyle(fontFamily: 'Inter', fontSize: 13, fontWeight: FontWeight.w500)),
const SizedBox(height: 8),
const Text('Lokasi Penyimpanan:', style: TextStyle(fontFamily: 'Inter', fontSize: 11, color: Colors.grey)),
Text(finalPath, style: const TextStyle(fontFamily: 'Inter', fontSize: 11, color: Colors.black87)),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text('Tutup', style: TextStyle(fontFamily: 'Inter', fontWeight: FontWeight.bold)),
),
],
),
);
}
} catch (e) {
if (context.mounted) {
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Gagal menyimpan Excel: $e'),
backgroundColor: Colors.red.shade600,
),
);
}
}
}
}