diff --git a/app/src/main/java/in/sminnovations/smiadmin/ui/utils/PDFGenerator daily.kt b/app/src/main/java/in/sminnovations/smiadmin/ui/utils/PDFGenerator daily.kt new file mode 100644 index 0000000..a77819d --- /dev/null +++ b/app/src/main/java/in/sminnovations/smiadmin/ui/utils/PDFGenerator daily.kt @@ -0,0 +1,500 @@ +package `in`.sminnovations.smiadmin.ui.utils + +import android.content.Context +import android.graphics.Color +import android.graphics.LinearGradient +import android.graphics.Paint +import android.graphics.Path +import android.graphics.Shader +import android.graphics.Typeface +import android.graphics.pdf.PdfDocument +import android.os.Environment +import com.google.firebase.Firebase +import com.google.firebase.firestore.firestore +import `in`.sminnovations.smiadmin.data.Employee +import kotlinx.coroutines.tasks.await +import java.io.File +import java.io.FileOutputStream +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale +class PDFGenerator_daily( + private val context: Context, + private val companyName: String +){ + private lateinit var document: PdfDocument + private lateinit var currentPage: PdfDocument.Page + private lateinit var canvas: android.graphics.Canvas + private var currentY: Float = 0f + + // Constants for page layout + private val pageWidth = 595f // A4 width + private val pageHeight = 842f // A4 height + private val margin = 40f + private val contentWidth = pageWidth - (2 * margin) + + // Professional color scheme + private val primaryColor = Color.rgb(41, 128, 185) // Professional blue + private val secondaryColor = Color.rgb(44, 62, 80) // Dark blue + private val accentColor = Color.rgb(230, 126, 34) // Orange for highlights + private val backgroundColor = Color.rgb(236, 240, 241) // Light gray + private val borderColor = Color.rgb(189, 195, 199) // Medium gray + private val textColor = Color.rgb(52, 73, 94) // Dark gray for text + + private var pageNumber = 1 + + // Data class for attendance record + data class AttendanceRecord( + val checkInTimes: List, + val checkOutTimes: List, + val isCompleted: Boolean + ) + + suspend fun generateDailyReport( + employees: List, + selectedDate: Calendar, + source: String + ): File { + document = PdfDocument() + createNewPage() + + // Create paint objects + val titlePaint = Paint().apply { + textSize = 32f + typeface = Typeface.create("sans-serif-light", Typeface.BOLD) + color = primaryColor + textAlign = Paint.Align.CENTER + } + + val subtitlePaint = Paint().apply { + textSize = 24f + typeface = Typeface.create("sans-serif-light", Typeface.NORMAL) + color = secondaryColor + textAlign = Paint.Align.CENTER + } + + val tableHeaderPaint = Paint().apply { + textSize = 14f + typeface = Typeface.create("sans-serif-medium", Typeface.BOLD) + color = Color.WHITE + } + + val regularPaint = Paint().apply { + textSize = 12f + typeface = Typeface.create("sans-serif", Typeface.NORMAL) + color = textColor + } + + val highlightPaint = Paint().apply { + textSize = 12f + typeface = Typeface.create("sans-serif-medium", Typeface.BOLD) + color = accentColor + } + + // Format date for display and Firebase query + val dateFormat = SimpleDateFormat("dd MMMM yyyy", Locale.getDefault()) + val formattedDate = dateFormat.format(selectedDate.time) + + val yearStr = selectedDate.get(Calendar.YEAR).toString() + val monthStr = String.format("%02d", selectedDate.get(Calendar.MONTH) + 1) + val dayStr = String.format("%02d", selectedDate.get(Calendar.DAY_OF_MONTH)) + val firebaseDateKey = "$dayStr-$monthStr-$yearStr" + + // Draw report header + drawReportHeader(formattedDate, titlePaint, subtitlePaint) + + // Draw table headers + drawTableHeaders(tableHeaderPaint) + + // Process each employee + employees.forEach { employee -> + if (currentY > pageHeight - 100) { + finishCurrentPage() + createNewPage() + drawTableHeaders(tableHeaderPaint) + } + + // Get attendance data for this employee for the selected date + val attendanceDoc = Firebase.firestore + .collection("attendance_${employee.Id}") + .document(firebaseDateKey) + .get() + .await() + + if (attendanceDoc.exists()) { + // Extract check-in and check-out times + val checkInTimes = mutableListOf() + val checkOutTimes = mutableListOf() + val isCompleted = attendanceDoc.getBoolean("DayCompleted") ?: false + + // Get all check-in times + var i = 1 + while (true) { + val checkInKey = "checkIn$i" + val checkInTime = attendanceDoc.getString(checkInKey) + if (checkInTime != null) { + checkInTimes.add(checkInTime) + i++ + } else { + break + } + } + + // Get all check-out times + i = 1 + while (true) { + val checkOutKey = "checkOut$i" + val checkOutTime = attendanceDoc.getString(checkOutKey) + if (checkOutTime != null) { + checkOutTimes.add(checkOutTime) + i++ + } else { + break + } + } + + val attendanceRecord = AttendanceRecord( + checkInTimes = checkInTimes, + checkOutTimes = checkOutTimes, + isCompleted = isCompleted + ) + + drawEmployeeAttendanceRow(employee, attendanceRecord, regularPaint, highlightPaint) + } else { + // Employee was absent + drawEmployeeAbsentRow(employee, regularPaint, highlightPaint) + } + } + + // Add footer with page numbers + drawFooter(regularPaint) + + // Finish and save document + finishCurrentPage() + return saveDocument(selectedDate) + } + + private fun drawReportHeader( + formattedDate: String, + titlePaint: Paint, + subtitlePaint: Paint + ) { + // Draw decorative header background + val headerPath = Path().apply { + moveTo(0f, 0f) + lineTo(pageWidth, 0f) + lineTo(pageWidth, 120f) + quadTo(pageWidth/2, 140f, 0f, 120f) + close() + } + + canvas.drawPath(headerPath, Paint().apply { + color = primaryColor + alpha = 20 + }) + + // Draw company name + canvas.drawText(companyName, pageWidth/2, 70f, titlePaint) + + // Draw report title + canvas.drawText( + "Daily Attendance Report", + pageWidth/2, + 105f, + subtitlePaint + ) + + canvas.drawText( + formattedDate, + pageWidth/2, + 135f, + subtitlePaint + ) + + currentY = 180f + } + + private fun drawTableHeaders(headerPaint: Paint) { + val headerHeight = 45f + + // Create gradient for header background + val gradient = LinearGradient( + margin, currentY, + margin, currentY + headerHeight, + primaryColor, + secondaryColor, + Shader.TileMode.CLAMP + ) + + // Draw header background with gradient + canvas.drawRoundRect( + margin, currentY, + pageWidth - margin, + currentY + headerHeight, + 8f, 8f, + Paint().apply { shader = gradient } + ) + + // Draw column headers + var xPos = margin + 15f + canvas.drawText("Employee Name", xPos, currentY + 30f, headerPaint) + + xPos += 150f + canvas.drawText("Status", xPos, currentY + 30f, headerPaint) + + xPos += 80f + canvas.drawText("Check-in Times", xPos, currentY + 30f, headerPaint) + + xPos += 150f + canvas.drawText("Check-out Times", xPos, currentY + 30f, headerPaint) + + xPos += 110f + canvas.drawText("Total Hours", xPos, currentY + 30f, headerPaint) + + currentY += headerHeight + 5f + } + + private fun drawEmployeeAttendanceRow( + employee: Employee, + attendanceRecord: AttendanceRecord, + regularPaint: Paint, + highlightPaint: Paint + ) { + // Calculate row height based on number of check-ins/outs + val entriesCount = maxOf(attendanceRecord.checkInTimes.size, attendanceRecord.checkOutTimes.size) + val rowHeight = 40f + (entriesCount * 20f) + + // Draw row background + canvas.drawRoundRect( + margin, currentY, + pageWidth - margin, + currentY + rowHeight, + 4f, 4f, + Paint().apply { + color = backgroundColor + } + ) + + // Draw employee name + canvas.drawText( + employee.Name, + margin + 15f, + currentY + 25f, + regularPaint + ) + + // Draw status + val status = if (attendanceRecord.isCompleted) "Present" else "Incomplete" + val statusPaint = if (attendanceRecord.isCompleted) { + Paint().apply { + textSize = 12f + typeface = Typeface.create("sans-serif-medium", Typeface.BOLD) + color = Color.rgb(46, 204, 113) // Green for present + } + } else { + Paint().apply { + textSize = 12f + typeface = Typeface.create("sans-serif-medium", Typeface.BOLD) + color = Color.rgb(231, 76, 60) // Red for incomplete + } + } + canvas.drawText( + status, + margin + 165f, + currentY + 25f, + statusPaint + ) + + // Draw check-in times + var checkInY = currentY + 25f + attendanceRecord.checkInTimes.forEachIndexed { index, time -> + // Extract only the time portion for display + val simplifiedTime = try { + val parsedTime = SimpleDateFormat("dd MMMM yyyy hh:mm:ss a", Locale.getDefault()).parse(time) + SimpleDateFormat("hh:mm:ss a", Locale.getDefault()).format(parsedTime) + } catch (e: Exception) { + time // Fallback if parsing fails + } + + canvas.drawText( + "$simplifiedTime", + margin + 245f, + checkInY, + regularPaint + ) + checkInY += 20f + } + + // Draw check-out times + var checkOutY = currentY + 25f + attendanceRecord.checkOutTimes.forEachIndexed { index, time -> + // Extract only the time portion for display + val simplifiedTime = try { + val parsedTime = SimpleDateFormat("dd MMMM yyyy hh:mm:ss a", Locale.getDefault()).parse(time) + SimpleDateFormat("hh:mm:ss a", Locale.getDefault()).format(parsedTime) + } catch (e: Exception) { + time // Fallback if parsing fails + } + + canvas.drawText( + "$simplifiedTime", + margin + 395f, + checkOutY, + regularPaint + ) + checkOutY += 20f + } + + // Calculate and draw total hours + val totalHours = calculateTotalHours(attendanceRecord.checkInTimes, attendanceRecord.checkOutTimes) + canvas.drawText( + String.format("%.2f hrs", totalHours), + margin + 505f, + currentY + 25f, + highlightPaint + ) + + currentY += rowHeight + 10f + } + + private fun drawEmployeeAbsentRow( + employee: Employee, + regularPaint: Paint, + highlightPaint: Paint + ) { + val rowHeight = 40f + + // Draw row background with a slight red tint for absent + canvas.drawRoundRect( + margin, currentY, + pageWidth - margin, + currentY + rowHeight, + 4f, 4f, + Paint().apply { + color = Color.rgb(245, 233, 233) // Light red tint + } + ) + + // Draw employee name + canvas.drawText( + employee.Name, + margin + 15f, + currentY + 25f, + regularPaint + ) + + // Draw absent status + val absentPaint = Paint().apply { + textSize = 12f + typeface = Typeface.create("sans-serif-medium", Typeface.BOLD) + color = Color.rgb(231, 76, 60) // Red for absent + } + + canvas.drawText( + "Absent", + margin + 165f, + currentY + 25f, + absentPaint + ) + + // Draw dashes for check-in and check-out + canvas.drawText( + "—", + margin + 245f, + currentY + 25f, + regularPaint + ) + + canvas.drawText( + "—", + margin + 395f, + currentY + 25f, + regularPaint + ) + + // Draw zero hours + canvas.drawText( + "0.00 hrs", + margin + 505f, + currentY + 25f, + highlightPaint + ) + + currentY += rowHeight + 10f + } + + private fun calculateTotalHours(checkInTimes: List, checkOutTimes: List): Float { + var totalMinutes = 0f + val dateFormat = SimpleDateFormat("dd MMMM yyyy hh:mm:ss a", Locale.getDefault()) + + // Calculate time for each check-in/check-out pair + for (i in 0 until minOf(checkInTimes.size, checkOutTimes.size)) { + try { + val checkInTime = dateFormat.parse(checkInTimes[i]) + val checkOutTime = dateFormat.parse(checkOutTimes[i]) + + if (checkInTime != null && checkOutTime != null) { + // Calculate difference in minutes + val diffMillis = checkOutTime.time - checkInTime.time + val diffMinutes = diffMillis / (1000 * 60) + totalMinutes += diffMinutes + } + } catch (e: Exception) { + // Skip if there's an error parsing the date + continue + } + } + + // Convert minutes to hours + return totalMinutes / 60 + } + + private fun drawFooter(paint: Paint) { + paint.textAlign = Paint.Align.CENTER + paint.textSize = 10f + + val currentDate = SimpleDateFormat("dd MMMM yyyy HH:mm", Locale.getDefault()) + .format(Calendar.getInstance().time) + + canvas.drawText( + "Generated on: $currentDate | Page $pageNumber", + pageWidth/2, + pageHeight - margin/2, + paint + ) + } + + private fun saveDocument(selectedDate: Calendar): File { + val dateStr = SimpleDateFormat("yyyy_MM_dd", Locale.getDefault()) + .format(selectedDate.time) + + val fileName = "Daily_Attendance_Report_$dateStr.pdf" + val file = File( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), + fileName + ) + + FileOutputStream(file).use { outputStream -> + document.writeTo(outputStream) + } + + document.close() + return file + } + + private fun createNewPage() { + val pageInfo = PdfDocument.PageInfo.Builder( + pageWidth.toInt(), + pageHeight.toInt(), + pageNumber++ + ).create() + + currentPage = document.startPage(pageInfo) + canvas = currentPage.canvas + currentY = margin + } + + private fun finishCurrentPage() { + document.finishPage(currentPage) + } +} \ No newline at end of file