updated PDFGenerator daily.kt
This commit is contained in:
@@ -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<String>,
|
||||
val checkOutTimes: List<String>,
|
||||
val isCompleted: Boolean
|
||||
)
|
||||
|
||||
suspend fun generateDailyReport(
|
||||
employees: List<Employee>,
|
||||
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<String>()
|
||||
val checkOutTimes = mutableListOf<String>()
|
||||
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<String>, checkOutTimes: List<String>): 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user